From 9ea0803b457a20ec31fb8039297e9cd3f89594e8 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 29 Feb 2016 12:05:23 -0500 Subject: [PATCH] added role list and deletion to projects object --- awx/ui/client/legacy-styles/lists.less | 2 +- awx/ui/client/src/access/main.js | 12 +++ awx/ui/client/src/access/roleList.block.less | 71 ++++++++++++++ .../client/src/access/roleList.directive.js | 44 +++++++++ .../client/src/access/roleList.partial.html | 13 +++ awx/ui/client/src/app.js | 40 ++++++-- awx/ui/client/src/controllers/Projects.js | 1 - awx/ui/client/src/filters.js | 1 - awx/ui/client/src/forms/Projects.js | 85 ++++------------ .../client/src/helpers/PaginationHelpers.js | 2 +- awx/ui/client/src/helpers/related-search.js | 5 + .../authentication.service.js | 4 +- .../client/src/main-menu/main-menu.block.less | 4 + awx/ui/client/src/shared/form-generator.js | 98 ++++++++++++------- awx/ui/client/src/shared/generator-helpers.js | 6 +- .../list-generator/list-generator.factory.js | 7 +- awx/ui/client/src/shared/prompt/prompt.less | 5 + 17 files changed, 278 insertions(+), 122 deletions(-) create mode 100644 awx/ui/client/src/access/main.js create mode 100644 awx/ui/client/src/access/roleList.block.less create mode 100644 awx/ui/client/src/access/roleList.directive.js create mode 100644 awx/ui/client/src/access/roleList.partial.html diff --git a/awx/ui/client/legacy-styles/lists.less b/awx/ui/client/legacy-styles/lists.less index ba6adba673..0929fd4a28 100644 --- a/awx/ui/client/legacy-styles/lists.less +++ b/awx/ui/client/legacy-styles/lists.less @@ -39,7 +39,7 @@ table, tbody { border-top-left-radius: 5px; } -.List-tableHeader:last-of-type { +.List-tableHeader--actions { border-top-right-radius: 5px; text-align: right; } diff --git a/awx/ui/client/src/access/main.js b/awx/ui/client/src/access/main.js new file mode 100644 index 0000000000..5b7063938b --- /dev/null +++ b/awx/ui/client/src/access/main.js @@ -0,0 +1,12 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import roleList from './roleList.directive'; +import addPermissions from './addPermissions/main'; + +export default + angular.module('access', []) + .directive('roleList', roleList); diff --git a/awx/ui/client/src/access/roleList.block.less b/awx/ui/client/src/access/roleList.block.less new file mode 100644 index 0000000000..2e006791cf --- /dev/null +++ b/awx/ui/client/src/access/roleList.block.less @@ -0,0 +1,71 @@ +/** @define RoleList */ + +.RoleList { + display: flex; + flex-wrap: wrap; + align-items: flex-start; +} + +.RoleList-tagContainer { + display: flex; + max-width: 100%; +} + +.RoleList-tag { + border-radius: 5px; + padding: 2px 10px; + margin: 4px 0px; + border: 1px solid #e1e1e1; + font-size: 12px; + color: #848992; + text-transform: uppercase; + background-color: #fff; + margin-right: 5px; + max-width: 100%; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.RoleList-tag--deletable { + margin-right: 0px; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + border-right: 0; + max-wdith: ~"calc(100% - 23px)"; +} + +.RoleList-deleteContainer { + border: 1px solid #e1e1e1; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + padding: 0 5px; + margin: 4px 0px; + margin-right: 5px; + align-items: center; + display: flex; + cursor: pointer; +} + +.RoleList-tagDelete { + font-size: 13px; + color: #b7b7b7; +} + +.RoleList-name { + flex: initial; + max-width: 100%; +} + +.RoleList-tag--deletable > .RoleList-name { + max-width: ~"calc(100% - 23px)"; +} + +.RoleList-deleteContainer:hover, { + border-color: #ff5850; + background-color: #ff5850; +} + +.RoleList-deleteContainer:hover > .RoleList-tagDelete { + color: #fff; +} diff --git a/awx/ui/client/src/access/roleList.directive.js b/awx/ui/client/src/access/roleList.directive.js new file mode 100644 index 0000000000..376a00f085 --- /dev/null +++ b/awx/ui/client/src/access/roleList.directive.js @@ -0,0 +1,44 @@ +/* jshint unused: vars */ +export default + [ 'templateUrl', + function(templateUrl) { + return { + restrict: 'E', + scope: false, + templateUrl: templateUrl('access/roleList'), + link: function(scope, element, attrs) { + // given a list of roles (things like "project + // auditor") which are pulled from two different + // places in summary fields, and creates a + // concatenated/sorted list + scope.roles = [] + .concat(scope.permission.summary_fields + .direct_access.map(function(i) { + return { + name: i.role.name, + roleId: i.role.id, + resourceName: i.role.resource_name, + explicit: true + }; + })) + .concat(scope.permission.summary_fields + .indirect_access.map(function(i) { + return { + name: i.role.name, + roleId: i.role.id, + explicit: false + }; + })) + .sort(function(a, b) { + if (a.name + .toLowerCase() > b.name + .toLowerCase()) { + return 1; + } else { + return -1; + } + }); + } + }; + } + ]; diff --git a/awx/ui/client/src/access/roleList.partial.html b/awx/ui/client/src/access/roleList.partial.html new file mode 100644 index 0000000000..bc49322d45 --- /dev/null +++ b/awx/ui/client/src/access/roleList.partial.html @@ -0,0 +1,13 @@ +
+
+ {{ role.name }} +
+
+ +
+
diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index a23e803de2..974e0e885e 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -31,6 +31,7 @@ import permissions from './permissions/main'; import managementJobs from './management-jobs/main'; import jobDetail from './job-detail/main'; import notifications from './notifications/main'; +import access from './access/main'; // modules import about from './about/main'; @@ -101,6 +102,7 @@ var tower = angular.module('Tower', [ jobDetail.name, notifications.name, standardOut.name, + access.name, 'templates', 'Utilities', 'OrganizationFormDefinition', @@ -884,16 +886,38 @@ var tower = angular.module('Tower', [ }]); }]) - .run(['$q', '$compile', '$cookieStore', '$rootScope', '$log', '$state', 'CheckLicense', - '$location', 'Authorization', 'LoadBasePaths', 'Timer', 'ClearScope', 'Socket', - 'LoadConfig', 'Store', 'ShowSocketHelp', 'pendoService', - function ( - $q, $compile, $cookieStore, $rootScope, $log, $state, CheckLicense, - $location, Authorization, LoadBasePaths, Timer, ClearScope, Socket, - LoadConfig, Store, ShowSocketHelp, pendoService) - { + .run(['$q', '$compile', '$cookieStore', '$rootScope', '$log', 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer', 'ClearScope', 'Socket', + 'LoadConfig', 'Store', 'ShowSocketHelp', 'AboutAnsibleHelp', 'pendoService', 'Prompt', 'Rest', 'Wait', 'ProcessErrors', '$state', + function ($q, $compile, $cookieStore, $rootScope, $log, CheckLicense, $location, Authorization, LoadBasePaths, Timer, ClearScope, Socket, + LoadConfig, Store, ShowSocketHelp, AboutAnsibleHelp, pendoService, Prompt, Rest, Wait, ProcessErrors, $state) { var sock; + $rootScope.deletePermission = function (user, role, userName, + roleName, resourceName) { + var action = function () { + $('#prompt-modal').modal('hide'); + Wait('start'); + var url = "/api/v1/users/" + user + "/roles/"; + Rest.setUrl(url); + Rest.post({"disassociate": true, "id": role}) + .success(function () { + Wait('stop'); + $rootScope.$broadcast("refreshList", "permission"); + }) + .error(function (data, status) { + ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); + }); + }; + + Prompt({ + hdr: 'Remove Role from ' + resourceName, + body: '
Confirm the removal of the ' + roleName + ' role associated with ' + userName + '.
', + action: action, + actionText: 'REMOVE' + }); + }; + function activateTab() { // Make the correct tab active var base = $location.path().replace(/^\//, '').split('/')[0]; diff --git a/awx/ui/client/src/controllers/Projects.js b/awx/ui/client/src/controllers/Projects.js index 911ebcf356..c75cc5193f 100644 --- a/awx/ui/client/src/controllers/Projects.js +++ b/awx/ui/client/src/controllers/Projects.js @@ -649,7 +649,6 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log, data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; } } - relatedSets = form.relatedSets(data.related); diff --git a/awx/ui/client/src/filters.js b/awx/ui/client/src/filters.js index da0123cad7..e6549bb779 100644 --- a/awx/ui/client/src/filters.js +++ b/awx/ui/client/src/filters.js @@ -7,7 +7,6 @@ import sanitizeFilter from './shared/xss-sanitizer.filter'; import capitalizeFilter from './shared/capitalize.filter'; import longDateFilter from './shared/long-date.filter'; - export { sanitizeFilter, capitalizeFilter, diff --git a/awx/ui/client/src/forms/Projects.js b/awx/ui/client/src/forms/Projects.js index dbf04c457f..aa39d4bd7d 100644 --- a/awx/ui/client/src/forms/Projects.js +++ b/awx/ui/client/src/forms/Projects.js @@ -278,83 +278,38 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition']) } } }, - - schedules: { + permissions: { type: 'collection', - title: 'Schedules', - iterator: 'schedule', + title: 'Permissions', + iterator: 'permission', index: false, open: false, - + searchType: 'select', actions: { - refresh: { - mode: 'all', - awToolTip: "Refresh the page", - ngClick: "refreshSchedules()", - actionClass: 'btn List-buttonDefault', - buttonContent: 'REFRESH', - ngHide: 'scheduleLoading == false && schedule_active_search == false && schedule_total_rows < 1' - }, add: { - mode: 'all', - ngClick: 'addSchedule()', - awToolTip: 'Add a new schedule', + ngClick: "addPermission", + label: 'Add', + awToolTip: 'Add a permission', actionClass: 'btn List-buttonSubmit', buttonContent: '+ ADD' } }, + fields: { - name: { + username: { key: true, - label: 'Name', - ngClick: "editSchedule(schedule.id)", - columnClass: "col-md-3 col-sm-3 col-xs-3" + label: 'User', + linkBase: 'users', + class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4' }, - dtstart: { - label: 'First Run', - filter: "longDate", - searchable: false, - columnClass: "col-md-2 col-sm-3 hidden-xs" - }, - next_run: { - label: 'Next Run', - filter: "longDate", - searchable: false, - columnClass: "col-md-2 col-sm-3 col-xs-3" - }, - dtend: { - label: 'Final Run', - filter: "longDate", - searchable: false, - columnClass: "col-md-2 col-sm-3 hidden-xs" - } - }, - fieldActions: { - "play": { - mode: "all", - ngClick: "toggleSchedule($event, schedule.id)", - awToolTip: "{{ schedule.play_tip }}", - dataTipWatch: "schedule.play_tip", - iconClass: "{{ 'fa icon-schedule-enabled-' + schedule.enabled }}", - dataPlacement: "top" - }, - edit: { - label: 'Edit', - ngClick: "editSchedule(schedule.id)", - icon: 'icon-edit', - awToolTip: 'Edit schedule', - dataPlacement: 'top' - }, - "delete": { - label: 'Delete', - ngClick: "deleteSchedule(schedule.id)", - icon: 'icon-trash', - awToolTip: 'Delete schedule', - dataPlacement: 'top' + role: { + label: 'Role', + type: 'role', + noSort: true, + class: 'col-lg-9 col-md-9 col-sm-9 col-xs-8' } } } - }, relatedSets: function(urls) { @@ -363,9 +318,9 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition']) iterator: 'organization', url: urls.organizations }, - schedules: { - iterator: 'schedule', - url: urls.schedules + permissions: { + iterator: 'permission', + url: urls.resource_access_list } }; } diff --git a/awx/ui/client/src/helpers/PaginationHelpers.js b/awx/ui/client/src/helpers/PaginationHelpers.js index 2b131c2dc0..4bbc46cd03 100644 --- a/awx/ui/client/src/helpers/PaginationHelpers.js +++ b/awx/ui/client/src/helpers/PaginationHelpers.js @@ -134,7 +134,7 @@ export default } else if (mode === 'lookup') { scope[iterator + '_page_size'] = 5; } else { - scope[iterator + '_page_size'] = 20; + scope[iterator + '_page_size'] = 2; } scope.getPage = function (page, set, iterator) { diff --git a/awx/ui/client/src/helpers/related-search.js b/awx/ui/client/src/helpers/related-search.js index cad094d349..04bb72f071 100644 --- a/awx/ui/client/src/helpers/related-search.js +++ b/awx/ui/client/src/helpers/related-search.js @@ -230,10 +230,15 @@ export default url += (url.match(/\/$/)) ? '?' : '&'; url += scope[iterator + 'SearchParams']; url += (scope[iterator + '_page_size']) ? '&page_size=' + scope[iterator + '_page_size'] : ""; + scope[iterator + '_active_search'] = true; RefreshRelated({ scope: scope, set: set, iterator: iterator, url: url }); }; + scope.$on("refreshList", function(e, iterator) { + scope.search(iterator); + }); + scope.sort = function (iterator, fld) { var sort_order, icon, direction, set; diff --git a/awx/ui/client/src/login/authenticationServices/authentication.service.js b/awx/ui/client/src/login/authenticationServices/authentication.service.js index d648bdd1eb..912ada41a8 100644 --- a/awx/ui/client/src/login/authenticationServices/authentication.service.js +++ b/awx/ui/client/src/login/authenticationServices/authentication.service.js @@ -85,7 +85,9 @@ export default } Store('sessionTime', x); - $rootScope.lastUser = $cookieStore.get('current_user').id; + if ($cookieStore.get('current_user')) { + $rootScope.lastUser = $cookieStore.get('current_user').id; + } $cookieStore.remove('token_expires'); $cookieStore.remove('current_user'); $cookieStore.remove('token'); diff --git a/awx/ui/client/src/main-menu/main-menu.block.less b/awx/ui/client/src/main-menu/main-menu.block.less index 5e9b342c2c..d64b5de3d3 100644 --- a/awx/ui/client/src/main-menu/main-menu.block.less +++ b/awx/ui/client/src/main-menu/main-menu.block.less @@ -136,6 +136,10 @@ .MainMenu-itemText--username { padding-left: 13px; margin-top: -4px; + max-width: 85px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .MainMenu-itemImage { diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index 693a090f68..c7187d7106 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -1723,32 +1723,52 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += "\n"; html += (collection.index === undefined || collection.index !== false) ? "#\n" : ""; for (fld in collection.fields) { - html += "" + - collection.fields[fld].label; - html += " " } else { - html += "fa fa-sort"; + html += ">"; } - html += "\">\n"; + + + html += collection.fields[fld].label; + + if (!collection.fields[fld].noSort) { + html += " " + } + + html += "\n"; + } + if (collection.fieldActions) { + html += "Actions\n"; } - html += "Actions\n"; html += "\n"; html += ""; html += "\n"; - html += "\n"; if (collection.index === undefined || collection.index !== false) { - html += "{{ $index + ((" + collection.iterator + "_page - 1) * " + + html += "{{ $index + ((" + collection.iterator + "_page - 1) * " + collection.iterator + "_page_size) + 1 }}.\n"; } cnt = 1; @@ -1765,31 +1785,33 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat } // Row level actions - html += ""; - for (act in collection.fieldActions) { - fAction = collection.fieldActions[act]; - html += ""; } - // html += SelectIcon({ action: act }); - //html += (fAction.label) ? " " + fAction.label + "": ""; - html += ""; + html += ""; + html += "\n"; } - html += ""; - html += "\n"; // Message for loading html += "\n"; diff --git a/awx/ui/client/src/shared/generator-helpers.js b/awx/ui/client/src/shared/generator-helpers.js index 1fc8880e63..44bff438d9 100644 --- a/awx/ui/client/src/shared/generator-helpers.js +++ b/awx/ui/client/src/shared/generator-helpers.js @@ -449,6 +449,8 @@ angular.module('GeneratorHelpers', [systemStatus.name]) if (field.type !== undefined && field.type === 'DropDown') { html = DropDown(params); + } else if (field.type === 'role') { + html += ""; } else if (field.type === 'badgeCount') { html = BadgeCount(params); } else if (field.type === 'badgeOnly') { @@ -520,7 +522,7 @@ angular.module('GeneratorHelpers', [systemStatus.name]) list: list, field: field, fld: fld, - base: base + base: field.linkBase || base }) + ' '; }); } @@ -532,7 +534,7 @@ angular.module('GeneratorHelpers', [systemStatus.name]) list: list, field: field, fld: fld, - base: base + base: field.linkBase || base }); } } diff --git a/awx/ui/client/src/shared/list-generator/list-generator.factory.js b/awx/ui/client/src/shared/list-generator/list-generator.factory.js index 5071dfee54..de6aed9081 100644 --- a/awx/ui/client/src/shared/list-generator/list-generator.factory.js +++ b/awx/ui/client/src/shared/list-generator/list-generator.factory.js @@ -665,10 +665,9 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate } } if (options.mode === 'select') { - html += "Select"; - } - else if (options.mode === 'edit' && list.fieldActions) { - html += "Select"; + } else if (options.mode === 'edit' && list.fieldActions) { + html += ""; html += (list.fieldActions.label === undefined || list.fieldActions.label) ? "Actions" : ""; diff --git a/awx/ui/client/src/shared/prompt/prompt.less b/awx/ui/client/src/shared/prompt/prompt.less index e0e5ef0c30..5c491b64e3 100644 --- a/awx/ui/client/src/shared/prompt/prompt.less +++ b/awx/ui/client/src/shared/prompt/prompt.less @@ -8,3 +8,8 @@ .Prompt-bodyTarget { color: @default-data-txt; } + +.Prompt-emphasis { + font-weight: bold; + text-transform: uppercase; +}