+
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 @@
+
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 @@
+
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 @@
+
+
+
+
Groups of {{user.handle}}
+
+
+
+
+
+
+
+
+
+
+
+
Group ID
+
Name
+
Action
+
+
+
+
+
+
{{group.id}}
+
{{group.name}}
+
+
+
+
+
+
+
+
No groups
+
+
+
+
+
+
+
+
+
+
+
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 @@
+