diff --git a/awx/ui/client/src/access/add-rbac-resource/main.js b/awx/ui/client/src/access/add-rbac-resource/main.js
new file mode 100644
index 0000000000..346e6106c6
--- /dev/null
+++ b/awx/ui/client/src/access/add-rbac-resource/main.js
@@ -0,0 +1,12 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import addRbacResourceDirective from './rbac-resource.directive';
+import rbacMultiselect from '../rbac-multiselect/main';
+
+export default
+ angular.module('AddRbacResourceModule', [rbacMultiselect.name])
+ .directive('addRbacResource', addRbacResourceDirective);
diff --git a/awx/ui/client/src/access/addPermissions/addPermissions.controller.js b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.controller.js
similarity index 91%
rename from awx/ui/client/src/access/addPermissions/addPermissions.controller.js
rename to awx/ui/client/src/access/add-rbac-resource/rbac-resource.controller.js
index d5774e7c79..99cf7e9e11 100644
--- a/awx/ui/client/src/access/addPermissions/addPermissions.controller.js
+++ b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.controller.js
@@ -18,16 +18,7 @@ export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'Pr
// the object permissions are being added to
scope.object = scope.resourceData.data;
// array for all possible roles for the object
- scope.roles = Object
- .keys(scope.object.summary_fields.object_roles)
- .map(function(key) {
- return {
- value: scope.object.summary_fields
- .object_roles[key].id,
- label: scope.object.summary_fields
- .object_roles[key].name
- };
- });
+ scope.roles = scope.object.summary_fields.object_roles;
// TODO: get working with api
// array w roles and descriptions for key
@@ -44,6 +35,11 @@ export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'Pr
scope.showKeyPane = false;
+ scope.removeObject = function(obj){
+ _.remove(scope.allSelected, {id: obj.id});
+ obj.isSelected = false;
+ };
+
scope.toggleKeyPane = function() {
scope.showKeyPane = !scope.showKeyPane;
};
diff --git a/awx/ui/client/src/access/addPermissions/addPermissions.directive.js b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.directive.js
similarity index 82%
rename from awx/ui/client/src/access/addPermissions/addPermissions.directive.js
rename to awx/ui/client/src/access/add-rbac-resource/rbac-resource.directive.js
index 284110b0ce..fde8ee4054 100644
--- a/awx/ui/client/src/access/addPermissions/addPermissions.directive.js
+++ b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.directive.js
@@ -3,7 +3,7 @@
*
* All Rights Reserved
*************************************************/
-import addPermissionsController from './addPermissions.controller';
+import controller from './rbac-resource.controller';
/* jshint unused: vars */
export default ['templateUrl', '$state',
@@ -16,8 +16,8 @@ export default ['templateUrl', '$state',
teamsDataset: '=',
resourceData: '=',
},
- controller: addPermissionsController,
- templateUrl: templateUrl('access/addPermissions/addPermissions'),
+ controller: controller,
+ templateUrl: templateUrl('access/add-rbac-resource/rbac-resource'),
link: function(scope, element, attrs) {
scope.toggleFormTabs('users');
$('#add-permissions-modal').modal('show');
diff --git a/awx/ui/client/src/access/addPermissions/addPermissions.partial.html b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html
similarity index 91%
rename from awx/ui/client/src/access/addPermissions/addPermissions.partial.html
rename to awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html
index 264a4cf834..e8c3361f91 100644
--- a/awx/ui/client/src/access/addPermissions/addPermissions.partial.html
+++ b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html
@@ -46,10 +46,10 @@
Please assign roles to the selected users/teams
-
Key
@@ -91,8 +91,8 @@
{{ obj.type }}
-
-
+
+
diff --git a/awx/ui/client/src/access/add-rbac-user-team/main.js b/awx/ui/client/src/access/add-rbac-user-team/main.js
new file mode 100644
index 0000000000..94a1fa7c60
--- /dev/null
+++ b/awx/ui/client/src/access/add-rbac-user-team/main.js
@@ -0,0 +1,13 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import addRbacUserTeamDirective from './rbac-user-team.directive';
+import rbacSelectedList from './rbac-selected-list.directive';
+
+export default
+ angular.module('AddRbacUserTeamModule', [])
+ .directive('addRbacUserTeam', addRbacUserTeamDirective)
+ .directive('rbacSelectedList', rbacSelectedList);
\ No newline at end of file
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
new file mode 100644
index 0000000000..f65d0324ad
--- /dev/null
+++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js
@@ -0,0 +1,127 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/* jshint unused: vars */
+export default ['$compile','templateUrl', 'i18n', 'generateList',
+ 'ProjectList', 'TemplateList', 'InventoryList', 'CredentialList',
+ function($compile, templateUrl, i18n, generateList,
+ ProjectList, TemplateList, InventoryList, CredentialList) {
+ return {
+ restrict: 'E',
+ scope: {
+ resourceType: "=",
+ collection: "=",
+ selected: "="
+ },
+ link: function(scope, element, attrs) {
+ let listMap, list, list_html;
+
+ listMap = {
+ projects: ProjectList,
+ job_templates: TemplateList,
+ workflow_templates: TemplateList,
+ inventories: InventoryList,
+ credentials: CredentialList
+ };
+
+ list = _.cloneDeep(listMap[scope.resourceType]);
+
+ list.fieldActions = {
+ remove: {
+ ngClick: `removeSelection(${list.iterator}, resourceType)`,
+ iconClass: 'fa fa-times-circle',
+ awToolTip: i18n._(`Remove ${list.iterator}`),
+ label: i18n._('Remove'),
+ class: 'btn-sm'
+ }
+ };
+ delete list.actions;
+
+ list.listTitleBadge = false;
+
+ // @issue - fix field.columnClass values for this view
+
+ switch(scope.resourceType){
+
+ case 'projects':
+ list.fields = {
+ name: list.fields.name,
+ scm_type: list.fields.scm_type
+ };
+ break;
+
+ case 'inventories':
+ list.fields = {
+ name: list.fields.name,
+ organization: list.fields.organization
+ };
+ break;
+
+ case 'job_templates':
+ list.name = 'job_templates';
+ list.iterator = 'job_template';
+ list.fields = {
+ name: list.fields.name,
+ description: list.fields.description
+ };
+ break;
+
+ case 'workflow_templates':
+ list.name = 'workflow_templates';
+ list.iterator = 'workflow_template';
+ list.basePath = 'workflow_job_templates';
+ list.fields = {
+ name: list.fields.name,
+ description: list.fields.description
+ };
+ break;
+ case 'credentials':
+ list.fields = {
+ name: list.fields.name,
+ description: list.fields.description
+ };
+ }
+
+ list.fields = _.each(list.fields, (field) => field.nosort = true);
+
+ list_html = generateList.build({
+ mode: 'edit',
+ list: list,
+ related: false,
+ title: false,
+ showSearch: false,
+ showEmptyPanel: false,
+ paginate: false
+ });
+
+ scope.list = list;
+
+ 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
new file mode 100644
index 0000000000..edbd5eaf6f
--- /dev/null
+++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.controller.js
@@ -0,0 +1,165 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/**
+ * @ngdoc function
+ * @name controllers.function:Access
+ * @description
+ * Controller for handling permissions adding
+ */
+
+export default ['$rootScope', '$scope', '$state', 'i18n', 'CreateSelect2', 'GetBasePath', 'Rest', '$q', 'Wait', 'ProcessErrors',
+function(rootScope, scope, $state, i18n, CreateSelect2, GetBasePath, Rest, $q, Wait, ProcessErrors) {
+
+ init();
+
+ function init(){
+
+ let resources = ['job_templates', 'workflow_templates', 'projects', 'inventories', 'credentials'];
+
+ // data model:
+ // 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, (type) => scope.selected[type] = {});
+
+ // collection of assignable roles per type of resource
+ scope.keys = {};
+ _.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,
+ projects: false,
+ 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], _.omit(item.summary_fields.object_roles, 'read_role'));
+ }
+
+ scope.closeModal = function() {
+ $state.go('^', null, {reload: true});
+ };
+
+ scope.currentTab = function(){
+ return _.findKey(scope.tab, (tab) => tab);
+ };
+
+ scope.toggleKeyPane = function() {
+ scope.showKeyPane = !scope.showKeyPane;
+ };
+
+ scope.showSection2Container = function(){
+ return _.any(scope.selected, (type) => Object.keys(type).length > 0);
+ };
+
+ scope.showSection2Tab = function(tab){
+ return Object.keys(scope.selected[tab]).length > 0;
+ };
+
+ scope.saveEnabled = function(){
+ let missingRole = false;
+ let resourceSelected = false;
+ _.forOwn(scope.selected, function(value, key) {
+ if(Object.keys(value).length > 0) {
+ // A resource from this tab has been selected
+ resourceSelected = true;
+ if(!scope.roleSelection[key]) {
+ missingRole = true;
+ }
+ }
+ });
+ return resourceSelected && !missingRole;
+ };
+
+ // handle form tab changes
+ scope.selectTab = function(selected){
+ _.each(scope.tab, (value, key, collection) => {
+ collection[key] = (selected === key);
+ });
+ };
+
+ // pop/push into unified collection of selected resourcesf
+ scope.$on("selectedOrDeselected", function(e, value) {
+ let resourceType = scope.currentTab(),
+ item = value.value;
+
+ if (item.isSelected) {
+ scope.selected[resourceType][item.id] = item;
+ scope.selected[resourceType][item.id].roles = [];
+ aggregateKey(item, resourceType);
+ } else {
+ delete scope.selected[resourceType][item.id];
+ }
+ });
+
+ // post roles to api
+ scope.saveForm = function() {
+ //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();
+
+ Rest.setUrl(scope.owner.related.roles);
+
+ $q.all( _.map(roles, (entity) => Rest.post({id: entity.id})) )
+ .then( () =>{
+ Wait('stop');
+ scope.closeModal();
+ }, (error) => {
+ scope.closeModal();
+ ProcessErrors(null, error.data, error.status, null, {
+ hdr: 'Error!',
+ msg: 'Failed to post role(s): POST returned status' +
+ error.status
+ });
+ });
+ };
+}];
diff --git a/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.directive.js b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.directive.js
new file mode 100644
index 0000000000..ced1ceb744
--- /dev/null
+++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.directive.js
@@ -0,0 +1,25 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+import controller from './rbac-user-team.controller';
+
+/* jshint unused: vars */
+export default ['templateUrl',
+ function(templateUrl) {
+ return {
+ restrict: 'E',
+ scope: {
+ resolve: "="
+ },
+ controller: controller,
+ templateUrl: templateUrl('access/add-rbac-user-team/rbac-user-team'),
+ link: function(scope, element, attrs) {
+ scope.selectTab('job_templates');
+ $('#add-permissions-modal').modal('show');
+ window.scrollTo(0, 0);
+ }
+ };
+ }
+];
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
new file mode 100644
index 0000000000..975870944c
--- /dev/null
+++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-user-team.partial.html
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+ Please select resources from the lists below.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2
+
+ Please assign roles to the selected resources
+
+ Key
+
+
+
+
+
+
+ {{ key.name }}
+
+
+ {{ key.description || "No description provided" }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awx/ui/client/src/access/addPermissions/addPermissions.block.less b/awx/ui/client/src/access/add-rbac.block.less
similarity index 95%
rename from awx/ui/client/src/access/addPermissions/addPermissions.block.less
rename to awx/ui/client/src/access/add-rbac.block.less
index 590d97d269..88f5b1211f 100644
--- a/awx/ui/client/src/access/addPermissions/addPermissions.block.less
+++ b/awx/ui/client/src/access/add-rbac.block.less
@@ -1,4 +1,4 @@
-@import "../../shared/branding/colors.default.less";
+@import "../shared/branding/colors.default.less";
/** @define AddPermissions */
@@ -168,13 +168,14 @@
.AddPermissions-keyToggle {
margin-left: auto;
text-transform: uppercase;
- padding: 3px 9px;
- font-size: 12px;
background-color: @default-bg;
border-radius: 5px;
color: @default-interface-txt;
border: 1px solid @d7grey;
cursor: pointer;
+ width: 70px;
+ height: 34px;
+ line-height: 20px;
}
.AddPermissions-keyToggle:hover {
@@ -185,6 +186,9 @@
background-color: @default-link;
border-color: @default-link;
color: @default-bg;
+ &:hover{
+ background-color: @default-link-hov;
+ }
}
.AddPermissions-keyPane {
diff --git a/awx/ui/client/src/access/addPermissions/addPermissionsList/addPermissionsList.directive.js b/awx/ui/client/src/access/addPermissions/addPermissionsList/addPermissionsList.directive.js
deleted file mode 100644
index 8051371436..0000000000
--- a/awx/ui/client/src/access/addPermissions/addPermissionsList/addPermissionsList.directive.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*************************************************
- * Copyright (c) 2015 Ansible, Inc.
- *
- * All Rights Reserved
- *************************************************/
-
-/* jshint unused: vars */
-export default ['addPermissionsTeamsList', 'addPermissionsUsersList', '$compile', 'generateList', 'GetBasePath', 'SelectionInit', function(addPermissionsTeamsList,
- addPermissionsUsersList, $compile, generateList,
- GetBasePath, SelectionInit) {
- return {
- restrict: 'E',
- scope: {
- allSelected: '=',
- view: '@',
- dataset: '='
- },
- template: "
",
- link: function(scope, element, attrs, ctrl) {
- let listMap, list, list_html;
-
- listMap = {Teams: addPermissionsTeamsList, Users: addPermissionsUsersList};
- list = listMap[scope.view];
- list_html = generateList.build({
- mode: 'edit',
- list: list
- });
-
- scope.list = listMap[scope.view];
- scope[`${list.iterator}_dataset`] = scope.dataset.data;
- scope[`${list.name}`] = scope[`${list.iterator}_dataset`].results;
-
- scope.$watch(list.name, function(){
- _.forEach(scope[`${list.name}`], isSelected);
- });
-
- function isSelected(item){
- if(_.find(scope.allSelected, {id: item.id})){
- item.isSelected = true;
- }
- return item;
- }
- element.append(list_html);
- $compile(element.contents())(scope);
- }
- };
-}];
diff --git a/awx/ui/client/src/access/addPermissions/main.js b/awx/ui/client/src/access/addPermissions/main.js
deleted file mode 100644
index ca627908de..0000000000
--- a/awx/ui/client/src/access/addPermissions/main.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/*************************************************
- * Copyright (c) 2015 Ansible, Inc.
- *
- * All Rights Reserved
- *************************************************/
-
-import addPermissionsDirective from './addPermissions.directive';
-import roleSelect from './roleSelect.directive';
-import addPermissionsList from './addPermissionsList/main';
-
-export default
- angular.module('AddPermissions', [addPermissionsList.name])
- .directive('addPermissions', addPermissionsDirective)
- .directive('roleSelect', roleSelect);
diff --git a/awx/ui/client/src/access/main.js b/awx/ui/client/src/access/main.js
index 084fe5ef87..eedfe0db8c 100644
--- a/awx/ui/client/src/access/main.js
+++ b/awx/ui/client/src/access/main.js
@@ -4,9 +4,13 @@
* All Rights Reserved
*************************************************/
-import roleList from './roleList.directive';
-import addPermissions from './addPermissions/main';
+import roleList from './rbac-role-column/roleList.directive';
+import addRbacResource from './add-rbac-resource/main';
+import addRbacUserTeam from './add-rbac-user-team/main';
export default
- angular.module('access', [addPermissions.name])
+ angular.module('RbacModule', [
+ addRbacResource.name,
+ addRbacUserTeam.name
+ ])
.directive('roleList', roleList);
diff --git a/awx/ui/client/src/access/addPermissions/addPermissionsList/main.js b/awx/ui/client/src/access/rbac-multiselect/main.js
similarity index 55%
rename from awx/ui/client/src/access/addPermissions/addPermissionsList/main.js
rename to awx/ui/client/src/access/rbac-multiselect/main.js
index c523ca2032..c5bc4f030f 100644
--- a/awx/ui/client/src/access/addPermissions/addPermissionsList/main.js
+++ b/awx/ui/client/src/access/rbac-multiselect/main.js
@@ -4,12 +4,14 @@
* All Rights Reserved
*************************************************/
-import addPermissionsListDirective from './addPermissionsList.directive';
+import rbacMultiselectList from './rbac-multiselect-list.directive';
+import rbacMultiselectRole from './rbac-multiselect-role.directive';
import teamsList from './permissionsTeams.list';
import usersList from './permissionsUsers.list';
export default
- angular.module('addPermissionsListModule', [])
- .directive('addPermissionsList', addPermissionsListDirective)
+ angular.module('rbacMultiselectModule', [])
+ .directive('rbacMultiselectList', rbacMultiselectList)
+ .directive('rbacMultiselectRole', rbacMultiselectRole)
.factory('addPermissionsTeamsList', teamsList)
.factory('addPermissionsUsersList', usersList);
diff --git a/awx/ui/client/src/access/addPermissions/addPermissionsList/permissionsTeams.list.js b/awx/ui/client/src/access/rbac-multiselect/permissionsTeams.list.js
similarity index 100%
rename from awx/ui/client/src/access/addPermissions/addPermissionsList/permissionsTeams.list.js
rename to awx/ui/client/src/access/rbac-multiselect/permissionsTeams.list.js
diff --git a/awx/ui/client/src/access/addPermissions/addPermissionsList/permissionsUsers.list.js b/awx/ui/client/src/access/rbac-multiselect/permissionsUsers.list.js
similarity index 100%
rename from awx/ui/client/src/access/addPermissions/addPermissionsList/permissionsUsers.list.js
rename to awx/ui/client/src/access/rbac-multiselect/permissionsUsers.list.js
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
new file mode 100644
index 0000000000..da80fa4a58
--- /dev/null
+++ b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js
@@ -0,0 +1,112 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/* jshint unused: vars */
+export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateList', 'ProjectList',
+ 'InventoryList', 'CredentialList', '$compile', 'generateList', 'GetBasePath', 'SelectionInit',
+ function(addPermissionsTeamsList, addPermissionsUsersList, TemplateList, ProjectList,
+ InventoryList, CredentialList, $compile, generateList, GetBasePath, SelectionInit) {
+ return {
+ restrict: 'E',
+ scope: {
+ allSelected: '=',
+ view: '@',
+ dataset: '='
+ },
+ template: "
",
+ link: function(scope, element, attrs, ctrl) {
+ let listMap, list, list_html;
+
+ listMap = {
+ Teams: addPermissionsTeamsList,
+ Users: addPermissionsUsersList,
+ Projects: ProjectList,
+ JobTemplates: TemplateList,
+ WorkflowTemplates: TemplateList,
+ Inventories: InventoryList,
+ Credentials: CredentialList
+ };
+ list = _.cloneDeep(listMap[scope.view]);
+ list.multiSelect = true;
+ list.multiSelectExtended = true;
+ list.listTitleBadge = false;
+ delete list.actions;
+ delete list.fieldActions;
+
+ switch(scope.view){
+
+ case 'Projects':
+ list.fields = {
+ name: list.fields.name,
+ scm_type: list.fields.scm_type
+ };
+ break;
+
+ case 'Inventories':
+ list.fields = {
+ name: list.fields.name,
+ organization: list.fields.organization
+ };
+ break;
+
+ case 'JobTemplates':
+ list.name = 'job_templates';
+ list.iterator = 'job_template';
+ list.fields = {
+ name: list.fields.name,
+ description: list.fields.description
+ };
+ break;
+
+ case 'WorkflowTemplates':
+ list.name = 'workflow_templates';
+ list.iterator = 'workflow_template';
+ list.basePath = 'workflow_job_templates';
+ list.fields = {
+ name: list.fields.name,
+ 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,
+ description: list.fields.description
+ };
+ }
+
+ list_html = generateList.build({
+ mode: 'edit',
+ list: list,
+ related: false,
+ title: false
+ });
+
+ scope.list = list;
+ scope[`${list.iterator}_dataset`] = scope.dataset.data;
+ scope[`${list.name}`] = scope[`${list.iterator}_dataset`].results;
+
+ scope.$watch(list.name, function(){
+ _.forEach(scope[`${list.name}`], isSelected);
+ });
+
+ function isSelected(item){
+ if(_.find(scope.allSelected, {id: item.id})){
+ item.isSelected = true;
+ }
+ return item;
+ }
+ element.append(list_html);
+ $compile(element.contents())(scope);
+ }
+ };
+}];
diff --git a/awx/ui/client/src/access/addPermissions/roleSelect.directive.js b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-role.directive.js
similarity index 67%
rename from awx/ui/client/src/access/addPermissions/roleSelect.directive.js
rename to awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-role.directive.js
index 53bc191a6d..99e3a1d8ed 100644
--- a/awx/ui/client/src/access/addPermissions/roleSelect.directive.js
+++ b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-role.directive.js
@@ -11,8 +11,12 @@ export default
function(CreateSelect2) {
return {
restrict: 'E',
- scope: false,
- template: ' ',
+ scope: {
+ roles: '=',
+ model: '='
+ },
+ // @issue why is the read-only role ommited from this selection?
+ template: ' ',
link: function(scope, element, attrs, ctrl) {
CreateSelect2({
element: '.roleSelect2',
diff --git a/awx/ui/client/src/access/roleList.block.less b/awx/ui/client/src/access/rbac-role-column/roleList.block.less
similarity index 96%
rename from awx/ui/client/src/access/roleList.block.less
rename to awx/ui/client/src/access/rbac-role-column/roleList.block.less
index 5cacfb1814..40b76717a3 100644
--- a/awx/ui/client/src/access/roleList.block.less
+++ b/awx/ui/client/src/access/rbac-role-column/roleList.block.less
@@ -1,5 +1,5 @@
/** @define RoleList */
-@import "../shared/branding/colors.default.less";
+@import "../../shared/branding/colors.default.less";
.RoleList {
display: flex;
diff --git a/awx/ui/client/src/access/roleList.directive.js b/awx/ui/client/src/access/rbac-role-column/roleList.directive.js
similarity index 95%
rename from awx/ui/client/src/access/roleList.directive.js
rename to awx/ui/client/src/access/rbac-role-column/roleList.directive.js
index 7bdd1b29d4..97682d0bc9 100644
--- a/awx/ui/client/src/access/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/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/access/roleList.partial.html b/awx/ui/client/src/access/rbac-role-column/roleList.partial.html
similarity index 100%
rename from awx/ui/client/src/access/roleList.partial.html
rename to awx/ui/client/src/access/rbac-role-column/roleList.partial.html
diff --git a/awx/ui/client/src/forms/Teams.js b/awx/ui/client/src/forms/Teams.js
index 6e50a55cf9..5ac0ee7aa1 100644
--- a/awx/ui/client/src/forms/Teams.js
+++ b/awx/ui/client/src/forms/Teams.js
@@ -64,7 +64,7 @@ export default
},
related: {
- permissions: {
+ users: {
dataPlacement: 'top',
awToolTip: i18n._('Please save before adding users'),
basePath: 'api/v1/teams/{{$stateParams.team_id}}/access_list/',
@@ -73,15 +73,14 @@ export default
},
type: 'collection',
title: i18n._('Users'),
- iterator: 'permission',
+ iterator: 'user',
index: false,
open: false,
actions: {
add: {
- // @issue https://github.com/ansible/ansible-tower/issues/3487
- //ngClick: "addPermissionWithoutTeamTab",
+ ngClick: "$state.go('.add')",
label: i18n._('Add'),
- awToolTip: i18n._('Add user to team'),
+ awToolTip: i18n._('Add User'),
actionClass: 'btn List-buttonSubmit',
buttonContent: '+ ' + i18n._('ADD'),
ngShow: '(team_obj.summary_fields.user_capabilities.edit || canAdd)'
@@ -104,45 +103,44 @@ export default
}
}
},
- roles: {
- hideSearchAndActions: true,
- dataPlacement: 'top',
- awToolTip: i18n._('Please save before assigning permissions'),
+ permissions: {
basePath: 'api/v1/teams/{{$stateParams.team_id}}/roles/',
search: {
page_size: '10',
// @todo ask about name field / serializer on this endpoint
order_by: 'id'
},
+ awToolTip: i18n._('Please save before assigning permissions'),
+ dataPlacement: 'top',
+ hideSearchAndActions: true,
type: 'collection',
- title: i18n._('Granted Permissions'),
- iterator: 'role',
+ title: i18n._('Permissions'),
+ iterator: 'permission',
open: false,
index: false,
- actions: {},
emptyListText: i18n._('No permissions have been granted'),
fields: {
name: {
label: i18n._('Name'),
- ngBind: 'role.summary_fields.resource_name',
- linkTo: '{{convertApiUrl(role.related[role.summary_fields.resource_type])}}',
+ ngBind: 'permission.summary_fields.resource_name',
+ linkTo: '{{convertApiUrl(permission.related[permission.summary_fields.resource_type])}}',
noSort: true
},
type: {
label: i18n._('Type'),
- ngBind: 'role.summary_fields.resource_type_display_name',
+ ngBind: 'permission.summary_fields.resource_type_display_name',
noSort: true
},
role: {
label: i18n._('Role'),
- ngBind: 'role.name',
+ ngBind: 'permission.name',
noSort: true
}
},
fieldActions: {
"delete": {
label: i18n._('Remove'),
- ngClick: 'deletePermissionFromTeam(team_id, team_obj.name, role.name, role.summary_fields.resource_name, role.related.teams)',
+ ngClick: 'deletePermissionFromTeam(team_id, team_obj.name, permission.name, permission.summary_fields.resource_name, permission.related.teams)',
'class': "List-actionButton--delete",
iconClass: 'fa fa-times',
awToolTip: i18n._('Dissasociate permission from team'),
@@ -150,7 +148,16 @@ export default
ngShow: 'permission.summary_fields.user_capabilities.unattach'
}
},
- //hideOnSuperuser: true // defunct with RBAC
+ actions: {
+ add: {
+ ngClick: "$state.go('.add')",
+ label: 'Add',
+ awToolTip: i18n._('Grant Permission'),
+ actionClass: 'btn List-buttonSubmit',
+ buttonContent: '+ ' + i18n._('ADD PERMISSIONS'),
+ ngShow: '(team_obj.summary_fields.user_capabilities.edit || canAdd)'
+ }
+ }
}
},
};}]); //InventoryForm
diff --git a/awx/ui/client/src/forms/Users.js b/awx/ui/client/src/forms/Users.js
index 20f8126a83..0ef16a4aff 100644
--- a/awx/ui/client/src/forms/Users.js
+++ b/awx/ui/client/src/forms/Users.js
@@ -173,14 +173,13 @@ export default
basePath: 'api/v1/users/{{$stateParams.user_id}}/roles/',
search: {
page_size: '10',
- // @todo ask about name field / serializer on this endpoint
order_by: 'id'
},
awToolTip: i18n._('Please save before assigning to organizations'),
dataPlacement: 'top',
hideSearchAndActions: true,
type: 'collection',
- title: i18n._('Granted permissions'),
+ title: i18n._('Permissions'),
iterator: 'permission',
open: false,
index: false,
@@ -203,12 +202,16 @@ export default
noSort: true
},
},
- // @issue https://github.com/ansible/ansible-tower/issues/3487
- // actions: {
- // add: {
-
- // }
- // }
+ actions: {
+ add: {
+ ngClick: "$state.go('.add')",
+ label: 'Add',
+ awToolTip: i18n._('Grant Permission'),
+ actionClass: 'btn List-buttonSubmit',
+ buttonContent: '+ ' + i18n._('ADD PERMISSIONS'),
+ ngShow: '(user_obj.summary_fields.user_capabilities.edit || canAdd)'
+ }
+ },
fieldActions: {
"delete": {
label: i18n._('Remove'),
diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js
index d487efa925..e2aa526257 100644
--- a/awx/ui/client/src/shared/form-generator.js
+++ b/awx/ui/client/src/shared/form-generator.js
@@ -1760,7 +1760,6 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
if (this.form.horizontal) {
html += " \n";
}
-
}
}
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 c41e059c81..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) {
@@ -443,12 +445,14 @@ export default ['$location', '$compile', '$rootScope', 'Attr', 'Icon',
html += "\n";
}
- html += `
`;
+ }
return html;
},
diff --git a/awx/ui/client/src/shared/stateDefinitions.factory.js b/awx/ui/client/src/shared/stateDefinitions.factory.js
index dccfebb30c..2c3bb8c4cb 100644
--- a/awx/ui/client/src/shared/stateDefinitions.factory.js
+++ b/awx/ui/client/src/shared/stateDefinitions.factory.js
@@ -236,7 +236,84 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat
*/
generateFormListDefinitions: function(form, formStateDefinition) {
- function buildPermissionDirective() {
+ function buildRbacUserTeamDirective(){
+ let states = [];
+
+ states.push($stateExtender.buildDefinition({
+ name: `${formStateDefinition.name}.permissions.add`,
+ squashSearchUrl: true,
+ url: '/add-permissions',
+ params: {
+ project_search: {
+ value: {order_by: 'name', page_size: '5', role_level: 'admin_role'},
+ dynamic: true
+ },
+ job_template_search: {
+ value: {order_by: 'name', page_size: '5', role_level: 'admin_role'},
+ dynamic: true
+ },
+ workflow_template_search: {
+ value: {order_by: 'name', page_size: '5', role_level: 'admin_role'},
+ dynamic: true
+ },
+ inventory_search: {
+ value: {order_by: 'name', page_size: '5', role_level: 'admin_role'},
+ dynamic: true
+ },
+ credential_search: {
+ value: {order_by: 'name', page_size: '5', role_level: 'admin_role'},
+ dynamic: true
+ }
+ },
+ views: {
+ [`modal@${formStateDefinition.name}`]: {
+ template: ` `
+ }
+ },
+ resolve: {
+ jobTemplatesDataset: ['QuerySet', '$stateParams', 'GetBasePath',
+ function(qs, $stateParams, GetBasePath) {
+ let path = GetBasePath('job_templates');
+ return qs.search(path, $stateParams.job_template_search);
+ }
+ ],
+ workflowTemplatesDataset: ['QuerySet', '$stateParams', 'GetBasePath',
+ function(qs, $stateParams, GetBasePath) {
+ let path = GetBasePath('workflow_job_templates');
+ return qs.search(path, $stateParams.workflow_template_search);
+ }
+ ],
+ projectsDataset: ['ProjectList', 'QuerySet', '$stateParams', 'GetBasePath',
+ function(list, qs, $stateParams, GetBasePath) {
+ let path = GetBasePath(list.basePath) || GetBasePath(list.name);
+ return qs.search(path, $stateParams[`${list.iterator}_search`]);
+ }
+ ],
+ inventoriesDataset: ['InventoryList', 'QuerySet', '$stateParams', 'GetBasePath',
+ function(list, qs, $stateParams, GetBasePath) {
+ let path = GetBasePath(list.basePath) || GetBasePath(list.name);
+ return qs.search(path, $stateParams[`${list.iterator}_search`]);
+ }
+ ],
+ credentialsDataset: ['CredentialList', 'QuerySet', '$stateParams', 'GetBasePath',
+ function(list, qs, $stateParams, GetBasePath) {
+ let path = GetBasePath(list.basePath) || GetBasePath(list.name);
+ return qs.search(path, $stateParams[`${list.iterator}_search`]);
+ }
+ ],
+ },
+ onExit: function($state) {
+ if ($state.transition) {
+ $('#add-permissions-modal').modal('hide');
+ $('.modal-backdrop').remove();
+ $('body').removeClass('modal-open');
+ }
+ },
+ }));
+ return states;
+ }
+
+ function buildRbacResourceDirective() {
let states = [];
states.push($stateExtender.buildDefinition({
@@ -255,7 +332,7 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat
},
views: {
[`modal@${formStateDefinition.name}`]: {
- template: ` `
+ template: ` `
}
},
resolve: {
@@ -288,7 +365,13 @@ export default ['$injector', '$stateExtender', '$log', function($injector, $stat
let states = [];
states.push(buildListDefinition(field));
if (field.iterator === 'permission' && field.actions && field.actions.add) {
- states.push(buildPermissionDirective());
+ if (form.name === 'user' || form.name === 'team'){
+ states.push(buildRbacUserTeamDirective());
+
+ }
+ else {
+ states.push(buildRbacResourceDirective());
+ }
states = _.flatten(states);
}
return states;