diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index dfa53e0d25..d554ce8cc0 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -729,64 +729,7 @@ legend { .navigation { margin: 15px 0 15px 0; } - -.page-number { - display: inline-block; - padding: 0; - margin: 0; -} - -.page-number-small { - display: inline-block; - margin-left: 10px; - font-size: 11px; -} - -/* Pagination */ - .page-label { - font-size: 12px; - margin-top: 0; - text-align: right; - } - - .pagination { - margin-top: 0; - margin-bottom: 7px; - } - - .pagination>li>a, - .pagination>li>span { - border: 1px solid @grey-border; - padding: 3px 6px; - font-size: 10px; - } - - .pagination li { - a#next-page { - border-radius: 0 4px 4px 0; - } - - a#previous-page { - border-radius: 4px 0 0 4px; - } - } - .modal-body { - .pagination { - margin-top: 15px; - margin-bottom: 0; - } - .pagination > li > a { - border: none; - padding-top: 0; - padding-bottom: 0; - } - .pagination > .active > a { - background-color: @default-bg; - color: #428bca; - border-color: none; - border: 1px solid @default-link; - } .alert { padding: 0; border: none; @@ -1623,6 +1566,10 @@ a.btn-disabled:hover { /* Sort link styles */ +.list-header-noSort:hover.list-header:hover{ + cursor: default; +} + .list-header:hover { cursor: pointer; } diff --git a/awx/ui/client/legacy-styles/jobs.less b/awx/ui/client/legacy-styles/jobs.less index 0ae748d0b4..86e14926e1 100644 --- a/awx/ui/client/legacy-styles/jobs.less +++ b/awx/ui/client/legacy-styles/jobs.less @@ -19,13 +19,7 @@ } .job-list { - .pagination li { - } - .pagination li a { - font-size: 12px; - padding: 3px 6px; - } i[class*="icon-job-"] { font-size: 13px; } diff --git a/awx/ui/client/legacy-styles/lists.less b/awx/ui/client/legacy-styles/lists.less index 098f936ab1..763694f6d2 100644 --- a/awx/ui/client/legacy-styles/lists.less +++ b/awx/ui/client/legacy-styles/lists.less @@ -116,44 +116,6 @@ table, tbody { margin-left: 15px; } -/* -- Pagination -- */ -.List-pagination { - margin-top: 20px; - font-size: 12px; - color: @list-pagin-text; - text-transform: uppercase; - height: 22px; - display: flex; -} - -.List-paginationPagerHolder { - display: flex; - flex: 1 0 auto; -} - -.List-paginationPager { - display: flex; -} - -.List-paginationPager--pageof { - line-height: 22px; - margin-left: 10px; -} - -.List-paginationPager--item { - border-color: @list-pagin-bord; -} - -.List-paginationPager--active { - border-color: @list-pagin-bord-act!important; - background-color: @list-pagin-bg-act!important; -} - -.List-paginationItemsOf { - display: flex; - justify-content: flex-end; -} - .List-header { display: flex; min-height: 34px; diff --git a/awx/ui/client/src/access/addPermissions/addPermissions.controller.js b/awx/ui/client/src/access/addPermissions/addPermissions.controller.js index 75177cc308..d5774e7c79 100644 --- a/awx/ui/client/src/access/addPermissions/addPermissions.controller.js +++ b/awx/ui/client/src/access/addPermissions/addPermissions.controller.js @@ -11,23 +11,12 @@ * Controller for handling permissions adding */ -export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'ProcessErrors', function (rootScope, scope, GetBasePath, Rest, $q, Wait, ProcessErrors) { - var manuallyUpdateChecklists = function(list, id, isSelected) { - var elemScope = angular - .element("#" + - list + "s_table #" + id + ".List-tableRow input") - .scope(); - if (elemScope) { - elemScope.isSelected = !!isSelected; - } - }; +export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'ProcessErrors', function(rootScope, scope, GetBasePath, Rest, $q, Wait, ProcessErrors) { scope.allSelected = []; // the object permissions are being added to - scope.object = scope[scope.$parent.list - .iterator + "_obj"]; - + scope.object = scope.resourceData.data; // array for all possible roles for the object scope.roles = Object .keys(scope.object.summary_fields.object_roles) @@ -36,7 +25,8 @@ export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'Pr value: scope.object.summary_fields .object_roles[key].id, label: scope.object.summary_fields - .object_roles[key].name }; + .object_roles[key].name + }; }); // TODO: get working with api @@ -48,7 +38,8 @@ export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'Pr name: scope.object.summary_fields .object_roles[key].name, description: scope.object.summary_fields - .object_roles[key].description }; + .object_roles[key].description + }; }); scope.showKeyPane = false; @@ -63,90 +54,44 @@ export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'Pr scope.teamsSelected = !scope.usersSelected; }; - // manually handle selection/deselection of user/team checkboxes - scope.$on("selectedOrDeselected", function(e, val) { - val = val.value; - if (val.isSelected) { - // deselected, so remove from the allSelected list - scope.allSelected = scope.allSelected.filter(function(i) { - // return all but the object who has the id and type - // of the element to deselect - return (!(val.id === i.id && val.type === i.type)); - }); + // pop/push into unified collection of selected users & teams + scope.$on("selectedOrDeselected", function(e, value) { + let item = value.value; + + function buildName(user) { + return (user.first_name && + user.last_name) ? + user.first_name + " " + + user.last_name : + user.username; + } + + if (item.isSelected) { + if (item.type === 'user') { + item.name = buildName(item); + } + scope.allSelected.push(item); } else { - // selected, so add to the allSelected list - var getName = function(val) { - if (val.type === "user") { - return (val.first_name && - val.last_name) ? - val.first_name + " " + - val.last_name : - val.username; - } else { - return val.name; - } - }; - scope.allSelected.push({ - name: getName(val), - type: val.type, - roles: [], - id: val.id - }); + scope.allSelected = _.remove(scope.allSelected, { id: item.id }); } }); - // used to handle changes to the itemsSelected scope var on "next page", - // "sorting etc." - scope.$on("itemsSelected", function(e, inList) { - // compile a list of objects that needed to be checked in the lists - scope.updateLists = scope.allSelected.filter(function(inMemory) { - var notInList = true; - inList.forEach(function(val) { - // if the object is part of the allSelected list and is - // selected, - // you don't need to add it updateLists - if (inMemory.id === val.id && - inMemory.type === val.type) { - notInList = false; - } - }); - return notInList; - }); - }); - - // handle changes to the updatedLists by manually selected those values in - // the UI - scope.$watch("updateLists", function(toUpdate) { - (toUpdate || []).forEach(function(obj) { - manuallyUpdateChecklists(obj.type, obj.id, true); - }); - - delete scope.updateLists; - }); - - // remove selected user/team - scope.removeObject = function(obj) { - manuallyUpdateChecklists(obj.type, obj.id, 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/"; + .map(function(owner) { + var url = GetBasePath(owner.type + "s") + owner.id + + "/roles/"; - return (owner.roles || []) - .map(function (role) { - return {url: url, - id: role.value}; - }); - })); + return (owner.roles || []) + .map(function(role) { + return { + url: url, + id: role.value + }; + }); + })); }, true); // post roles to api @@ -156,22 +101,22 @@ export default ['$rootScope', '$scope', 'GetBasePath', 'Rest', '$q', 'Wait', 'Pr var requests = scope.posts .map(function(post) { Rest.setUrl(post.url); - return Rest.post({"id": post.id}); + return Rest.post({ "id": post.id }); }); $q.all(requests) - .then(function () { + .then(function() { Wait('stop'); rootScope.$broadcast("refreshList", "permission"); scope.closeModal(); - }, function (error) { + }, function(error) { Wait('stop'); rootScope.$broadcast("refreshList", "permission"); scope.closeModal(); ProcessErrors(null, error.data, error.status, null, { hdr: 'Error!', msg: 'Failed to post role(s): POST returned status' + - error.status + error.status }); }); }; diff --git a/awx/ui/client/src/access/addPermissions/addPermissions.directive.js b/awx/ui/client/src/access/addPermissions/addPermissions.directive.js index 57bb658788..284110b0ce 100644 --- a/awx/ui/client/src/access/addPermissions/addPermissions.directive.js +++ b/awx/ui/client/src/access/addPermissions/addPermissions.directive.js @@ -6,55 +6,28 @@ 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.withoutTeamPermissions = attrs.withoutTeamPermissions; - scope.toggleFormTabs('users'); +export default ['templateUrl', '$state', + 'Wait', 'addPermissionsUsersList', 'addPermissionsTeamsList', + function(templateUrl, $state, Wait, usersList, teamsList) { + return { + restrict: 'E', + scope: { + usersDataset: '=', + teamsDataset: '=', + resourceData: '=', + }, + controller: addPermissionsController, + templateUrl: templateUrl('access/addPermissions/addPermissions'), + link: function(scope, element, attrs) { + scope.toggleFormTabs('users'); + $('#add-permissions-modal').modal('show'); - $("body").addClass("is-modalOpen"); + scope.closeModal = function() { + $state.go('^', null, {reload: true}); + }; - $("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() { - $("body").removeClass("is-modalOpen"); - $('#add-permissions-modal').on('hidden.bs.modal', - function () { - $('.AddPermissions').remove(); - }); - $('#add-permissions-modal').modal('hide'); - }; - - scope.$on('closePermissionsModal', function() { - scope.closeModal(); - }); - - Wait('stop'); - - window.scrollTo(0,0); - } - }; - } - ]; + 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 index cc2f7ee0c7..264a4cf834 100644 --- a/awx/ui/client/src/access/addPermissions/addPermissions.partial.html +++ b/awx/ui/client/src/access/addPermissions/addPermissions.partial.html @@ -45,13 +45,11 @@ -
Example URLs for GIT SCM include:
Note: When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' + 'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' + 'SSH. GIT read only protocol (git://) does not use username or password information.'); - break; + break; case 'svn': $scope.urlPopover = i18n._('
Example URLs for Subversion SCM include:
' + 'Example URLs for GIT SCM include:
Note: When using SSH protocol for GitHub or Bitbucket, enter an SSH key only, ' + 'do not enter a username (other than git). Additionally, GitHub and Bitbucket do not support password authentication when using ' + 'SSH. GIT read only protocol (git://) does not use username or password information.'); - break; + break; case 'svn': $scope.urlPopover = i18n._('
Example URLs for Subversion SCM include:
' + 'Indicates if a host is available and should be included in running jobs.
For hosts that " + "are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process.
", dataTitle: 'Host Enabled' @@ -28,7 +28,7 @@ export default function(){ name: { label: 'Host Name', type: 'text', - editRequired: true, + value: '{{name}}', awPopOver: "Provide a host name, ip address, or ip address:port. Examples include:
" + "myserver.domain.com
" + @@ -43,12 +43,10 @@ export default function(){ description: { label: 'Description', type: 'text', - editRequired: false }, variables: { label: 'Variables', type: 'textarea', - editRequired: false, rows: 6, class: 'modal-input-xlarge Form-textArea Form-formGroup--fullWidth', dataTitle: 'Host Variables', diff --git a/awx/ui/client/src/dashboard/hosts/dashboard-hosts.list.js b/awx/ui/client/src/dashboard/hosts/dashboard-hosts.list.js index eb312af291..0c3c0adb9d 100644 --- a/awx/ui/client/src/dashboard/hosts/dashboard-hosts.list.js +++ b/awx/ui/client/src/dashboard/hosts/dashboard-hosts.list.js @@ -21,11 +21,7 @@ export default [ 'i18n', function(i18n){ basePath: 'unified_jobs', label: '', iconOnly: true, - searchable: false, - searchType: 'select', nosort: true, - searchOptions: [], - searchLabel: 'Job Status', icon: 'icon-job-{{ host.active_failures }}', awToolTip: '{{ host.badgeToolTip }}', awTipPlacement: 'right', @@ -54,24 +50,9 @@ export default [ 'i18n', function(i18n){ columnClass: 'List-staticColumn--toggle', type: 'toggle', ngClick: 'toggleHostEnabled(host)', - searchable: false, nosort: true, awToolTip: "Indicates if a host is available and should be included in running jobs.
For hosts that are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process.
", dataTitle: 'Host Enabled', - }, - has_active_failures: { - label: 'Has failed jobs?', - searchSingleValue: true, - searchType: 'boolean', - searchValue: 'true', - searchOnly: true - }, - has_inventory_sources: { - label: 'Has external source?', - searchSingleValue: true, - searchType: 'boolean', - searchValue: 'true', - searchOnly: true } }, diff --git a/awx/ui/client/src/dashboard/hosts/dashboard-hosts.route.js b/awx/ui/client/src/dashboard/hosts/dashboard-hosts.route.js deleted file mode 100644 index 4af54c92bb..0000000000 --- a/awx/ui/client/src/dashboard/hosts/dashboard-hosts.route.js +++ /dev/null @@ -1,61 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import {templateUrl} from '../../shared/template-url/template-url.factory'; -import listController from './dashboard-hosts-list.controller'; -import editController from './dashboard-hosts-edit.controller'; - -var dashboardHostsList = { - name: 'dashboardHosts', - url: '/home/hosts?:active-failures', - controller: listController, - templateUrl: templateUrl('dashboard/hosts/dashboard-hosts-list'), - data: { - activityStream: true, - activityStreamTarget: 'host' - }, - ncyBreadcrumb: { - parent: 'dashboard', - label: "HOSTS" - }, - resolve: { - hosts: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams){ - var defaultUrl = GetBasePath('hosts') + '?page_size=10' + ($stateParams['active-failures'] ? '&has_active_failures=true' : '' ); - Rest.setUrl(defaultUrl); - return Rest.get().then(function(res){ - var results = _.map(res.data.results, function(value){ - value.inventory_name = value.summary_fields.inventory.name; - value.inventory_id = value.summary_fields.inventory.id; - return value; - }); - res.data.results = results; - return res.data; - }); - }] - } -}; - -var dashboardHostsEdit = { - name: 'dashboardHosts.edit', - url: '/:id', - controller: editController, - templateUrl: templateUrl('dashboard/hosts/dashboard-hosts-edit'), - ncyBreadcrumb: { - parent: 'dashboardHosts', - label: "{{host.name}}" - }, - resolve: { - host: ['$stateParams', 'Rest', 'GetBasePath', function($stateParams, Rest, GetBasePath){ - var defaultUrl = GetBasePath('hosts') + '?id=' + $stateParams.id; - Rest.setUrl(defaultUrl); - return Rest.get().then(function(res){ - return res.data.results[0]; - }); - }] - } -}; - -export {dashboardHostsList, dashboardHostsEdit}; diff --git a/awx/ui/client/src/dashboard/hosts/main.js b/awx/ui/client/src/dashboard/hosts/main.js index 7a02e597d0..935dec2d49 100644 --- a/awx/ui/client/src/dashboard/hosts/main.js +++ b/awx/ui/client/src/dashboard/hosts/main.js @@ -4,17 +4,43 @@ * All Rights Reserved *************************************************/ -import {dashboardHostsList, dashboardHostsEdit} from './dashboard-hosts.route'; import list from './dashboard-hosts.list'; import form from './dashboard-hosts.form'; +import listController from './dashboard-hosts-list.controller'; +import editController from './dashboard-hosts-edit.controller'; import service from './dashboard-hosts.service'; export default - angular.module('dashboardHosts', []) +angular.module('dashboardHosts', []) .service('DashboardHostService', service) .factory('DashboardHostsList', list) .factory('DashboardHostsForm', form) - .run(['$stateExtender', function($stateExtender){ - $stateExtender.addState(dashboardHostsList); - $stateExtender.addState(dashboardHostsEdit); - }]); + .config(['$stateProvider', 'stateDefinitionsProvider', + function($stateProvider, stateDefinitionsProvider) { + let stateDefinitions = stateDefinitionsProvider.$get(); + + $stateProvider.state({ + name: 'dashboardHosts', + url: '/home/hosts', + lazyLoad: () => stateDefinitions.generateTree({ + url: '/home/hosts', + parent: 'dashboardHosts', + modes: ['edit'], + list: 'DashboardHostsList', + form: 'DashboardHostsForm', + controllers: { + list: listController, + edit: editController + }, + data: { + activityStream: true, + activityStreamTarget: 'host' + }, + ncyBreadcrumb: { + parent: 'dashboard', + label: "HOSTS" + }, + }) + }); + } + ]); diff --git a/awx/ui/client/src/forms/Credentials.js b/awx/ui/client/src/forms/Credentials.js index eb4a61b923..8f82f75d55 100644 --- a/awx/ui/client/src/forms/Credentials.js +++ b/awx/ui/client/src/forms/Credentials.js @@ -18,6 +18,8 @@ export default addTitle: i18n._('Create Credential'), //Legend in add mode editTitle: '{{ name }}', //Legend in edit mode name: 'credential', + // the top-most node of generated state tree + stateTree: 'credentials', forceListeners: true, subFormTitles: { credentialSubForm: i18n._('Type Details'), @@ -31,24 +33,22 @@ export default name: { label: i18n._('Name'), type: 'text', - addRequired: true, - editRequired: true, + required: true, autocomplete: false, - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, description: { label: i18n._('Description'), type: 'text', - addRequired: false, - editRequired: false, - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, organization: { - addRequired: false, - editRequired: false, + // interpolated with $rootScope + basePath: "{{$rootScope.current_user.is_superuser ? 'api/v1/organizations' : $rootScope.current_user.url + 'admin_of_organizations'}}", ngShow: 'canShareCredential', label: i18n._('Organization'), type: 'lookup', + list: 'OrganizationList', sourceModel: 'organization', sourceField: 'name', ngClick: 'lookUpOrganization()', @@ -56,7 +56,7 @@ export default dataTitle: i18n._('Organization') + ' ', dataPlacement: 'bottom', dataContainer: "body", - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, kind: { label: i18n._('Type'), @@ -64,8 +64,7 @@ export default type: 'select', ngOptions: 'kind.label for kind in credential_kind_options track by kind.value', // select as label for value in array 'kind.label for kind in credential_kind_options', ngChange: 'kindChange()', - addRequired: true, - editRequired: true, + required: true, awPopOver: i18n._('\n' + '
- Machine
\n' + '- Authentication for remote machine access. This can include SSH keys, usernames, passwords, ' + @@ -88,7 +87,7 @@ export default dataPlacement: 'right', dataContainer: "body", hasSubForm: true, - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, access_key: { label: i18n._('Access Key'), @@ -101,7 +100,7 @@ export default autocomplete: false, apiField: 'username', subForm: 'credentialSubForm', - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, secret_key: { label: i18n._('Secret Key'), @@ -130,7 +129,7 @@ export default dataPlacement: 'right', dataContainer: "body", subForm: 'credentialSubForm', - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, "host": { labelBind: 'hostLabel', @@ -147,7 +146,7 @@ export default init: false }, subForm: 'credentialSubForm', - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, "subscription": { label: i18n._("Subscription ID"), @@ -157,15 +156,15 @@ export default reqExpression: 'subscription_required', init: false }, - addRequired: false, - editRequired: false, + + autocomplete: false, awPopOver: i18n._('
Subscription ID is an Azure construct, which is mapped to a username.
'), dataTitle: i18n._('Subscription ID'), dataPlacement: 'right', dataContainer: "body", subForm: 'credentialSubForm', - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, "username": { labelBind: 'usernameLabel', @@ -178,7 +177,7 @@ export default }, autocomplete: false, subForm: "credentialSubForm", - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, "email_address": { labelBind: 'usernameLabel', @@ -194,7 +193,7 @@ export default dataPlacement: 'right', dataContainer: "body", subForm: 'credentialSubForm', - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, "api_key": { label: i18n._('API Key'), @@ -208,7 +207,7 @@ export default hasShowInputButton: true, clear: false, subForm: 'credentialSubForm', - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, "password": { labelBind: 'passwordLabel', @@ -222,15 +221,13 @@ export default init: false }, subForm: "credentialSubForm", - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, "ssh_password": { label: i18n._('Password'), type: 'sensitive', ngShow: "kind.value == 'ssh'", ngDisabled: "ssh_password_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)", - addRequired: false, - editRequired: false, subCheckbox: { variable: 'ssh_password_ask', text: i18n._('Ask at runtime?'), @@ -251,8 +248,8 @@ export default }, class: 'Form-textAreaLabel Form-formGroup--fullWidth', elementClass: 'Form-monospace', - addRequired: false, - editRequired: false, + + awDropFile: true, rows: 10, awPopOver: i18n._("SSH key description"), @@ -261,14 +258,12 @@ export default dataPlacement: 'right', dataContainer: "body", subForm: "credentialSubForm", - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, "ssh_key_unlock": { label: i18n._('Private Key Passphrase'), type: 'sensitive', ngShow: "kind.value == 'ssh' || kind.value == 'scm'", - addRequired: false, - editRequired: false, ngDisabled: "keyEntered === false || ssh_key_unlock_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)", subCheckbox: { variable: 'ssh_key_unlock_ask', @@ -293,25 +288,23 @@ export default dataPlacement: 'right', dataContainer: "body", subForm: 'credentialSubForm', - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, "become_username": { labelBind: 'becomeUsernameLabel', type: 'text', ngShow: "(kind.value == 'ssh' && (become_method && become_method.value)) ", - addRequired: false, - editRequired: false, + + autocomplete: false, subForm: 'credentialSubForm', - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, "become_password": { labelBind: 'becomePasswordLabel', type: 'sensitive', ngShow: "(kind.value == 'ssh' && (become_method && become_method.value)) ", ngDisabled: "become_password_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)", - addRequired: false, - editRequired: false, subCheckbox: { variable: 'become_password_ask', text: i18n._('Ask at runtime?'), @@ -326,7 +319,7 @@ export default label: i18n._('Client ID'), subForm: 'credentialSubForm', ngShow: "kind.value === 'azure_rm'", - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, secret:{ type: 'sensitive', @@ -335,14 +328,14 @@ export default label: i18n._('Client Secret'), subForm: 'credentialSubForm', ngShow: "kind.value === 'azure_rm'", - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, tenant: { type: 'text', label: i18n._('Tenant ID'), subForm: 'credentialSubForm', ngShow: "kind.value === 'azure_rm'", - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, authorize: { label: i18n._('Authorize'), @@ -350,7 +343,7 @@ export default ngChange: "toggleCallback('host_config_key')", subForm: 'credentialSubForm', ngShow: "kind.value === 'net'", - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, authorize_password: { label: i18n._('Authorize Password'), @@ -359,7 +352,7 @@ export default autocomplete: false, subForm: 'credentialSubForm', ngShow: "authorize && authorize !== 'false'", - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, "project": { labelBind: 'projectLabel', @@ -370,14 +363,12 @@ export default dataTitle: i18n._('Project Name'), dataPlacement: 'right', dataContainer: "body", - addRequired: false, - editRequired: false, awRequiredWhen: { reqExpression: 'project_required', init: false }, subForm: 'credentialSubForm', - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, "domain": { labelBind: 'domainLabel', @@ -391,18 +382,14 @@ export default dataTitle: i18n._('Domain Name'), dataPlacement: 'right', dataContainer: "body", - addRequired: false, - editRequired: false, - subForm: 'credentialSubForm', - ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)', + subForm: 'credentialSubForm' }, "vault_password": { label: i18n._("Vault Password"), type: 'sensitive', ngShow: "kind.value == 'ssh'", ngDisabled: "vault_password_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)", - addRequired: false, - editRequired: false, subCheckbox: { variable: 'vault_password_ask', text: i18n._('Ask at runtime?'), @@ -417,17 +404,17 @@ export default buttons: { cancel: { ngClick: 'formCancel()', - ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngShow: '(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, close: { ngClick: 'formCancel()', - ngShow: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngShow: '!(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' }, save: { label: 'Save', ngClick: 'formSave()', //$scope.function to call on click, optional ngDisabled: true, - ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)' //Disable when $pristine or $invalid, optional + ngShow: '(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' //Disable when $pristine or $invalid, optional } }, @@ -437,24 +424,25 @@ export default awToolTip: '{{permissionsTooltip}}', dataTipWatch: 'permissionsTooltip', dataPlacement: 'top', - basePath: 'credentials/:id/access_list/', + basePath: 'api/v1/credentials/{{$stateParams.credential_id}}/access_list/', + search: { + order_by: 'username' + }, type: 'collection', title: i18n._('Permissions'), iterator: 'permission', index: false, open: false, - searchType: 'select', actions: { add: { - ngClick: "addPermission", + ngClick: "$state.go('.add')", label: 'Add', awToolTip: i18n._('Add a permission'), actionClass: 'btn List-buttonSubmit', buttonContent: i18n._('+ ADD'), - ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)' + ngShow: '(credential_obj.summary_fields.user_capabilities.edit || !canAdd)' } }, - fields: { username: { key: true, diff --git a/awx/ui/client/src/forms/Groups.js b/awx/ui/client/src/forms/Groups.js index 32723ff5f2..60ad2d39a1 100644 --- a/awx/ui/client/src/forms/Groups.js +++ b/awx/ui/client/src/forms/Groups.js @@ -18,31 +18,31 @@ export default editTitle: '{{ name }}', showTitle: true, name: 'group', + basePath: 'groups', + // the parent node this generated state definition tree expects to attach to + stateTree: 'inventoryManage', + // form generator inspects the current state name to determine whether or not to set an active (.is-selected) class on a form tab + // this setting is optional on most forms, except where the form's edit state name is not parentStateName.edit + activeEditState: 'inventoryManage.editGroup', well: false, - fields: { name: { label: 'Name', type: 'text', - addRequired: true, - editRequired: true, - tab: 'properties', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '(!group_obj.summary_fields.user_capabilities.edit || !canAdd)', + required: true, + tab: 'properties' }, description: { label: 'Description', type: 'text', - addRequired: false, - editRequired: false, - tab: 'properties', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '(!group_obj.summary_fields.user_capabilities.edit || !canAdd)', + tab: 'properties' }, variables: { label: 'Variables', type: 'textarea', class: 'Form-textAreaLabel Form-formGroup--fullWidth', - addRequired: false, - editRequird: false, rows: 12, 'default': '---', dataTitle: 'Group Variables', @@ -65,23 +65,23 @@ export default type: 'select', ngOptions: 'source.label for source in source_type_options track by source.value', ngChange: 'sourceChange(source)', - addRequired: false, - editRequired: false, - ngModel: 'source', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '(!group_obj.summary_fields.user_capabilities.edit || !canAdd)', + ngModel: 'source' }, credential: { label: 'Cloud Credential', type: 'lookup', + list: 'CredentialList', + basePath: 'credentials', ngShow: "source && source.value !== '' && source.value !== 'custom'", sourceModel: 'credential', sourceField: 'name', - ngClick: 'lookUpCredential()', + ngClick: 'lookupCredential()', awRequiredWhen: { reqExpression: "cloudCredentialRequired", init: "false" }, - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '(!group_obj.summary_fields.user_capabilities.edit || !canAdd)' }, source_regions: { label: 'Regions', @@ -89,22 +89,20 @@ export default ngOptions: 'source.label for source in source_region_choices track by source.value', multiSelect: true, ngShow: "source && (source.value == 'rax' || source.value == 'ec2' || source.value == 'gce' || source.value == 'azure' || source.value == 'azure_rm')", - addRequired: false, - editRequired: false, + + dataTitle: 'Source Regions', dataPlacement: 'right', awPopOver: "Click on the regions field to see a list of regions for your cloud provider. You can select multiple regions, " + "or choose All to include all regions. Tower will only be updated with Hosts associated with the selected regions." + "
", dataContainer: 'body', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)' }, instance_filters: { label: 'Instance Filters', type: 'text', ngShow: "source && source.value == 'ec2'", - addRequired: false, - editRequired: false, dataTitle: 'Instance Filters', dataPlacement: 'right', awPopOver: "Provide a comma-separated list of filter expressions. " + @@ -118,15 +116,13 @@ export default "
View the Describe Instances documentation " + "for a complete list of supported filters.
", dataContainer: 'body', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)' }, group_by: { label: 'Only Group By', type: 'select', ngShow: "source && source.value == 'ec2'", ngOptions: 'source.label for source in group_by_choices track by source.value', - addRequired: false, - editRequired: false, multiSelect: true, dataTitle: 'Only Group By', dataPlacement: 'right', @@ -144,19 +140,19 @@ export default "- Tag None: tags » tag_none
" + "
If blank, all groups above are created except Instance ID.
", dataContainer: 'body', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)' }, inventory_script: { label : "Custom Inventory Script", type: 'lookup', + basePath: 'inventory_scripts', + list: 'InventoryScriptList', ngShow: "source && source.value === 'custom'", sourceModel: 'inventory_script', sourceField: 'name', ngClick: 'lookUpInventory_script()' , - addRequired: true, - editRequired: true, ngRequired: "source && source.value === 'custom'", - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)', + ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)', }, custom_variables: { id: 'custom_variables', @@ -164,8 +160,6 @@ export default ngShow: "source && source.value=='custom' ", type: 'textarea', class: 'Form-textAreaLabel Form-formGroup--fullWidth', - addRequired: false, - editRequired: false, rows: 6, 'default': '---', parseTypeName: 'envParseType', @@ -187,8 +181,6 @@ export default ngShow: "source && source.value == 'ec2'", type: 'textarea', class: 'Form-textAreaLabel Form-formGroup--fullWidth', - addRequired: false, - editRequird: false, rows: 6, 'default': '---', parseTypeName: 'envParseType', @@ -209,12 +201,9 @@ export default vmware_variables: { id: 'vmware_variables', label: 'Source Variables', //"{{vars_label}}" , - ngShow: "source && source.value == 'vmware'", type: 'textarea', - addRequired: false, class: 'Form-textAreaLabel Form-formGroup--fullWidth', - editRequird: false, rows: 6, 'default': '---', parseTypeName: 'envParseType', @@ -235,12 +224,9 @@ export default openstack_variables: { id: 'openstack_variables', label: 'Source Variables', //"{{vars_label}}" , - ngShow: "source && source.value == 'openstack'", type: 'textarea', - addRequired: false, class: 'Form-textAreaLabel Form-formGroup--fullWidth', - editRequird: false, rows: 6, 'default': '---', parseTypeName: 'envParseType', @@ -263,14 +249,13 @@ export default type: 'checkbox_group', ngShow: "source && (source.value !== '' && source.value !== null)", class: 'Form-checkbox--stacked', - fields: [{ name: 'overwrite', label: 'Overwrite', type: 'checkbox', ngShow: "source.value !== '' && source.value !== null", - addRequired: false, - editRequired: false, + + awPopOver: 'If checked, all child groups and hosts not found on the external source will be deleted from ' + 'the local inventory.
When not checked, local child hosts and groups not found on the external source will ' + 'remain untouched by the inventory update process.
', @@ -278,14 +263,14 @@ export default dataContainer: 'body', dataPlacement: 'right', labelClass: 'checkbox-options', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)' }, { name: 'overwrite_vars', label: 'Overwrite Variables', type: 'checkbox', ngShow: "source.value !== '' && source.value !== null", - addRequired: false, - editRequired: false, + + awPopOver: 'If checked, all variables for child groups and hosts will be removed and replaced by those ' + 'found on the external source.
When not checked, a merge will be performed, combining local variables with ' + 'those found on the external source.
', @@ -293,21 +278,19 @@ export default dataContainer: 'body', dataPlacement: 'right', labelClass: 'checkbox-options', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)' }, { name: 'update_on_launch', label: 'Update on Launch', type: 'checkbox', ngShow: "source.value !== '' && source.value !== null", - addRequired: false, - editRequired: false, awPopOver: 'Each time a job runs using this inventory, refresh the inventory from the selected source before ' + 'executing job tasks.
', dataTitle: 'Update on Launch', dataContainer: 'body', dataPlacement: 'right', labelClass: 'checkbox-options', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)' }] }, update_cache_timeout: { @@ -319,8 +302,6 @@ export default ngShow: "source && source.value !== '' && update_on_launch", spinner: true, "default": 0, - addRequired: false, - editRequired: false, awPopOver: 'Time in seconds to consider an inventory sync to be current. During job runs and callbacks the task system will ' + 'evaluate the timestamp of the latest sync. If it is older than Cache Timeout, it is not considered current, ' + 'and a new inventory sync will be performed.
', @@ -333,16 +314,16 @@ export default buttons: { cancel: { ngClick: 'formCancel()', - ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngShow: '(group_obj.summary_fields.user_capabilities.edit || !canAdd)' }, close: { ngClick: 'formCancel()', - ngShow: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngShow: '!(group_obj.summary_fields.user_capabilities.edit || !canAdd)' }, save: { ngClick: 'formSave()', ngDisabled: true, - ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngShow: '(group_obj.summary_fields.user_capabilities.edit || !canAdd)' } }, diff --git a/awx/ui/client/src/forms/HostGroups.js b/awx/ui/client/src/forms/HostGroups.js index cfa8aea02a..2c920e79f5 100644 --- a/awx/ui/client/src/forms/HostGroups.js +++ b/awx/ui/client/src/forms/HostGroups.js @@ -26,8 +26,7 @@ export default type: 'select', multiple: true, ngOptions: 'group.name for group in inventory_groups track by group.value', - addRequired: true, - editRequired: true, + required: true, awPopOver: "Provide a host name, ip address, or ip address:port. Examples include:
" + "myserver.domain.com
" + "127.0.0.1
" + diff --git a/awx/ui/client/src/forms/Hosts.js b/awx/ui/client/src/forms/Hosts.js index a1c0e69537..eae5d5c8dc 100644 --- a/awx/ui/client/src/forms/Hosts.js +++ b/awx/ui/client/src/forms/Hosts.js @@ -17,6 +17,7 @@ export default addTitle: 'Create Host', editTitle: '{{ host.name }}', name: 'host', + basePath: 'hosts', well: false, formLabelSize: 'col-lg-3', formFieldSize: 'col-lg-9', @@ -26,7 +27,6 @@ export default class: 'Form-header-field', ngClick: 'toggleHostEnabled(host)', type: 'toggle', - editRequired: false, awToolTip: "Indicates if a host is available and should be included in running jobs.
For hosts that " + "are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process.
", dataTitle: 'Host Enabled', @@ -36,8 +36,7 @@ export default name: { label: 'Host Name', type: 'text', - addRequired: true, - editRequired: true, + required: true, awPopOver: "Provide a host name, ip address, or ip address:port. Examples include:
" + "myserver.domain.com
" + "127.0.0.1
" + @@ -47,22 +46,18 @@ export default dataTitle: 'Host Name', dataPlacement: 'right', dataContainer: 'body', - ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(host.summary_fields.user_capabilities.edit || !canAdd)' }, description: { label: 'Description', - type: 'text', - addRequired: false, - editRequired: false, - ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(host.summary_fields.user_capabilities.edit || !canAdd)', + type: 'text' }, variables: { label: 'Variables', type: 'textarea', - addRequired: false, - editRequird: false, rows: 6, - "class": "modal-input-xlarge Form-textArea Form-formGroup--fullWidth", + class: 'Form-formGroup--fullWidth', "default": "---", awPopOver: "Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.
" + "JSON:
\n" + @@ -85,19 +80,16 @@ export default buttons: { cancel: { ngClick: 'formCancel()', - ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)' + ngShow: '(host.summary_fields.user_capabilities.edit || !canAdd)' }, close: { ngClick: 'formCancel()', - ngShow: '!(host.summary_fields.user_capabilities.edit || canAdd)' + ngShow: '!(host.summary_fields.user_capabilities.edit || !canAdd)' }, save: { ngClick: 'formSave()', ngDisabled: true, - ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)' + ngShow: '(host.summary_fields.user_capabilities.edit || !canAdd)' } }, - - related: {} - }); diff --git a/awx/ui/client/src/forms/Inventories.js b/awx/ui/client/src/forms/Inventories.js index 0207124476..323ddd1364 100644 --- a/awx/ui/client/src/forms/Inventories.js +++ b/awx/ui/client/src/forms/Inventories.js @@ -4,91 +4,136 @@ * All Rights Reserved *************************************************/ - /** +/** * @ngdoc function * @name forms.function:Inventories * @description This form is for adding/editing an inventory -*/ + */ export default - angular.module('InventoryFormDefinition', ['ScanJobsListDefinition']) - .factory('InventoryFormObject', ['i18n', function(i18n) { +angular.module('InventoryFormDefinition', ['ScanJobsListDefinition']) + .factory('InventoryFormObject', ['i18n', function(i18n) { return { - addTitle: i18n._('New Inventory'), - editTitle: '{{ inventory_name }}', - name: 'inventory', - tabs: true, + addTitle: 'New Inventory', + editTitle: '{{ inventory_name }}', + name: 'inventory', + basePath: 'inventory', + // the top-most node of this generated state tree + stateTree: 'inventories', + tabs: true, - fields: { - inventory_name: { - realName: 'name', - label: i18n._('Name'), - type: 'text', - addRequired: true, - editRequired: true, - capitalize: false, - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' + fields: { + inventory_name: { + realName: 'name', + label: i18n._('Name'), + type: 'text', + required: true, + capitalize: false, + ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)' + }, + inventory_description: { + realName: 'description', + label: i18n._('Description'), + type: 'text', + ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)' + }, + organization: { + label: i18n._('Organization'), + type: 'lookup', + basePath: 'organizations', + list: 'OrganizationList', + sourceModel: 'organization', + sourceField: 'name', + awRequiredWhen: { + reqExpression: "organizationrequired", + init: "true" }, - inventory_description: { - realName: 'description', - label: i18n._('Description'), - type: 'text', - addRequired: false, - editRequired: false, - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)' + }, + variables: { + label: i18n._('Variables'), + type: 'textarea', + class: 'Form-formGroup--fullWidth', + rows: 6, + "default": "---", + awPopOver: "Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.
" + + "JSON:
\n" + + "{\n" + + "YAML:
\"somevar\": \"somevalue\",
\"password\": \"magic\"
}
\n" + + "---\n" + + '
somevar: somevalue
password: magicView JSON examples at www.json.org
' + + 'View YAML examples at docs.ansible.com
', + dataTitle: 'Inventory Variables', + dataPlacement: 'right', + dataContainer: 'body', + ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)' // TODO: get working + } + }, + + buttons: { + cancel: { + ngClick: 'formCancel()', + ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)' + }, + close: { + ngClick: 'formCancel()', + ngHide: '(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)' + }, + save: { + ngClick: 'formSave()', + ngDisabled: true, + ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)' + } + }, + related: { + permissions: { + awToolTip: i18n._('Please save before assigning permissions'), + dataPlacement: 'top', + basePath: 'api/v1/inventories/{{$stateParams.inventory_id}}/access_list/', + type: 'collection', + title: 'Permissions', + iterator: 'permission', + index: false, + open: false, + search: { + order_by: 'username' }, - organization: { - label: i18n._('Organization'), - type: 'lookup', - sourceModel: 'organization', - sourceField: 'name', - ngClick: 'lookUpOrganization()', - awRequiredWhen: { - reqExpression: "organizationrequired", - init: "true" + actions: { + add: { + label: i18n._('Add'), + ngClick: "$state.go('.add')", + awToolTip: 'Add a permission', + actionClass: 'btn List-buttonSubmit', + buttonContent: '+ ADD', + ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || !canAdd)' + + } + }, + fields: { + username: { + label: i18n._('User'), + linkBase: 'users', + class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4' }, - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - variables: { - label: i18n._('Variables'), - type: 'textarea', - class: 'Form-formGroup--fullWidth', - addRequired: false, - editRequird: false, - rows: 6, - "default": "---", - awPopOver: i18n._("Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.
" + - "JSON:
\n" + - "{\n" + - "YAML:
\"somevar\": \"somevalue\",
\"password\": \"magic\"
}
\n" + - "---\n" + - '
somevar: somevalue
password: magicView JSON examples at www.json.org
' + - 'View YAML examples at docs.ansible.com
'), - dataTitle: i18n._('Inventory Variables'), - dataPlacement: 'right', - dataContainer: 'body', - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working + role: { + label: i18n._('Role'), + type: 'role', + noSort: true, + class: 'col-lg-4 col-md-4 col-sm-4 col-xs-4', + }, + team_roles: { + label: i18n._('Team Roles'), + type: 'team_roles', + noSort: true, + class: 'col-lg-5 col-md-5 col-sm-5 col-xs-4', + } } - }, + } + }, - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - close: { - ngClick: 'formCancel()', - ngHide: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - - related: { + relatedSets: function(urls) { + return { permissions: { awToolTip: i18n._('Please save before assigning permissions'), dataPlacement: 'top', @@ -102,7 +147,7 @@ export default actions: { add: { ngClick: "addPermission", - label: 'Add', + label: i18n._('Add'), awToolTip: i18n._('Add a permission'), actionClass: 'btn List-buttonSubmit', buttonContent: i18n._('+ ADD'), @@ -133,28 +178,22 @@ export default } } } - }, - - relatedSets: function(urls) { - return { - permissions: { - iterator: 'permission', - url: urls.access_list - } - }; - } + }; + } };}]) - .factory('InventoryForm', ['InventoryFormObject', 'ScanJobsList', + + .factory('InventoryForm', ['InventoryFormObject', 'ScanJobsList', function(InventoryFormObject, ScanJobsList) { return function() { var itm; for (itm in InventoryFormObject.related) { if (InventoryFormObject.related[itm].include === "ScanJobsList") { - InventoryFormObject.related[itm] = ScanJobsList; - InventoryFormObject.related[itm].generateList = true; // tell form generator to call list generator and inject a list + InventoryFormObject.related[itm] = ScanJobsList; + InventoryFormObject.related[itm].generateList = true; // tell form generator to call list generator and inject a list } } return InventoryFormObject; }; - }]); + } + ]); diff --git a/awx/ui/client/src/forms/JobTemplates.js b/awx/ui/client/src/forms/JobTemplates.js index 10d5119407..bc61cf1c7d 100644 --- a/awx/ui/client/src/forms/JobTemplates.js +++ b/awx/ui/client/src/forms/JobTemplates.js @@ -19,26 +19,27 @@ export default addTitle: i18n._('New Job Template'), editTitle: '{{ name }}', - name: 'job_templates', - base: 'job_templates', + name: 'job_template', + basePath: 'job_templates', + // the top-most node of generated state tree + stateTree: 'jobTemplates', tabs: true, + // (optional) array of supporting templates to ng-include inside generated html + include: ['/static/partials/survey-maker-modal.html'], fields: { name: { label: i18n._('Name'), type: 'text', - addRequired: true, - editRequired: true, - column: 1, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)', + required: true, + column: 1 }, description: { label: i18n._('Description'), type: 'text', - addRequired: false, - editRequired: false, column: 1, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, job_type: { label: i18n._('Job Type'), @@ -46,8 +47,7 @@ export default ngOptions: 'type.label for type in job_type_options track by type.value', ngChange: 'jobTypeChange()', "default": 0, - addRequired: true, - editRequired: true, + required: true, column: 1, awPopOver: i18n._("When this template is submitted as a job, setting the type to run will execute the playbook, running tasks " + " on the selected hosts.
Setting the type to check will not execute the playbook. Instead,
ansiblewill check playbook " + @@ -61,14 +61,15 @@ export default ngShow: "!job_type.value || job_type.value !== 'scan'", text: i18n._('Prompt on launch') }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, inventory: { label: i18n._('Inventory'), type: 'lookup', + basePath: 'inventory', + list: 'InventoryList', sourceModel: 'inventory', sourceField: 'name', - ngClick: 'lookUpInventory()', awRequiredWhen: { reqExpression: '!ask_inventory_on_launch', alwaysShowAsterisk: true @@ -84,7 +85,7 @@ export default ngShow: "!job_type.value || job_type.value !== 'scan'", text: i18n._('Prompt on launch') }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, project: { label: i18n._('Project'), @@ -94,9 +95,10 @@ export default 'class': "{{!(job_type.value === 'scan' && project_name !== 'Default') ? 'hidden' : ''}}", }, type: 'lookup', + list: 'ProjectList', + basePath: 'projects', sourceModel: 'project', sourceField: 'name', - ngClick: 'lookUpProject()', awRequiredWhen: { reqExpression: "projectrequired", init: "true" @@ -106,7 +108,7 @@ export default dataTitle: i18n._('Project'), dataPlacement: 'right', dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, playbook: { label: i18n._('Playbook'), @@ -128,9 +130,13 @@ export default credential: { label: i18n._('Machine Credential'), type: 'lookup', + list: 'CredentialList', + basePath: 'credentials', + search: { + kind: 'ssh' + }, sourceModel: 'credential', sourceField: 'name', - ngClick: 'lookUpCredential()', awRequiredWhen: { reqExpression: '!ask_credential_on_launch', alwaysShowAsterisk: true @@ -146,38 +152,42 @@ export default variable: 'ask_credential_on_launch', text: i18n._('Prompt on launch') }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, cloud_credential: { label: i18n._('Cloud Credential'), type: 'lookup', + list: 'CredentialList', + basePath: 'credentials', + search: { + cloud: 'true' + }, sourceModel: 'cloud_credential', sourceField: 'name', - ngClick: 'lookUpCloudcredential()', - addRequired: false, - editRequired: false, column: 1, awPopOver: i18n._("Selecting an optional cloud credential in the job template will pass along the access credentials to the " + "running playbook, allowing provisioning into the cloud without manually passing parameters to the included modules.
"), dataTitle: i18n._('Cloud Credential'), dataPlacement: 'right', dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, network_credential: { label: i18n._('Network Credential'), type: 'lookup', + list: 'CredentialList', + basePath: 'credentials', + search: { + kind: 'net' + }, sourceModel: 'network_credential', sourceField: 'name', - ngClick: 'lookUpNetworkcredential()', - addRequired: false, - editRequired: false, column: 1, awPopOver: i18n._("Network credentials are used by Ansible networking modules to connect to and manage networking devices.
"), dataTitle: i18n._('Network Credential'), dataPlacement: 'right', dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, forks: { label: i18n._('Forks'), @@ -187,8 +197,6 @@ export default min: 0, spinner: true, "default": '0', - addRequired: false, - editRequired: false, 'class': "input-small", column: 1, awPopOver: i18n._('The number of parallel or simultaneous processes to use while executing the playbook. 0 signifies ' + @@ -197,13 +205,11 @@ export default dataTitle: i18n._('Forks'), dataPlacement: 'right', dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' // TODO: get working }, limit: { label: i18n._('Limit'), type: 'text', - addRequired: false, - editRequired: false, column: 1, awPopOver: i18n._("
Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. " + "Multiple patterns can be separated by ; : or ,
For more information and examples see " + @@ -215,28 +221,25 @@ export default variable: 'ask_limit_on_launch', text: i18n._('Prompt on launch') }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, verbosity: { label: i18n._('Verbosity'), type: 'select', ngOptions: 'v.label for v in verbosity_options track by v.value', "default": 1, - addRequired: true, - editRequired: true, + required: true, column: 1, awPopOver: i18n._("
Control the level of output ansible will produce as the playbook executes.
"), dataTitle: i18n._('Verbosity'), dataPlacement: 'right', dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, job_tags: { label: i18n._('Job Tags'), type: 'textarea', rows: 5, - addRequired: false, - editRequired: false, 'elementClass': 'Form-textInput', column: 2, awPopOver: i18n._("Provide a comma separated list of tags.
\n" + @@ -249,14 +252,12 @@ export default variable: 'ask_tags_on_launch', text: i18n._('Prompt on launch') }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, skip_tags: { label: i18n._('Skip Tags'), type: 'textarea', rows: 5, - addRequired: false, - editRequired: false, 'elementClass': 'Form-textInput', column: 2, awPopOver: i18n._("Provide a comma separated list of tags.
\n" + @@ -269,7 +270,7 @@ export default variable: 'ask_skip_tags_on_launch', text: i18n._('Prompt on launch') }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, checkbox_group: { label: i18n._('Options'), @@ -278,21 +279,17 @@ export default name: 'become_enabled', label: i18n._('Enable Privilege Escalation'), type: 'checkbox', - addRequired: false, - editRequird: false, column: 2, awPopOver: i18n._("If enabled, run this playbook as an administrator. This is the equivalent of passing the
"), dataPlacement: 'right', dataTitle: i18n._('Become Privilege Escalation'), dataContainer: "body", labelClass: 'stack-inline', - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, { name: 'allow_callbacks', label: i18n._('Allow Provisioning Callbacks'), type: 'checkbox', - addRequired: false, - editRequird: false, ngChange: "toggleCallback('host_config_key')", column: 2, awPopOver: i18n._("--becomeoption to theansible-playbookcommand.Enables creation of a provisioning callback URL. Using the URL a host can contact Tower and request a configuration update " + @@ -301,14 +298,12 @@ export default dataTitle: i18n._('Allow Provisioning Callbacks'), dataContainer: "body", labelClass: 'stack-inline', - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }] }, callback_url: { label: i18n._('Provisioning Callback URL'), type: 'text', - addRequired: false, - editRequired: false, readonly: true, ngShow: "allow_callbacks && allow_callbacks !== 'false'", column: 2, @@ -317,7 +312,7 @@ export default dataPlacement: 'top', dataTitle: i18n._('Provisioning Callback URL'), dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, host_config_key: { label: i18n._('Host Config Key'), @@ -331,7 +326,7 @@ export default dataPlacement: 'right', dataTitle: i18n._("Host Config Key"), dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, labels: { label: i18n._('Labels'), @@ -339,21 +334,17 @@ export default class: 'Form-formGroup--fullWidth', ngOptions: 'label.label for label in labelOptions track by label.value', multiSelect: true, - addRequired: false, - editRequired: false, dataTitle: i18n._('Labels'), dataPlacement: 'right', awPopOver: i18n._("
Optional labels that describe this job template, such as 'dev' or 'test'. Labels can be used to group and filter job templates and completed jobs in the Tower display.
"), dataContainer: 'body', - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' }, variables: { label: i18n._('Extra Variables'), type: 'textarea', class: 'Form-textAreaLabel Form-formGroup--fullWidth', rows: 6, - addRequired: false, - editRequired: false, "default": "---", column: 2, awPopOver: i18n._("Pass extra command line variables to the playbook. This is the
-eor--extra-varscommand line parameter " + @@ -369,14 +360,14 @@ export default variable: 'ask_variables_on_launch', text: i18n._('Prompt on launch') }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working + ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || !canAdd)' // TODO: get working } }, buttons: { //for now always generates
| Status | "; + html += "Time | "; + html += "
|---|---|
| `; + html += " | " + ($filter('longDate')(row.created)).replace(/ /, ' ') + " | \n";
+ html += "
No recent notifications.
\n"; + } + notification_template.template_status_html = html; + } - function setStatus(notification_template) { - var html, recent_notifications = notification_template.summary_fields.recent_notifications; - if (recent_notifications.length > 0) { - html = "| Status | "; - html += "Time | "; - html += "
|---|---|
| `; - html += " | " + ($filter('longDate')(row.created)).replace(/ /,' ') + " | \n";
- html += "
No recent notifications.
\n"; - } - notification_template.template_status_html = html; - } - - scope.testNotification = function(){ - var name = $filter('sanitize')(this.notification_template.name), - pending_retries = 10; - - Rest.setUrl(defaultUrl + this.notification_template.id +'/test/'); - Rest.post({}) - .then(function (data) { - if(data && data.data && data.data.notification){ - Wait('start'); - // Using a setTimeout here to wait for the - // notification to be processed and for a status - // to be returned from the API. - retrieveStatus(data.data.notification); - } - else { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to notifcatin templates failed. Notification returned status: ' + status }); - } - }) - .catch(function () { - ngToast.danger({ - content: ` ${name}: Notification Failed.`, + Rest.setUrl(defaultUrl + this.notification_template.id + '/test/'); + Rest.post({}) + .then(function(data) { + if (data && data.data && data.data.notification) { + Wait('start'); + // Using a setTimeout here to wait for the + // notification to be processed and for a status + // to be returned from the API. + retrieveStatus(data.data.notification); + } else { + ProcessErrors($scope, data, status, null, { + hdr: 'Error!', + msg: 'Call to notifcatin templates failed. Notification returned status: ' + status + }); + } + }) + .catch(function() { + ngToast.danger({ + content: ` ${name}: Notification Failed.`, }); - }); + }); - function retrieveStatus(id){ - setTimeout(function(){ - var url = GetBasePath('notifications') + id; - Rest.setUrl(url); - Rest.get() - .then(function (res) { - if(res && res.data && res.data.status && res.data.status === "successful"){ - scope.search(list.iterator); - ngToast.success({ - content: ` ${name}: Notification sent.` - }); - Wait('stop'); - } - else if(res && res.data && res.data.status && res.data.status === "failed"){ - scope.search(list.iterator); - ngToast.danger({ - content: ` ${name}: Notification failed.` - }); - Wait('stop'); - } - else if(res && res.data && res.data.status && res.data.status === "pending" && pending_retries>0){ - pending_retries--; - retrieveStatus(id); - } - else { - Wait('stop'); - ProcessErrors(scope, null, status, null, { hdr: 'Error!', - msg: 'Call to test notifications failed.' }); - } + function retrieveStatus(id) { + setTimeout(function() { + var url = GetBasePath('notifications') + id; + Rest.setUrl(url); + Rest.get() + .then(function(res) { + if (res && res.data && res.data.status && res.data.status === "successful") { + ngToast.success({ + content: ` ${name}: Notification sent.` + }); + $state.reload(); + } else if (res && res.data && res.data.status && res.data.status === "failed") { + ngToast.danger({ + content: ` ${name}: Notification failed.` + }); + $state.reload(); + } else if (res && res.data && res.data.status && res.data.status === "pending" && pending_retries > 0) { + pending_retries--; + retrieveStatus(id); + } else { + Wait('stop'); + ProcessErrors($scope, null, status, null, { + hdr: 'Error!', + msg: 'Call to test notifications failed.' + }); + } - }); - } , 5000); - } - }; + }); + }, 5000); + } + }; - scope.addNotification = function(){ - $state.transitionTo('notifications.add'); - }; + $scope.addNotification = function() { + $state.go('notifications.add'); + }; - scope.editNotification = function(){ - $state.transitionTo('notifications.edit',{ - notification_template_id: this.notification_template.id, - notification_template: this.notification_templates - }); - }; + $scope.editNotification = function() { + $state.go('notifications.edit', { + notification_template_id: this.notification_template.id, + notification_template: this.notification_templates + }); + }; - scope.deleteNotification = function(id, name){ - var action = function () { - $('#prompt-modal').modal('hide'); - Wait('start'); - var url = defaultUrl + id + '/'; - Rest.setUrl(url); - Rest.destroy() - .success(function () { - if (parseInt($state.params.notification_template_id) === id) { - $state.go("^", null, {reload: true}); - } else { - scope.search(list.iterator); - } - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); - }); - }; - var bodyHtml = '