Update local user table to new design
This commit will introduce a reusable data table component.
By creating a reusable component, we can ensure tables in the
GUI will look consistent and common table actions (sort, select row)
are shared.
- Created new components directory to store shared components
- Add password-confirmation directive
- Remove some error handling from API utils so it can be
handled in the UI
TODO:
- Add show/hide toggle to password fields
- Enhance table component with icons
- Manual user unlock
- Batch table actions
- Role table
Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I03c95874d2942a2450a5da2f1d2a8bb895aa1746
diff --git a/app/users/controllers/user-accounts-controller.js b/app/users/controllers/user-accounts-controller.js
index 12ec170..11ba13d 100644
--- a/app/users/controllers/user-accounts-controller.js
+++ b/app/users/controllers/user-accounts-controller.js
@@ -10,211 +10,362 @@
'use strict';
angular.module('app.users').controller('userAccountsController', [
- '$scope', '$q', 'APIUtils', 'toastService',
- function($scope, $q, APIUtils, toastService) {
- $scope.users = [];
- $scope.roles = [];
- $scope.loading = true;
- $scope.properties = {};
- $scope.origProp = {};
- $scope.submitted = false;
+ '$scope', 'APIUtils', 'toastService', '$uibModal',
+ function($scope, APIUtils, toastService, $uibModal) {
+ $scope.loading;
+ $scope.accountSettings;
+ $scope.userRoles;
+ $scope.localUsers;
- function loadUserInfo() {
+ $scope.tableModel = {};
+ $scope.tableModel.data = [];
+ $scope.tableModel.header = ['Username', 'Privilege', 'Account status']
+ $scope.tableModel.actions = ['Edit', 'Delete'];
+
+ /**
+ * Data table mapper
+ * @param {*} user
+ */
+ function mapTableData(user) {
+ let accountStatus =
+ user.Locked ? 'Locked' : user.Enabled ? 'Enabled' : 'Disabled';
+ user.uiData = [user.UserName, user.RoleId, accountStatus];
+ return user;
+ }
+
+ /**
+ * API call to get all user accounts
+ */
+ function getLocalUsers() {
$scope.loading = true;
- $scope.submitted = false;
- $scope.isUserSelected = false;
- $scope.selectedUser = {};
- $scope.togglePassword = false;
- $scope.toggleVerify = false;
+ APIUtils.getAllUserAccounts()
+ .then((users) => {
+ $scope.localUsers = users;
+ $scope.tableModel.data = users.map(mapTableData);
+ })
+ .catch((error) => {
+ console.log(JSON.stringify(error));
+ toastService.error('Failed to load users.');
+ })
+ .finally(() => {
+ $scope.loading = false;
+ })
+ }
- $q.all([
- APIUtils.getAllUserAccounts().then(
- function(res) {
- $scope.users = res;
- },
- function(error) {
- console.log(JSON.stringify(error));
- }),
+ /**
+ * API call to get current Account settings
+ */
+ function getAccountSettings() {
+ APIUtils.getAllUserAccountProperties()
+ .then((settings) => {
+ $scope.accountSettings = settings;
+ })
+ .catch((error) => {
+ console.log(JSON.stringify(error));
+ $scope.accountSettings = null;
+ })
+ }
- APIUtils.getAllUserAccountProperties().then(
- function(res) {
- $scope.properties = res;
- $scope.origProp = angular.copy($scope.properties);
- },
- function(error) {
- console.log(JSON.stringify(error));
- }),
+ /**
+ * API call to get local user roles
+ */
+ function getUserRoles() {
+ APIUtils.getAccountServiceRoles()
+ .then((roles) => {
+ $scope.userRoles = roles;
+ })
+ .catch((error) => {
+ console.log(JSON.stringify(error));
+ $scope.userRoles = null;
+ })
+ }
- APIUtils.getAccountServiceRoles().then(
- function(res) {
- $scope.roles = res;
- },
- function(error) {
- console.log(JSON.stringify(error));
- })
- ]).finally(function() {
- $scope.loading = false;
- });
- };
-
- $scope.cancel = function() {
- loadUserInfo();
- };
-
- $scope.saveAllValues = function() {
+ /**
+ * API call to create new user
+ * @param {*} user
+ */
+ function createUser(username, password, role, enabled) {
$scope.loading = true;
- var data = {};
- if ($scope.properties.AccountLockoutDuration !=
- $scope.origProp.AccountLockoutDuration) {
- data['AccountLockoutDuration'] =
- $scope.properties.AccountLockoutDuration;
- }
- if ($scope.properties.AccountLockoutThreshold !=
- $scope.origProp.AccountLockoutThreshold) {
- data['AccountLockoutThreshold'] =
- $scope.properties.AccountLockoutThreshold;
- }
-
- if ($scope.properties.AccountLockoutDuration ==
- $scope.origProp.AccountLockoutDuration &&
- $scope.properties.AccountLockoutThreshold ==
- $scope.origProp.AccountLockoutThreshold) {
- // No change in properties, just return;
- $scope.loading = false;
- return;
- }
-
- APIUtils
- .saveUserAccountProperties(
- data['AccountLockoutDuration'], data['AccountLockoutThreshold'])
- .then(
- function(response) {
- toastService.success(
- 'User account properties have been updated successfully');
- },
- function(error) {
- toastService.error('Unable to update account properties');
- })
- .finally(function() {
- loadUserInfo();
+ APIUtils.createUser(username, password, role, enabled)
+ .then(() => {
+ getLocalUsers();
+ toastService.success(`User '${username}' has been created.`);
+ })
+ .catch((error) => {
+ console.log(JSON.stringify(error));
+ toastService.error(`Failed to create new user '${username}'.`);
+ })
+ .finally(() => {
$scope.loading = false;
});
- };
+ }
- $scope.setSelectedUser = function(user) {
- $scope.isUserSelected = true;
- $scope.selectedUser = angular.copy(user);
- $scope.selectedUser.VerifyPassword = null;
- // Used while renaming the user.
- $scope.selectedUser.CurrentUserName = $scope.selectedUser.UserName;
- };
- $scope.createNewUser = function() {
- if ($scope.users.length >= 15) {
- toastService.error(
- 'Cannot create user. The maximum number of users that can be created is 15');
- return;
- }
- if (!$scope.selectedUser.UserName || !$scope.selectedUser.Password) {
- toastService.error('Username or password cannot be empty');
- return;
- }
- if ($scope.selectedUser.Password !==
- $scope.selectedUser.VerifyPassword) {
- toastService.error('Passwords do not match');
- return;
- }
- if ($scope.doesUserExist()) {
- toastService.error('Username already exists');
- return;
- }
- var user = $scope.selectedUser.UserName;
- var passwd = $scope.selectedUser.Password;
- var role = $scope.selectedUser.RoleId;
- var enabled = false;
- if ($scope.selectedUser.Enabled != null) {
- enabled = $scope.selectedUser.Enabled;
- }
-
+ /**
+ * API call to update existing user
+ */
+ function updateUser(originalUsername, username, password, role, enabled) {
$scope.loading = true;
- APIUtils.createUser(user, passwd, role, enabled)
- .then(
- function(response) {
- toastService.success('User has been created successfully');
- },
- function(error) {
- toastService.error('Failed to create new user');
- })
- .finally(function() {
- loadUserInfo();
+ APIUtils.updateUser(originalUsername, username, password, role, enabled)
+ .then(() => {
+ getLocalUsers();
+ toastService.success('User has been updated successfully.')
+ })
+ .catch((error) => {
+ console.log(JSON.stringify(error));
+ toastService.error(`Unable to update user '${originalUsername}'.`)
+ })
+ .finally(() => {
+ $scope.loading = false;
+ })
+ }
+
+ /**
+ * API call to delete user
+ * @param {*} username
+ */
+ function deleteUser(username) {
+ $scope.loading = true;
+ APIUtils.deleteUser(username)
+ .then(() => {
+ getLocalUsers();
+ toastService.success(`User '${username}' has been deleted.`);
+ })
+ .catch((error) => {
+ console.log(JSON.stringify(error));
+ toastService.error(`Failed to delete user '${username}'.`);
+ })
+ .finally(() => {
$scope.loading = false;
});
- };
- $scope.updateUserInfo = function() {
- if ($scope.selectedUser.Password !==
- $scope.selectedUser.VerifyPassword) {
- toastService.error('Passwords do not match');
- return;
- }
- if ($scope.doesUserExist()) {
- toastService.error('Username already exists');
- return;
- }
- var data = {};
- if ($scope.selectedUser.UserName !==
- $scope.selectedUser.CurrentUserName) {
- data['UserName'] = $scope.selectedUser.UserName;
- }
- $scope.selectedUser.VerifyPassword = null;
- if ($scope.selectedUser.Password != null) {
- data['Password'] = $scope.selectedUser.Password;
- }
- data['RoleId'] = $scope.selectedUser.RoleId;
- data['Enabled'] = $scope.selectedUser.Enabled;
+ }
+ /**
+ * API call to save account policy settings
+ * @param {number} lockoutDuration
+ * @param {number} lockoutThreshold
+ */
+ function updateAccountSettings(lockoutDuration, lockoutThreshold) {
$scope.loading = true;
- APIUtils
- .updateUser(
- $scope.selectedUser.CurrentUserName, data['UserName'],
- data['Password'], data['RoleId'], data['Enabled'])
- .then(
- function(response) {
- toastService.success('User has been updated successfully');
- },
- function(error) {
- toastService.error('Unable to update user');
- })
- .finally(function() {
- loadUserInfo();
+ APIUtils.saveUserAccountProperties(lockoutDuration, lockoutThreshold)
+ .then(() => {
+ $scope.accountSettings['AccountLockoutDuration'] =
+ lockoutDuration;
+ $scope.accountSettings['AccountLockoutThreshold'] =
+ lockoutThreshold;
+ toastService.success(
+ 'Account policy settings have been updated.');
+ })
+ .catch((error) => {
+ console.log(JSON.stringify(error));
+ toastService.error('Failed to update account policy settings.');
+ })
+ .finally(() => {
$scope.loading = false;
});
- };
- $scope.deleteUser = function(userName) {
- $scope.loading = true;
- APIUtils.deleteUser(userName)
- .then(
- function(response) {
- toastService.success('User has been deleted successfully');
- },
- function(error) {
- toastService.error('Unable to delete user');
- })
- .finally(function() {
- loadUserInfo();
- $scope.loading = false;
- });
- };
+ }
- $scope.doesUserExist = function() {
- for (var i in $scope.users) {
- // If a user exists with the same user name and a different Id then
- // the username already exists and isn't valid
- if (($scope.users[i].UserName === $scope.selectedUser.UserName) &&
- ($scope.users[i].Id !== $scope.selectedUser.Id)) {
- return true;
- }
+ /**
+ * Initiate account settings modal
+ */
+ function initAccountSettingsModal() {
+ const template = require('./user-accounts-modal-settings.html');
+ $uibModal
+ .open({
+ template,
+ windowTopClass: 'uib-modal',
+ ariaLabelledBy: 'dialog_label',
+ controllerAs: 'modalCtrl',
+ controller: function() {
+ // If AccountLockoutDuration is not 0 the lockout
+ // method is automatic. If AccountLockoutDuration is 0 the
+ // lockout method is manual
+ const lockoutMethod =
+ $scope.accountSettings.AccountLockoutDuration ? 1 : 0;
+ this.settings = {};
+ this.settings.maxLogin =
+ $scope.accountSettings.AccountLockoutThreshold;
+ this.settings.lockoutMethod = lockoutMethod;
+ this.settings.timeoutDuration = !lockoutMethod ?
+ null :
+ $scope.accountSettings.AccountLockoutDuration;
+ }
+ })
+ .result
+ .then((form) => {
+ if (form.$valid) {
+ const lockoutDuration = form.lockoutMethod.$modelValue ?
+ form.timeoutDuration.$modelValue :
+ 0;
+ const lockoutThreshold = form.maxLogin.$modelValue;
+ updateAccountSettings(lockoutDuration, lockoutThreshold);
+ }
+ })
+ .catch(
+ () => {
+ // do nothing
+ })
+ }
+
+ /**
+ * Initiate user modal
+ * Can be triggered by clicking edit in table or 'Add user' button
+ * If triggered from the table, user parameter will be provided
+ * If triggered by add user button, user parameter will be undefined
+ * @optional @param {*} user
+ */
+ function initUserModal(user) {
+ if ($scope.userRoles === null || $scope.userRoles === undefined) {
+ // If userRoles failed to load, do not allow add/edit
+ // functionality
+ return;
+ }
+ const newUser = user ? false : true;
+ const originalUsername = user ? angular.copy(user.UserName) : null;
+ const template = require('./user-accounts-modal-user.html');
+ $uibModal
+ .open({
+ template,
+ windowTopClass: 'uib-modal',
+ ariaLabelledBy: 'dialog_label',
+ controllerAs: 'modalCtrl',
+ controller: function() {
+ // Set default status to Enabled
+ const status = newUser ? true : user.Enabled;
+ // Check if UserName is root
+ // Some form controls will be disabled for root users:
+ // edit enabled status, edit username, edit role
+ const isRoot =
+ newUser ? false : user.UserName === 'root' ? true : false;
+ // Array of existing usernames (excluding current user instance)
+ const existingUsernames =
+ $scope.localUsers.reduce((acc, val) => {
+ if (user && (val.UserName === user.UserName)) {
+ return acc;
+ }
+ acc.push(val.UserName);
+ return acc;
+ }, []);
+
+ this.user = {};
+ this.user.isRoot = isRoot;
+ this.user.new = newUser;
+ this.user.accountStatus = status;
+ this.user.username = newUser ? '' : user.UserName;
+ this.user.privilege = newUser ? '' : user.RoleId;
+
+ this.privilegeRoles = $scope.userRoles;
+ this.existingUsernames = existingUsernames;
+ this.minPasswordLength = $scope.accountSettings ?
+ $scope.accountSettings.MinPasswordLength :
+ null;
+ this.maxPasswordLength = $scope.accountSettings ?
+ $scope.accountSettings.MaxPasswordLength :
+ null;
+ }
+ })
+ .result
+ .then((form) => {
+ if (form.$valid) {
+ // If form control is pristine set property to null
+ // this will make sure only changed values are updated when
+ // modifying existing users
+ // API utils checks for null values
+ const username =
+ form.username.$pristine ? null : form.username.$modelValue;
+ const password =
+ form.password.$pristine ? null : form.password.$modelValue;
+ const role = form.privilege.$pristine ?
+ null :
+ form.privilege.$modelValue;
+ const enabled = (form.accountStatus.$pristine &&
+ form.accountStatus1.$pristine) ?
+ null :
+ form.accountStatus.$modelValue;
+
+ if (!newUser) {
+ updateUser(
+ originalUsername, username, password, role, enabled);
+ } else {
+ createUser(
+ username, password, role, form.accountStatus.$modelValue);
+ }
+ }
+ })
+ .catch(
+ () => {
+ // do nothing
+ })
+ }
+
+ /**
+ * Intiate remove user modal
+ * @param {*} user
+ */
+ function initRemoveModal(user) {
+ const template = require('./user-accounts-modal-remove.html');
+ $uibModal
+ .open({
+ template,
+ windowTopClass: 'uib-modal',
+ ariaLabelledBy: 'dialog_label',
+ controllerAs: 'modalCtrl',
+ controller: function() {
+ this.user = user.UserName;
+ }
+ })
+ .result
+ .then(() => {
+ const isRoot = user.UserName === 'root' ? true : false;
+ if (isRoot) {
+ toastService.error(`Cannot delete 'root' user.`)
+ return;
+ }
+ deleteUser(user.UserName);
+ })
+ .catch(
+ () => {
+ // do nothing
+ })
+ }
+
+ /**
+ * Callback when action emitted from table
+ * @param {*} value
+ */
+ $scope.onEmitAction = (value) => {
+ switch (value.action) {
+ case 'Edit':
+ initUserModal(value.row);
+ break;
+ case 'Delete':
+ initRemoveModal(value.row);
+ break;
+ default:
}
};
- loadUserInfo();
+
+ /**
+ * Callback when 'Account settings policy' button clicked
+ */
+ $scope.onClickAccountSettingsPolicy = () => {
+ initAccountSettingsModal();
+ };
+
+ /**
+ * Callback when 'Add user' button clicked
+ */
+ $scope.onClickAddUser = () => {
+ initUserModal();
+ };
+
+ /**
+ * Callback when controller view initially loaded
+ */
+ $scope.$on('$viewContentLoaded', () => {
+ getLocalUsers();
+ getUserRoles();
+ getAccountSettings();
+ })
}
]);
})(angular);