From 6b37054621ef3ae0ab82db37374de3a502f2efd8 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Fri, 4 Mar 2016 00:39:03 -0500 Subject: [PATCH] working commit rbac add permissions --- awx/ui/client/legacy-styles/ansible-ui.less | 25 ++ awx/ui/client/legacy-styles/lists.less | 5 + .../addPermissions/addPermissions.block.less | 144 ++++++++++++ .../addPermissions.controller.js | 217 ++++++++++++++++++ .../addPermissions.directive.js | 51 ++++ .../addPermissions.partial.html | 105 +++++++++ .../client/src/access/addPermissions/main.js | 15 ++ .../addPermissions/roleSelect.directive.js | 25 ++ .../src/access/addPermissions/teams/main.js | 13 ++ .../teams/permissionsTeams.directive.js | 44 ++++ .../teams/permissionsTeams.list.js | 27 +++ .../src/access/addPermissions/users/main.js | 13 ++ .../users/permissionsUsers.directive.js | 44 ++++ .../users/permissionsUsers.list.js | 37 +++ awx/ui/client/src/access/main.js | 2 +- awx/ui/client/src/app.js | 4 + awx/ui/client/src/controllers/Teams.js | 48 ++-- awx/ui/client/src/controllers/Users.js | 55 ++--- awx/ui/client/src/forms/Users.js | 126 +++++----- .../client/src/helpers/PaginationHelpers.js | 2 +- awx/ui/client/src/shared/Utilities.js | 4 +- awx/ui/client/src/shared/form-generator.js | 4 +- awx/ui/client/src/shared/generator-helpers.js | 2 + .../list-generator/list-generator.factory.js | 8 +- .../select-list-item.directive.js | 6 +- 25 files changed, 906 insertions(+), 120 deletions(-) create mode 100644 awx/ui/client/src/access/addPermissions/addPermissions.block.less create mode 100644 awx/ui/client/src/access/addPermissions/addPermissions.controller.js create mode 100644 awx/ui/client/src/access/addPermissions/addPermissions.directive.js create mode 100644 awx/ui/client/src/access/addPermissions/addPermissions.partial.html create mode 100644 awx/ui/client/src/access/addPermissions/main.js create mode 100644 awx/ui/client/src/access/addPermissions/roleSelect.directive.js create mode 100644 awx/ui/client/src/access/addPermissions/teams/main.js create mode 100644 awx/ui/client/src/access/addPermissions/teams/permissionsTeams.directive.js create mode 100644 awx/ui/client/src/access/addPermissions/teams/permissionsTeams.list.js create mode 100644 awx/ui/client/src/access/addPermissions/users/main.js create mode 100644 awx/ui/client/src/access/addPermissions/users/permissionsUsers.directive.js create mode 100644 awx/ui/client/src/access/addPermissions/users/permissionsUsers.list.js diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index 717ab7bd30..742e9a24c1 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -634,6 +634,13 @@ dd { box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 5px rgba(255, 88, 80, 0.6); } + .form-control.ng-dirty.ng-invalid + .select2 .select2-selection, + .form-control.ng-dirty.ng-invalid + .select2 .select2-selection:focus { + border-color: rgba(255, 88, 80, 0.8) !important; + outline: 0 !important; + box-shadow: none !important; + } + .form-control.ng-dirty.ng-pristine { border-color: @default-second-border; box-shadow: none; @@ -2008,15 +2015,33 @@ tr td button i { box-shadow: none; } +.form-control + .select2 .select2-selection { + border-color: @default-second-border !important; + background-color: #f6f6f6 !important; + color: @default-data-txt !important; + transition: border-color 0.3s !important; + box-shadow: none !important; +} + .form-control:active, .form-control:focus { box-shadow: none; border-color: #167ec4; } +.form-control:active + .select2 .select2-selection, .form-control:focus + .select2 .select2-selection { + box-shadow: none !important; + border-color: #167ec4 !important; +} + .form-control.ng-dirty.ng-invalid, .form-control.ng-dirty.ng-invalid:focus { box-shadow: none; } +.form-control.ng-dirty.ng-invalid + .select2 .select2-selection, .form-control.ng-dirty.ng-invalid:focus + .select2 .select2-selection { + box-shadow: none !important; +} + + .error { opacity: 1; transition: opacity 0.2s; diff --git a/awx/ui/client/legacy-styles/lists.less b/awx/ui/client/legacy-styles/lists.less index 0929fd4a28..2e6f5e7b0c 100644 --- a/awx/ui/client/legacy-styles/lists.less +++ b/awx/ui/client/legacy-styles/lists.less @@ -320,6 +320,11 @@ table, tbody { height: 34px; } +.List-searchWidget--compact { + max-width: ~"calc(100% - 91px)"; + margin-top: 10px; +} + .List-searchRow { margin-bottom: 20px; } diff --git a/awx/ui/client/src/access/addPermissions/addPermissions.block.less b/awx/ui/client/src/access/addPermissions/addPermissions.block.less new file mode 100644 index 0000000000..06e666e248 --- /dev/null +++ b/awx/ui/client/src/access/addPermissions/addPermissions.block.less @@ -0,0 +1,144 @@ +@import "../../shared/branding/colors.default.less"; + +/** @define AddPermissions */ +.AddPermissions { + position: absolute; + top: 0; + width: 100%; + height: 100%; +} + +.AddPermissions-content { + max-width: 750px !important; +} + +.AddPermissions-header { + padding: 20px; + padding-bottom: 10px; + padding-top: 15px; +} + +.AddPermissions-body { + padding-top: 0px !important; + max-height: 70vh; + overflow: scroll; +} + +.AddPermissions-footer { + padding-top: 20px !important; +} + +.AddPermissions-list .List-searchRow { + height: 0px; +} + +.AddPermissions-list .List-searchWidget { + height: 66px; +} + +.AddPermissions-list .List-tableHeader:last-child { + border-top-right-radius: 5px; +} + +.AddPermissions-list select-all { + display: none; +} + +.AddPermissions-title { + margin-top: 5px; + margin-bottom: 20px; +} + +.AddPermissions-buttons { + margin-left: auto; + margin-bottom: 20px; +} + +.AddPermissions-directions { + margin-top: 10px; + margin-bottom: 20px; + color: #848992; +} + +.AddPermissions-directionNumber { + font-size: 14px; + font-weight: bold; + border-radius: 50%; + background-color: #ebebeb; + padding-left: 6px; + padding-right: 1px; + padding-bottom: 3px; + margin-right: 10px; +} + +.AddPermissions-separator { + margin-top: 20px; + margin-bottom: 20px; + width: 100%; + border-bottom: 1px solid #e1e1e1; +} + +.AddPermissions-roleRow { + display: flex; + margin-bottom: 10px; + align-items: center; +} + +.AddPermissions-roleName { + width: 30%; + padding-right: 10px; + display: flex; + align-items: center; +} + +.AddPermissions-roleNameVal { + font-size: 14px; + max-width: ~"calc(100% - 46px)"; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.AddPermissions-roleType { + border-radius: 5px; + padding: 0px 6px; + border: 1px solid #e1e1e1; + font-size: 10px; + color: #848992; + text-transform: uppercase; + background-color: #fff; + margin-left: 6px; +} + +.AddPermissions-roleSelect { + width: ~"calc(70% - 40px)"; + margin-right: 20px; +} + +.AddPermissions-roleSelect .Form-dropDown { + height: inherit !important; +} + +.AddPermissions-roleRemove { + border-radius: 50%; + padding: 3px; + line-height: 11px; + padding-left: 5px; + padding-right: 5px; + color: #b7b7b7; + background-color: #fafafa; + border: 0; +} + +.AddPermissions-roleRemove:hover { + background-color: #ff5850; + color: #fff; +} + +.AddPermissions-selectHide { + display: none; +} + +.AddPermissions .select2-search__field { + text-transform: uppercase; +} diff --git a/awx/ui/client/src/access/addPermissions/addPermissions.controller.js b/awx/ui/client/src/access/addPermissions/addPermissions.controller.js new file mode 100644 index 0000000000..24f152d1d7 --- /dev/null +++ b/awx/ui/client/src/access/addPermissions/addPermissions.controller.js @@ -0,0 +1,217 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Authentication + * @description + * Controller for handling /#/login and /#/logout routes. + * + * Tower (app.js) verifies the user is authenticated and that the user session is not expired. If either condition is not true, + * the user is redirected to /#/login and the Authentication controller. + * + * Methods for checking the session state are found in [js/shared/AuthService.js](/static/docs/api/shared.function:AuthService), which is referenced here as Authorization. + * + * #Login Modal Dialog + * + * The modal dialog prompting for username and password is found in templates/ui/index.html. + *``` + * + * + *``` + * HTML for the login form is generated, compiled and injected into
by the controller. This is done to associate the form with the controller's scope. Because + *
is outside of the ng-view container, it gets associated with $rootScope by default. In the controller we create a new scope using $rootScope.$new() and associate + * that with the login form. Doing this each time the controller is instantiated insures the form is clean and not pre-populated with a prior user's username and password. + * + * Just before the release of 2.0 a bug was discovered where clicking logout and then immediately clicking login without providing a username and password would successfully log + * the user back into Tower. Implementing the above approach fixed this, forcing a new username/password to be entered each time the login dialog appears. + * + * #Login Workflow + * + * When the the login button is clicked, the following occurs: + * + * - Call Authorization.retrieveToken(username, password) - sends a POST request to /api/v1/authtoken to get a new token value. + * - Call Authorization.setToken(token, expires) to store the token and exipration time in a session cookie. + * - Start the expiration timer by calling the init() method of [js/shared/Timer.js](/static/docs/api/shared.function:Timer) + * - Get user informaton by calling Authorization.getUser() - sends a GET request to /api/v1/me + * - Store user information in the session cookie by calling Authorization.setUser(). + * - Get the Tower license by calling Authorization.getLicense() - sends a GET request to /api/vi/config + * - Stores the license object in local storage by calling Authorization.setLicense(). This adds the Tower version and a tested flag to the license object. The tested flag is initially set to false. + * + * Note that there is a session timer kept on the server side as well as the client side. Each time an API request is made, Tower (in app.js) calls + * Timer.isExpired(). This verifies the UI does not think the session is expired, and if not, moves the expiration time into the future. The number of + * seconds between API calls before a session is considered expired is set in config.js as session_timeout. + * + * @Usage + * This is usage information. + */ + +export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', function (rootScope, scope, GetBasePath, Rest, $q) { + scope.allSelected = []; + + // the object permissions are being added to + scope.object = scope[scope.$parent.list + .iterator + "_obj"]; + + // array for all possible roles for the object + scope.roles = Object + .keys(scope.object.summary_fields.roles) + .map(function(key) { + return { + value: scope.object.summary_fields + .roles[key].id, + label: scope.object.summary_fields + .roles[key].name }; + }); + + // handle form tabs + scope.toggleFormTabs = function(list) { + scope.usersSelected = (list === 'users'); + scope.teamsSelected = !scope.usersSelected; + }; + + // TODO: manually handle selection/deselection + // of user/team checkboxes + scope.$on("selectedOrDeselected", function(e, val) { + val = val.value; + if (val.isSelected) { + scope.allSelected = scope.allSelected.filter(function(i) { + return (!(val.id === i.id && val.type === i.type)); + }); + } else { + var name; + + if (val.type === "user") { + name = (val.first_name && + val.last_name) ? + val.first_name + " " + + val.last_name : + val.username; + } else { + name = val.name; + } + + scope.allSelected.push({ + name: name, + type: val.type, + roles: [], + id: val.id + }); + } + }); + + scope.$on("itemsSelected", function(e, inList) { + scope.updateLists = scope.allSelected.filter(function(inMemory) { + var notInList = true; + inList.forEach(function(val) { + if (inMemory.id === val.id && + inMemory.type === val.type) { + notInList = false; + } + }); + return notInList; + }); + }); + + scope.$watch("updateLists", function(toUpdate) { + (toUpdate || []).forEach(function(obj) { + var elemScope = angular + .element("#" + + obj.type + "s_table #" + obj.id + + ".List-tableRow input") + .scope() + if (elemScope) { + elemScope.isSelected = true; + } + }); + + delete scope.updateLists; + }); + + // create array of users/teams + // scope.$watchGroup(['selectedUsers', 'selectedTeams'], + // function(val) { + // scope.allSelected = (val[0] || []) + // .map(function(i) { + // var roles = i.roles || []; + // var name = (i.first_name && + // i.last_name) ? + // i.first_name + " " + + // i.last_name : + // i.username; + // + // return { + // name: name, + // type: "user", + // roles: roles, + // id: i.id + // }; + // }).concat((val[1] || []) + // .map(function(i) { + // var roles = i.roles || []; + // + // return { + // name: i.name, + // type: "team", + // roles: roles, + // id: i.id + // }; + // })); + // }); + + // remove selected user/team + scope.removeObject = function(obj) { + var elemScope = angular + .element("#" + + obj.type + "s_table #" + obj.id + ".List-tableRow input") + .scope() + if (elemScope) { + elemScope.isSelected = false; + } + + scope.allSelected = scope.allSelected.filter(function(i) { + return (!(obj.id === i.id && obj.type === i.type)); + }); + }; + + // update post url list + scope.$watch("allSelected", function(val) { + scope.posts = _ + .flatten((val || []) + .map(function (owner) { + var url = GetBasePath(owner.type + "s") + "/" + owner.id + + "/roles/"; + + return (owner.roles || []) + .map(function (role) { + return {url: url, + id: role.value}; + }); + })); + }, true); + + // post roles to api + scope.updatePermissions = function() { + var requests = scope.posts + .map(function(post) { + Rest.setUrl(post.url); + return Rest.post({"id": post.id}); + }); + + $q.all(requests) + .then(function (responses) { + rootScope.$broadcast("refreshList", "permission"); + scope.closeModal(); + }, function (error) { + // TODO: request(s) errored out. Call process errors + }); + }; +}]; diff --git a/awx/ui/client/src/access/addPermissions/addPermissions.directive.js b/awx/ui/client/src/access/addPermissions/addPermissions.directive.js new file mode 100644 index 0000000000..7a631f19a7 --- /dev/null +++ b/awx/ui/client/src/access/addPermissions/addPermissions.directive.js @@ -0,0 +1,51 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ +import addPermissionsController from './addPermissions.controller'; + +/* jshint unused: vars */ +export default + [ 'templateUrl', + 'Wait', + function(templateUrl, Wait) { + return { + restrict: 'E', + scope: true, + controller: addPermissionsController, + templateUrl: templateUrl('access/addPermissions/addPermissions'), + link: function(scope, element, attrs, ctrl) { + scope.toggleFormTabs('users'); + + $("body").append(element); + + Wait('start'); + + scope.$broadcast("linkLists"); + + setTimeout(function() { + $('#add-permissions-modal').modal("show"); + }, 200); + + $('.modal[aria-hidden=false]').each(function () { + if ($(this).attr('id') !== 'add-permissions-modal') { + $(this).modal('hide'); + } + }); + + scope.closeModal = function() { + $('#add-permissions-modal').on('hidden.bs.modal', + function () { + $('.AddPermissions').remove(); + }); + $('#add-permissions-modal').modal('hide'); + }; + + Wait('stop'); + + window.scrollTo(0,0); + } + }; + } + ]; diff --git a/awx/ui/client/src/access/addPermissions/addPermissions.partial.html b/awx/ui/client/src/access/addPermissions/addPermissions.partial.html new file mode 100644 index 0000000000..68eb34cffa --- /dev/null +++ b/awx/ui/client/src/access/addPermissions/addPermissions.partial.html @@ -0,0 +1,105 @@ + diff --git a/awx/ui/client/src/access/addPermissions/main.js b/awx/ui/client/src/access/addPermissions/main.js new file mode 100644 index 0000000000..001aa08b59 --- /dev/null +++ b/awx/ui/client/src/access/addPermissions/main.js @@ -0,0 +1,15 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import addPermissionsDirective from './addPermissions.directive'; +import roleSelect from './roleSelect.directive'; +import teamsPermissions from './teams/main'; +import usersPermissions from './users/main'; + +export default + angular.module('AddPermissions', [teamsPermissions.name, usersPermissions.name]) + .directive('addPermissions', addPermissionsDirective) + .directive('roleSelect', roleSelect); diff --git a/awx/ui/client/src/access/addPermissions/roleSelect.directive.js b/awx/ui/client/src/access/addPermissions/roleSelect.directive.js new file mode 100644 index 0000000000..c11dbe0e67 --- /dev/null +++ b/awx/ui/client/src/access/addPermissions/roleSelect.directive.js @@ -0,0 +1,25 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/* jshint unused: vars */ +export default + [ + 'CreateSelect2', + function(CreateSelect2) { + return { + restrict: 'E', + scope: false, + template: '', + link: function(scope, element, attrs, ctrl) { + CreateSelect2({ + element: '.roleSelect2', + multiple: true, + placeholder: 'Select roles' + }); + } + }; + } + ]; diff --git a/awx/ui/client/src/access/addPermissions/teams/main.js b/awx/ui/client/src/access/addPermissions/teams/main.js new file mode 100644 index 0000000000..ccba15fe86 --- /dev/null +++ b/awx/ui/client/src/access/addPermissions/teams/main.js @@ -0,0 +1,13 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import teamsDirective from './permissionsTeams.directive'; +import teamsList from './permissionsTeams.list'; + +export default + angular.module('PermissionsTeams', []) + .directive('addPermissionsTeams', teamsDirective) + .factory('addPermissionsTeamsList', teamsList); diff --git a/awx/ui/client/src/access/addPermissions/teams/permissionsTeams.directive.js b/awx/ui/client/src/access/addPermissions/teams/permissionsTeams.directive.js new file mode 100644 index 0000000000..158aeb1a23 --- /dev/null +++ b/awx/ui/client/src/access/addPermissions/teams/permissionsTeams.directive.js @@ -0,0 +1,44 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/* jshint unused: vars */ +export default + ['addPermissionsTeamsList', 'generateList', 'GetBasePath', 'SelectionInit', 'SearchInit', + 'PaginateInit', function(addPermissionsTeamsList, generateList, + GetBasePath, SelectionInit, SearchInit, PaginateInit) { + return { + restrict: 'E', + scope: { + }, + template: "
", + link: function(scope, element, attrs, ctrl) { + scope.$on("linkLists", function() { + var generator = generateList, + list = addPermissionsTeamsList, + url = GetBasePath("teams"), + set = "teams", + id = "addPermissionsTeamsList", + mode = "edit"; + + scope.$watch("selectedItems", function() { + scope.$emit("itemsSelected", scope.selectedItems); + }); + + generator.inject(list, { id: id, + title: false, mode: mode, scope: scope }); + + SearchInit({ scope: scope, set: set, + list: list, url: url }); + + PaginateInit({ scope: scope, + list: list, url: url, pageSize: 5 }); + + scope.search(list.iterator); + }); + } + }; + } + ]; diff --git a/awx/ui/client/src/access/addPermissions/teams/permissionsTeams.list.js b/awx/ui/client/src/access/addPermissions/teams/permissionsTeams.list.js new file mode 100644 index 0000000000..dc30bfbaf5 --- /dev/null +++ b/awx/ui/client/src/access/addPermissions/teams/permissionsTeams.list.js @@ -0,0 +1,27 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + + export default function() { + return { + + name: 'teams', + iterator: 'team', + listTitleBadge: false, + multiSelect: true, + multiSelectExtended: true, + index: false, + hover: true, + + fields: { + name: { + key: true, + label: 'name' + }, + }, + + }; +} diff --git a/awx/ui/client/src/access/addPermissions/users/main.js b/awx/ui/client/src/access/addPermissions/users/main.js new file mode 100644 index 0000000000..e565e6c410 --- /dev/null +++ b/awx/ui/client/src/access/addPermissions/users/main.js @@ -0,0 +1,13 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import usersDirective from './permissionsUsers.directive'; +import usersList from './permissionsUsers.list'; + +export default + angular.module('PermissionsUsers', []) + .directive('addPermissionsUsers', usersDirective) + .factory('addPermissionsUsersList', usersList); diff --git a/awx/ui/client/src/access/addPermissions/users/permissionsUsers.directive.js b/awx/ui/client/src/access/addPermissions/users/permissionsUsers.directive.js new file mode 100644 index 0000000000..aa36c9c7eb --- /dev/null +++ b/awx/ui/client/src/access/addPermissions/users/permissionsUsers.directive.js @@ -0,0 +1,44 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/* jshint unused: vars */ +export default + ['addPermissionsUsersList', 'generateList', 'GetBasePath', 'SelectionInit', 'SearchInit', + 'PaginateInit', function(addPermissionsUsersList, generateList, + GetBasePath, SelectionInit, SearchInit, PaginateInit) { + return { + restrict: 'E', + scope: { + }, + template: "
", + link: function(scope, element, attrs, ctrl) { + scope.$on("linkLists", function() { + var generator = generateList, + list = addPermissionsUsersList, + url = GetBasePath("users") + "?is_superuser=false", + set = "users", + id = "addPermissionsUsersList", + mode = "edit"; + + scope.$watch("selectedItems", function() { + scope.$emit("itemsSelected", scope.selectedItems); + }); + + generator.inject(list, { id: id, + title: false, mode: mode, scope: scope }); + + SearchInit({ scope: scope, set: set, + list: list, url: url }); + + PaginateInit({ scope: scope, + list: list, url: url, pageSize: 5 }); + + scope.search(list.iterator); + }); + } + }; + } + ]; diff --git a/awx/ui/client/src/access/addPermissions/users/permissionsUsers.list.js b/awx/ui/client/src/access/addPermissions/users/permissionsUsers.list.js new file mode 100644 index 0000000000..ced865e944 --- /dev/null +++ b/awx/ui/client/src/access/addPermissions/users/permissionsUsers.list.js @@ -0,0 +1,37 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + + export default function() { + return { + + name: 'users', + iterator: 'user', + title: false, + listTitleBadge: false, + multiSelect: true, + multiSelectExtended: true, + index: false, + hover: true, + + fields: { + first_name: { + label: 'First Name', + columnClass: 'col-md-3 col-sm-3 hidden-xs' + }, + last_name: { + label: 'Last Name', + columnClass: 'col-md-3 col-sm-3 hidden-xs' + }, + username: { + key: true, + label: 'Username', + columnClass: 'col-md-3 col-sm-3 col-xs-9' + }, + }, + + }; +} diff --git a/awx/ui/client/src/access/main.js b/awx/ui/client/src/access/main.js index 5b7063938b..084fe5ef87 100644 --- a/awx/ui/client/src/access/main.js +++ b/awx/ui/client/src/access/main.js @@ -8,5 +8,5 @@ import roleList from './roleList.directive'; import addPermissions from './addPermissions/main'; export default - angular.module('access', []) + angular.module('access', [addPermissions.name]) .directive('roleList', roleList); diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 892c5f66b6..14e955f423 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -892,6 +892,10 @@ var tower = angular.module('Tower', [ LoadConfig, Store, ShowSocketHelp, AboutAnsibleHelp, pendoService, Prompt, Rest, Wait, ProcessErrors, $state, GetBasePath) { var sock; + $rootScope.addPermission = function (scope) { + $compile("")(scope); + } + $rootScope.deletePermission = function (user, role, userName, roleName, resourceName) { var action = function () { diff --git a/awx/ui/client/src/controllers/Teams.js b/awx/ui/client/src/controllers/Teams.js index 7d71bd8ee2..0bb7e9e2d4 100644 --- a/awx/ui/client/src/controllers/Teams.js +++ b/awx/ui/client/src/controllers/Teams.js @@ -210,36 +210,40 @@ export function TeamsEdit($scope, $rootScope, $compile, $location, $log, $scope.$emit("RefreshTeamsList"); // return a promise from the options request with the permission type choices (including adhoc) as a param - var permissionsChoice = fieldChoices({ - scope: $scope, - url: 'api/v1/' + base + '/' + id + '/permissions/', - field: 'permission_type' - }); + // var permissionsChoice = fieldChoices({ + // scope: $scope, + // url: 'api/v1/' + base + '/' + id + '/permissions/', + // field: 'permission_type' + // }); - // manipulate the choices from the options request to be set on - // scope and be usable by the list form - permissionsChoice.then(function (choices) { - choices = - fieldLabels({ - choices: choices - }); - _.map(choices, function(n, key) { - $scope.permission_label[key] = n; - }); - }); + // // manipulate the choices from the options request to be set on + // // scope and be usable by the list form + // permissionsChoice.then(function (choices) { + // choices = + // fieldLabels({ + // choices: choices + // }); + // _.map(choices, function(n, key) { + // $scope.permission_label[key] = n; + // }); + // }); // manipulate the choices from the options request to be usable // by the search option for permission_type, you can't inject the // list until this is done! - permissionsChoice.then(function (choices) { - form.related.permissions.fields.permission_type.searchOptions = - permissionsSearchSelect({ - choices: choices - }); + // permissionsChoice.then(function (choices) { + // form.related.permissions.fields.permission_type.searchOptions = + // permissionsSearchSelect({ + // choices: choices + // }); generator.inject(form, { mode: 'edit', related: true, scope: $scope }); generator.reset(); $scope.$emit('loadTeam'); - }); + // }); + + generator.inject(form, { mode: 'edit', related: true, scope: $scope }); + generator.reset(); + $scope.$emit('loadTeam'); $scope.team_id = id; diff --git a/awx/ui/client/src/controllers/Users.js b/awx/ui/client/src/controllers/Users.js index f6b6b74a13..c17d6456f5 100644 --- a/awx/ui/client/src/controllers/Users.js +++ b/awx/ui/client/src/controllers/Users.js @@ -240,37 +240,38 @@ export function UsersEdit($scope, $rootScope, $compile, $location, $log, $scope.$emit("RefreshUsersList"); - // return a promise from the options request with the permission type choices (including adhoc) as a param - var permissionsChoice = fieldChoices({ - scope: $scope, - url: 'api/v1/' + base + '/' + id + '/permissions/', - field: 'permission_type' - }); - - // manipulate the choices from the options request to be set on - // scope and be usable by the list form - permissionsChoice.then(function (choices) { - choices = - fieldLabels({ - choices: choices - }); - _.map(choices, function(n, key) { - $scope.permission_label[key] = n; - }); - }); + // // return a promise from the options request with the permission type choices (including adhoc) as a param + // var permissionsChoice = fieldChoices({ + // scope: $scope, + // url: 'api/v1/' + base + '/' + id + '/permissions/', + // field: 'permission_type' + // }); + // + // // manipulate the choices from the options request to be set on + // // scope and be usable by the list form + // permissionsChoice.then(function (choices) { + // choices = + // fieldLabels({ + // choices: choices + // }); + // _.map(choices, function(n, key) { + // $scope.permission_label[key] = n; + // }); + // }); // manipulate the choices from the options request to be usable // by the search option for permission_type, you can't inject the // list until this is done! - permissionsChoice.then(function (choices) { - form.related.permissions.fields.permission_type.searchOptions = - permissionsSearchSelect({ - choices: choices - }); - generator.inject(form, { mode: 'edit', related: true, scope: $scope }); - generator.reset(); - $scope.$emit("loadForm"); - }); + // permissionsChoice.then(function (choices) { + // form.related.permissions.fields.permission_type.searchOptions = + // permissionsSearchSelect({ + // choices: choices + // }); + // }); + + generator.inject(form, { mode: 'edit', related: true, scope: $scope }); + generator.reset(); + $scope.$emit("loadForm"); if ($scope.removeFormReady) { $scope.removeFormReady(); diff --git a/awx/ui/client/src/forms/Users.js b/awx/ui/client/src/forms/Users.js index 5d87cac2db..820f86a8cf 100644 --- a/awx/ui/client/src/forms/Users.js +++ b/awx/ui/client/src/forms/Users.js @@ -158,69 +158,69 @@ export default } }, - permissions: { - type: 'collection', - title: 'Permissions', - iterator: 'permission', - open: false, - index: false, - - actions: { - add: { - ngClick: "add('permissions')", - label: 'Add', - awToolTip: 'Add a permission for this user', - ngShow: 'PermissionAddAllowed', - actionClass: 'btn List-buttonSubmit', - buttonContent: '+ ADD' - } - }, - - fields: { - name: { - key: true, - label: 'Name', - ngClick: "edit('permissions', permission.id, permission.name)" - }, - inventory: { - label: 'Inventory', - sourceModel: 'inventory', - sourceField: 'name', - ngBind: 'permission.summary_fields.inventory.name' - }, - project: { - label: 'Project', - sourceModel: 'project', - sourceField: 'name', - ngBind: 'permission.summary_fields.project.name' - }, - permission_type: { - label: 'Permission', - ngBind: 'getPermissionText()', - searchType: 'select' - } - }, - - fieldActions: { - edit: { - label: 'Edit', - ngClick: "edit('permissions', permission.id, permission.name)", - icon: 'icon-edit', - awToolTip: 'Edit the permission', - 'class': 'btn btn-default' - }, - - "delete": { - label: 'Delete', - ngClick: "delete('permissions', permission.id, permission.name, 'permission')", - icon: 'icon-trash', - "class": 'btn-danger', - awToolTip: 'Delete the permission', - ngShow: 'PermissionAddAllowed' - } - } - - }, + // permissions: { + // type: 'collection', + // title: 'Permissions', + // iterator: 'permission', + // open: false, + // index: false, + // + // actions: { + // add: { + // ngClick: "add('permissions')", + // label: 'Add', + // awToolTip: 'Add a permission for this user', + // ngShow: 'PermissionAddAllowed', + // actionClass: 'btn List-buttonSubmit', + // buttonContent: '+ ADD' + // } + // }, + // + // fields: { + // name: { + // key: true, + // label: 'Name', + // ngClick: "edit('permissions', permission.id, permission.name)" + // }, + // inventory: { + // label: 'Inventory', + // sourceModel: 'inventory', + // sourceField: 'name', + // ngBind: 'permission.summary_fields.inventory.name' + // }, + // project: { + // label: 'Project', + // sourceModel: 'project', + // sourceField: 'name', + // ngBind: 'permission.summary_fields.project.name' + // }, + // permission_type: { + // label: 'Permission', + // ngBind: 'getPermissionText()', + // searchType: 'select' + // } + // }, + // + // fieldActions: { + // edit: { + // label: 'Edit', + // ngClick: "edit('permissions', permission.id, permission.name)", + // icon: 'icon-edit', + // awToolTip: 'Edit the permission', + // 'class': 'btn btn-default' + // }, + // + // "delete": { + // label: 'Delete', + // ngClick: "delete('permissions', permission.id, permission.name, 'permission')", + // icon: 'icon-trash', + // "class": 'btn-danger', + // awToolTip: 'Delete the permission', + // ngShow: 'PermissionAddAllowed' + // } + // } + // + // }, admin_of_organizations: { // Assumes a plural name (e.g. things) type: 'collection', diff --git a/awx/ui/client/src/helpers/PaginationHelpers.js b/awx/ui/client/src/helpers/PaginationHelpers.js index 2b131c2dc0..2fd9d57bf2 100644 --- a/awx/ui/client/src/helpers/PaginationHelpers.js +++ b/awx/ui/client/src/helpers/PaginationHelpers.js @@ -32,7 +32,7 @@ export default // Which page are we on? if (Empty(next) && previous) { // no next page, but there is a previous page - scope[iterator + '_page'] = scope[iterator + '_num_pages']; + scope[iterator + '_page'] = /page=\d+/.test(previous) ? parseInt(previous.match(/page=(\d+)/)[1]) + 1 : 2; } else if (next && Empty(previous)) { // next page available, but no previous page scope[iterator + '_page'] = 1; diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js index 434526cd7d..7aeae22b6b 100644 --- a/awx/ui/client/src/shared/Utilities.js +++ b/awx/ui/client/src/shared/Utilities.js @@ -614,7 +614,8 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter']) var element = params.element, options = params.opts, - multiple = (params.multiple!==undefined) ? params.multiple : true; + multiple = (params.multiple!==undefined) ? params.multiple : true, + placeholder = params.placeholder; $.fn.select2.amd.require([ 'select2/utils', @@ -632,6 +633,7 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter']) }, Dropdown); $(element).select2({ + placeholder: placeholder, multiple: multiple, containerCssClass: 'Form-dropDown', width: '100%', diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index c7187d7106..8525ea7997 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -1548,7 +1548,9 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat 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 de6aed9081..2205f84390 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 @@ -416,6 +416,7 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate function buildTable() { var extraClasses = list['class']; var multiSelect = list.multiSelect ? 'multi-select-list' : null; + var multiSelectExtended = list.multiSelectExtended ? 'true' : 'false'; if (options.mode === 'summary') { extraClasses += ' table-summary'; @@ -425,7 +426,8 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate .attr('id', list.name + '_table') .addClass('List-table') .addClass(extraClasses) - .attr('multi-select-list', multiSelect); + .attr('multi-select-list', multiSelect) + .attr('is-extended', multiSelectExtended); } @@ -460,7 +462,7 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate } if (list.multiSelect) { - innerTable += ''; + innerTable += ''; } // Change layout if a lookup list, place radio buttons before labels @@ -609,7 +611,7 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate function buildSelectAll() { return $('') - .addClass('col-xs-1 select-column List-tableHeader') + .addClass('col-xs-1 select-column List-tableHeader List-staticColumn--smallStatus') .append( $('') .attr('selections-empty', 'selectedItems.length === 0') diff --git a/awx/ui/client/src/shared/multi-select-list/select-list-item.directive.js b/awx/ui/client/src/shared/multi-select-list/select-list-item.directive.js index f701288090..fb90d045b8 100644 --- a/awx/ui/client/src/shared/multi-select-list/select-list-item.directive.js +++ b/awx/ui/client/src/shared/multi-select-list/select-list-item.directive.js @@ -30,7 +30,7 @@ export default item: '=item' }, require: '^multiSelectList', - template: '', + template: '', link: function(scope, element, attrs, multiSelectList) { scope.isSelected = false; @@ -52,6 +52,10 @@ export default multiSelectList.deregisterItem(scope.decoratedItem); }); + scope.userInteractionSelect = function() { + scope.$emit("selectedOrDeselected", scope.decoratedItem); + } + } }; }];