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 @@
+
+
+
+
+
+
+
+
+
+ 1.
+
+ Please select Users / Teams from the lists below.
+
+
+
+
+
+
+
+
+
+
+
+ 2.
+
+ Please assign roles to the selected users/teams
+
+
+
+
+
+
+
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";
if (this.form.horizontal) {
diff --git a/awx/ui/client/src/shared/generator-helpers.js b/awx/ui/client/src/shared/generator-helpers.js
index 44bff438d9..ae841fffd4 100644
--- a/awx/ui/client/src/shared/generator-helpers.js
+++ b/awx/ui/client/src/shared/generator-helpers.js
@@ -635,6 +635,7 @@ angular.module('GeneratorHelpers', [systemStatus.name])
var iterator = params.iterator,
form = params.template,
size = params.size,
+ mini = params.mini,
includeSize = (params.includeSize === undefined) ? true : params.includeSize,
ngShow = (params.ngShow) ? params.ngShow : false,
i, html = '',
@@ -668,6 +669,7 @@ angular.module('GeneratorHelpers', [systemStatus.name])
if (includeSize) {
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);
+ }
+
}
};
}];