diff --git a/src/app/app.js b/src/app/app.js index 8a565a5..eb8e647 100755 --- a/src/app/app.js +++ b/src/app/app.js @@ -92,14 +92,6 @@ angular.module('supportAdminApp', [ data: { pageTitle: 'User Management' }, resolve: { auth: authenticate } }) - .state('index.permission_management', { - url: '/permission_management', - templateUrl: 'app/permission_management/permission_management.html', - data: { pageTitle: 'Permission Management' }, - controller: 'PermissionManagementCtrl', - controllerAs: 'ctrl', - resolve: { auth: authenticate } - }) .state('index.submissions', { abstract: true, url: '/submissions', @@ -303,6 +295,41 @@ angular.module('supportAdminApp', [ data: { pageTitle: 'Add Group Members' }, resolve: { auth: authenticate } }) + .state('index.roles', { + abstract: true, + url: '/roles', + templateUrl: 'app/roles/roles.html', + data: { pageTitle: 'Roles' }, + controller: 'permissionmanagement.RolesController' + }) + .state('index.roles.list', { + url: '/list', + templateUrl: 'app/roles/roles.list.html', + data: { pageTitle: 'Roles' }, + controller: 'permissionmanagement.RolesListController', + controllerAs: 'ctrl', + resolve: { auth: authenticate } + }) + .state('index.rolemembers', { + abstract: true, + url: '/rolemembers/:roleId', + templateUrl: 'app/rolemembers/rolemembers.html', + data: { pageTitle: 'Role Members' }, + controller: 'permissionmanagement.RoleMembersController' + }) + .state('index.rolemembers.list', { + url: '/list', + templateUrl: 'app/rolemembers/rolemembers.list.html', + controller: 'permissionmanagement.RoleMembersListController', + resolve: { auth: authenticate } + }) + .state('index.rolemembers.new', { + url: '/new', + templateUrl: 'app/rolemembers/rolemembers.new.html', + controller: 'permissionmanagement.RoleMembersNewController', + data: { pageTitle: 'Add Role Members' }, + resolve: { auth: authenticate } + }) .state('index.billingaccounts', { abstract: true, url: '/billingaccounts', diff --git a/src/app/groups/groups.service.js b/src/app/groups/groups.service.js index 547170f..9576dd0 100644 --- a/src/app/groups/groups.service.js +++ b/src/app/groups/groups.service.js @@ -54,6 +54,31 @@ angular.module('supportAdminApp') }).catch(GroupService.handleError); }; + /** + * Get a groups of the particular member + * + * @param {String} memberId member id + * @param {String} membershipType membership type: 'user' or 'group' + * @return {Promise} promise get a members group list + */ + GroupService.findByMember = function(memberId, membershipType) { + return $http({ + method: 'GET', + url: GroupService.getBasePath() + '/groups/?memberId=' + memberId + '&membershipType=' + membershipType, + headers: { + "Content-Type": "application/json" + } + }).then(function (response) { + if (response && response.data && response.data.result) { + return response.data.result.content; + } else { + return $q.reject({ + error : 'Cannot find data in response' + }) + } + }).catch(GroupService.handleError); + }; + /** * Handle API response error * diff --git a/src/app/rolemembers/rolemembers.controller.js b/src/app/rolemembers/rolemembers.controller.js new file mode 100644 index 0000000..acd15eb --- /dev/null +++ b/src/app/rolemembers/rolemembers.controller.js @@ -0,0 +1,19 @@ +'use strict'; + +var module = angular.module('supportAdminApp'); + +/** + * The parent controller for the rolemembers states + */ +module.controller('permissionmanagement.RoleMembersController', ['$scope', 'AuthService', '$state', + function ($scope, $authService, $state) { + $scope.$state = $state; + + /** + * Validate the user authentication + */ + $scope.authorized = function() { + return $authService.isLoggedIn(); + }; + } +]); diff --git a/src/app/rolemembers/rolemembers.html b/src/app/rolemembers/rolemembers.html new file mode 100644 index 0000000..93dd092 --- /dev/null +++ b/src/app/rolemembers/rolemembers.html @@ -0,0 +1,7 @@ +
+
+

{{$state.current.data.pageTitle}}

+
+
+
+
diff --git a/src/app/rolemembers/rolemembers.list.controller.js b/src/app/rolemembers/rolemembers.list.controller.js new file mode 100644 index 0000000..4732260 --- /dev/null +++ b/src/app/rolemembers/rolemembers.list.controller.js @@ -0,0 +1,196 @@ +'use strict'; + +var module = angular.module('supportAdminApp'); + +module.controller('permissionmanagement.RoleMembersListController', [ + '$scope', '$rootScope', 'RoleService', 'IdResolverService', '$stateParams', '$state', '$q', 'Alert', '$timeout', + function ($scope, $rootScope, RoleService, IdResolverService, $stateParams, $state, $q, $alert, $timeout) { + + // true if role is loading + $scope.isLoading = false; + + // true if we are removing a bulk of entries + $scope.isProcessing = false; + + // list of members + $scope.members = []; + + // current role object + $scope.role = null; + + // true if any members were selected in the list + $scope.hasSelected = false; + + // if checkbox in table header is selected + $scope.isAllSelected = false; + + // keep the list of members visible on the current page + var currentPageMembers = []; + + /* Maps user ids, present in the page, into user handles. */ + $scope.users = {}; + var loadUser = IdResolverService.getUserResolverFunction($scope.users); + + /** + * Return members which are selected in the table by checkboxes + * + * @return {Array} member records + */ + function getSelectedMembers() { + // return only members selected on the current page + // to make 100% sure we never delete members we cannot see + return _.filter(currentPageMembers, { isSelected: true }); + } + + /** + * Get role with members list + * + * @param {String} roleId role id to get + */ + $scope.loadRole = function(roleId) { + $alert.clear(); + $scope.isLoading = true; + + RoleService.getRole(roleId, ['id', 'roleName', 'subjects']).then(function(role) { + $scope.role = role; + $scope.members = $scope.role.subjects.map(function(memberId) { + return { + id: memberId + } + }); + // if have some members we will redraw table using footable plugin + if ($scope.members.length) { + // make sure changes to scope are applied + // and redraw footable table with current member list + $timeout(function() { + $('.footable').trigger('footable_redraw'); + $scope.isLoading = false; + }); + } else { + $scope.isLoading = false; + } + }).catch(function (error) { + $scope.isLoading = false; + $alert.error(error.error, $rootScope); + }); + }; + + /** + * Checks if any member records are selected in the table + * and updates $scope.hasSelected value + */ + $scope.checkSelected = function() { + $scope.hasSelected = !!getSelectedMembers().length; + } + + /** + * Toggle all selected member records of specified type + */ + $scope.toggleAll = function() { + // toggle checkboxes only for current page + currentPageMembers.forEach(function(member) { member.isSelected = $scope.isAllSelected }); + } + + /** + * Removes member from the current role + * After removing record from the server, it removes the record from the table + * + * @param {Object} member member record + * @return {Promise} promise to remove member + */ + $scope.removeMember = function(member) { + member.isRemoving = true; + return RoleService.unassignRole($stateParams.roleId, member.id).then(function() { + _.remove($scope.members, { id: member.id }); + // we remove row of deleted member from footable table + // which will also triggers footable table redraw + // we don't worry to call it after $scope.members is updated so we don't use $timeout here + var $footable = $('.footable'); + var ft = $footable.data('footable'); + ft.removeRow($footable.find('tr#' + member.id)); + }).catch(function(error) { + member.isRemoving = false; + $alert.error('Cannot remove member with id `' + member.memberId + '`. ' + error.error, $rootScope); + }); + } + + /** + * Remove all selected member records + */ + $scope.removeSelected = function() { + $alert.clear(); + $scope.isProcessing = true; + + var selectedMembers = getSelectedMembers(); + + // for now we remove all members in parallel + // it's preferable, because it's faster + // though if there will be any issues with server overload + // it can be rewritten so requests go one by one + $q.all(selectedMembers.map(function(member) { + return $scope.removeMember(member); + })).then(function() { + // uncheck select all checkbox as we already removed all selected items + $scope.isAllSelected = false; + $scope.checkSelected(); + }).catch(function(error) { + $alert.error(error.error, $rootScope); + }).finally(function() { + $scope.isProcessing = false; + }); + } + + /** + * Uncheck all checkboxes + */ + function uncheckAll() { + $scope.isAllSelected = false; + $scope.members.forEach(function(member) { + member.isSelected = false; + }); + $scope.checkSelected(); + } + + /** + * Updates current page member list + * + * @param {Event} event event which contains ft property + */ + function updateCurrentPage(event) { + var ft = event.ft; + + // if pager plugin of footable plugin was completely initialized + if (ft.pageInfo && ft.pageInfo.pages && ft.pageInfo.pages.length) { + // get the list of member on the current page + currentPageMembers = ft.pageInfo.pages[ft.pageInfo.currentPage].map(function(row) { + return _.find($scope.members, { id: row.id }); + }); + // clear queue of currently loading user handles + loadUser.clearQueue(); + // load user handles for members visible on the current page + currentPageMembers.forEach(function(member) { + loadUser(member.id); + }); + } + } + + angular.element(document).ready(function() { + $('.footable').on({ + // we watch footable jquery plugin footable_page_filled event + // to update current page member list when rows on the page are changed + footable_page_filled: updateCurrentPage, + // when changing sort order or page, we uncheck all checkboxes + // to avoid having checked but invisible rows + footable_paging: function() { + $scope.$apply(uncheckAll); + }, + footable_sorted: function() { + $scope.$apply(uncheckAll); + } + }).footable(); + }); + + // load role on init + $scope.loadRole($stateParams.roleId); + } +]); diff --git a/src/app/rolemembers/rolemembers.list.html b/src/app/rolemembers/rolemembers.list.html new file mode 100644 index 0000000..5b61970 --- /dev/null +++ b/src/app/rolemembers/rolemembers.list.html @@ -0,0 +1,70 @@ +
+ +
+ +
+ +
+
+
+
+

+ {{role.roleName}} + loading... +

+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
User IdHandle 
{{member.id}} + {{users[member.id]}} + loading... + + +
+
    +
    +
    No members

    + + +
    +
    +
    +
    +
    +
    diff --git a/src/app/rolemembers/rolemembers.new.controller.js b/src/app/rolemembers/rolemembers.new.controller.js new file mode 100644 index 0000000..982346c --- /dev/null +++ b/src/app/rolemembers/rolemembers.new.controller.js @@ -0,0 +1,125 @@ +'use strict'; + +var module = angular.module('supportAdminApp'); + +module.controller('permissionmanagement.RoleMembersNewController', [ + '$scope', '$rootScope', 'RoleService', 'UserService', '$q', 'Alert', '$state', '$stateParams', + function ($scope, $rootScope, RoleService, UserService, $q, $alert, $state, $stateParams) { + + // current role object + $scope.role = null; + + // true if adding new members now + $scope.isProcessing = false; + + // if there were any errors during form submission + var hasSubmissionErrors = false; + + /** + * Get role object + * + * @param {String} roleId role id to get + */ + function loadRole(roleId) { + $alert.clear(); + + RoleService.getRole(roleId, ['id', 'roleName']).then(function(role) { + $scope.role = role; + }).catch(function (error) { + $alert.error(error.error, $rootScope); + }); + }; + + + /** + * Add a member to the current role + * + * @param {String} memberId member id + * @return {Promise} promise to add a member + */ + function addMember(memberId) { + return RoleService.assignRole($scope.role.id, memberId).catch(function(error) { + hasSubmissionErrors = true; + $alert.error('Cannot add member with id: ' + memberId + '. Server error: "' + error.error + '".', $rootScope ); + }); + } + + /** + * Get user id by its handle + * + * @param {String} userHandle handle of the user + * @return {Promise} promise to get user id + */ + function getUserId(userHandle) { + return UserService.find({ + filter: 'handle=' + userHandle + }).then(function (data) { + var userId = ''; + + if (data.length) { + userId = data[0].id; + } else { + // if response was successful, but user was not found, just return error about that + return $q.reject({ + status: 404, + error: 'Cannot find user id with handle: ' + userHandle + }); + } + + return userId; + }).catch(function(error) { + if (error.status === 404) { + // if the error has status 404, we return it without changes + return $q.reject(error); + } else { + // if there was some other error during request, then we also return specific error from the server + return $q.reject({ + status: error.status, + error: 'Cannot find user id with handle: `' + userHandle + '`. Server error: "' + error.error + '".' + }); + } + }) + } + + /** + * Submit all + */ + $scope.submitMember = function () { + $alert.clear(); + + // get list of user handles to add + var handles = $scope.userHandles.split(',').map(function(handle) { + return handle.trim(); + }); + + $scope.isProcessing = true; + hasSubmissionErrors = false; + // for now we submit all new members in parallel + // it's preferable, because it's faster + // though if there will be any issues with server overload + // it can be rewritten so requests go one by one + $q.all(handles.map(function(handle) { + // we have to find userId first + return getUserId(handle).then(function(userId) { + return addMember(userId); + }).catch(function(error) { + // if user is not found, show error + hasSubmissionErrors = true; + $alert.error(error.error, $rootScope); + }); + })).then(function() { + // if there were any errors we don't return to the list, so user can see error messages + if (!hasSubmissionErrors) { + $state.go('index.rolemembers.list', { roleId: $scope.role.id }); + } + }).catch(function (error) { + $alert.error(error.error, $rootScope); + }).finally(function() { + $scope.isProcessing = false; + }); + }; + + // load role object on init + loadRole($stateParams.roleId); + } +]); diff --git a/src/app/rolemembers/rolemembers.new.html b/src/app/rolemembers/rolemembers.new.html new file mode 100644 index 0000000..2cfac57 --- /dev/null +++ b/src/app/rolemembers/rolemembers.new.html @@ -0,0 +1,36 @@ +
    +
    +
    +
    +
    +

    + {{role.roleName}} + loading... +

    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/src/app/roles/roles.controller.js b/src/app/roles/roles.controller.js new file mode 100644 index 0000000..e0fb8d7 --- /dev/null +++ b/src/app/roles/roles.controller.js @@ -0,0 +1,20 @@ +'use strict'; + +var module = angular.module('supportAdminApp'); + +/** + * The parent controller for the roles states + */ +module.controller('permissionmanagement.RolesController', [ + '$scope', 'AuthService', '$state', + function ($scope, $authService, $state) { + $scope.$state = $state; + + /** + * Validate the user authentication + */ + $scope.authorized = function() { + return $authService.isLoggedIn(); + }; + } +]); diff --git a/src/app/roles/roles.html b/src/app/roles/roles.html new file mode 100644 index 0000000..93dd092 --- /dev/null +++ b/src/app/roles/roles.html @@ -0,0 +1,7 @@ +
    +
    +

    {{$state.current.data.pageTitle}}

    +
    +
    +
    +
    diff --git a/src/app/roles/roles.list.controller.js b/src/app/roles/roles.list.controller.js new file mode 100644 index 0000000..9c12928 --- /dev/null +++ b/src/app/roles/roles.list.controller.js @@ -0,0 +1,91 @@ +angular.module('supportAdminApp') +.controller('permissionmanagement.RolesListController', [ + '$scope', '$timeout', 'RoleService', 'IdResolverService', + function ($scope, $timeout, RoleService, IdResolverService) { + + var vm = this; + + /* Table initialization. */ + + angular.element(document).ready(function() { + $('.footable').footable({ + addRowToggle: true + }); + }); + + $scope.$on('permissionManagement.DataUpdated', function(event) { + $timeout(function () { + $('.footable').trigger('footable_redraw'); + }, 100); + }); + + /* Role search by name. */ + + vm.searchCreateRoleText = ''; + + vm.clearRoleSearch = function() { + vm.searchCreateRoleText = ''; + vm.searchRole(); + } + + vm.searchRole = function() { + $('.footable').trigger('footable_filter', { + filter: vm.searchCreateRoleText, + }); + } + + /* Role creation. */ + + vm.creatingNewRole = false; + vm.createNewRoleError = ''; + + vm.createNewRole = function() { + vm.creatingNewRole = true; + vm.createNewRoleError = ''; + RoleService.createRole(vm.searchCreateRoleText) + .then(function(res) { + vm.roles.push(res); + loadUser(res.createdBy); + loadUser(res.modifiedBy); + vm.assignment[res.id] = {}; + vm.roles = vm.roles.sort(function(a, b) { + return a.roleName.localeCompare(b.roleName); + }); + }, function(error) { + vm.createNewRoleError = + 'Error: ' + (error.error || 'Failed to create role!'); + }).then(function() { + vm.creatingNewRole = false; + $scope.$broadcast('permissionManagement.DataUpdated'); + }); + } + + /* Role assignments. */ + + vm.assignment = {}; + + /* Loading roles. */ + + vm.loadingRoles = true; + vm.loadingRolesError = null; + RoleService.getRoles().then(function(roles) { + vm.roles = roles; + roles.forEach(function(role) { + vm.assignment[role.id] = {}; + loadUser(role.createdBy); + loadUser(role.modifiedBy); + }); + }, function(error) { + vm.loadingRolesError = + 'Error: ' + (error.message || 'Failed to load roles!'); + }).then(function() { + vm.loadingRoles = false; + $scope.$broadcast('permissionManagement.DataUpdated'); + }); + + /* Maps user ids, present in the page, into user handles. */ + vm.users = {}; + var loadUser = IdResolverService.getUserResolverFunction(vm.users); + + } +]); diff --git a/src/app/roles/roles.list.html b/src/app/roles/roles.list.html new file mode 100644 index 0000000..bbfb5bf --- /dev/null +++ b/src/app/roles/roles.list.html @@ -0,0 +1,122 @@ +
    +
    +
    +
    +
    +
    +
    {{ctrl.loadingRolesError}}
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    {{ctrl.createNewRoleError}}
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Role IDRole NameCreated byCreated atModified byModified at
    {{role.id}}{{role.roleName}} + + {{ctrl.users[role.createdBy]}} + + loading... + {{role.createdAt | date : 'MM dd yyyy HH:mm' : 'EDT'}} {{role.createdAt ? 'EDT' : ''}} + + {{ctrl.users[role.modifiedBy]}} + + loading... + {{role.modifiedAt | date : 'MM dd yyyy HH:mm' : 'EDT'}} {{role.modifiedAt ? 'EDT' : ''}}
    +
      +
      +
      +
      +
      +
      diff --git a/src/app/roles/roles.service.js b/src/app/roles/roles.service.js new file mode 100644 index 0000000..aee89c0 --- /dev/null +++ b/src/app/roles/roles.service.js @@ -0,0 +1,170 @@ +angular.module('supportAdminApp') +.factory('RoleService', [ + '$http', 'API_URL', '$log', '$q', + function($http, API_URL, $log, $q) { + var Service = {}; + + /** + * Assigns role to the user. + * @param {String} roleId + * @param {String} userId + * @return {Promise} Resolves to the roleId, if success. + */ + Service.assignRole = function(roleId, userId) { + return $http.post(API_URL + '/v3/roles/' + roleId + + '/assign?action=true&filter=subjectID%3D' + userId) + .then(function(res) { + return res.data.result.content; + }).catch(Service.handleError); + }; + + /** + * Creates a new role. + * @param {String} roleName + * @return {Promise} Resolves to the created role object. + */ + Service.createRole = function(roleName) { + return $http({ + method: 'POST', + url: API_URL + '/v3/roles', + data: angular.toJson({ + param: { + roleName: roleName + } + }), + headers: { + 'Content-Type': 'application/json' + } + }).then(function(res) { + return res.data.result.content; + }).catch(Service.handleError); + }; + + /** + * Gets roles. + * @return {Promise} Resolves to the array of role objects, sorted + * by names. + */ + Service.getRoles = function() { + return $http.get(API_URL + '/v3/roles') + .then(function(res) { + return res.data.result.content.sort(function(a, b) { + return a.roleName.localeCompare(b.roleName); + }); + }).catch(Service.handleError); + }; + + /** + * Gets roles of the specified subject + * + * @return {Promise} Resolves to the array of role objects, sorted + * by names. + */ + Service.getRolesBySubject = function(subjectId) { + return $http.get(API_URL + '/v3/roles/?filter=subjectID=' + subjectId) + .then(function(res) { + return res.data.result.content.sort(function(a, b) { + return a.roleName.localeCompare(b.roleName); + }); + }).catch(Service.handleError); + }; + + /** + * Unassigns the role from the user. + * @param {String} roleId + * @param {String} userId + * @return {Promise} Resolves to the roleId, if success. + */ + Service.unassignRole = function(roleId, userId) { + return $http.delete(API_URL + '/v3/roles/' + roleId + + '/deassign?action=true&filter=subjectID%3D' + userId) + .then(function(res) { + return res.data.result.content; + }).catch(Service.handleError); + }; + + /** + * Gets role by id. + * + * @return {Promise} Resolves to the object of one role. + */ + Service.getRole = function(roleId, fields) { + // there is a bug in backend, when we ask to get role subjects + // but there are no subjects, backend returns 404 even if role exists + // as a workaround we get role without subjects first to check if it exists + // and only after we try to get it subject + // TODO: remove code in this if, after this bug is fixed at the backend + // keep only the part after else + if (fields && _.includes(fields, 'subjects')) { + var fieldsWithouSubjects = _.without(fields, 'subjects'); + // if there are no fields after removing 'subjects', add 'id' to retrieve minimum data + if (!fieldsWithouSubjects.length) { + fieldsWithouSubjects.push('id'); + } + + return $http.get(API_URL + '/v3/roles/' + roleId + (fields ? '?fields=' + fieldsWithouSubjects.join(',') : '')).then(function(res) { + var roleWithoutSubjects = res.data.result.content; + + // now let's try to get subjects + return $http.get(API_URL + '/v3/roles/' + roleId + '?fields=subjects').then(function(res) { + // populate role with subjects and return it + return _.assign(roleWithoutSubjects, { + subjects: res.data.result.content.subjects + }); + }).catch(function(error) { + // if get error 404 in this case we know role exits + // so just return roleWithoutSubjects with subjects as en empty array + if (error.data && error.data.result && error.data.result.status === 404) { + return _.assign(roleWithoutSubjects, { + subjects: [] + }); + + // for other errors return rejected promise with error + } else { + return $q.reject(error); + } + }); + }).catch(Service.handleError); + + // if don't ask for subjects, then just normal request + } else { + return $http.get(API_URL + '/v3/roles/' + roleId + (fields ? '?fields=' + fields.join(',') : '')) + .then(function(res) { + return res.data.result.content; + }).catch(Service.handleError); + } + }; + + + + /** + * Handle API response error + * + * @param {Error} error the error as received in catch callback + * @return {Promise} rejected promise with error + */ + Service.handleError = function(error) { + var err; + + $log.error(error); + + if (error && error.data) { + err = { + status: error.status, + error : error.data.result.content + }; + } + + if (!err) { + err = { + status: error.status, + error : error.message + }; + } + + return $q.reject(err); + } + + return Service; + } +]); diff --git a/src/app/users/groups-edit-dialog.controller.js b/src/app/users/groups-edit-dialog.controller.js new file mode 100644 index 0000000..0bef9d3 --- /dev/null +++ b/src/app/users/groups-edit-dialog.controller.js @@ -0,0 +1,143 @@ +module.controller('users.GroupsEditDialogController', [ + '$scope', '$uibModalInstance', 'GroupService', 'GroupMemberService', 'Alert', 'user', '$q', + function ($scope, $modalInstance, GroupService, GroupMemberService, $alert, user, $q) { + + // currently selected user object + $scope.user = user; + + // currently selected user group list + $scope.userGroups = []; + + // true if group list is being loaded + $scope.isLoading = false; + + // true if are removing a group + $scope.isRemoving = false; + + // true if we are adding a group + $scope.isAdding = false; + + // group id selected to add + $scope.groupToAdd = ''; + + // list of existent groups where user is not a member + $scope.availableGroups = []; + + // array of all the existent groups + var allGroups = []; + + /** + * Update list of available groups which + * @return {[type]} [description] + */ + function updateAvailableGroups() { + var userGroupIds = $scope.userGroups.map(function(group) { + return group.id; + }); + + $scope.availableGroups = allGroups.filter(function(group) { + return userGroupIds.indexOf(group.id) === -1; + }); + } + + /** + * Close dialog + */ + $scope.close = function() { + $modalInstance.close(); + }; + + /** + * Load user groups and full list of existent groups + */ + $scope.loadData = function() { + $scope.isLoading = true; + $q.all([ + GroupService.findByMember(user.id, 'user'), + GroupService.fetch() + ]).then(function(data) { + // sort group list for easy navigation + $scope.userGroups = _.sortBy(data[0], 'name'); + allGroups = data[1].content; + updateAvailableGroups(); + }).catch(function(error) { + $alert.error(error.error, $scope); + }).finally(function() { + $scope.isLoading = false; + }); + } + + /** + * Submit add group form + */ + $scope.addGroup = function() { + $scope.isAdding = true; + GroupMemberService.addMember($scope.groupToAdd, { + memberId: user.id, + membershipType: 'user' + }).then(function() { + $scope.userGroups.push(_.find($scope.availableGroups, { id: $scope.groupToAdd })); + // keep sorting after adding new group + $scope.userGroups = _.sortBy($scope.userGroups, 'name'); + $scope.groupToAdd = ''; + updateAvailableGroups(); + }).catch(function(error) { + $alert.error(error.error, $scope); + }).finally(function() { + $scope.isAdding = false; + }); + } + + /** + * Helper function which removes group from group list + * + * @param {Object} group group object to remove + */ + function removeGroupFromList(group) { + _.remove($scope.userGroups, { id: group.id }); + updateAvailableGroups(); + } + + /** + * Remove user from specified group + * @param {Object} group group from where we have to remove user + */ + $scope.removeGroup = function(group) { + $scope.isRemoving = true; + group.isRemoving = true; + // to delete a member from a group we have to know membership id + // the only way to get it is to get all membership records of the group + // and then find the one for current user + GroupMemberService.fetch(group.id).then(function(data) { + var memberships = data.content; + + var membership = _.find(memberships, function(membership) { + // as memberId is a string and user.id is a number + // it's safer to convert memberId to a string, + // because when converting to a number there lots of issues can appear + return membership.memberId.toString() === user.id; + }); + + if (!membership) { + // if user is already not a member, show warning but remove group from the list + $alert.warning('User is already not a member of the group `' + group.name + '`.', $scope); + removeGroupFromList(group); + } else { + return GroupMemberService.removeMember(group.id, membership.id).then(function() { + removeGroupFromList(group); + }); + } + }).catch(function(error) { + $alert.error('Cannot remove from the group `' + group.name + '`. Server error: "' + error.error + '"', $scope); + }).finally(function() { + // always clean the flag as we can add this group back + group.isRemoving = false; + // check if anything is still removing + $scope.isRemoving = !!_.find($scope.userGroups, { isRemoving: true }); + }); + } + + $scope.loadData(); + + } +]); diff --git a/src/app/users/groups-edit-dialog.html b/src/app/users/groups-edit-dialog.html new file mode 100644 index 0000000..c7d7200 --- /dev/null +++ b/src/app/users/groups-edit-dialog.html @@ -0,0 +1,64 @@ +
      + + + +
      + +
      +
      diff --git a/src/app/users/roles-edit-dialog.controller.js b/src/app/users/roles-edit-dialog.controller.js new file mode 100644 index 0000000..956f70d --- /dev/null +++ b/src/app/users/roles-edit-dialog.controller.js @@ -0,0 +1,129 @@ +module.controller('users.RolesEditDialogController', [ + '$scope', '$uibModalInstance', 'RoleService', 'Alert', 'user', '$q', + function ($scope, $modalInstance, RoleService, $alert, user, $q) { + + // currently selected user object + $scope.user = user; + + // currently selected user role list + $scope.userRoles = []; + + // true if role list is being loaded + $scope.isLoading = false; + + // true if are removing a role + $scope.isRemoving = false; + + // true if we are adding a role + $scope.isAdding = false; + + // role id selected to add + $scope.roleToAdd = ''; + + // list of existent roles where user is not a member + $scope.availableRoles = []; + + // array of all the existent roles + var allRoles = []; + + /** + * Update list of available roles which + * @return {[type]} [description] + */ + function updateAvailableRoles() { + var userRoleIds = $scope.userRoles.map(function(role) { + return role.id; + }); + + $scope.availableRoles = allRoles.filter(function(role) { + return userRoleIds.indexOf(role.id) === -1; + }); + } + + /** + * Close dialog + */ + $scope.close = function() { + $modalInstance.close(); + }; + + /** + * Load user roles and full list of existent roles + */ + $scope.loadData = function() { + $scope.isLoading = true; + $q.all([ + RoleService.getRolesBySubject(user.id).catch(function(error) { + // if use doesn't have any roles, backend return error 404 + // we catch this error and return empty error in this case + if (error.status === 404) { + return []; + + // in other cases we return rejected promise further + } else { + return $q.reject(error); + } + }), + RoleService.getRoles() + ]).then(function(data) { + $scope.userRoles = data[0]; + allRoles = data[1]; + updateAvailableRoles(); + }).catch(function(error) { + $alert.error(error.error, $scope); + }).finally(function() { + $scope.isLoading = false; + }); + } + + /** + * Submit add role form + */ + $scope.addRole = function() { + $scope.isAdding = true; + RoleService.assignRole($scope.roleToAdd, user.id).then(function() { + $scope.userRoles.push(_.find($scope.availableRoles, { id: $scope.roleToAdd })); + // as roles sorted by default we keep them sorted + $scope.userRoles = _.sortBy($scope.userRoles, 'roleName'); + $scope.roleToAdd = ''; + updateAvailableRoles(); + }).catch(function(error) { + $alert.error(error.error, $scope); + }).finally(function() { + $scope.isAdding = false; + }); + } + + /** + * Helper function which removes role from role list + * + * @param {Object} role role object to remove + */ + function removeRoleFromList(role) { + _.remove($scope.userRoles, { id: role.id }); + updateAvailableRoles(); + } + + /** + * Remove user from specified role + * @param {Object} role role from where we have to remove user + */ + $scope.removeRole = function(role) { + $scope.isRemoving = true; + role.isRemoving = true; + return RoleService.unassignRole(role.id, user.id).then(function() { + removeRoleFromList(role); + }).catch(function(error) { + $alert.error('Cannot remove from the role `' + role.roleName + '`. Server error: "' + error.error + '"', $scope); + }).finally(function() { + // always clean the flag as we can add this role back + role.isRemoving = false; + // check if anything is still removing + $scope.isRemoving = !!_.find($scope.userRoles, { isRemoving: true }); + }); + } + + $scope.loadData(); + + } +]); diff --git a/src/app/users/roles-edit-dialog.html b/src/app/users/roles-edit-dialog.html new file mode 100644 index 0000000..718d310 --- /dev/null +++ b/src/app/users/roles-edit-dialog.html @@ -0,0 +1,64 @@ +
      + + + +
      + +
      +
      diff --git a/src/app/users/users.controller.js b/src/app/users/users.controller.js index 4dbc962..5942817 100644 --- a/src/app/users/users.controller.js +++ b/src/app/users/users.controller.js @@ -176,6 +176,29 @@ module.controller('users.UserSearchController', [ }); }; + + $scope.openRolesEditDialog = function(index) { + var modalInstance = $modal.open({ + size: 'md', + templateUrl: 'app/users/roles-edit-dialog.html', + controller: 'users.RolesEditDialogController', + resolve: { + user: function(){ return $scope.users[index]; } + } + }); + }; + + $scope.openGroupsEditDialog = function(index) { + var modalInstance = $modal.open({ + size: 'md', + templateUrl: 'app/users/groups-edit-dialog.html', + controller: 'users.GroupsEditDialogController', + resolve: { + user: function(){ return $scope.users[index]; } + } + }); + }; + } ]); @@ -208,7 +231,7 @@ module.controller('users.UserEditDialogController', [ $alert.error('Handle is not changed.', $scope); return; } - + if(window.confirm('Are you sure you want to save changes?')) { $scope.form.setLoading(true); $userService.updateHandle(user.id, $scope.form.handle).then( @@ -224,7 +247,7 @@ module.controller('users.UserEditDialogController', [ ); } }; - + $scope.checkProfile = function() { $userService.getProfile($scope.form.handle).then( function(profile) { diff --git a/src/app/users/users.html b/src/app/users/users.html index 02e5101..093d281 100644 --- a/src/app/users/users.html +++ b/src/app/users/users.html @@ -112,6 +112,8 @@

      Users

      diff --git a/src/app/utils/idresolver.service.js b/src/app/utils/idresolver.service.js index 386fd1b..a700c6a 100644 --- a/src/app/utils/idresolver.service.js +++ b/src/app/utils/idresolver.service.js @@ -53,6 +53,19 @@ angular.module('supportAdminApp') } } + /** + * Clears the queue of user handles to load + * + * We define clearQueue function this way because loadUser + * function is widely used in the project, to avoid changing + * it everywhere. + * + * So in the new places where we need it, we can call loadUser.clearQueue() + */ + loadUser.clearQueue = function() { + userLoadQueue = []; + } + return loadUser; } @@ -96,6 +109,19 @@ angular.module('supportAdminApp') } } + /** + * Clears the queue of group names to load + * + * We define clearQueue function this way because loadUser + * function is widely used in the project, to avoid changing + * it everywhere. And for loadGroup we just want to keep it same way. + * + * So in the new places where we need it, we can call loadGroup.clearQueue() + */ + loadGroup.clearQueue = function() { + groupLoadQueue = []; + } + return loadGroup; } diff --git a/src/components/common/navigation.html b/src/components/common/navigation.html index d2bc3c8..6cba4e4 100755 --- a/src/components/common/navigation.html +++ b/src/components/common/navigation.html @@ -24,7 +24,7 @@