diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index e03acc43b0..0512604253 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -737,6 +737,7 @@ var tower = angular.module('Tower', [ CheckLicense.notify(); } $rootScope.$broadcast("closePermissionsModal"); + $rootScope.$broadcast("closeUsersModal"); // this line removes the query params attached to a route if(prev && prev.$$route && prev.$$route.name === 'systemTracking'){ diff --git a/awx/ui/client/src/organizations/linkout/addUsers/addUsers.block.less b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.block.less new file mode 100644 index 0000000000..abadd06a9b --- /dev/null +++ b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.block.less @@ -0,0 +1,218 @@ +@import "../../../shared/branding/colors.default.less"; + +/** @define AddUsers */ + +.AddUsers-backDrop { + width: 100vw; + height: 100vh; + position: fixed; + top: 0; + left: 0; + z-index: 1041; + opacity: 0.2; + transition: 0.5s opacity; + background: @login-backdrop; +} + +.AddUsers-dialog { + margin: 30px auto; + margin-top: 95px; +} + +.AddUsers-content { + max-width: 750px; + margin: 0 auto; + border: 0; + box-shadow: none; + background-color: @login-bg; + border-radius: 4px; + transition: opacity 0.5s; + z-index: 1042; + position: relative; + opacity: 1; +} + +.AddUsers-header { + padding: 20px; + padding-bottom: 10px; + padding-top: 15px; +} + +.AddUsers-body { + padding: 0px 20px; +} + +.AddUsers-footer { + display: flex; + flex-wrap: wrap-reverse; + align-items: center; + padding: 20px; + padding-bottom: 0px; + padding-top: 20px; +} + +.AddUsers-list .List-searchRow { + height: 0px; +} + +.AddUsers-list .List-searchWidget { + height: 66px; +} + +.AddUsers-list .List-tableHeader:last-child { + border-top-right-radius: 5px; +} + +.AddUsers-list select-all { + display: none; +} + +.AddUsers-title { + margin-top: 5px; + margin-bottom: 20px; +} + +.AddUsers-buttons { + margin-left: auto; + margin-bottom: 20px; +} + +.AddUsers-directions { + margin-top: 10px; + margin-bottom: 20px; + color: @default-interface-txt; + display: flex; + align-items: center; +} + +.AddUsers-directionNumber { + font-size: 14px; + font-weight: bold; + border-radius: 50%; + background-color: @default-list-header-bg; + padding: 1px 8px; + margin-right: 10px; + width: 23px; + height: 23px; +} + +.AddUsers-separator { + margin-top: 20px 0px; + width: 100%; + border-bottom: 1px solid @d7grey; +} + +.AddUsers-roleRow { + display: flex; + margin-bottom: 10px; + align-items: center; +} + +.AddUsers-roleName { + width: 30%; + padding-right: 10px; + display: flex; + align-items: center; +} + +.AddUsers-roleNameVal { + font-size: 14px; + max-width: ~"calc(100% - 46px)"; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.AddUsers-roleType { + padding: 0px 6px; + font-size: 10px; + color: @default-interface-txt; + text-transform: uppercase; + background-color: @default-bg; + margin-left: 6px; +} + +.AddUsers-roleSelect { + width: ~"calc(70% - 40px)"; + margin-right: 20px; +} + +.AddUsers-roleSelect .Form-dropDown { + height: inherit !important; +} + +.AddUsers-roleRemove { + border-radius: 50%; + padding: 1px 0; + line-height: 11px; + color: @default-icon; + background-color: @default-tertiary-bg; + border: 0; + height: 23px; + width: 23px; +} + +.AddUsers-roleRemove:hover { + background-color: @default-err; + color: @default-bg; +} + +.AddUsers-selectHide { + display: none; +} + +.AddUsers .select2-search__field { + text-transform: uppercase; +} + +.AddUsers-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; +} + +.AddUsers-keyToggle:hover { + background-color: @default-tertiary-bg; +} + +.AddUsers-keyToggle.is-active { + background-color: @default-link; + border-color: @default-link; + color: @default-bg; +} + +.AddUsers-keyPane { + margin: 20px 0; + font-size: 12px; + width: 100%; + padding: 15px; + padding-top: 10px; + margin-bottom: 15px; + border-radius: 4px; + border: 1px solid @login-notice-border; + background-color: @login-notice-bg; + color: @login-notice-text; +} + +.AddUsers-keyRow { + display: flex; + flex-direction: column; + margin-bottom: 15px; +} + +.AddUsers-keyName { + flex: 1 0 auto; + text-transform: uppercase; + font-weight: bold; + padding-bottom: 3px; +} + +.AddUsers-keyDescription { + flex: 1 0 auto; +} diff --git a/awx/ui/client/src/organizations/linkout/addUsers/addUsers.controller.js b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.controller.js new file mode 100644 index 0000000000..a0ae7a28f0 --- /dev/null +++ b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.controller.js @@ -0,0 +1,82 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/** + * @ngdoc function + * @name controllers.function:Access + * @description + * Controller for handling permissions adding + */ + +export default ['$scope', '$rootScope', 'ProcessErrors', 'UserList', 'generateList', 'GetBasePath', 'SelectionInit', 'SearchInit', 'templateUrl', 'PaginateInit', '$state', 'Rest', '$q', 'Wait', function($scope, $rootScope, ProcessErrors, UserList, generateList, GetBasePath, SelectionInit, SearchInit, templateUrl, PaginateInit, $state, Rest, $q, Wait) { + $scope.$on("linkLists", function() { + var generator = generateList, + list = _.cloneDeep(UserList), + url = GetBasePath("users"), + set = "users", + id = "addUsersList", + mode = "add"; + + if ($state.current.name.split(".")[1] === "users") { + $scope.addType = "Users"; + } else { + $scope.addType = "Administrators"; + } + + + + list.multiSelect = true; + + 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); + + $scope.updateUsers = function() { + var url, listToClose, + payloads = $scope.selectedItems.map(function(val) { + return {id: val.id}; + }); + if ($scope.addType === 'Users') { + url = $scope.$parent.orgRelatedUrls.users; + listToClose = 'user'; + } else { + url = $scope.$parent.orgRelatedUrls.admins; + listToClose = 'admin'; + } + + Wait('start'); + + var requests = payloads + .map(function(post) { + Rest.setUrl(url); + return Rest.post(post); + }); + + $q.all(requests) + .then(function () { + Wait('stop'); + $scope.$emit('ReloadOrganzationCards', $scope.$parent.org_id); + $scope.$parent.search('user'); + $scope.closeModal(); + }, function (error) { + Wait('stop'); + $rootScope.$broadcast("refreshList", listToClose); + ProcessErrors(null, error.data, error.status, null, { + hdr: 'Error!', + msg: 'Failed to post ' + $scope.addType + + ': POST returned status' + error.status + }); + }); + }; + }); +}]; diff --git a/awx/ui/client/src/organizations/linkout/addUsers/addUsers.directive.js b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.directive.js new file mode 100644 index 0000000000..2990093720 --- /dev/null +++ b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.directive.js @@ -0,0 +1,54 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +/* jshint unused: vars */ +import addUsers from './addUsers.controller'; +export default + ['Wait', 'templateUrl', function(Wait, templateUrl) { + return { + restrict: 'E', + scope: {}, + controller: addUsers, + templateUrl: templateUrl('organizations/linkout/addUsers/addUsers'), + link: function(scope, element, attrs, ctrl) { + $("body").addClass("is-modalOpen"); + + $("body").append(element); + + Wait('start'); + + scope.$broadcast("linkLists"); + + setTimeout(function() { + $('#add-users-modal').modal("show"); + }, 200); + + $('.modal[aria-hidden=false]').each(function () { + if ($(this).attr('id') !== 'add-users-modal') { + $(this).modal('hide'); + } + }); + + scope.closeModal = function() { + $("body").removeClass("is-modalOpen"); + $('#add-users-modal').on('hidden.bs.modal', + function () { + $('.AddUsers').remove(); + }); + $('#add-users-modal').modal('hide'); + }; + + scope.$on('closeUsersModal', function() { + scope.closeModal(); + }); + + Wait('stop'); + + window.scrollTo(0,0); + } + }; + } + ]; diff --git a/awx/ui/client/src/organizations/linkout/addUsers/addUsers.partial.html b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.partial.html new file mode 100644 index 0000000000..708f7925b6 --- /dev/null +++ b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.partial.html @@ -0,0 +1,39 @@ + diff --git a/awx/ui/client/src/organizations/linkout/addUsers/main.js b/awx/ui/client/src/organizations/linkout/addUsers/main.js new file mode 100644 index 0000000000..83604f2c77 --- /dev/null +++ b/awx/ui/client/src/organizations/linkout/addUsers/main.js @@ -0,0 +1,11 @@ +/************************************************* + * Copyright (c) 2015 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import addUsersDirective from './addUsers.directive'; + +export default + angular.module('AddUsers', []) + .directive('addUsers', addUsersDirective); diff --git a/awx/ui/client/src/organizations/linkout/main.js b/awx/ui/client/src/organizations/linkout/main.js index ed07ead60a..754ab804dd 100644 --- a/awx/ui/client/src/organizations/linkout/main.js +++ b/awx/ui/client/src/organizations/linkout/main.js @@ -1,6 +1,7 @@ import routes from './organizations-linkout.route'; +import AddUsers from './addUsers/main'; -export default angular.module('organizationsLinkout', []) +export default angular.module('organizationsLinkout', [AddUsers.name]) .run(['$stateExtender', function($stateExtender) { routes.forEach(function(route) { $stateExtender.addState(route); diff --git a/awx/ui/client/src/organizations/linkout/organizations-linkout.controller.js b/awx/ui/client/src/organizations/linkout/organizations-linkout.controller.js index 5568f8eb7e..ea5ae1cd22 100644 --- a/awx/ui/client/src/organizations/linkout/organizations-linkout.controller.js +++ b/awx/ui/client/src/organizations/linkout/organizations-linkout.controller.js @@ -1,11 +1,14 @@ -export default ['$scope', '$stateParams', '$state', 'Rest', 'UserList', 'InventoryList', 'JobTemplateList', 'TeamList', 'ProjectList', 'generateList', 'SearchInit', 'PaginateInit', function($scope, $stateParams, $state, Rest, UserList, InventoryList, JobTemplateList, TeamList, ProjectList, GenerateList, SearchInit, PaginateInit) { +export default ['$compile', '$scope', '$stateParams', '$state', 'Rest', 'UserList', 'InventoryList', 'JobTemplateList', 'TeamList', 'ProjectList', 'generateList', 'SearchInit', 'PaginateInit', function($compile, $scope, $stateParams, $state, Rest, UserList, InventoryList, JobTemplateList, TeamList, ProjectList, GenerateList, SearchInit, PaginateInit) { var getList = function(mode) { var list = {}; if (mode === 'users') { list = _.cloneDeep(UserList); list.emptyListText = "Please add items to this list"; + list.actions.add.label = "Add a user to the organization"; list.actions.add.buttonContent = '+ ADD user'; + list.actions.add.awToolTip = 'Add existing user to organization'; + list.actions.add.ngClick = 'addUsers()'; } else if (mode === 'inventories') { list = _.cloneDeep(InventoryList); list.emptyListText = "List is empty"; @@ -26,6 +29,8 @@ export default ['$scope', '$stateParams', '$state', 'Rest', 'UserList', 'Invento list = _.cloneDeep(UserList); list.emptyListText = "Please add items to this list"; list.actions.add.buttonContent = '+ ADD administrator'; + list.actions.add.awToolTip = 'Add existing user to organization as administrator'; + list.actions.add.ngClick = 'addUsers()'; } return list; }; @@ -61,18 +66,27 @@ export default ['$scope', '$stateParams', '$state', 'Rest', 'UserList', 'Invento generator = GenerateList; $scope.$parent.activeCard = parseInt($stateParams.organization_id); $scope.$parent.activeMode = mode; + $scope.org_name = data.name; + $scope.org_id = data.id; + var listMode = (mode === 'admins') ? 'users' : mode; list = getList(mode); - list.listTitle = listTitle; - url = getUrl(mode, data); + list.listTitle = listTitle; + list.basePath = url; + + $scope.orgRelatedUrls = data.related; generator .inject(list, { mode: 'edit', scope: $scope }); + $scope.addUsers = function () { + $compile("")($scope); + }; + SearchInit({ scope: $scope, - set: mode, + set: listMode, list: list, url: url }); diff --git a/awx/ui/client/src/search/getSearchHtml.service.js b/awx/ui/client/src/search/getSearchHtml.service.js index f64e5a12bf..3ae5dbfe00 100644 --- a/awx/ui/client/src/search/getSearchHtml.service.js +++ b/awx/ui/client/src/search/getSearchHtml.service.js @@ -31,7 +31,11 @@ export default ['GetBasePath', function(GetBasePath) { if (endPoint === 'inventories') { endPoint = 'inventory'; } - return GetBasePath(endPoint); + if (endPoint.indexOf("/api/v1") > -1) { + return endPoint; + } else { + return GetBasePath(endPoint); + } }; // inject the directive with the list and endpoint diff --git a/awx/ui/client/src/search/tagSearch.service.js b/awx/ui/client/src/search/tagSearch.service.js index fc8111e8ed..84e699fd65 100644 --- a/awx/ui/client/src/search/tagSearch.service.js +++ b/awx/ui/client/src/search/tagSearch.service.js @@ -162,7 +162,10 @@ export default ['Rest', '$q', 'GetBasePath', 'Wait', 'ProcessErrors', '$log', fu tags = relatedTags.concat(nonRelatedTags); } - return basePath + "?" + + var returnedUrl = basePath; + returnedUrl += (basePath.indexOf("?") > - 1) ? "&" : "?"; + + return returnedUrl + (tags || []).map(function (t) { return t.url; }).join("&") + "&page_size=" + pageSize;