diff --git a/src/app/addmembers/add.html b/src/app/addmembers/add.html index cf7091a..dca2213 100644 --- a/src/app/addmembers/add.html +++ b/src/app/addmembers/add.html @@ -10,7 +10,7 @@

Import Users

-
+
diff --git a/src/app/admintool/admintool.html b/src/app/admintool/admintool.html index 8d1c6c0..ae9bfe2 100644 --- a/src/app/admintool/admintool.html +++ b/src/app/admintool/admintool.html @@ -84,10 +84,10 @@

Admins / Copilots / Reviewers

-
+

No users found.

-
+
@@ -135,6 +135,7 @@

Admins / Copilots / Reviewers

+
diff --git a/src/app/billing_account_resources/billingaccountresources.list.html b/src/app/billing_account_resources/billingaccountresources.list.html index f3cffbe..85d9619 100644 --- a/src/app/billing_account_resources/billingaccountresources.list.html +++ b/src/app/billing_account_resources/billingaccountresources.list.html @@ -18,7 +18,7 @@
-
+
@@ -46,6 +46,7 @@
+
diff --git a/src/app/billing_accounts/billingaccounts.list.html b/src/app/billing_accounts/billingaccounts.list.html index 1411f45..3e4c97d 100644 --- a/src/app/billing_accounts/billingaccounts.list.html +++ b/src/app/billing_accounts/billingaccounts.list.html @@ -64,7 +64,7 @@
-
+
@@ -131,6 +131,7 @@
+
diff --git a/src/app/clients/clients.list.html b/src/app/clients/clients.list.html index f668f79..d918bb9 100644 --- a/src/app/clients/clients.list.html +++ b/src/app/clients/clients.list.html @@ -59,7 +59,7 @@
-
+
@@ -116,6 +116,7 @@
+
diff --git a/src/app/groupmembers/groupmembers.list.controller.js b/src/app/groupmembers/groupmembers.list.controller.js index a67379a..e916d33 100644 --- a/src/app/groupmembers/groupmembers.list.controller.js +++ b/src/app/groupmembers/groupmembers.list.controller.js @@ -3,14 +3,17 @@ var module = angular.module('supportAdminApp'); module.controller('permissionmanagement.GroupMembersListController', [ - '$scope', '$rootScope', 'GroupMemberService', 'IdResolverService', '$stateParams', '$state', '$q', 'Alert', - function ($scope, $rootScope, GroupMemberService, IdResolverService, $stateParams, $state, $q, $alert) { + '$scope', '$rootScope', 'GroupMemberService', 'IdResolverService', 'UserService', 'GroupService', '$stateParams', '$state', '$q', 'Alert', '$timeout', 'moment', '$animate', + function ($scope, $rootScope, GroupMemberService, IdResolverService, UserService, GroupService, $stateParams, $state, $q, $alert, $timeout, moment, $animate) { // keep member types for easy iterating $scope.memberTypes = ['group', 'user']; // true if list is loading - $scope.isLoading = false; + $scope.isLoading = {}; + $scope.memberTypes.forEach(function(memberType) { + $scope.isLoading[memberType] = false; + }); // true if something is we are removing a bulk of entries $scope.processing = false; @@ -41,6 +44,27 @@ module.controller('permissionmanagement.GroupMembersListController', [ $scope.groups = {}; var loadGroup = IdResolverService.getGroupResolverFunction($scope.groups); + // all memberships to implement client side filters + var allMemberships = {}; + $scope.memberTypes.forEach(function(memberType) { + allMemberships[memberType] = []; + }); + + // filter criteria + $scope.filterCriteria = {}; + $scope.memberTypes.forEach(function(memberType) { + $scope.filterCriteria[memberType] = { + memberId: '', + memberName: '', + createdBy: '', + modifiedBy: '', + createdAtFrom: '', + createdAtTo: '', + modifiedAtFrom: '', + modifiedAtTo: '' + }; + }); + /** * Return membership records which are selected in the table by checkboxes * @@ -61,11 +85,14 @@ module.controller('permissionmanagement.GroupMembersListController', [ */ $scope.fetch = function(groupId) { $alert.clear(); - $scope.isLoading = true; + $scope.memberTypes.forEach(function(memberType) { + $scope.isLoading[memberType] = true; + }); GroupMemberService.fetch(groupId).then(function(data) { $scope.memberTypes.forEach(function(memberType) { $scope.memberships[memberType] = data.content.filter(function(membership) { return membership.membershipType === memberType }); + allMemberships[memberType] = _.clone($scope.memberships[memberType]); $scope.memberships[memberType].forEach(function(membership) { loadUser(membership.createdBy); loadUser(membership.modifiedBy); @@ -78,11 +105,24 @@ module.controller('permissionmanagement.GroupMembersListController', [ loadGroup(membership.memberId); } }); + + // if have some members we will init footable plugin + if ($scope.memberships[memberType].length) { + // make sure changes to scope are applied + // and redraw footable table with current member list + $timeout(function() { + $('.footable.table-type-' + memberType).footable(); + $scope.isLoading[memberType] = false; + }); + } else { + $scope.isLoading[memberType] = false; + } }); }).catch(function (error) { $alert.error(error.error, $rootScope); - }).finally(function() { - $scope.isLoading = false; + $scope.memberTypes.forEach(function(memberType) { + $scope.isLoading[memberType] = false; + }); }); }; @@ -116,6 +156,9 @@ module.controller('permissionmanagement.GroupMembersListController', [ $scope.memberships[membership.membershipType] = $scope.memberships[membership.membershipType].filter(function(record) { return record.id !== membership.id; }); + allMemberships[membership.membershipType] = allMemberships[membership.membershipType].filter(function(record) { + return record.id !== membership.id; + }); }).catch(function(error) { membership.isRemoving = false; $alert.error('Cannot remove member with id `' + membership.memberId + '`. ' + error.error, $rootScope); @@ -144,10 +187,323 @@ module.controller('permissionmanagement.GroupMembersListController', [ }); } + /** + * Open datetimepicker + * + * @param {Object} e event object + * @param {String} inputName name of the input + * @param {String} memberType member type + */ + $scope.openCalendar = function (e, inputName, memberType) { + if (!$scope[inputName + 'Open']) { + $scope[inputName + 'Open'] = {}; + } + + if ($scope[inputName + 'Open'][memberType]) { + return; + } + + $scope[inputName + 'Open'][memberType] = true; + + if (e && e.preventDefault) { + e.preventDefault(); + } + + if (e && e.stopPropagation) { + e.stopPropagation(); + } + }; + + /** + * Helper function which redraws table with new member list + * and properly takes care about updating footable plugin + * + * The essential problem this function is solving is that after we + * update $scope.members, rows from the table are not being deleted immediately + * because we are using animations for rows. + * That's why if we try to force update footable plugin it still find old rows which are in + * process of animation and still in DOM tree. + * As a workaround in this function we disable animations for rows before updating them, + * thus footable plugin can see changes immediately after scope is updated. + * + * @param {String} memberType type of membership we are redrawing + * @param {Array} memberships new array of memberships which has to displayed + * + * @return {Promise} resolved after table was completely updated + */ + function redrawTableWithMemberships(memberType, memberships) { + var $footable = $('.footable.table-type-' + memberType); + + // disable animations for rows, so old rows will be removed immediately + // from the DOM tree and footable can see changes + $footable.find('tr').each(function(index, el) { + $animate.enabled(el, false); + }); + + // update memberships list in scope + $scope.memberships[memberType] = memberships; + + // make sure that changes are applied to the scope + return $timeout(function() { + // force footable plugin to redraw + $footable.trigger('footable_redraw'); + // enable animation for table rows again + $footable.find('tr').each(function(index, el) { + $animate.enabled(el, true); + }); + }); + } + + // promise to get list of groups + // this list is used to resolve groups ids by its group names + var groupIdsByNamePromise = null; + + /** + * Find group id by its name + * + * This is helper function which return value in special shape + * along with group ids array it return label defined by 'valueType' + * + * This function just retrieves all the groups and after resolves names by + * searching in group list client side. + * + * @param {String} groupName group name + * @param {String} valueType label of the returned value + * @return {Promise} resolved to object {value: , type: valueType} + */ + function getGroupIdsFilteredByName(groupName, valueType) { + if (!groupIdsByNamePromise) { + groupIdsByNamePromise = GroupService.fetch().then(function(data) { + return data.content; + }); + } + + groupName = groupName.toLowerCase(); + + return groupIdsByNamePromise.then(function(groups) { + var filteredGroups = _.filter(groups, function(group) { + return _.includes(group.name.toLowerCase(), groupName); + }); + + return { + type: valueType, + value: _.map(filteredGroups, 'id') + } + }); + } + + // object keys of which are user handles + // and values are promises to resolve according user handles by ids + var userIdsByHandlePromises = {}; + + /** + * Find user id by its handle + * + * This is helper function which return value in special shape + * along with user ids array it return label defined by 'valueType' + * + * This function makes request for each user handle and saves resolved promise, + * so second time server request is not being sent to the server for the same user handle. + * + * @param {String} userHandle user handle + * @param {String} valueType label of the returned value + * @return {Promise} resolved to object {value: , type: valueType} + */ + function getUserIdsFilteredByHandle(userHandle, valueType) { + if (!userIdsByHandlePromises[userHandle]) { + userIdsByHandlePromises[userHandle] = UserService.find({ + fields: 'id', + filter: 'handle=*' + userHandle + '*&like=true', + limit: 1000000 // set big limit to make sure server returns all records + }).then(function(users) { + return _.map(users, 'id'); + }); + } + + return userIdsByHandlePromises[userHandle].then(function(userIds) { + return { + type: valueType, + value: userIds + } + }); + } + + /** + * Helper function which performs all the requests to the server which are required to filter membership tables + * + * @param {String} memberType member type + * @param {Array} filteredMembersips list of membership to filter + * @return {Promise} resolves to filtered membership list + */ + function filterWithRequests(memberType, filteredMembersips) { + // list of all the server requests which we have to make to filter members + var requests = []; + + // as on client side we don't know user handles for member, createdBy, modifiedBy users + // and we don't group names + // to filter by them we have to get their according user ids and group ids + // so we create requests to the server + + if (memberType === 'group' && $scope.filterCriteria[memberType].memberName) { + requests.push(getGroupIdsFilteredByName($scope.filterCriteria[memberType].memberName, 'memberName')); + } + + if (memberType === 'user' && $scope.filterCriteria[memberType].memberName) { + requests.push(getUserIdsFilteredByHandle($scope.filterCriteria[memberType].memberName, 'memberName')); + } + + if ($scope.filterCriteria[memberType].createdBy) { + requests.push(getUserIdsFilteredByHandle($scope.filterCriteria[memberType].createdBy, 'createdBy')); + } + + if ($scope.filterCriteria[memberType].modifiedBy) { + requests.push(getUserIdsFilteredByHandle($scope.filterCriteria[memberType].modifiedBy, 'modifiedBy')); + } + + // after we get all ids from the server we can filter data client side + return $q.all(requests).then(function(ids) { + var idsObj = {}; + + ids.forEach(function(result) { + idsObj[result.type] = result.value; + }); + + // memberName + if ($scope.filterCriteria[memberType].memberName) { + if (!idsObj['memberName'].length) { + filteredMembersips = []; + } else { + filteredMembersips = filteredMembersips = _.filter(filteredMembersips, function(membership) { + return _.includes(idsObj['memberName'], membership.memberId.toString()); + }); + } + } + + // createdBy + if ($scope.filterCriteria[memberType].createdBy) { + if (!idsObj['createdBy']) { + filteredMembersips = []; + } else { + filteredMembersips = filteredMembersips = _.filter(filteredMembersips, function(membership) { + return membership.createdBy && _.includes(idsObj['createdBy'], membership.createdBy.toString()); + }); + } + } + + // modifiedBy + if ($scope.filterCriteria[memberType].modifiedBy) { + if (!idsObj['modifiedBy']) { + filteredMembersips = []; + } else { + filteredMembersips = filteredMembersips = _.filter(filteredMembersips, function(membership) { + return membership.modifiedBy && _.includes(idsObj['modifiedBy'], membership.modifiedBy.toString()); + }); + } + } + + return filteredMembersips; + }); + } + + /** + * Returns date in EDT timezone + * Ignores time and set it to 00:00:00.0000 + * + * As we display dates in EDT timezone, but datepicker selects dates in a local user timezone + * we have to treat returned date by datepicker as date in EDT + * + * @param {Mixed} date date from datepicker + * @return {Moment} date in EDT timezone + */ + function getDateInEDTTimezone(date) { + var momentDate = moment(date); + var momentDateEDT = moment.tz([momentDate.year(), momentDate.month(), momentDate.date()], 'America/New_York'); + + return momentDateEDT; + } + + /** + * Applies filter to the member list + */ + $scope.applyFilter = function(memberType) { + // wait until we filter everything not matter synchronously or making requests to server + var filteredMembersips = _.clone(allMemberships[memberType]); + + // memberId + if ($scope.filterCriteria[memberType].memberId) { + filteredMembersips = _.filter(filteredMembersips, function(membership) { + return membership.memberId.toString() === $scope.filterCriteria[memberType].memberId; + }); + } + + // createdAtFrom + if ($scope.filterCriteria[memberType].createdAtFrom) { + var createdAtFrom = getDateInEDTTimezone($scope.filterCriteria[memberType].createdAtFrom); + + filteredMembersips = _.filter(filteredMembersips, function(membership) { + return membership.createdAt && createdAtFrom.isSameOrBefore(membership.createdAt, 'day'); + }); + } + + // createdAtTo + if ($scope.filterCriteria[memberType].createdAtTo) { + var createdAtTo = getDateInEDTTimezone($scope.filterCriteria[memberType].createdAtTo); + + filteredMembersips = _.filter(filteredMembersips, function(membership) { + return membership.createdAt && createdAtTo.isSameOrAfter(membership.createdAt, 'day'); + }); + } + + // modifiedAtFrom + if ($scope.filterCriteria[memberType].modifiedAtFrom) { + var modifiedAtFrom = getDateInEDTTimezone($scope.filterCriteria[memberType].modifiedAtFrom); + + filteredMembersips = _.filter(filteredMembersips, function(membership) { + return membership.modifiedAt && modifiedAtFrom.isSameOrBefore(membership.modifiedAt, 'day'); + }); + } + + // modifiedAtTo + if ($scope.filterCriteria[memberType].modifiedAtTo) { + var modifiedAtTo = getDateInEDTTimezone($scope.filterCriteria[memberType].modifiedAtTo).add(1, 'days'); + + filteredMembersips = _.filter(filteredMembersips, function(membership) { + return membership.modifiedAt && modifiedAtTo.isSameOrAfter(membership.modifiedAt, 'day'); + }); + } + + // if we still have some membership to filter + // and have any filter that require making requests to server + if (filteredMembersips.length > 0 && ( + $scope.filterCriteria[memberType].memberName || + $scope.filterCriteria[memberType].createdBy || + $scope.filterCriteria[memberType].modifiedBy + )) { + + $scope.isLoading[memberType] = true; + + return filterWithRequests(memberType, filteredMembersips).then(function(filteredMembersips) { + // after we filtered data we redraw table + redrawTableWithMemberships(memberType, filteredMembersips).then(function() { + $scope.isLoading[memberType] = false; + }); + }).catch(function(error) { + // is any error occurs show it and leave table untouched + $scope.isLoading[memberType] = false; + $alert.error(error.error, $rootScope); + }); + + // if we don't filter by handle which makes server request + // redraw table immediately + } else { + redrawTableWithMemberships(memberType, filteredMembersips); + } + } + // load the clients on controller init $scope.fetch($stateParams.groupId); // load name of current group - loadGroup($scope.groupId) + loadGroup($scope.groupId); } ]); diff --git a/src/app/groupmembers/groupmembers.list.html b/src/app/groupmembers/groupmembers.list.html index 33ae5bd..644ec85 100644 --- a/src/app/groupmembers/groupmembers.list.html +++ b/src/app/groupmembers/groupmembers.list.html @@ -15,29 +15,105 @@
-

- {{groups[groupId]}} - loading... -

+
+
+

+ {{groups[groupId]}} + loading... +

+
+
+
-
- -
-
-
-

{{memberType === 'user' ? 'Users' : 'Groups'}}

- +
+

{{memberType === 'user' ? 'Users' : 'Groups'}}

+
+
+ +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ +
+
+ +
+
+
+ +
+
+
- - + + - + @@ -59,12 +135,12 @@

{{memberType === 'user' ? 'Users' : 'Groups'}}

{{users[membership.createdBy]}}loading... - + - +
{{memberType === 'user' ? 'User Id' : 'Group Id'}}{{memberType === 'user' ? 'User Id' : 'Group Id'}} {{memberType === 'user' ? 'Handle' : 'Name'}} Created by Created at Modified by Modified at  
{{membership.createdAt | date:'MMM dd, yyyy'}}{{membership.createdAt | date : 'yyyy-MM-dd HH:mm' : 'EDT'}} {{membership.createdAt ? 'EDT' : ''}} {{users[membership.modifiedBy]}} loading... {{membership.modifiedAt | date:'MMM dd, yyyy'}}{{membership.modifiedAt | date : 'yyyy-MM-dd HH:mm' : 'EDT'}} {{membership.modifiedAt ? 'EDT' : ''}}
-
No members

- - + +
No members

+ +
diff --git a/src/app/groups/groups.list.controller.js b/src/app/groups/groups.list.controller.js index c196a95..2501efa 100644 --- a/src/app/groups/groups.list.controller.js +++ b/src/app/groups/groups.list.controller.js @@ -3,8 +3,8 @@ var module = angular.module('supportAdminApp'); module.controller('permissionmanagement.GroupsListController', [ - '$scope', '$rootScope', 'GroupService', 'UserService', 'IdResolverService', 'Alert', - function ($scope, $rootScope, GroupService, UserService, IdResolverService, $alert) { + '$scope', '$rootScope', 'GroupService', 'UserService', 'IdResolverService', 'Alert', '$timeout', + function ($scope, $rootScope, GroupService, UserService, IdResolverService, $alert, $timeout) { // true data is loading $scope.isLoading = false; @@ -27,14 +27,28 @@ module.controller('permissionmanagement.GroupsListController', [ $scope.groups.forEach(function(group) { loadUser(group.createdBy); loadUser(group.modifiedBy); - }) + }); + if ($scope.groups.length) { + // make sure changes to scope are applied + // and redraw footable table with current group list + $timeout(function() { + $('.footable').trigger('footable_redraw'); + $scope.isLoading = false; + }); + } else { + $scope.isLoading = false; + } }).catch(function (error) { $alert.error(error.error, $rootScope); - }).finally(function() { $scope.isLoading = false; }); }; + // init footable plugin + angular.element(document).ready(function() { + $('.footable').footable(); + }); + // load the groups on controller init $scope.fetch(); } diff --git a/src/app/groups/groups.list.html b/src/app/groups/groups.list.html index 77837c1..a800759 100644 --- a/src/app/groups/groups.list.html +++ b/src/app/groups/groups.list.html @@ -6,11 +6,11 @@
-
- +
+
- + @@ -33,18 +33,27 @@ {{users[group.createdBy]}}loading... - + - + + + + + + +
Group IDGroup ID Name Description Created by {{group.createdAt | date:'MMM dd, yyyy'}}{{group.createdAt | date : 'yyyy-MM-dd HH:mm' : 'EDT'}} {{group.createdAt ? 'EDT' : ''}} {{users[group.modifiedBy]}} loading... {{group.modifiedAt | date:'MMM dd, yyyy'}}{{group.modifiedAt | date : 'yyyy-MM-dd HH:mm' : 'EDT'}} {{group.modifiedAt ? 'EDT' : ''}}
+
    +
    No records found
    + diff --git a/src/app/less/base.less b/src/app/less/base.less old mode 100755 new mode 100644 index 83c4d0c..05913c1 --- a/src/app/less/base.less +++ b/src/app/less/base.less @@ -8,8 +8,11 @@ body { } html,body { - height: 100%; + min-height: 100%; +} +.app-wrapper { + height: inherit; } body.full-height-layout #wrapper, @@ -18,7 +21,7 @@ body.full-height-layout #page-wrapper { } #page-wrapper { - min-height: auto; + height: 100%; } body.boxed-layout { @@ -171,6 +174,7 @@ video { #wrapper { width: 100%; overflow-x: hidden; + height: inherit; } .wrapper { @@ -183,16 +187,17 @@ video { #page-wrapper { padding: 0 15px; - min-height: 568px; + min-height: 667px; position: relative !important; } @media (min-width: 768px) { #page-wrapper { position: inherit; + min-height: 959px; margin: 0 0 0 240px; - min-height: 1000px; } + } .title-action { diff --git a/src/app/less/buttons.less b/src/app/less/buttons.less old mode 100755 new mode 100644 index 246b24a..b9dd815 --- a/src/app/less/buttons.less +++ b/src/app/less/buttons.less @@ -7,6 +7,29 @@ margin-bottom: 5px; } +@media (max-width: 768px) { + + .float-e-margins .btn { + margin: 15px 0 0 14px; + } + + .float-e-margins .col-md-2 .btn, + .float-e-margins .col-sm-3 .btn { + margin-left: 0; + } + + .float-e-margins table .btn { + margin: 0; + width: 100%; + display: block; + + &:first-child { + margin-bottom: 15px; + } + } +} + + .btn-w-m { min-width: 120px; } diff --git a/src/app/less/media.less b/src/app/less/media.less old mode 100755 new mode 100644 index 5c27f41..795d265 --- a/src/app/less/media.less +++ b/src/app/less/media.less @@ -2,7 +2,11 @@ #page-wrapper { position: inherit; margin: 0 0 0 @sidebar-width; - min-height: 1200px; + height: 100%; + } + + .m-menu { + display: none; } .navbar-static-side { @@ -23,14 +27,57 @@ #page-wrapper { position: inherit; margin: 0 0 0 0px; - min-height: 1000px; + } + + .m-menu { + display: block; + position: absolute; + top: 5px; + right: 10px; + z-index: 2; + &.menu-open { + color: #fff; + } + } + + .table-responsive { + overflow-y: auto; + border: none; + padding-bottom: 49px; + margin: 0; + } + + // Now at mobile, paignation will be position absolute and display 0 left + .table-responsive .pagination { + position: absolute; + left: 34px; + bottom: 42px; + } + + .roles-table .pagination { + bottom: 22px; + } + + .dropdown-menu { + right: 0; + left: auto; + top: 30px; } .body-small .navbar-static-side { - display: none; z-index: 2001; position: absolute; - width: 70px; + width: 100%; + top: 0; + } + + .metismenu { + display: none; + } + + .metismenu.menu-open { + background: rgb(47, 64, 80); + display: block; } .body-small.mini-navbar .navbar-static-side { diff --git a/src/app/less/pages.less b/src/app/less/pages.less old mode 100755 new mode 100644 index 057dc4b..cbec701 --- a/src/app/less/pages.less +++ b/src/app/less/pages.less @@ -610,6 +610,10 @@ a.compose-mail { margin-bottom: 20px; } +.word-wrap { + word-break: break-all; +} + .mail-box-header { background-color: #ffffff; border: 1px solid @border-color; @@ -1319,3 +1323,24 @@ body.body-small .vote-icon { display: none; } +// Mobile new style +@media (max-width: 768px) { + + .float-e-margins { + &.import-form { + .row { + margin-left: 0; + margin-right: 0; + } + + .block { + word-break: break-all; + } + } + } + + .float-e-margins table .btn-group { + width: 100%; + } + +} diff --git a/src/app/less/rtl.less b/src/app/less/rtl.less old mode 100755 new mode 100644 index 3aaf4e8..ad63b00 --- a/src/app/less/rtl.less +++ b/src/app/less/rtl.less @@ -233,8 +233,7 @@ body.rtls .top-navigation .footer.fixed, body.rtls.top-navigation .footer.fixed .body-small.rtls #page-wrapper { position: inherit; - margin: 0 0 0 0px; - min-height: 1000px; + margin: 0; } .body-small.rtls .navbar-static-side { diff --git a/src/app/permission_management/permission_management.html b/src/app/permission_management/permission_management.html index 9bb00bf..0c0dc59 100644 --- a/src/app/permission_management/permission_management.html +++ b/src/app/permission_management/permission_management.html @@ -73,7 +73,7 @@

    Roles

    -
    +

    Press on a role row to assign/unassign users.

    Roles
    +
    \ No newline at end of file diff --git a/src/app/rolemembers/rolemembers.list.controller.js b/src/app/rolemembers/rolemembers.list.controller.js index 4732260..517d649 100644 --- a/src/app/rolemembers/rolemembers.list.controller.js +++ b/src/app/rolemembers/rolemembers.list.controller.js @@ -3,8 +3,8 @@ 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) { + '$scope', '$rootScope', 'RoleService', 'IdResolverService', 'UserService', '$animate', '$stateParams', '$state', '$q', 'Alert', '$timeout', + function ($scope, $rootScope, RoleService, IdResolverService, UserService, $animate, $stateParams, $state, $q, $alert, $timeout) { // true if role is loading $scope.isLoading = false; @@ -31,6 +31,15 @@ module.controller('permissionmanagement.RoleMembersListController', [ $scope.users = {}; var loadUser = IdResolverService.getUserResolverFunction($scope.users); + // keep list of all members for client side filtering + var allMembers = []; + + // criteria to filter members + $scope.filterCriteria = { + userId: '', + userHandle: '' + }; + /** * Return members which are selected in the table by checkboxes * @@ -58,6 +67,8 @@ module.controller('permissionmanagement.RoleMembersListController', [ id: memberId } }); + // save all members list for filtering + allMembers = _.clone($scope.members); // if have some members we will redraw table using footable plugin if ($scope.members.length) { // make sure changes to scope are applied @@ -102,6 +113,7 @@ module.controller('permissionmanagement.RoleMembersListController', [ member.isRemoving = true; return RoleService.unassignRole($stateParams.roleId, member.id).then(function() { _.remove($scope.members, { id: member.id }); + _.remove(allMembers, { 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 @@ -140,6 +152,93 @@ module.controller('permissionmanagement.RoleMembersListController', [ }); } + /** + * Helper function which redraws table with new member list + * and properly takes care about updating footable plugin + * + * The essential problem this function is solving is that after we + * update $scope.members, rows from the table are not being deleted immediately + * because we are using animations for rows. + * That's why if we try to force update footable plugin it still find old rows which are in + * process of animation and still in DOM tree. + * As a workaround in this function we disable animations for rows before updating them, + * thus footable plugin can see changes immediately after scope is updated. + * + * @param {Array} members new array of members which has to displayed + * @param {Function} callback optional callback after table is fully redrawn + * + * @return {Promise} resolved after table was completely updated + */ + function redrawTableWithMembers(members) { + var $footable = $('.footable'); + + // disable animations for rows, so old rows will be removed immediately + // from the DOM tree and footable can see changes + $footable.find('tr').each(function(index, el) { + $animate.enabled(el, false); + }); + + // update members list in scope + $scope.members = members; + + // make sure that changes are applied to the scope + return $timeout(function() { + // force footable plugin to redraw + $footable.trigger('footable_redraw'); + // enable animation for table rows again + $footable.find('tr').each(function(index, el) { + $animate.enabled(el, true); + }); + }); + } + + /** + * Applies filter to the member list + */ + $scope.applyFilter = function() { + var filteredMembers = _.clone(allMembers); + + // filter by ids first, it works immediately as we know all the data + // so we don't need to show loader for this + if ($scope.filterCriteria.userId) { + filteredMembers = _.filter(filteredMembers, { id: $scope.filterCriteria.userId }); + } + + // if handle filter is defined and we still have some rows to filter + if ($scope.filterCriteria.userHandle && filteredMembers.length > 0) { + // we show loader as we need to make request to the server + $scope.isLoading = true; + + // As there is no server API to filter role members and we don't have + // user handles to filter, we first have to find user ids by it's handle + // and after we can filter users by id + UserService.find({ + fields: 'id', + filter: 'handle=*' + $scope.filterCriteria.userHandle + '*&like=true', + limit: 1000000 // set big limit to make sure server returns all records + }).then(function(users) { + var foundIds = _.map(users, 'id'); + + filteredMembers = _.filter(filteredMembers, function(member) { + return _.includes(foundIds, member.id); + }); + + redrawTableWithMembers(filteredMembers).then(function() { + $scope.isLoading = false; + }); + }).catch(function(error) { + // is any error occurs show it and leave table untouched + $scope.isLoading = false; + $alert.error(error.error, $rootScope); + }); + + // if we don't filter by handle which makes server request + // redraw table immediately + } else { + redrawTableWithMembers(filteredMembers); + } + } + /** * Uncheck all checkboxes */ @@ -174,6 +273,7 @@ module.controller('permissionmanagement.RoleMembersListController', [ } } + // init footable plugin angular.element(document).ready(function() { $('.footable').on({ // we watch footable jquery plugin footable_page_filled event diff --git a/src/app/rolemembers/rolemembers.list.html b/src/app/rolemembers/rolemembers.list.html index 5b61970..346d80a 100644 --- a/src/app/rolemembers/rolemembers.list.html +++ b/src/app/rolemembers/rolemembers.list.html @@ -15,16 +15,41 @@
    -

    - {{role.roleName}} - loading... -

    +
    +
    +

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

    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    -
    +
    @@ -63,6 +88,7 @@

    + diff --git a/src/app/roles/roles.list.html b/src/app/roles/roles.list.html index bbfb5bf..ac0ead3 100644 --- a/src/app/roles/roles.list.html +++ b/src/app/roles/roles.list.html @@ -65,7 +65,7 @@ -
    +

    loading... - + - + @@ -117,6 +117,7 @@
    {{role.createdAt | date : 'MM dd yyyy HH:mm' : 'EDT'}} {{role.createdAt ? 'EDT' : ''}}{{role.createdAt | date : 'yyyy-MM-dd HH:mm' : 'EDT'}} {{role.createdAt ? 'EDT' : ''}} {{ctrl.users[role.modifiedBy]}} @@ -104,7 +104,7 @@ ng-if="role.modifiedBy && !ctrl.users[role.modifiedBy]" >loading... {{role.modifiedAt | date : 'MM dd yyyy HH:mm' : 'EDT'}} {{role.modifiedAt ? 'EDT' : ''}}{{role.modifiedAt | date : 'yyyy-MM-dd HH:mm' : 'EDT'}} {{role.modifiedAt ? 'EDT' : ''}}
    +
    diff --git a/src/app/sso/sso.html b/src/app/sso/sso.html index 98fcca0..a7ab50c 100644 --- a/src/app/sso/sso.html +++ b/src/app/sso/sso.html @@ -10,7 +10,7 @@

    Import SSO Users

    -
    +
    diff --git a/src/app/submissions/submissions.html b/src/app/submissions/submissions.html index 906c46b..93f9bfd 100644 --- a/src/app/submissions/submissions.html +++ b/src/app/submissions/submissions.html @@ -20,7 +20,7 @@

    Submissions

    -
    @@ -38,25 +38,28 @@

    Submissions

    {{challengeObj.subTrack}}
    (Submittable) Phases
    - - - - - - - - - - - - - - - - - - -
    IdTypeStatusActual / Schedule End Time
    {{p.value.id}}{{p.name}}{{p.value.phaseStatus}}{{p.value.actualEndTime|amFromUnix| amTimezone:'America/New_York'| amDateFormat:'MM.DD.YYYY HH:mm z'}} (A){{p.value.scheduledEndTime|amFromUnix| amTimezone:'America/New_York'| amDateFormat:'MM.DD.YYYY HH:mm z'}} (S)
    +
    + + + + + + + + + + + + + + + + + + +
    IdTypeStatusActual / Schedule End Time
    {{p.value.id}}{{p.name}}{{p.value.phaseStatus}}{{p.value.actualEndTime|amFromUnix| amTimezone:'America/New_York'| amDateFormat:'MM.DD.YYYY HH:mm z'}} (A){{p.value.scheduledEndTime|amFromUnix| amTimezone:'America/New_York'| amDateFormat:'MM.DD.YYYY HH:mm z'}} (S)
    +
    +
    diff --git a/src/app/submissions/submissions.list.html b/src/app/submissions/submissions.list.html index c123ecc..ba8efe6 100644 --- a/src/app/submissions/submissions.list.html +++ b/src/app/submissions/submissions.list.html @@ -58,6 +58,7 @@

    Submissions for Challenge # {{challengeObj.name}}

    +
    diff --git a/src/app/tags/tags.list.html b/src/app/tags/tags.list.html old mode 100755 new mode 100644 index d40d51d..57c91df --- a/src/app/tags/tags.list.html +++ b/src/app/tags/tags.list.html @@ -45,7 +45,7 @@

    No Tags found.

    -
    +
    @@ -100,6 +100,7 @@
    +
    diff --git a/src/app/users/groups-edit-dialog.html b/src/app/users/groups-edit-dialog.html index c7d7200..3e1d57d 100644 --- a/src/app/users/groups-edit-dialog.html +++ b/src/app/users/groups-edit-dialog.html @@ -9,7 +9,7 @@
    -
    +
    @@ -34,6 +34,7 @@
    No groups

    +
    diff --git a/src/app/users/roles-edit-dialog.html b/src/app/users/roles-edit-dialog.html index 718d310..fa43692 100644 --- a/src/app/users/roles-edit-dialog.html +++ b/src/app/users/roles-edit-dialog.html @@ -9,7 +9,7 @@
    -
    +
    @@ -34,6 +34,7 @@
    No roles

    +
    diff --git a/src/app/users/status-history-dialog.html b/src/app/users/status-history-dialog.html index 0c8e4a4..7788825 100644 --- a/src/app/users/status-history-dialog.html +++ b/src/app/users/status-history-dialog.html @@ -5,6 +5,7 @@
    +
    @@ -19,6 +20,8 @@
    +
    +
    diff --git a/src/app/users/users.html b/src/app/users/users.html index 093d281..32766e7 100644 --- a/src/app/users/users.html +++ b/src/app/users/users.html @@ -54,7 +54,7 @@

    Users

    -
    +
    @@ -135,6 +135,7 @@

    Users

    +
    diff --git a/src/app/users/users.service.js b/src/app/users/users.service.js index 9c4499a..50cc8f1 100644 --- a/src/app/users/users.service.js +++ b/src/app/users/users.service.js @@ -54,12 +54,14 @@ angular.module('supportAdminApp') var query = ""; angular.forEach({ "fields": opts.fields || "id,handle,email,active,emailActive,status,credential,firstName,lastName,createdAt,modifiedAt", - "filter": opts.filter - //"limit" : null, + "filter": opts.filter, + "limit" : opts.limit, //"offset": null, //"orderBy": null, }, function(value, key) { - query += ('&' + key + '=' + encodeURIComponent(value)); + if (value) { + query += ('&' + key + '=' + encodeURIComponent(value)); + } }); var request = $http({ diff --git a/src/components/alert/alert.html b/src/components/alert/alert.html index e888b0f..4e564f4 100644 --- a/src/components/alert/alert.html +++ b/src/components/alert/alert.html @@ -1,5 +1,5 @@
    -