diff --git a/src/js/client.js b/src/js/client.js index 9d1599ebb..be7861745 100644 --- a/src/js/client.js +++ b/src/js/client.js @@ -1837,8 +1837,8 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout role: user.role }; - if (user.username !== null) data.username = user.username; - if (user.password !== null) data.password = user.password; + if (user.username) data.username = user.username; + if (user.password) data.password = user.password; post('/api/v1/users', data, null, function (error, data, status) { if (error) return callback(error); diff --git a/src/translation/en.json b/src/translation/en.json index daead878d..0997f8e62 100644 --- a/src/translation/en.json +++ b/src/translation/en.json @@ -355,10 +355,11 @@ "title": "Import Users", "fileInput": "Select JSON or CSV file", "importAction": "Import", - "description": "The import requires a specific schema for both JSON and CSV. The detailed schema is described in our documentation", - "usersFound": "Found {{ count }} users to import.", - "success": "{{ count }} users successfully imported.", - "failed": "The following users were not imported:" + "description": "The import requires a specific schema for both JSON and CSV. The detailed schema is described in our documentation.", + "usersFound": "Found {{ count }} user(s) to import.", + "success": "{{ count }} user(s) imported.", + "failed": "The following users were not imported:", + "sendInviteCheckbox": "Send invitation email to imported users" }, "userExport": { "csv": "Export as CSV", diff --git a/src/translation/nl.json b/src/translation/nl.json index 790b2fdcf..74cb6d9e4 100644 --- a/src/translation/nl.json +++ b/src/translation/nl.json @@ -81,7 +81,9 @@ "users": "Gebruikers" }, "disableAction": "Uitschakelen", - "enableAction": "Inschakelen" + "enableAction": "Inschakelen", + "statusEnabled": "Ingeschakeld", + "statusDisabled": "Uitgeschakeld" }, "appstore": { "title": "App Store", @@ -579,7 +581,8 @@ "diskPath": "Schijf pad", "user": "Gebruiker", "privateKey": "Private sleutel", - "cifsSealSupport": "Gebruik seal encryptie. SMB v3 is hiervoor minimaal benodigd" + "cifsSealSupport": "Gebruik seal encryptie. SMB v3 is hiervoor minimaal benodigd", + "chown": "Extern bestandssysteem ondersteunt chown" }, "backupFailed": { "title": "Backup maken niet mogelijk" @@ -1428,7 +1431,9 @@ "owner": "Eigenaar", "aliases": "Aliassen", "usage": "Gebruik", - "title": "E-mailboxen" + "title": "E-mailboxen", + "importTooltip": "Import Mailboxen", + "exportTooltip": "Export Mailboxen" }, "mailinglists": { "title": "E-maillijsten", @@ -1577,7 +1582,16 @@ "updateMailinglistDialog": { "activeCheckbox": "Mailing-lijst is actief" }, - "howToConnectInfoModal": "Configureren e-mail clients" + "howToConnectInfoModal": "Configureren e-mail clients", + "mailboxImportDialog": { + "title": "Importeer Mailboxen", + "description": "Voor de import is een specifiek schema voor JSON benodigd. Het gedetailleerde schema is beschreven in onze documentatie", + "fileInput": "Selecteer JSON bestand", + "mailboxesFound": "{{ count }} mailboxen gevonden om te importeren", + "success": "{{ count }} mailboxen succesvol geïmporteerd.", + "failed": "De volgende mailboxen zijn niet geïmporteerd:", + "importAction": "Importeren" + } }, "login": { "loginTo": "Inloggen bij", diff --git a/src/translation/ru.json b/src/translation/ru.json index 778b0a541..2ab2aaa46 100644 --- a/src/translation/ru.json +++ b/src/translation/ru.json @@ -81,7 +81,9 @@ "navbar": { "users": "Пользователи" }, - "enableAction": "Включить" + "enableAction": "Включить", + "statusEnabled": "Включено", + "statusDisabled": "Выключено" }, "appstore": { "category": { @@ -863,7 +865,8 @@ "copyConcurrencyDescription": "Количество удаленных копий файлов, выгружаемых одновременно во время резервного копирования.", "password": "Пароль", "setupMountDescription": "Если опция включена, Cloudron настроит точку монтирования на сервере", - "cifsSealSupport": "Использовать SEAL шифрование. Требуется SMB v3" + "cifsSealSupport": "Использовать SEAL шифрование. Требуется SMB v3", + "chown": "Удалённая файловая система поддерживает chown" }, "title": "Резервные копии", "logs": { @@ -1463,7 +1466,9 @@ "name": "Имя", "owner": "Владелец", "usage": "Использовано", - "aliases": "Псевдонимы" + "aliases": "Псевдонимы", + "importTooltip": "Импортировать почтовые ящики", + "exportTooltip": "Экспортировать почтовые ящики" }, "title": "Входящие письма", "sieveServerInfo": "Сервис ManageSieve", @@ -1577,7 +1582,16 @@ "updateMailinglistDialog": { "activeCheckbox": "Список рассылки активен" }, - "howToConnectInfoModal": "Настройка почтовых клиентов" + "howToConnectInfoModal": "Настройка почтовых клиентов", + "mailboxImportDialog": { + "title": "Импортировать почтовые ящики", + "description": "Для импорта требуются файлы JSON заранее установленной разметкой. Подробнее о ней можно прочитать в нашей документации", + "fileInput": "Выбрать JSON файл", + "mailboxesFound": "Найдено {{ count }} почтовых ящиков для импорта", + "success": "{{ count }} почтовых ящиков успешно импортировано.", + "failed": "Следующие почтовые ящики не были импортированы:", + "importAction": "Импортировать" + } }, "login": { "password": "Пароль", diff --git a/src/views/users.html b/src/views/users.html index 1c299f74c..fff97530b 100644 --- a/src/views/users.html +++ b/src/views/users.html @@ -355,9 +355,13 @@
-
+
+ +

{{ userImport.error.file }}

-

{{ 'users.userImportDialog.usersFound' | tr:{ count: userImport.users.length } }}

+

{{ 'users.userImportDialog.usersFound' | tr:{ count: userImport.users.length } }}

diff --git a/src/views/users.js b/src/views/users.js index 1b39aefb6..42af81333 100644 --- a/src/views/users.js +++ b/src/views/users.js @@ -109,6 +109,7 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio percent: 0, success: 0, users: [], + sendInvite: false, reset: function () { $scope.userImport.busy = false; @@ -117,6 +118,7 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio $scope.userImport.percent = 0; $scope.userImport.success = 0; $scope.userImport.done = false; + $scope.userImport.sendInvite = false; }, handleFileChanged: function () { @@ -131,30 +133,45 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio const reader = new FileReader(); reader.addEventListener('load', function () { $scope.$apply(function () { + $scope.userImport.users = []; + var users = []; if (file.type === 'text/csv') { - $scope.userImport.users = []; - var lines = reader.result.split('\n'); if (lines.length === 0) return $scope.userImport.error = { file: 'Imported file has no lines' }; - lines.forEach(function (line) { + for (var i = 0; i < lines.length; i++) { + var line = lines[i].trim(); + if (!line) continue; var items = line.split(','); - if (items.length !== 4) return console.log('Wrong column count for:', line); - $scope.userImport.users.push({ - id: items[0].trim(), - username: items[1].trim(), - email: items[2].trim(), - displayName: items[3].trim() + if (items.length !== 5) { + $scope.userImport.error = { file: 'Line ' + (i+1) + ' has wrong column count. Expecting 5' }; + break; + } + users.push({ + username: items[0].trim(), + email: items[1].trim(), + fallbackEmail: items[2].trim(), + displayName: items[3].trim(), + role: items[4].trim() }); - }); + } } else { try { - $scope.userImport.users = JSON.parse(reader.result); + users = JSON.parse(reader.result).map(function (user) { + return { + username: user.username, + email: user.email, + fallbackEmail: user.fallbackEmail, + displayName: user.displayName, + role: user.role + }; + }); } catch (e) { console.error('Failed to parse users.', e); - $scope.userImport.error = { file: 'Imported file is not valid JSON' }; + $scope.userImport.error = { file: 'Imported file is not valid JSON:' + e.message }; } } + $scope.userImport.users = users; }); }, false); reader.readAsText(file); @@ -183,13 +200,20 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio var processed = 0; async.eachSeries($scope.userImport.users, function (user, callback) { - Client.addUser(user, function (error) { + Client.addUser(user, function (error, userId) { if (error) $scope.userImport.error.import.push({ error: error, user: user }); else ++$scope.userImport.success; ++processed; $scope.userImport.percent = 100 * processed / $scope.userImport.users.length; + if (!error && $scope.userImport.sendInvite) { + console.log('sending', userId, user.email); + Client.sendInviteEmail(userId, user.email, function (error) { + if (error) console.error('Failed to send invite.', error); + }); + } + callback(); }); }, function (error) { @@ -197,6 +221,10 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio $scope.userImport.busy = false; $scope.userImport.done = true; + if ($scope.userImport.success) { + refresh(); + refreshAllUsers(); + } }); } }; @@ -233,7 +261,7 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio var file = new Blob([ content ], { type: 'application/json' }); var a = document.createElement('a'); a.href = URL.createObjectURL(file); - a.download = 'users.json'; + a.download = type === 'json' ? 'users.json' : 'users.csv'; document.body.appendChild(a); a.click(); });