diff --git a/awx/ui/client/src/configuration/configuration.controller.js b/awx/ui/client/src/configuration/configuration.controller.js index 16f40901db..76e00ce8b7 100644 --- a/awx/ui/client/src/configuration/configuration.controller.js +++ b/awx/ui/client/src/configuration/configuration.controller.js @@ -75,9 +75,6 @@ export default [ if(key === "AD_HOC_COMMANDS"){ $scope[key] = data[key].toString(); } - else if(key === "AUTH_LDAP_USER_SEARCH" || key === "AUTH_LDAP_GROUP_SEARCH"){ - $scope[key] = JSON.stringify(data[key]); - } else { $scope[key] = ConfigurationUtils.arrayToList(data[key], key); } @@ -356,38 +353,27 @@ export default [ clearApiErrors(); _.each(keys, function(key) { if($scope.configDataResolve[key].type === 'choice' || multiselectDropdowns.indexOf(key) !== -1) { - - // Handle AD_HOC_COMMANDS - if(multiselectDropdowns.indexOf(key) !== -1) { - let newModules = $("#configuration_jobs_template_AD_HOC_COMMANDS > option") - .filter("[data-select2-tag=true]") - .map((i, val) => ({value: $(val).text()})); - newModules.each(function(i, val) { - $scope[key].push(val); - }); - - payload[key] = ConfigurationUtils.listToArray(_.map($scope[key], 'value').join(',')); - } - //Parse dropdowns and dropdowns labeled as lists - else if($scope[key] === null) { + if($scope[key] === null) { payload[key] = null; } else if($scope[key][0] && $scope[key][0].value !== undefined) { - payload[key] = _.map($scope[key], 'value').join(','); + if(multiselectDropdowns.indexOf(key) !== -1) { + // Handle AD_HOC_COMMANDS + payload[key] = ConfigurationUtils.listToArray(_.map($scope[key], 'value').join(',')); + } else { + payload[key] = _.map($scope[key], 'value').join(','); + } } else { - payload[key] = $scope[key].value; + if(multiselectDropdowns.indexOf(key) !== -1) { + // Default AD_HOC_COMMANDS to an empty list + payload[key] = $scope[key].value || []; + } else { + payload[key] = $scope[key].value; + } } } else if($scope.configDataResolve[key].type === 'list' && $scope[key] !== null) { - - if(key === "AUTH_LDAP_USER_SEARCH" || key === "AUTH_LDAP_GROUP_SEARCH"){ - payload[key] = $scope[key] === "{}" ? [] : ToJSON($scope.parseType, - $scope[key]); - } - else { - // Parse lists - payload[key] = ConfigurationUtils.listToArray($scope[key], key); - } - + // Parse lists + payload[key] = ConfigurationUtils.listToArray($scope[key], key); } else if($scope.configDataResolve[key].type === 'nested object') { if($scope[key] === '') { diff --git a/awx/ui/client/src/inventories/groups/groups.form.js b/awx/ui/client/src/inventories/groups/groups.form.js index c8655fa4e6..308d3555d8 100644 --- a/awx/ui/client/src/inventories/groups/groups.form.js +++ b/awx/ui/client/src/inventories/groups/groups.form.js @@ -10,8 +10,9 @@ * @description This form is for adding/editing a Group on the inventory page */ -export default ['i18n', 'nestedGroupListState', -function(i18n, nestedGroupListState){ +export default ['i18n', 'nestedGroupListState', 'nestedHostsListState', + 'buildHostAddState', +function(i18n, nestedGroupListState, nestedHostsListState, buildHostAddState){ return { addTitle: 'CREATE GROUP', editTitle: '{{ name }}', @@ -91,6 +92,16 @@ function(i18n, nestedGroupListState){ // addState: buildGroupsAddState, // editState: buildGroupsEditState }, + nested_hosts: { + name: 'nested_hosts', + ngClick: "$state.go('inventories.edit.groups.edit.nested_hosts')", + include: "NestedHostsListDefinition", + title: i18n._('Hosts'), + iterator: 'nested_hosts', + listState: nestedHostsListState, + addState: buildHostAddState, + // editState: buildGroupsEditState + }, } }; diff --git a/awx/ui/client/src/inventories/groups/main.js b/awx/ui/client/src/inventories/groups/main.js index b189cae4a2..d5a40f97ff 100644 --- a/awx/ui/client/src/inventories/groups/main.js +++ b/awx/ui/client/src/inventories/groups/main.js @@ -8,6 +8,7 @@ import groupList from './list/main'; import groupAdd from './add/main'; import groupEdit from './edit/main'; import nestedGroups from './nested-groups/main'; +import nestedHosts from './nested-hosts/main'; import groupFormDefinition from './groups.form'; import groupListDefinition from './groups.list'; import service from './groups.service'; @@ -22,7 +23,8 @@ export default groupList.name, groupAdd.name, groupEdit.name, - nestedGroups.name + nestedGroups.name, + nestedHosts.name ]) .factory('GroupForm', groupFormDefinition) .value('GroupList', groupListDefinition) diff --git a/awx/ui/client/src/inventories/groups/nested-hosts/main.js b/awx/ui/client/src/inventories/groups/nested-hosts/main.js new file mode 100644 index 0000000000..0d1ef630e9 --- /dev/null +++ b/awx/ui/client/src/inventories/groups/nested-hosts/main.js @@ -0,0 +1,17 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import nestedHostsListState from './nested-hosts-list-state.factory'; +import nestedHostsListDefinition from './nested-hosts.list'; +import nestedHostsFormDefinition from './nested-hosts.form'; +import controller from './nested-hosts-list.controller'; + +export default + angular.module('nestedHosts', []) + .factory('nestedHostsListState', nestedHostsListState) + .value('NestedHostsListDefinition', nestedHostsListDefinition) + .factory('NestedHostsFormDefinition', nestedHostsFormDefinition) + .controller('NestedHostsListController', controller); diff --git a/awx/ui/client/src/inventories/groups/nested-hosts/nested-hosts-list-state.factory.js b/awx/ui/client/src/inventories/groups/nested-hosts/nested-hosts-list-state.factory.js new file mode 100644 index 0000000000..96b7402292 --- /dev/null +++ b/awx/ui/client/src/inventories/groups/nested-hosts/nested-hosts-list-state.factory.js @@ -0,0 +1,79 @@ +/************************************************* +* Copyright (c) 2017 Ansible, Inc. +* +* All Rights Reserved +*************************************************/ +import NestedHostsListController from './nested-hosts-list.controller'; +export default ['NestedHostsListDefinition', '$stateExtender', 'templateUrl', '$injector', + function(NestedHostsListDefinition, $stateExtender, templateUrl, $injector){ + var val = function(field, formStateDefinition) { + let state, + list = field.include ? $injector.get(field.include) : field, + breadcrumbLabel = (field.iterator.replace('_', ' ') + 's').toUpperCase(), + stateConfig = { + searchPrefix: `${list.iterator}`, + squash: '', + name: `${formStateDefinition.name}.nested_hosts`, + url: `/${list.iterator}s`, + ncyBreadcrumb: { + parent: `${formStateDefinition.name}`, + label: `${breadcrumbLabel}` + }, + params: { + [list.iterator + '_search']: { + value: { order_by: field.order_by ? field.order_by : 'name' } + }, + }, + views: { + // 'related@inventories.edit.groups.edit': { + 'related': { + templateProvider: function(NestedHostsListDefinition, generateList) { + let list = _.cloneDeep(NestedHostsListDefinition); + + let html = generateList.build({ + list: list, + mode: 'edit' + }); + // Include the custom group delete modal template + // return $templateRequest(templateUrl('inventories/groups/list/groups-list')).then((template) => { + // return html.concat(template); + // }); + return html; + }, + controller: NestedHostsListController + } + }, + resolve: { + ListDefinition: () => { + return list; + }, + Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', + (list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => { + // allow related list definitions to use interpolated $rootScope / $stateParams in basePath field + let path, interpolator; + if (GetBasePath(list.basePath)) { + path = GetBasePath(list.basePath); + } else { + interpolator = $interpolate(list.basePath); + path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams }); + } + path = `api/v2/groups/${$stateParams.group_id}/all_hosts`; + return qs.search(path, $stateParams[`${list.iterator}_search`]); + } + ], + inventoryData: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) { + return InventoryManageService.getInventory($stateParams.inventory_id).then(res => res.data); + }] + } + }; + + state = $stateExtender.buildDefinition(stateConfig); + // appy any default search parameters in form definition + if (field.search) { + state.params[`${field.iterator}_search`].value = _.merge(state.params[`${field.iterator}_search`].value, field.search); + } + return state; + }; + return val; + } +]; diff --git a/awx/ui/client/src/inventories/groups/nested-hosts/nested-hosts.list.js b/awx/ui/client/src/inventories/groups/nested-hosts/nested-hosts.list.js new file mode 100644 index 0000000000..7234327573 --- /dev/null +++ b/awx/ui/client/src/inventories/groups/nested-hosts/nested-hosts.list.js @@ -0,0 +1,131 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default { + name: 'nested_hosts', + iterator: 'nested_host', + editTitle: '{{ nested_host.name }}', // i don't think this is correct + // showTitle: false, + well: true, + wellOverride: true, + index: false, + hover: true, + // hasChildren: true, + multiSelect: true, + trackBy: 'nested_host.id', + basePath: 'api/v2/groups/{{$stateParams.group_id}}/all_hosts/', + + fields: { + active_failures: { + label: '', + iconOnly: true, + nosort: true, + // do not remove this ng-click directive + // the list generator case to handle fields without ng-click + // cannot handle the aw-* directives + ngClick: 'noop()', + awPopOver: "{{ nested_host.job_status_html }}", + dataTitle: "{{ nested_host.job_status_title }}", + awToolTip: "{{ nested_host.badgeToolTip }}", + dataPlacement: 'top', + icon: "{{ 'fa icon-job-' + nested_host.active_failures }}", + id: 'active-failures-action', + columnClass: 'status-column List-staticColumn--smallStatus' + }, + name: { + key: true, + label: 'Hosts', + ngClick: "editHost(nested_host.id)", + ngClass: "{ 'host-disabled-label': !nested_host.enabled }", + columnClass: 'col-lg-6 col-md-8 col-sm-8 col-xs-7', + dataHostId: "{{ nested_host.id }}", + dataType: "nested_host", + class: 'InventoryManage-breakWord' + } + }, + + fieldActions: { + + columnClass: 'col-lg-6 col-md-4 col-sm-4 col-xs-5 text-right', + copy: { + mode: 'all', + ngClick: "copyMoveHost(nested_host.id)", + awToolTip: 'Copy or move host to another group', + dataPlacement: "top", + ngShow: 'nested_host.summary_fields.user_capabilities.edit' + }, + edit: { + //label: 'Edit', + ngClick: "editHost(nested_host.id)", + icon: 'icon-edit', + awToolTip: 'Edit host', + dataPlacement: 'top', + ngShow: 'nested_host.summary_fields.user_capabilities.edit' + }, + view: { + //label: 'Edit', + ngClick: "editHost(nested_host.id)", + awToolTip: 'View host', + dataPlacement: 'top', + ngShow: '!nested_host.summary_fields.user_capabilities.edit' + }, + "delete": { + //label: 'Delete', + ngClick: "deleteHost(nested_host.id, nested_host.name)", + icon: 'icon-trash', + awToolTip: 'Delete host', + dataPlacement: 'top', + ngShow: 'nested_host.summary_fields.user_capabilities.delete' + } + }, + + actions: { + launch: { + mode: 'all', + ngDisabled: '!hostsSelected', + ngClick: 'setAdhocPattern()', + awToolTip: "Select an inventory source by clicking the check box beside it. The inventory source can be a single group or host, a selection of multiple hosts, or a selection of multiple groups.", + dataTipWatch: "adhocCommandTooltip", + actionClass: 'btn List-buttonDefault', + buttonContent: 'RUN COMMANDS', + showTipWhenDisabled: true, + tooltipInnerClass: "Tooltip-wide", + // TODO: we don't always want to show this + ngShow: true + }, + system_tracking: { + buttonContent: 'System Tracking', + ngClick: 'systemTracking()', + awToolTip: "Select one or two hosts by clicking the checkbox beside the host. System tracking offers the ability to compare the results of two scan runs from different dates on one host or the same date on two hosts.", + dataTipWatch: "systemTrackingTooltip", + dataPlacement: 'top', + awFeature: 'system_tracking', + actionClass: 'btn List-buttonDefault system-tracking', + ngDisabled: 'systemTrackingDisabled || !hostsSelected', + showTipWhenDisabled: true, + tooltipInnerClass: "Tooltip-wide", + ngShow: true + }, + refresh: { + mode: 'all', + awToolTip: "Refresh the page", + ngClick: "refreshGroups()", + ngShow: "socketStatus == 'error'", + actionClass: 'btn List-buttonDefault', + buttonContent: 'REFRESH' + }, + create: { + mode: 'all', + ngClick: "createHost()", + awToolTip: "Create a new host", + actionClass: 'btn List-buttonSubmit', + buttonContent: '+ ADD HOST', + ngShow: 'canAdd', + dataPlacement: "top", + } + } + +}; diff --git a/awx/ui/client/src/inventories/inventories.partial.html b/awx/ui/client/src/inventories/inventories.partial.html index d8fc0b2c60..e9cbce9432 100644 --- a/awx/ui/client/src/inventories/inventories.partial.html +++ b/awx/ui/client/src/inventories/inventories.partial.html @@ -1,8 +1,8 @@