Update users navigation section

- Changed the section name to be access-control
- Moved LDAP Settings and Certificate Management to access-control navigation
- Changed Manage User Account subsection name to Local User Management

Resolves: openbmc/phosphor-webui#619

Signed-off-by: Mira Murali <miramurali23@gmail.com>
Signed-off-by: Derick Montague <derick.montague@ibm.com>
Change-Id: I0d94c80c295b997d94c04330fd87f4fc4d229bf8
diff --git a/app/access-control/controllers/user-controller.js b/app/access-control/controllers/user-controller.js
new file mode 100644
index 0000000..8ee8f88
--- /dev/null
+++ b/app/access-control/controllers/user-controller.js
@@ -0,0 +1,481 @@
+/**
+ * Controller for user Accounts
+ *
+ * @module app/access-control
+ * @exports userController
+ * @name userController
+ */
+
+window.angular && (function(angular) {
+  'use strict';
+
+  angular.module('app.accessControl').controller('userController', [
+    '$scope', 'APIUtils', 'toastService', '$uibModal', '$q',
+    function($scope, APIUtils, toastService, $uibModal, $q) {
+      $scope.loading;
+      $scope.accountSettings;
+      $scope.userRoles;
+      $scope.localUsers;
+
+      $scope.tableData = [];
+      $scope.tableHeader = [
+        {label: 'Username'}, {label: 'Privilege'}, {label: 'Account status'}
+      ];
+      $scope.tableBatchActions = [
+        {type: 'delete', label: 'Remove'},
+        {type: 'enable', label: 'Enable'},
+        {type: 'disable', label: 'Disable'},
+      ];
+
+      /**
+       * Returns true if username is 'root'
+       * @param {*} user
+       */
+      function checkIfRoot(user) {
+        return user.UserName === 'root' ? true : false;
+      }
+
+      /**
+       * Data table mapper
+       * @param {*} user
+       * @returns user
+       */
+      function mapTableData(user) {
+        const accountStatus =
+            user.Locked ? 'Locked' : user.Enabled ? 'Enabled' : 'Disabled';
+        const editAction = {type: 'Edit', enabled: true, file: 'icon-edit.svg'};
+        const deleteAction = {
+          type: 'Delete',
+          enabled: checkIfRoot(user) ? false : true,
+          file: 'icon-trashcan.svg'
+        };
+        user.selectable = checkIfRoot(user) ? false : true;
+        user.actions = [editAction, deleteAction];
+        user.uiData = [user.UserName, user.RoleId, accountStatus];
+        return user;
+      }
+
+      /**
+       * Returns lockout method based on the lockout duration property
+       * If the lockoutDuration is greater than 0 the lockout method
+       * is automatic otherwise the lockout method is manual
+       * @param {number} lockoutDuration
+       * @returns {number} : returns the account lockout method
+       *                     1(automatic) / 0(manual)
+       */
+      function mapLockoutMethod(lockoutDuration) {
+        return lockoutDuration > 0 ? 1 : 0;
+      }
+
+      /**
+       * API call to get all user accounts
+       */
+      function getLocalUsers() {
+        $scope.loading = true;
+        APIUtils.getAllUserAccounts()
+            .then((users) => {
+              $scope.localUsers = users;
+              $scope.tableData = users.map(mapTableData);
+            })
+            .catch((error) => {
+              console.log(JSON.stringify(error));
+              toastService.error('Failed to load users.');
+            })
+            .finally(() => {
+              $scope.loading = false;
+            })
+      }
+
+      /**
+       * 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;
+            })
+      }
+
+      /**
+       * 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;
+            })
+      }
+
+      /**
+       * API call to create new user
+       * @param {*} user
+       */
+      function createUser(username, password, role, enabled) {
+        $scope.loading = true;
+        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;
+            });
+      }
+
+      /**
+       * API call to update existing user
+       */
+      function updateUser(
+          originalUsername, username, password, role, enabled, locked) {
+        $scope.loading = true;
+        APIUtils
+            .updateUser(
+                originalUsername, username, password, role, enabled, locked)
+            .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 users
+       * @param {*} users : Array of users to delete
+       */
+      function deleteUsers(users = []) {
+        $scope.loading = true;
+        const promises =
+            users.map((user) => APIUtils.deleteUser(user.UserName));
+        $q.all(promises)
+            .then(() => {
+              let message;
+              if (users.length > 1) {
+                message = 'Users have been removed.'
+              } else {
+                message = `User '${users[0].UserName}' has been removed.`
+              }
+              toastService.success(message);
+            })
+            .catch((error) => {
+              console.log(JSON.stringify(error));
+              let message;
+              if (users.length > 1) {
+                message = 'Failed to remove users.'
+              } else {
+                message = `Failed to remove user '${users[0].UserName}'.`
+              }
+              toastService.error(message);
+            })
+            .finally(() => {
+              getLocalUsers();
+              $scope.loading = false;
+            });
+      }
+
+      /**
+       * API call to update user status enabled/disabled
+       * @param {*} users : Array of users to update
+       * @param {boolean} enabled : status
+       */
+      function updateUserStatus(users = [], enabled = true) {
+        $scope.loading = true;
+        const promises = users.map(
+            (user) => APIUtils.updateUser(
+                user.UserName, null, null, null, enabled, null));
+        $q.all(promises)
+            .then(() => {
+              let message;
+              let statusLabel = enabled ? 'enabled' : 'disabled';
+              if (users.length > 1) {
+                message = `Users ${statusLabel}.`
+              } else {
+                message = `User '${users[0].UserName}' ${statusLabel}.`;
+              }
+              toastService.success(message);
+            })
+            .catch((error) => {
+              console.log(JSON.stringify(error));
+              let message;
+              let statusLabel = enabled ? 'enable' : 'disable';
+              if (users.length > 1) {
+                message = `Failed to ${statusLabel} users.`
+              } else {
+                message =
+                    `Failed to ${statusLabel} user '${users[0].UserName}'.`
+              }
+              toastService.error(message);
+            })
+            .finally(() => {
+              getLocalUsers();
+              $scope.loading = false;
+            });
+      }
+
+      /**
+       * API call to save account policy settings
+       * @param {number} lockoutDuration
+       * @param {number} lockoutThreshold
+       */
+      function updateAccountSettings(lockoutDuration, lockoutThreshold) {
+        $scope.loading = true;
+        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;
+            });
+      }
+
+      /**
+       * 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() {
+                const lockoutMethod = mapLockoutMethod(
+                    $scope.accountSettings.AccountLockoutDuration);
+
+                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 : checkIfRoot(user) ? 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.user.locked = newUser ? null : user.Locked;
+
+                this.manualUnlockProperty = false;
+                this.automaticLockout = mapLockoutMethod(
+                    $scope.accountSettings.AccountLockoutDuration);
+                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;
+                const locked = (form.lock && form.lock.$dirty) ?
+                    form.lock.$modelValue :
+                    null;
+
+                if (!newUser) {
+                  updateUser(
+                      originalUsername, username, password, role, enabled,
+                      locked);
+                } else {
+                  createUser(
+                      username, password, role, form.accountStatus.$modelValue);
+                }
+              }
+            })
+            .catch(
+                () => {
+                    // do nothing
+                })
+      }
+
+      /**
+       * Intiate remove users modal
+       * @param {*} users
+       */
+      function initRemoveModal(users) {
+        const template = require('./user-accounts-modal-remove.html');
+        $uibModal
+            .open({
+              template,
+              windowTopClass: 'uib-modal',
+              ariaLabelledBy: 'dialog_label',
+              controllerAs: 'modalCtrl',
+              controller: function() {
+                this.users = users;
+              }
+            })
+            .result
+            .then(() => {
+              deleteUsers(users);
+            })
+            .catch(
+                () => {
+                    // do nothing
+                })
+      }
+
+      /**
+       * Callback when action emitted from table
+       * @param {*} value
+       */
+      $scope.onEmitRowAction = (value) => {
+        switch (value.action) {
+          case 'Edit':
+            initUserModal(value.row);
+            break;
+          case 'Delete':
+            initRemoveModal([value.row]);
+            break;
+          default:
+        }
+      };
+
+      /**
+       * Callback when batch action emitted from table
+       */
+      $scope.onEmitBatchAction = (value) => {
+        switch (value.action) {
+          case 'delete':
+            initRemoveModal(value.filteredRows);
+            break;
+          case 'enable':
+            updateUserStatus(value.filteredRows, true)
+            break;
+          case 'disable':
+            updateUserStatus(value.filteredRows, false)
+            break;
+          default:
+            break;
+        }
+      };
+
+      /**
+       * 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);