'use strict'; /* global angular */ /* global Clipboard */ /* global async */ /* global ROLES */ /* global $ */ angular.module('Application').controller('UsersController', ['$scope', '$location', '$translate', '$timeout', 'Client', function ($scope, $location, $translate, $timeout, Client) { Client.onReady(function () { if (!Client.getUserInfo().isAtLeastUserManager) $location.path('/'); }); $scope.ready = false; $scope.users = []; // users of current page $scope.allUsersById = []; $scope.groups = []; $scope.groupsById = { }; $scope.config = Client.getConfig(); $scope.userInfo = Client.getUserInfo(); $scope.domains = []; $scope.roles = []; $scope.allUsers = []; // all the users and not just current page, have to load this for group assignment $scope.userSearchString = ''; $scope.currentPage = 1; $scope.pageItems = localStorage.cloudronPageSize || 15; $scope.userRefreshBusy = true; $scope.userStates = [ { state: 'ALL', value: null, label: 'All Users' }, { state: 'ACTIVE', value: true, label: 'Active Users' }, { state: 'INACTIVE', value: false, label: 'Inactive Users' } ]; $translate(['users.stateFilter.all', 'users.stateFilter.active', 'users.stateFilter.inactive']).then(function (tr) { if (tr['users.stateFilter.all']) $scope.userStates.find(function (a) { return a.state === 'ALL'; }).label = tr['users.stateFilter.all']; if (tr['users.stateFilter.active']) $scope.userStates.find(function (a) { return a.state === 'ACTIVE'; }).label = tr['users.stateFilter.active']; if (tr['users.stateFilter.inactive']) $scope.userStates.find(function (a) { return a.state === 'INACTIVE'; }).label = tr['users.stateFilter.inactive']; }); $scope.userStateFilter = $scope.userStates[0]; $scope.$watch('userStateFilter', function (newVal, oldVal) { if (newVal === oldVal) return; $scope.updateFilter(); }); $scope.groupMembers = function (group) { return group.userIds.filter(function (uid) { return !!$scope.allUsersById[uid]; }).map(function (uid) { return $scope.allUsersById[uid].username || $scope.allUsersById[uid].email; }).join(' '); }; $scope.canEdit = function (user) { let roleInt1 = $scope.roles.findIndex(function (role) { return role.id === $scope.userInfo.role; }); let roleInt2 = $scope.roles.findIndex(function (role) { return role.id === user.role; }); return (roleInt1 - roleInt2) >= 0; }; $scope.canImpersonate = function (user) { // only admins can impersonate if (!$scope.userInfo.isAtLeastAdmin) return false; // only users with username can be impersonated if (!user.username) return false; // normal admins cannot impersonate owners if (!$scope.userInfo.isAtLeastOwner && [ ROLES.OWNER ].indexOf(user.role) !== -1) return false; return true; }; $scope.userImport = { busy: false, done: false, error: null, percent: 0, success: 0, users: [], sendInvite: false, reset: function () { $scope.userImport.busy = false; $scope.userImport.error = null; $scope.userImport.users = []; $scope.userImport.percent = 0; $scope.userImport.success = 0; $scope.userImport.done = false; $scope.userImport.sendInvite = false; }, handleFileChanged: function () { $scope.userImport.reset(); var fileInput = document.getElementById('userImportFileInput'); if (!fileInput.files || !fileInput.files[0]) return; var file = fileInput.files[0]; if (file.type !== 'application/json' && file.type !== 'text/csv') return console.log('Unsupported file type.'); const reader = new FileReader(); reader.addEventListener('load', function () { $scope.$apply(function () { $scope.userImport.users = []; var users = []; if (file.type === 'text/csv') { var lines = reader.result.split('\n'); if (lines.length === 0) return $scope.userImport.error = { file: 'Imported file has no lines' }; for (var i = 0; i < lines.length; i++) { var line = lines[i].trim(); if (!line) continue; var items = line.split(','); if (items.length !== 5) { $scope.userImport.error = { file: 'Line ' + (i+1) + ' has wrong column count. Expecting 5' }; return; } users.push({ username: items[0].trim(), email: items[1].trim(), fallbackEmail: items[2].trim(), displayName: items[3].trim(), role: items[4].trim() }); } } else { try { 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:' + e.message }; } } $scope.userImport.users = users; }); }, false); reader.readAsText(file); }, show: function () { $scope.userImport.reset(); // named so no duplactes document.getElementById('userImportFileInput').addEventListener('change', $scope.userImport.handleFileChanged); $('#userImportModal').modal('show'); }, openFileInput: function () { $('#userImportFileInput').click(); }, import: function () { $scope.userImport.percent = 0; $scope.userImport.success = 0; $scope.userImport.done = false; $scope.userImport.error = { import: [] }; $scope.userImport.busy = true; var processed = 0; async.eachSeries($scope.userImport.users, function (user, callback) { 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) { if (error) return console.error(error); $scope.userImport.busy = false; $scope.userImport.done = true; if ($scope.userImport.success) { refresh(); refreshAllUsers(); } }); } }; // supported types are 'json' and 'csv' $scope.userExport = function (type) { Client.getAllUsers(function (error, result) { if (error) { Client.error('Failed to list users. Full error in the webinspector.'); return console.error('Failed to list users.', error); } var content = ''; if (type === 'json') { content = JSON.stringify(result.map(function (user) { return { id: user.id, username: user.username, email: user.email, fallbackEmail: user.fallbackEmail, displayName: user.displayName, role: user.role, active: user.active }; }), null, 2); } else if (type === 'csv') { content = result.map(function (user) { return [ user.id, user.username, user.email, user.fallbackEmail, user.displayName, user.role, user.active ].join(','); }).join('\n'); } else { return; } var file = new Blob([ content ], { type: type === 'json' ? 'application/json' : 'text/csv' }); var a = document.createElement('a'); a.href = URL.createObjectURL(file); a.download = type === 'json' ? 'users.json' : 'users.csv'; document.body.appendChild(a); a.click(); }); }; $scope.userremove = { busy: false, error: null, userInfo: {}, show: function (userInfo) { $scope.userremove.error = null; $scope.userremove.userInfo = userInfo; $('#userRemoveModal').modal('show'); }, submit: function () { $scope.userremove.busy = true; Client.removeUser($scope.userremove.userInfo.id, function (error) { $scope.userremove.busy = false; if (error && error.statusCode === 403) return $scope.userremove.error = error.message; else if (error) return console.error('Unable to delete user.', error); $scope.userremove.userInfo = {}; refresh(); refreshAllUsers(); $('#userRemoveModal').modal('hide'); }); } }; $scope.useradd = { busy: false, alreadyTaken: false, error: {}, email: '', fallbackEmail: '', username: '', displayName: '', selectedGroups: [], role: 'user', sendInvite: false, show: function () { $scope.useradd.error = {}; $scope.useradd.email = ''; $scope.useradd.fallbackEmail = ''; $scope.useradd.username = ''; $scope.useradd.displayName = ''; $scope.useradd.selectedGroups = []; $scope.useradd.role = 'user'; $scope.useradd.sendInvite = false; $scope.useraddForm.$setUntouched(); $scope.useraddForm.$setPristine(); $('#userAddModal').modal('show'); }, submit: function () { $scope.useradd.busy = true; $scope.useradd.alreadyTaken = false; $scope.useradd.error.email = null; $scope.useradd.error.fallbackEmail = null; $scope.useradd.error.username = null; $scope.useradd.error.displayName = null; var user = { username: $scope.useradd.username || null, email: $scope.useradd.email, fallbackEmail: $scope.useradd.fallbackEmail, displayName: $scope.useradd.displayName, role: $scope.useradd.role }; Client.addUser(user, function (error, userId) { if (error) { $scope.useradd.busy = false; if (error.statusCode === 409) { if (error.message.toLowerCase().indexOf('email') !== -1) { $scope.useradd.error.email = 'Email already taken'; $scope.useraddForm.email.$setPristine(); $('#inputUserAddEmail').focus(); } else if (error.message.toLowerCase().indexOf('username') !== -1 || error.message.toLowerCase().indexOf('mailbox') !== -1) { $scope.useradd.error.username = 'Username already taken'; $scope.useraddForm.username.$setPristine(); $('#inputUserAddUsername').focus(); } else { // should not happen!! console.error(error.message); } return; } else if (error.statusCode === 400) { if (error.message.toLowerCase().indexOf('email') !== -1) { $scope.useradd.error.email = 'Invalid Email'; $scope.useradd.error.emailAttempted = $scope.useradd.email; $scope.useraddForm.email.$setPristine(); $('#inputUserAddEmail').focus(); } else if (error.message.toLowerCase().indexOf('username') !== -1) { $scope.useradd.error.username = error.message; $scope.useraddForm.username.$setPristine(); $('#inputUserAddUsername').focus(); } else { console.error('Unable to create user.', error.statusCode, error.message); } return; } else { return console.error('Unable to create user.', error.statusCode, error.message); } } var groupIds = $scope.useradd.selectedGroups.map(function (g) { return g.id; }); Client.setGroups(userId, groupIds, function (error) { $scope.useradd.busy = false; if (error) return console.error(error); if ($scope.useradd.sendInvite) Client.sendInviteEmail(userId, user.email, function (error) { if (error) console.error('Failed to send invite.', error); }); refresh(); refreshAllUsers(); $('#userAddModal').modal('hide'); }); }); } }; $scope.useredit = { busy: false, reset2FABusy: false, error: {}, userInfo: {}, // form fields username: '', email: '', fallbackEmail: '', aliases: {}, displayName: '', active: false, source: '', selectedGroups: [], role: '', show: function (userInfo) { $scope.useredit.error = {}; $scope.useredit.username = userInfo.username; $scope.useredit.email = userInfo.email; $scope.useredit.displayName = userInfo.displayName; $scope.useredit.fallbackEmail = userInfo.fallbackEmail; $scope.useredit.userInfo = userInfo; $scope.useredit.selectedGroups = userInfo.groupIds.map(function (gid) { return $scope.groupsById[gid]; }); $scope.useredit.active = userInfo.active; $scope.useredit.source = userInfo.source; $scope.useredit.role = userInfo.role; $scope.useredit_form.$setPristine(); $scope.useredit_form.$setUntouched(); $('#userEditModal').modal('show'); }, submit: function () { $scope.useredit.error = {}; $scope.useredit.busy = true; var userId = $scope.useredit.userInfo.id; var data = { id: userId }; // only send if not the current active user if (userId !== $scope.userInfo.id) { data.active = $scope.useredit.active; data.role = $scope.useredit.role; } // only change those if it is a local user if (!$scope.useredit.source) { // username is settable only if it was empty previously. it's editable for the "lock" profiles feature if (!$scope.useredit.userInfo.username) data.username = $scope.useredit.username; data.email = $scope.useredit.email; data.displayName = $scope.useredit.displayName; data.fallbackEmail = $scope.useredit.fallbackEmail; } Client.updateUser(data, function (error) { if (error) { $scope.useredit.busy = false; if (error.statusCode === 409) { if (error.message.toLowerCase().indexOf('email') !== -1) { $scope.useredit.error.email = 'Email already taken'; } else if (error.message.toLowerCase().indexOf('username') !== -1) { $scope.useredit.error.username = 'Username already taken'; } $scope.useredit_form.email.$setPristine(); $('#inputUserEditEmail').focus(); } else { $scope.useredit.error.generic = error.message; console.error('Unable to update user:', error); } return; } var groupIds = $scope.useredit.selectedGroups.map(function (g) { return g.id; }); Client.setGroups(data.id, groupIds, function (error) { $scope.useredit.busy = false; if (error) return console.error('Unable to update groups for user:', error); refreshUsers(false); $('#userEditModal').modal('hide'); }); }); }, reset2FA: function () { $scope.useredit.reset2FABusy = true; Client.disableTwoFactorAuthenticationByUserId($scope.useredit.userInfo.id, function (error) { if (error) return console.error(error); $timeout(function () { $scope.useredit.userInfo.twoFactorAuthenticationEnabled = false; $scope.useredit.reset2FABusy = false; }, 3000); }); } }; $scope.groupAdd = { busy: false, error: {}, name: '', selectedUsers: [], show: function () { $scope.groupAdd.busy = false; $scope.groupAdd.error = {}; $scope.groupAdd.name = ''; $scope.groupAddForm.$setUntouched(); $scope.groupAddForm.$setPristine(); $('#groupAddModal').modal('show'); }, submit: function () { $scope.groupAdd.busy = true; $scope.groupAdd.error = {}; Client.createGroup($scope.groupAdd.name, function (error, result) { if (error) { $scope.groupAdd.busy = false; if (error.statusCode === 409) { $scope.groupAdd.error.name = 'Name already taken'; $scope.groupAddForm.name.$setPristine(); $('#groupAddName').focus(); return; } else if (error.statusCode === 400) { $scope.groupAdd.error.name = error.message; $scope.groupAddForm.name.$setPristine(); $('#groupAddName').focus(); return; } else { return console.error('Unable to create group.', error.statusCode, error.message); } } var userIds = $scope.groupAdd.selectedUsers.map(function (u) { return u.id; }); Client.setGroupMembers(result.id, userIds, function (error) { $scope.groupAdd.busy = false; if (error) return console.error('Unable to add memebers.', error.statusCode, error.message); refresh(); $('#groupAddModal').modal('hide'); }); }); } }; $scope.groupEdit = { busy: false, error: {}, groupInfo: {}, name: '', source: '', selectedUsers: [], selectedApps: [], selectedAppsOriginal: [], apps: [], show: function (groupInfo) { $scope.groupEdit.error = {}; $scope.groupEdit.groupInfo = groupInfo; $scope.groupEdit.name = groupInfo.name; $scope.groupEdit.source = groupInfo.source; $scope.groupEdit.selectedUsers = groupInfo.userIds.map(function (uid) { return $scope.allUsersById[uid]; }); $scope.groupEdit.apps = Client.getInstalledApps(); $scope.groupEdit.selectedApps = Client.getInstalledApps().filter(function (app) { if (app.accessRestriction === null || !Array.isArray(app.accessRestriction.groups)) return false; return app.accessRestriction.groups.indexOf(groupInfo.id) !== -1; }); angular.copy($scope.groupEdit.selectedApps, $scope.groupEdit.selectedAppsOriginal); $scope.groupEdit_form.$setPristine(); $scope.groupEdit_form.$setUntouched(); $('#groupEditModal').modal('show'); }, submit: function () { $scope.groupEdit.busy = true; $scope.groupEdit.error = {}; Client.updateGroup($scope.groupEdit.groupInfo.id, $scope.groupEdit.name, function (error) { if (error) { $scope.groupEdit.busy = false; if (error.statusCode === 409) { $scope.groupEdit.error.name = 'Name already taken'; $scope.groupEditForm.name.$setPristine(); $('#groupEditName').focus(); return; } else if (error.statusCode === 400) { $scope.groupEdit.error.name = error.message; $scope.groupEditForm.name.$setPristine(); $('#groupEditName').focus(); return; } else { return console.error('Unable to edit group.', error.statusCode, error.message); } } var userIds = $scope.groupEdit.selectedUsers.map(function (u) { return u.id; }); Client.setGroupMembers($scope.groupEdit.groupInfo.id, userIds, function (error) { if (error) { $scope.groupEdit.busy = false; return console.error('Unable to set group members.', error.statusCode, error.message); } // find apps where ACL has changed var addedApps = $scope.groupEdit.selectedApps.filter(function (a) { return !$scope.groupEdit.selectedAppsOriginal.find(function (b) { return b.id === a.id; }); }); var removedApps = $scope.groupEdit.selectedAppsOriginal.filter(function (a) { return !$scope.groupEdit.selectedApps.find(function (b) { return b.id === a.id; }); }); async.eachSeries(addedApps, function (app, callback) { var accessRestriction = app.accessRestriction; if (!accessRestriction) accessRestriction = { users: [], groups: [] }; if (!Array.isArray(accessRestriction.groups)) accessRestriction.groups = []; accessRestriction.groups.push($scope.groupEdit.groupInfo.id); Client.configureApp(app.id, 'access_restriction', { accessRestriction: accessRestriction }, callback); }, function (error) { if (error) { $scope.groupEdit.busy = false; return console.error('Unable to set added app access.', error.statusCode, error.message); } async.eachSeries(removedApps, function (app, callback) { var accessRestriction = app.accessRestriction; if (!accessRestriction) accessRestriction = { users: [], groups: [] }; if (!Array.isArray(accessRestriction.groups)) accessRestriction.groups = []; var deleted = accessRestriction.groups.splice(accessRestriction.groups.indexOf($scope.groupEdit.groupInfo.id), 1); // if not found return early if (deleted.length === 0) return callback(); Client.configureApp(app.id, 'access_restriction', { accessRestriction: accessRestriction }, callback); }, function (error) { $scope.groupEdit.busy = false; if (error) return console.error('Unable to set removed app access.', error.statusCode, error.message); refresh(); // refresh apps to reflect change Client.refreshInstalledApps(); $('#groupEditModal').modal('hide'); }); }); }); }); } }; $scope.groupRemove = { busy: false, error: {}, group: null, memberCount: 0, show: function (group) { $scope.groupRemove.busy = false; $scope.groupRemove.error = {}; $scope.groupRemove.group = angular.copy(group); Client.getGroup(group.id, function (error, result) { if (error) return console.error('Unable to fetch group information.', error.statusCode, error.message); $scope.groupRemove.memberCount = result.userIds.length; $('#groupRemoveModal').modal('show'); }); }, submit: function () { $scope.groupRemove.busy = true; $scope.groupRemove.error = {}; Client.removeGroup($scope.groupRemove.group.id, function (error) { $scope.groupRemove.busy = false; if (error) return console.error('Unable to remove group.', error.statusCode, error.message); refresh(); $('#groupRemoveModal').modal('hide'); }); } }; $scope.isMe = function (user) { return user.username === Client.getUserInfo().username; }; $scope.passwordReset = { busy: false, resetLink: '', user: null, email: '', emailError: null, show: function (user) { $scope.passwordReset.busy = false; $scope.passwordReset.resetLink = ''; $scope.passwordReset.user = user; $scope.passwordReset.email = user.fallbackEmail || user.email; $scope.passwordReset.emailError = null; Client.getPasswordResetLink(user.id, function (error, result) { if (error) return console.error('Failed to get password reset link.', error); $scope.passwordReset.resetLink = result.passwordResetLink; $('#passwordResetModal').modal('show'); }); }, sendEmail: function () { $scope.passwordReset.busy = true; $scope.passwordReset.emailError = null; Client.sendPasswordResetEmail($scope.passwordReset.user.id, $scope.passwordReset.email, function (error) { $scope.passwordReset.busy = false; if (error) { $scope.passwordReset.emailError = error.message; } else { $scope.passwordReset.emailError = ''; } }); } }; $scope.makeLocal = { busy: false, user: null, show: function (user) { $scope.makeLocal.busy = false; $scope.makeLocal.user = user; $('#makeLocalModal').modal('show'); }, submit: function () { $scope.makeLocal.busy = false; Client.makeUserLocal($scope.makeLocal.user.id, function (error) { if (error) return console.error('Failed to make user local.', error); $scope.makeLocal.busy = false; refreshUsers(); $('#makeLocalModal').modal('hide'); }); } }; $scope.invitation = { busy: false, inviteLink: '', user: null, email: '', show: function (user) { $scope.invitation.busy = false; $scope.invitation.inviteLink = ''; $scope.invitation.user = user; $scope.invitation.email = user.email; Client.getInviteLink(user.id, function (error, result) { if (error) return console.error('Failed to get invite link.', error); $scope.invitation.inviteLink = result.inviteLink; $('#invitationModal').modal('show'); }); }, sendEmail: function () { $scope.invitation.busy = true; Client.sendInviteEmail($scope.invitation.user.id, $scope.invitation.email, function (error) { if (error) return console.error('Failed to send invite email.', error); $scope.invitation.busy = false; Client.notify($translate.instant('users.invitationNotification.title'), $translate.instant('users.invitationNotification.body', { email: $scope.invitation.email }), false, 'success'); }); } }; // https://stackoverflow.com/questions/1497481/javascript-password-generator function generatePassword() { var length = 12, charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', retVal = ''; for (var i = 0, n = charset.length; i < length; ++i) { retVal += charset.charAt(Math.floor(Math.random() * n)); } return retVal; } $scope.setGhost = { busy: false, error: null, success: false, user: null, password: '', show: function (user) { $scope.setGhost.busy = false; $scope.setGhost.success = false; $scope.setGhost.error = null; $scope.setGhost.user = user; $scope.setGhost.password = ''; $('#setGhostModal').modal('show'); }, generatePassword: function () { $scope.setGhost.password = generatePassword(); }, submit: function () { $scope.setGhost.busy = true; Client.setGhost($scope.setGhost.user.id, $scope.setGhost.password, null, function (error) { $scope.setGhost.busy = false; if (error) { $scope.setGhost.error = error.message; return console.error(error); } $scope.setGhost.success = true; }); } }; $scope.profileConfig = { editableUserProfiles: true, mandatory2FA: false, errorMessage: '', refresh: function () { Client.getProfileConfig(function (error, result) { if (error) return console.error('Unable to get profile config.', error); $scope.profileConfig.editableUserProfiles = !result.lockUserProfiles; $scope.profileConfig.mandatory2FA = !!result.mandatory2FA; }); }, submit: function () { // prevent the current user from getting locked out if ($scope.profileConfig.mandatory2FA && !$scope.userInfo.twoFactorAuthenticationEnabled) return Client.notify('', $translate.instant('users.settings.require2FAWarning'), true, 'error', '#/profile'); $scope.profileConfig.error = ''; $scope.profileConfig.busy = true; $scope.profileConfig.success = false; var data = { lockUserProfiles: !$scope.profileConfig.editableUserProfiles, mandatory2FA: $scope.profileConfig.mandatory2FA }; Client.setProfileConfig(data, function (error) { if (error) $scope.profileConfig.errorMessage = error.message; $scope.profileConfig.success = true; $scope.profileConfigForm.$setUntouched(); $scope.profileConfigForm.$setPristine(); Client.refreshConfig(); // refresh the $scope.config $timeout(function () { $scope.profileConfig.busy = false; }, 500); }); } }; function getUsers(callback) { var users = []; Client.getUsers($scope.userSearchString, $scope.userStateFilter.value, $scope.currentPage, $scope.pageItems, function (error, results) { if (error) return console.error(error); async.eachOf(results, function (result, index, iteratorDone) { Client.getUser(result.id, function (error, user) { if (error) return iteratorDone(error); users[index] = user; // keep the sorting order iteratorDone(); }); }, function (error) { callback(error, users); }); }); } function refreshUsers(showBusy) { // loads users on current page only if (showBusy) $scope.userRefreshBusy = true; getUsers(function (error, result) { if (error) return console.error('Unable to get user listing.', error); angular.copy(result, $scope.users); $scope.ready = true; $scope.userRefreshBusy = false; }); } function refreshGroups(callback) { Client.getGroups(function (error, result) { if (error) { if (callback) return callback(error); else return console.error('Unable to get group listing.', error); } angular.copy(result, $scope.groups); $scope.groupsById = { }; for (var i = 0; i < result.length; i++) { $scope.groupsById[result[i].id] = result[i]; } if (callback) callback(); }); } function refresh() { refreshGroups(function (error) { if (error) return console.error('Unable to get group listing.', error); refreshUsers(true); }); } $scope.showNextPage = function () { $scope.currentPage++; refreshUsers(); }; $scope.showPrevPage = function () { if ($scope.currentPage > 1) $scope.currentPage--; else $scope.currentPage = 1; refreshUsers(); }; $scope.updateFilter = function () { refreshUsers(); }; function refreshAllUsers() { // this loads all users on Cloudron, not just current page Client.getAllUsers(function (error, results) { if (error) return console.error(error); $scope.allUsers = results; $scope.allUsersById = {}; for (var i = 0; i < results.length; i++) { $scope.allUsersById[results[i].id] = results[i]; } }); } function getAllDomains() { Client.getDomains(function (error, domains) { if (error) return console.error('Unable to get domains:', error); $scope.domains = domains; }); } Client.onReady(function () { refresh(); if ($scope.user.isAtLeastAdmin) $scope.profileConfig.refresh(); if ($scope.user.isAtLeastAdmin) getAllDomains(); refreshAllUsers(); // Order matters for permissions used in canEdit $scope.roles = [ { id: 'user', name: $translate.instant('users.role.user'), disabled: false }, { id: 'usermanager', name: $translate.instant('users.role.usermanager'), disabled: false }, { id: 'mailmanager', name: $translate.instant('users.role.mailmanager'), disabled: false }, { id: 'admin', name: $translate.instant('users.role.admin'), disabled: !$scope.user.isAtLeastAdmin }, { id: 'owner', name: $translate.instant('users.role.owner'), disabled: !$scope.user.isAtLeastOwner } ]; // give search the initial focus setTimeout(function () { $('#userSearchInput').focus(); }, 1); }); // setup all the dialog focus handling ['userAddModal', 'userRemoveModal', 'userEditModal', 'groupAddModal', 'groupEditModal', 'groupRemoveModal'].forEach(function (id) { $('#' + id).on('shown.bs.modal', function () { $(this).find("[autofocus]:first").focus(); }); }); new Clipboard('#passwordResetLinkClipboardButton').on('success', function(e) { $('#passwordResetLinkClipboardButton').tooltip({ title: 'Copied!', trigger: 'manual' }).tooltip('show'); $timeout(function () { $('#passwordResetLinkClipboardButton').tooltip('hide'); }, 2000); e.clearSelection(); }).on('error', function(/*e*/) { $('#passwordResetLinkClipboardButton').tooltip({ title: 'Press Ctrl+C to copy', trigger: 'manual' }).tooltip('show'); $timeout(function () { $('#passwordResetLinkClipboardButton').tooltip('hide'); }, 2000); }); new Clipboard('#invitationLinkClipboardButton').on('success', function(e) { $('#invitationLinkClipboardButton').tooltip({ title: 'Copied!', trigger: 'manual' }).tooltip('show'); $timeout(function () { $('#invitationLinkClipboardButton').tooltip('hide'); }, 2000); e.clearSelection(); }).on('error', function(/*e*/) { $('#invitationLinkClipboardButton').tooltip({ title: 'Press Ctrl+C to copy', trigger: 'manual' }).tooltip('show'); $timeout(function () { $('#invitationLinkClipboardButton').tooltip('hide'); }, 2000); }); new Clipboard('#setGhostClipboardButton').on('success', function(e) { $('#setGhostClipboardButton').tooltip({ title: 'Copied!', trigger: 'manual' }).tooltip('show'); $timeout(function () { $('#setGhostClipboardButton').tooltip('hide'); }, 2000); e.clearSelection(); }).on('error', function(/*e*/) { $('#setGhostClipboardButton').tooltip({ title: 'Press Ctrl+C to copy', trigger: 'manual' }).tooltip('show'); $timeout(function () { $('#setGhostClipboardButton').tooltip('hide'); }, 2000); }); $('.modal-backdrop').remove(); }]);