diff --git a/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js b/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js index 131a060523..99e2cb451f 100644 --- a/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js +++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js @@ -17,7 +17,6 @@ export default ['$compile','templateUrl', 'i18n', 'generateList', selected: "=" }, link: function(scope, element, attrs) { - console.log(scope.resourceType) let listMap, list, list_html; listMap = { @@ -28,12 +27,12 @@ export default ['$compile','templateUrl', 'i18n', 'generateList', credentials: CredentialList }; - list = _.cloneDeep(listMap[scope.resourceType]) + list = _.cloneDeep(listMap[scope.resourceType]); list.fieldActions = { remove: { ngClick: `removeSelection(${list.iterator}, resourceType)`, - icon: 'fa-remove', + iconClass: 'fa fa-times-circle', awToolTip: i18n._(`Remove ${list.iterator}`), label: i18n._('Remove'), class: 'btn-sm' @@ -43,6 +42,8 @@ export default ['$compile','templateUrl', 'i18n', 'generateList', list.listTitleBadge = false; + // @issue - fix field.columnClass values for this view + switch(scope.resourceType){ case 'projects': @@ -77,8 +78,7 @@ export default ['$compile','templateUrl', 'i18n', 'generateList', description: list.fields.description }; break; - - default: + case 'credentials': list.fields = { name: list.fields.name, description: list.fields.description @@ -93,12 +93,31 @@ export default ['$compile','templateUrl', 'i18n', 'generateList', related: false, title: false, showSearch: false, + showEmptyPanel: false, paginate: false }); scope.list = list; - scope[`${list.iterator}_dataset`] = scope.collection; - scope[list.name] = scope.collection; + + scope.$watchCollection('collection', function(selected){ + scope[`${list.iterator}_dataset`] = scope.collection; + scope[list.name] = _.values(scope.collection); + }); + + scope.removeSelection = function(resource, type){ + let multiselect_scope, deselectedIdx; + + delete scope.collection[resource.id]; + delete scope.selected[type][resource.id]; + + // a quick & dirty hack + // section 1 and section 2 elements produce sibling scopes + // This means events propogated from section 2 are not received in section 1 + // The following code directly accesses the right scope by list table id + multiselect_scope = angular.element('#AddPermissions-body').find(`#${type}_table`).scope() + deselectedIdx = _.findIndex(multiselect_scope[type], {id: resource.id}); + multiselect_scope[type][deselectedIdx].isSelected = false; + }; element.append(list_html); $compile(element.contents())(scope); diff --git a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.controller.js b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.controller.js index 3e41460487..665f5f6c26 100644 --- a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.controller.js +++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.controller.js @@ -11,8 +11,8 @@ * Controller for handling permissions adding */ -export default ['$rootScope', '$scope', '$state', 'GetBasePath', 'Rest', '$q', 'Wait', 'ProcessErrors', -function(rootScope, scope, $state, GetBasePath, Rest, $q, Wait, ProcessErrors) { +export default ['$rootScope', '$scope', '$state', 'i18n', 'CreateSelect2', 'GetBasePath', 'Rest', '$q', 'Wait', 'ProcessErrors', +function(rootScope, scope, $state, i18n, CreateSelect2, GetBasePath, Rest, $q, Wait, ProcessErrors) { init(); @@ -24,12 +24,20 @@ function(rootScope, scope, $state, GetBasePath, Rest, $q, Wait, ProcessErrors) { // selected - keyed by type of resource // selected[type] - keyed by each resource object's id // selected[type][id] === { roles: [ ... ], ... } + + // collection of resources selected in section 1 scope.selected = {}; - _.each(resources, (resource) => scope.selected[resource] = {}); + _.each(resources, (type) => scope.selected[type] = {}); + // collection of assignable roles per type of resource scope.keys = {}; - _.each(resources, (resource) => scope.keys[resource] = {}); + _.each(resources, (type) => scope.keys[type] = {}); + // collection of currently-selected role to assign in section 2 + scope.roleSelection = {}; + _.each(resources, (type) => scope.roleSelection[type] = null); + + // tracks currently-selected tabs, initialized with the job templates tab open scope.tab = { job_templates: true, workflow_templates: false, @@ -37,13 +45,39 @@ function(rootScope, scope, $state, GetBasePath, Rest, $q, Wait, ProcessErrors) { inventories: false, credentials: false }; + + // initializes select2 per select field + // html snippet: + /* +
+ +
+ */ + _.each(resources, (type) => buildSelect2(type)); + + function buildSelect2(type){ + CreateSelect2({ + element: `#${type}-role-select`, + multiple: false, + placeholder: i18n._('Select a role') + }); + } + scope.showKeyPane = false; + + // the user or team being assigned permissions scope.owner = scope.resolve.resourceData.data; } // aggregate name/descriptions for each available role, based on resource type + // reasoning: function aggregateKey(item, type){ - _.merge(scope.keys[type], item.summary_fields.object_roles); + _.merge(scope.keys[type], _.omit(item.summary_fields.object_roles, 'read_role')); } scope.closeModal = function() { @@ -66,11 +100,6 @@ function(rootScope, scope, $state, GetBasePath, Rest, $q, Wait, ProcessErrors) { return Object.keys(scope.selected[tab]).length > 0; }; - scope.removeSelection = function(resource, type){ - delete scope.selected[type][resource.id]; - resource.isSelected = false; - }; - // handle form tab changes scope.selectTab = function(selected){ _.each(scope.tab, (value, key, collection) => { @@ -78,7 +107,7 @@ function(rootScope, scope, $state, GetBasePath, Rest, $q, Wait, ProcessErrors) { }); }; - // pop/push into unified collection of selected users & teams + // pop/push into unified collection of selected resourcesf scope.$on("selectedOrDeselected", function(e, value) { let resourceType = scope.currentTab(), item = value.value; @@ -94,15 +123,20 @@ function(rootScope, scope, $state, GetBasePath, Rest, $q, Wait, ProcessErrors) { // post roles to api scope.saveForm = function() { - Wait('start'); - // scope.selected => { n: {id: n}, ... } => [ {id: n}, ... ] - let requests = _(scope.selected).map((type) => { - return _.map(type, (resource) => resource.roles); + //Wait('start'); + + // builds an array of role entities to apply to current user or team + let roles = _(scope.selected).map( (resources, type) =>{ + return _.map(resources, (resource) => { + return resource.summary_fields.object_roles[scope.roleSelection[type]] + }); }).flattenDeep().value(); + + debugger; Rest.setUrl(scope.owner.related.roles); - $q.all( _.map(requests, (entity) => Rest.post({id: entity.id})) ) + $q.all( _.map(roles, (entity) => Rest.post({id: entity.id})) ) .then( () =>{ Wait('stop'); scope.closeModal(); diff --git a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html index dc330bc035..de79fe03f0 100644 --- a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html +++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html @@ -20,7 +20,7 @@ -
+
@@ -65,7 +65,7 @@
-
+
@@ -141,7 +141,15 @@
- + +
+ +
@@ -149,7 +157,8 @@ + selected="selected" + ng-show="tab[type]">
@@ -167,7 +176,7 @@
diff --git a/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js index d94411ff0f..855c15e093 100644 --- a/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js +++ b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js @@ -70,7 +70,13 @@ export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateL description: list.fields.description }; break; - + case 'Users': + list.fields = { + username: list.fields.username, + first_name: list.fields.first_name, + last_name: list.fields.last_name + } + break; default: list.fields = { name: list.fields.name, diff --git a/awx/ui/client/src/access/rbac-role-column/roleList.directive.js b/awx/ui/client/src/access/rbac-role-column/roleList.directive.js index 2ee33b8652..97682d0bc9 100644 --- a/awx/ui/client/src/access/rbac-role-column/roleList.directive.js +++ b/awx/ui/client/src/access/rbac-role-column/roleList.directive.js @@ -5,7 +5,7 @@ export default return { restrict: 'E', scope: true, - templateUrl: templateUrl('access/rbac-role-list/roleList'), + templateUrl: templateUrl('access/rbac-role-column/roleList'), link: function(scope, element, attrs) { // given a list of roles (things like "project // auditor") which are pulled from two different 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 b1a74db51a..cac1e129aa 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 @@ -241,9 +241,11 @@ export default ['$location', '$compile', '$rootScope', 'Attr', 'Icon', } // Show the "no items" box when loading is done and the user isn't actively searching and there are no results - html += `
`; - html += (list.emptyListText) ? list.emptyListText : i18n._("PLEASE ADD ITEMS TO THIS LIST"); - html += "
"; + if (options.showEmptyPanel === undefined || options.showEmptyPanel === true){ + html += `
`; + html += (list.emptyListText) ? list.emptyListText : i18n._("PLEASE ADD ITEMS TO THIS LIST"); + html += "
"; + } // Add a title and optionally a close button (used on Inventory->Groups) if (options.mode !== 'lookup' && list.showTitle) {