From 1176b9b057f3c2f11f299313d5a344fc4ebe822a Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Mon, 22 May 2017 16:55:17 -0400 Subject: [PATCH] Smart inventory implementation --- awx/ui/client/legacy-styles/forms.less | 14 + .../credential-types/credential-types.form.js | 2 +- .../src/credentials/credentials.form.js | 2 +- .../counts/dashboard-counts.directive.js | 4 +- .../hosts/dashboard-hosts-edit.controller.js | 79 ---- .../hosts/dashboard-hosts-list.controller.js | 58 --- .../dashboard/hosts/dashboard-hosts.form.js | 83 ---- .../dashboard/hosts/dashboard-hosts.list.js | 75 ---- .../hosts/dashboard-hosts.service.js | 30 -- .../client/src/home/dashboard/hosts/main.js | 60 --- awx/ui/client/src/home/dashboard/main.js | 3 +- .../adhoc/adhoc-credential.route.js | 1 - .../src/inventories/adhoc/adhoc.controller.js | 7 +- .../src/inventories/adhoc/adhoc.route.js | 1 - .../completed_jobs/completed_jobs.route.js | 10 +- .../nested-hosts-list.controller.js | 23 +- .../hosts/edit/host-edit.controller.js | 8 +- .../client/src/inventories/hosts/host.list.js | 9 +- .../src/inventories/hosts/hosts.partial.html | 97 +++++ .../hosts/list/host-list.controller.js | 34 +- awx/ui/client/src/inventories/hosts/main.js | 5 +- ...ynamic-inventory-host-filter.controller.js | 15 - ...dynamic-inventory-host-filter.partial.html | 12 - .../edit/smart-inventory-edit.controller.js | 14 - .../smart-inventory/smart-inventory.form.js | 179 --------- .../client/src/inventories/inventory.list.js | 18 +- .../list/inventory-list.controller.js | 24 +- awx/ui/client/src/inventories/main.js | 74 ++-- .../edit/host-edit.controller.js | 8 +- .../list/host-list.controller.js | 31 +- .../related-hosts/related-host.list.js | 8 - .../related-hosts/related-host.route.js | 13 +- .../{hosts => }/smart-inventory/add/main.js | 0 .../add/smart-inventory-add.controller.js | 36 +- .../{hosts => }/smart-inventory/edit/main.js | 0 .../edit/smart-inventory-edit.controller.js | 103 +++++ .../{hosts => }/smart-inventory/main.js | 10 +- .../host-filter-modal.directive.js | 4 +- .../host-filter-modal.partial.html | 0 .../smart-inventory-host-filter.controller.js | 32 ++ .../smart-inventory-host-filter.directive.js} | 6 +- .../smart-inventory-host-filter.partial.html | 12 + .../smart-inventory-hosts.route.js | 63 +++ .../smart-inventory/smart-inventory.form.js | 169 +++++++++ .../sources/list/sources-list.controller.js | 2 +- .../add/inventory-add.controller.js | 0 .../inventories/{ => standard}/add/main.js | 0 .../edit/inventory-edit.controller.js | 2 - .../inventories/{ => standard}/edit/main.js | 0 .../{ => standard}/inventory.form.js | 2 +- .../client/src/inventories/standard/main.js | 16 + .../src/job-results/job-results.controller.js | 8 + .../src/scheduler/schedulerList.controller.js | 23 +- awx/ui/client/src/shared/form-generator.js | 4 + .../list-generator/list-generator.factory.js | 6 +- .../shared/smart-search/queryset.service.js | 55 +-- .../smart-search/smart-search.controller.js | 359 ++++++++++-------- .../smart-search/smart-search.directive.js | 3 +- .../smart-search/smart-search.partial.html | 8 +- .../standard-out/standard-out.controller.js | 12 +- .../templates/labels/labelsList.block.less | 5 +- .../job-results.controller-test.js | 21 +- 62 files changed, 963 insertions(+), 999 deletions(-) delete mode 100644 awx/ui/client/src/home/dashboard/hosts/dashboard-hosts-edit.controller.js delete mode 100644 awx/ui/client/src/home/dashboard/hosts/dashboard-hosts-list.controller.js delete mode 100644 awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.form.js delete mode 100644 awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.list.js delete mode 100644 awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.service.js delete mode 100644 awx/ui/client/src/home/dashboard/hosts/main.js create mode 100644 awx/ui/client/src/inventories/hosts/hosts.partial.html delete mode 100644 awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.controller.js delete mode 100644 awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.partial.html delete mode 100644 awx/ui/client/src/inventories/hosts/smart-inventory/edit/smart-inventory-edit.controller.js delete mode 100644 awx/ui/client/src/inventories/hosts/smart-inventory/smart-inventory.form.js rename awx/ui/client/src/inventories/{hosts => }/smart-inventory/add/main.js (100%) rename awx/ui/client/src/inventories/{hosts => }/smart-inventory/add/smart-inventory-add.controller.js (69%) rename awx/ui/client/src/inventories/{hosts => }/smart-inventory/edit/main.js (100%) create mode 100644 awx/ui/client/src/inventories/smart-inventory/edit/smart-inventory-edit.controller.js rename awx/ui/client/src/inventories/{hosts => }/smart-inventory/main.js (51%) rename awx/ui/client/src/inventories/{hosts/smart-inventory/dynamic-inventory-host-filter => smart-inventory/smart-inventory-host-filter}/host-filter-modal/host-filter-modal.directive.js (92%) rename awx/ui/client/src/inventories/{hosts/smart-inventory/dynamic-inventory-host-filter => smart-inventory/smart-inventory-host-filter}/host-filter-modal/host-filter-modal.partial.html (100%) create mode 100644 awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.controller.js rename awx/ui/client/src/inventories/{hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.directive.js => smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.directive.js} (68%) create mode 100644 awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.partial.html create mode 100644 awx/ui/client/src/inventories/smart-inventory/smart-inventory-hosts.route.js create mode 100644 awx/ui/client/src/inventories/smart-inventory/smart-inventory.form.js rename awx/ui/client/src/inventories/{ => standard}/add/inventory-add.controller.js (100%) rename awx/ui/client/src/inventories/{ => standard}/add/main.js (100%) rename awx/ui/client/src/inventories/{ => standard}/edit/inventory-edit.controller.js (99%) rename awx/ui/client/src/inventories/{ => standard}/edit/main.js (100%) rename awx/ui/client/src/inventories/{ => standard}/inventory.form.js (99%) create mode 100644 awx/ui/client/src/inventories/standard/main.js diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index ed4fcb82ab..632da3de92 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -59,6 +59,7 @@ min-height: 40px; } +.Form-title--is_smartinventory, .Form-title--is_superuser, .Form-title--is_system_auditor, .Form-title--is_ldap_user, @@ -665,6 +666,19 @@ input[type='radio']:checked:before { .noselect; } +.Form-textInput--variableHeight { + height: inherit; + min-height: 30px; +} + +.Form-variableHeightButtonGroup { + height: 100%; +} + +.Form-lookupButton--variableHeight { + height: 100%; +} + @media only screen and (max-width: 650px) { .Form-formGroup { flex: 1 0 auto; diff --git a/awx/ui/client/src/credential-types/credential-types.form.js b/awx/ui/client/src/credential-types/credential-types.form.js index 417abbb926..289ed260d1 100644 --- a/awx/ui/client/src/credential-types/credential-types.form.js +++ b/awx/ui/client/src/credential-types/credential-types.form.js @@ -56,7 +56,7 @@ export default ['i18n', function(i18n) { 'used by Projects.') + '\n' + '
' + i18n._('Cloud') + '
\n' + '
' + i18n._('Usernames, passwords, and access keys for authenticating to the specified cloud or infrastructure ' + - 'provider. These are used for dynamic inventory sources and for cloud provisioning and deployment ' + + 'provider. These are used for smart inventory sources and for cloud provisioning and deployment ' + 'in playbook runs.') + '
\n' + '\n', dataTitle: i18n._('Kind'), diff --git a/awx/ui/client/src/credentials/credentials.form.js b/awx/ui/client/src/credentials/credentials.form.js index d2a201fdd1..c8a125b9bf 100644 --- a/awx/ui/client/src/credentials/credentials.form.js +++ b/awx/ui/client/src/credentials/credentials.form.js @@ -78,7 +78,7 @@ export default ['i18n', function(i18n) { 'used by Projects.') + '\n' + '
' + i18n._('Others (Cloud Providers)') + '
\n' + '
' + i18n._('Usernames, passwords, and access keys for authenticating to the specified cloud or infrastructure ' + - 'provider. These are used for dynamic inventory sources and for cloud provisioning and deployment ' + + 'provider. These are used for smart inventory sources and for cloud provisioning and deployment ' + 'in playbook runs.') + '
\n' + '\n', dataTitle: i18n._('Type'), diff --git a/awx/ui/client/src/home/dashboard/counts/dashboard-counts.directive.js b/awx/ui/client/src/home/dashboard/counts/dashboard-counts.directive.js index bc91c9c8ba..e605c21c86 100644 --- a/awx/ui/client/src/home/dashboard/counts/dashboard-counts.directive.js +++ b/awx/ui/client/src/home/dashboard/counts/dashboard-counts.directive.js @@ -34,12 +34,12 @@ export default function createCounts(data) { scope.counts = _.map([ { - url: "/#/home/hosts", + url: "/#/hosts", number: scope.data.hosts.total, label: i18n._("Hosts") }, { - url: "/#/home/hosts?host_search=has_active_failures:true", + url: "/#/hosts?host_search=has_active_failures:true", number: scope.data.hosts.failed, label: i18n._("Failed Hosts"), isFailureCount: true diff --git a/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts-edit.controller.js b/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts-edit.controller.js deleted file mode 100644 index 2426a2bed8..0000000000 --- a/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts-edit.controller.js +++ /dev/null @@ -1,79 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default - ['$scope', '$state', '$stateParams', 'DashboardHostsForm', 'GenerateForm', 'ParseTypeChange', 'DashboardHostService', 'host', - function($scope, $state, $stateParams, DashboardHostsForm, GenerateForm, ParseTypeChange, DashboardHostService, host){ - $scope.parseType = 'yaml'; - $scope.formCancel = function(){ - $state.go('^', null, {reload: true}); - }; - $scope.toggleHostEnabled = function(){ - if ($scope.host.has_inventory_sources){ - return; - } - $scope.host.enabled = !$scope.host.enabled; - }; - $scope.toggleEnabled = function(){ - $scope.host.enabled = !$scope.host.enabled; - }; - $scope.formSave = function(){ - var host = { - id: $scope.host.id, - variables: $scope.variables === '---' || $scope.variables === '{}' ? null : $scope.variables, - name: $scope.name, - description: $scope.description, - enabled: $scope.host.enabled - }; - DashboardHostService.putHost(host).then(function(){ - $state.go('^', null, {reload: true}); - }); - - }; - var init = function(){ - $scope.host = host.data; - $scope.name = host.data.name; - $scope.description = host.data.description; - $scope.variables = getVars(host.data.variables); - ParseTypeChange({ - scope: $scope, - field_id: 'host_variables', - variable: 'variables', - }); - }; - - // Adding this function b/c sometimes extra vars are returned to the - // UI as a string (ex: "foo: bar"), and other times as a - // json-object-string (ex: "{"foo": "bar"}"). CodeMirror wouldn't know - // how to prettify the latter. The latter occurs when host vars were - // system generated and not user-input (such as adding a cloud host); - function getVars(str){ - - // Quick function to test if the host vars are a json-object-string, - // by testing if they can be converted to a JSON object w/o error. - function IsJsonString(str) { - try { - JSON.parse(str); - } catch (e) { - return false; - } - return true; - } - - if(str === ''){ - return '---'; - } - else if(IsJsonString(str)){ - str = JSON.parse(str); - return jsyaml.safeDump(str); - } - else if(!IsJsonString(str)){ - return str; - } - } - - init(); - }]; diff --git a/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts-list.controller.js b/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts-list.controller.js deleted file mode 100644 index b4ce108167..0000000000 --- a/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts-list.controller.js +++ /dev/null @@ -1,58 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', '$state', '$stateParams', 'GetBasePath', 'DashboardHostsList', - 'generateList', 'SetStatus', 'DashboardHostService', '$rootScope', 'Dataset', - function($scope, $state, $stateParams, GetBasePath, DashboardHostsList, - GenerateList, SetStatus, DashboardHostService, $rootScope, Dataset) { - - let list = DashboardHostsList; - init(); - - function init() { - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - $scope.$watchCollection(list.name, function() { - $scope[list.name] = _.map($scope.hosts, function(value) { - value.inventory_name = value.summary_fields.inventory.name; - value.inventory_id = value.summary_fields.inventory.id; - return value; - }); - setJobStatus(); - }); - } - - - function setJobStatus(){ - _.forEach($scope.hosts, function(value) { - SetStatus({ - scope: $scope, - host: value - }); - }); - } - - $scope.editHost = function(id) { - $state.go('dashboardHosts.edit', { host_id: id }); - }; - - $scope.toggleHostEnabled = function(host) { - if (host.has_inventory_sources){ - return; - } - DashboardHostService.setHostStatus(host, !host.enabled) - .then(function(res) { - var index = _.findIndex($scope.hosts, function(o) { - return o.id === res.data.id; - }); - $scope.hosts[index].enabled = res.data.enabled; - }); - }; - } -]; diff --git a/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.form.js b/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.form.js deleted file mode 100644 index 1344786990..0000000000 --- a/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.form.js +++ /dev/null @@ -1,83 +0,0 @@ -/************************************************* -* Copyright (c) 2016 Ansible, Inc. -* -* All Rights Reserved -*************************************************/ - -export default ['i18n', function(i18n){ - return { - editTitle: '{{host.name}}', - name: 'host', - well: true, - formLabelSize: 'col-lg-3', - formFieldSize: 'col-lg-9', - iterator: 'host', - basePath: 'hosts', - headerFields:{ - enabled: { - //flag: 'host.enabled', - class: 'Form-header-field', - ngClick: 'toggleHostEnabled()', - type: 'toggle', - awToolTip: "

" + - i18n._("Indicates if a host is available and should be included in running jobs.") + - "

" + - i18n._("For hosts that are part of an external inventory, this" + - " flag cannot be changed. It will be set by the inventory" + - " sync process.") + - "

", - dataTitle: i18n._('Host Enabled'), - ngDisabled: 'host.has_inventory_sources' - } - }, - fields: { - name: { - label: i18n._('Host Name'), - type: 'text', - - value: '{{name}}', - awPopOver: "

" + - i18n._("Provide a host name, ip address, or ip address:port. Examples include:") + - "

" + - "
myserver.domain.com
" + - "127.0.0.1
" + - "10.1.0.140:25
" + - "server.example.com:25" + - "
", - dataTitle: i18n._('Host Name'), - dataPlacement: 'right', - dataContainer: 'body' - }, - description: { - label: i18n._('Description'), - type: 'text', - }, - variables: { - label: i18n._('Variables'), - type: 'textarea', - rows: 6, - class: 'modal-input-xlarge Form-textArea Form-formGroup--fullWidth', - dataTitle: i18n._('Host Variables'), - dataPlacement: 'right', - dataContainer: 'body', - default: '---', - awPopOver: "

" + i18n._("Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + - "JSON:
\n" + - "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - "YAML:
\n" + - "
---
somevar: somevalue
password: magic
\n" + - '

' + i18n.sprintf(i18n._('View JSON examples at %s'), 'www.json.org') + '

' + - '

' + i18n.sprintf(i18n._('View YAML examples at %s'), 'docs.ansible.com') + '

', - } - }, - buttons: { - cancel: { - ngClick: 'formCancel()' - }, - save: { - ngClick: 'formSave()', //$scope.function to call on click, optional - ngDisabled: "host_form.$invalid"//true //Disable when $pristine or $invalid, optional and when can_edit = false, for permission reasons - } - } - }; -}]; diff --git a/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.list.js b/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.list.js deleted file mode 100644 index fefc0fe834..0000000000 --- a/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.list.js +++ /dev/null @@ -1,75 +0,0 @@ -/************************************************* -* Copyright (c) 2015 Ansible, Inc. -* -* All Rights Reserved -*************************************************/ - - -export default [ 'i18n', function(i18n){ - return { - name: 'hosts', - iterator: 'host', - selectTitle: i18n._('Add Existing Hosts'), - editTitle: i18n._('HOSTS'), - listTitle: i18n._('HOSTS'), - index: false, - hover: true, - well: true, - emptyListText: i18n._('NO HOSTS FOUND'), - fields: { - status: { - basePath: 'unified_jobs', - label: '', - iconOnly: true, - nosort: true, - icon: 'icon-job-{{ host.active_failures }}', - awToolTip: '{{ host.badgeToolTip }}', - awTipPlacement: 'right', - dataPlacement: 'right', - awPopOver: '{{ host.job_status_html }}', - dataTitle: '{{host.job_status_title}}', - ngClick:'viewHost(host.id)', - columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumn--smallStatus' - }, - name: { - key: true, - label: i18n._('Name'), - columnClass: 'col-lg-5 col-md-5 col-sm-5 col-xs-8 ellipsis List-staticColumnAdjacent', - ngClick: 'editHost(host.id)' - }, - inventory_name: { - label: i18n._('Inventory'), - sourceModel: 'inventory', - sourceField: 'name', - columnClass: 'col-lg-5 col-md-4 col-sm-4 hidden-xs elllipsis', - linkTo: "{{ '/#/inventories/' + host.inventory_id }}" - }, - enabled: { - label: i18n._('Status'), - columnClass: 'List-staticColumn--toggle', - type: 'toggle', - ngClick: 'toggleHostEnabled(host)', - nosort: true, - awToolTip: "

" + i18n._("Indicates if a host is available and should be included in running jobs.") + "

" + i18n._("For hosts that are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process.") + "

", - dataTitle: i18n._('Host Enabled'), - ngDisabled: 'host.has_inventory_sources' - } - }, - - fieldActions: { - - columnClass: 'col-lg-2 col-md-3 col-sm-3 col-xs-4', - edit: { - label: i18n._('Edit'), - ngClick: 'editHost(host.id)', - icon: 'icon-edit', - awToolTip: i18n._('Edit host'), - dataPlacement: 'top' - } - }, - - actions: { - - } - }; -}]; diff --git a/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.service.js b/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.service.js deleted file mode 100644 index 997167b90a..0000000000 --- a/awx/ui/client/src/home/dashboard/hosts/dashboard-hosts.service.js +++ /dev/null @@ -1,30 +0,0 @@ -export default - ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){ - return { - - setHostStatus: function(host, enabled){ - var url = GetBasePath('hosts') + host.id; - Rest.setUrl(url); - return Rest.put({enabled: enabled, name: host.name}) - .success(function(data){ - return data; - }) - .error(function(data, status) { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - }, - putHost: function(host){ - var url = GetBasePath('hosts') + host.id; - Rest.setUrl(url); - return Rest.put(host) - .success(function(data){ - return data; - }) - .error(function(data, status) { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); - } - }; - }]; \ No newline at end of file diff --git a/awx/ui/client/src/home/dashboard/hosts/main.js b/awx/ui/client/src/home/dashboard/hosts/main.js deleted file mode 100644 index e68efc7300..0000000000 --- a/awx/ui/client/src/home/dashboard/hosts/main.js +++ /dev/null @@ -1,60 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -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'; -import { N_ } from '../../../i18n'; - -export default -angular.module('dashboardHosts', []) - .service('DashboardHostService', service) - .factory('DashboardHostsList', list) - .factory('DashboardHostsForm', form) - .config(['$stateProvider', 'stateDefinitionsProvider', - function($stateProvider, stateDefinitionsProvider) { - let stateDefinitions = stateDefinitionsProvider.$get(); - - $stateProvider.state({ - name: 'dashboardHosts', - url: '/home/hosts', - lazyLoad: () => stateDefinitions.generateTree({ - urls: { - list: '/home/hosts' - }, - parent: 'dashboardHosts', - modes: ['edit'], - list: 'DashboardHostsList', - form: 'DashboardHostsForm', - controllers: { - list: listController, - edit: editController - }, - resolve: { - edit: { - host: ['Rest', '$stateParams', 'GetBasePath', - function(Rest, $stateParams, GetBasePath) { - let path = GetBasePath('hosts') + $stateParams.host_id; - Rest.setUrl(path); - return Rest.get(); - } - ] - } - }, - data: { - activityStream: true, - activityStreamTarget: 'host' - }, - ncyBreadcrumb: { - parent: 'dashboard', - label: N_("HOSTS") - }, - }) - }); - } - ]); diff --git a/awx/ui/client/src/home/dashboard/main.js b/awx/ui/client/src/home/dashboard/main.js index bd0fd75743..80f27a1ff1 100644 --- a/awx/ui/client/src/home/dashboard/main.js +++ b/awx/ui/client/src/home/dashboard/main.js @@ -2,8 +2,7 @@ import dashboardCounts from './counts/main'; import dashboardGraphs from './graphs/main'; import dashboardLists from './lists/main'; import dashboardDirective from './dashboard.directive'; -import dashboardHosts from './hosts/main'; export default - angular.module('dashboard', [dashboardHosts.name, dashboardCounts.name, dashboardGraphs.name, dashboardLists.name]) + angular.module('dashboard', [dashboardCounts.name, dashboardGraphs.name, dashboardLists.name]) .directive('dashboard', dashboardDirective); diff --git a/awx/ui/client/src/inventories/adhoc/adhoc-credential.route.js b/awx/ui/client/src/inventories/adhoc/adhoc-credential.route.js index 457e6288bb..9ec32d6b04 100644 --- a/awx/ui/client/src/inventories/adhoc/adhoc-credential.route.js +++ b/awx/ui/client/src/inventories/adhoc/adhoc-credential.route.js @@ -1,6 +1,5 @@ export default { searchPrefix: 'credential', - name: 'inventories.edit.adhoc.credential', url: '/credential', data: { formChildState: true diff --git a/awx/ui/client/src/inventories/adhoc/adhoc.controller.js b/awx/ui/client/src/inventories/adhoc/adhoc.controller.js index fcff3cb3a1..f1b8db2d9b 100644 --- a/awx/ui/client/src/inventories/adhoc/adhoc.controller.js +++ b/awx/ui/client/src/inventories/adhoc/adhoc.controller.js @@ -22,7 +22,7 @@ function adhocController($q, $scope, $stateParams, var privateFn = {}; this.privateFn = privateFn; - var id = $stateParams.inventory_id, + var id = $stateParams.inventory_id ? $stateParams.inventory_id : $stateParams.smartinventory_id, hostPattern = $stateParams.pattern; // note: put any urls that the controller will use in here!!!! @@ -189,7 +189,7 @@ function adhocController($q, $scope, $stateParams, // launch the job with the provided form data $scope.launchJob = function () { - var adhocUrl = GetBasePath('inventory') + $stateParams.inventory_id + + var adhocUrl = GetBasePath('inventory') + id + '/ad_hoc_commands/', fld, data={}, html; html = '
+ +
+
+
+
+
+
INVENTORIES
+
HOSTS
+
+
+
+
+
+
+
+
+
+
+ + +
+ +
+
No records matched your search.
+
+
PLEASE ADD ITEMS TO THIS LIST
+
+ + + + + + + + + + + + + + + + + + + +
Actions
+
+ + +
+
+
+ + + +
+
+ + +
+ + +
+
+
+ + +
+
+ diff --git a/awx/ui/client/src/inventories/hosts/list/host-list.controller.js b/awx/ui/client/src/inventories/hosts/list/host-list.controller.js index 7f89b64e99..47c41f508d 100644 --- a/awx/ui/client/src/inventories/hosts/list/host-list.controller.js +++ b/awx/ui/client/src/inventories/hosts/list/host-list.controller.js @@ -39,22 +39,17 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath, }); $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) { - if(toState.name === 'hosts.addSmartInventory') { - $scope.enableSmartInventoryButton = false; + if(toParams && toParams.host_search) { + let hasMoreThanDefaultKeys = false; + angular.forEach(toParams.host_search, function(value, key) { + if(key !== 'order_by' && key !== 'page_size') { + hasMoreThanDefaultKeys = true; + } + }); + $scope.enableSmartInventoryButton = hasMoreThanDefaultKeys ? true : false; } else { - if(toParams && toParams.host_search) { - let hasMoreThanDefaultKeys = false; - angular.forEach(toParams.host_search, function(value, key) { - if(key !== 'order_by' && key !== 'page_size') { - hasMoreThanDefaultKeys = true; - } - }); - $scope.enableSmartInventoryButton = hasMoreThanDefaultKeys ? true : false; - } - else { - $scope.enableSmartInventoryButton = false; - } + $scope.enableSmartInventoryButton = false; } }); @@ -131,6 +126,17 @@ function HostsList($scope, HostsList, $rootScope, GetBasePath, $state.go('inventories.addSmartInventory', {hostfilter: JSON.stringify(stateParamsCopy)}); }; + $scope.editInventory = function(host) { + if(host.summary_fields && host.summary_fields.inventory) { + if(host.summary_fields.inventory.kind && host.summary_fields.inventory.kind === 'smart') { + $state.go('inventories.editSmartInventory', {smartinventory_id: host.inventory}); + } + else { + $state.go('inventories.edit', {inventory_id: host.inventory}); + } + } + }; + } export default ['$scope', 'HostsList', '$rootScope', 'GetBasePath', diff --git a/awx/ui/client/src/inventories/hosts/main.js b/awx/ui/client/src/inventories/hosts/main.js index 39d36fb762..2e41773701 100644 --- a/awx/ui/client/src/inventories/hosts/main.js +++ b/awx/ui/client/src/inventories/hosts/main.js @@ -11,13 +11,12 @@ import HostManageService from './hosts.service'; import SetStatus from './set-status.factory'; import SetEnabledMsg from './set-enabled-msg.factory'; - import SmartInventory from './smart-inventory/main'; + export default angular.module('host', [ hostEdit.name, - hostList.name, - SmartInventory.name, + hostList.name ]) .factory('HostsForm', HostsForm) .factory('HostsList', HostsList) diff --git a/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.controller.js b/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.controller.js deleted file mode 100644 index 6a96c0d74c..0000000000 --- a/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.controller.js +++ /dev/null @@ -1,15 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', 'QuerySet', - function($scope, qs) { - $scope.hostFilterTags = []; - - $scope.$watch('hostFilter', function(){ - $scope.hostFilterTags = qs.stripDefaultParams($scope.hostFilter); - }); - } -]; diff --git a/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.partial.html b/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.partial.html deleted file mode 100644 index 6b3546d2b9..0000000000 --- a/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.partial.html +++ /dev/null @@ -1,12 +0,0 @@ -
- - - - - - {{tag}} - - -
diff --git a/awx/ui/client/src/inventories/hosts/smart-inventory/edit/smart-inventory-edit.controller.js b/awx/ui/client/src/inventories/hosts/smart-inventory/edit/smart-inventory-edit.controller.js deleted file mode 100644 index 26d10c9234..0000000000 --- a/awx/ui/client/src/inventories/hosts/smart-inventory/edit/smart-inventory-edit.controller.js +++ /dev/null @@ -1,14 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -function SmartInventoryEdit() { - -console.log('inside smart inventory add'); - -} - -export default [ SmartInventoryEdit -]; diff --git a/awx/ui/client/src/inventories/hosts/smart-inventory/smart-inventory.form.js b/awx/ui/client/src/inventories/hosts/smart-inventory/smart-inventory.form.js deleted file mode 100644 index 0ba2c72899..0000000000 --- a/awx/ui/client/src/inventories/hosts/smart-inventory/smart-inventory.form.js +++ /dev/null @@ -1,179 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['i18n', function(i18n) { - return { - - addTitle: i18n._('NEW SMART INVENTORY'), - editTitle: '{{ inventory_name }}', - name: 'smartinventory', - basePath: 'inventory', - breadcrumbName: 'SMART INVENTORY', - stateTree: 'hosts', - - 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" - }, - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd) || !canEditOrg', - awLookupWhen: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd) && canEditOrg' - }, - dynamic_hosts: { - label: i18n._('Dynamic Hosts'), - type: 'custom', - control: '', - basePath: 'hosts', - list: 'HostsList', - sourceModel: 'host', - sourceField: 'name', - required: true - // TODO: add required, ngDisabled, awLookupWhen (?) - }, - variables: { - label: i18n._('Variables'), - type: 'textarea', - class: 'Form-formGroup--fullWidth', - 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" + - "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - "YAML:
\n" + - "
---
somevar: somevalue
password: magic
\n" + - '

' + i18n.sprintf(i18n._('View JSON examples at %s'), 'www.json.org') + '

' + - '

' + i18n.sprintf(i18n._('View YAML examples at %s'), 'docs.ansible.com') + '

', - dataTitle: i18n._('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()', - ngShow: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - related: { - permissions: { - name: 'permissions', - awToolTip: i18n._('Please save before assigning permissions'), - dataPlacement: 'top', - basePath: 'api/v1/inventories/{{$stateParams.inventory_id}}/access_list/', - type: 'collection', - title: i18n._('Permissions'), - iterator: 'permission', - index: false, - open: false, - search: { - order_by: 'username' - }, - actions: { - add: { - label: i18n._('Add'), - ngClick: "$state.go('.add')", - awToolTip: i18n._('Add a permission'), - actionClass: 'btn List-buttonSubmit', - buttonContent: '+ ADD', - ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - - } - }, - fields: { - username: { - key: true, - label: i18n._('User'), - linkBase: 'users', - class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4' - }, - 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', - } - } - }, - hosts: { - name: 'hosts', - include: "RelatedHostsListDefinition", - title: i18n._('Hosts'), - iterator: 'host' - }, - //this is a placeholder for when we're ready for completed jobs - completed_jobs: { - name: 'completed_jobs', - // awToolTip: i18n._('Please save before assigning permissions'), - // dataPlacement: 'top', - basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/completed_jobs/', - type: 'collection', - title: i18n._('Completed Jobs'), - iterator: 'completed_job', - index: false, - open: false, - // search: { - // order_by: 'username' - // }, - actions: { - add: { - label: i18n._('Add'), - ngClick: "$state.go('.add')", - awToolTip: i18n._('Add a permission'), - actionClass: 'btn List-buttonSubmit', - buttonContent: '+ ADD', - // ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - - } - }, - fields: { - name: { - label: i18n._('Name'), - // linkBase: 'users', - class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4' - } - } - } - } - - };}]; diff --git a/awx/ui/client/src/inventories/inventory.list.js b/awx/ui/client/src/inventories/inventory.list.js index 201b9b9138..a876de231b 100644 --- a/awx/ui/client/src/inventories/inventory.list.js +++ b/awx/ui/client/src/inventories/inventory.list.js @@ -37,11 +37,16 @@ export default ['i18n', function(i18n) { name: { key: true, label: i18n._('Name'), - columnClass: 'col-md-5 col-sm-4 col-xs-6 List-staticColumnAdjacent', + columnClass: 'col-md-4 col-sm-3 col-xs-6 List-staticColumnAdjacent', modalColumnClass: 'col-md-11', awToolTip: "{{ inventory.description }}", awTipPlacement: "top", - linkTo: '/#/inventories/basic_inventory/{{inventory.id}}' + ngClick: 'editInventory(inventory)' + }, + kind: { + label: i18n._('Type'), + ngBind: 'inventory.kind_label', + columnClass: 'col-md-2 col-sm-2 hidden-xs' }, organization: { label: i18n._('Organization'), @@ -50,7 +55,7 @@ export default ['i18n', function(i18n) { sourceModel: 'organization', sourceField: 'name', excludeModal: true, - columnClass: 'col-md-4 col-sm-2 hidden-xs' + columnClass: 'col-md-3 col-sm-2 hidden-xs' } }, @@ -71,7 +76,6 @@ export default ['i18n', function(i18n) { { optionContent: i18n._('Smart Inventory'), optionSref: 'inventories.addSmartInventory', - //TODO: this should have its own permission ngShow: 'canAddInventory' } ], @@ -81,18 +85,18 @@ export default ['i18n', function(i18n) { fieldActions: { - columnClass: 'col-md-2 col-sm-4 col-xs-4', + columnClass: 'col-md-2 col-sm-3 col-xs-4', edit: { label: i18n._('Edit'), - ngClick: 'editInventory(inventory.id)', + ngClick: 'editInventory(inventory)', awToolTip: i18n._('Edit inventory'), dataPlacement: 'top', ngShow: 'inventory.summary_fields.user_capabilities.edit' }, view: { label: i18n._('View'), - ngClick: 'editInventory(inventory.id)', + ngClick: 'editInventory(inventory)', awToolTip: i18n._('View inventory'), dataPlacement: 'top', ngShow: '!inventory.summary_fields.user_capabilities.edit' diff --git a/awx/ui/client/src/inventories/list/inventory-list.controller.js b/awx/ui/client/src/inventories/list/inventory-list.controller.js index 0063b94fa4..854c70be6b 100644 --- a/awx/ui/client/src/inventories/list/inventory-list.controller.js +++ b/awx/ui/client/src/inventories/list/inventory-list.controller.js @@ -28,7 +28,7 @@ function InventoriesList($scope, $rootScope, $location, }); $scope.$watchCollection(list.name, function(){ - _.forEach($scope[list.name], buildStatusIndicators); + _.forEach($scope[list.name], processInventoryRow); }); // Search init @@ -40,6 +40,11 @@ function InventoriesList($scope, $rootScope, $location, } + function processInventoryRow(inventory) { + buildStatusIndicators(inventory); + buildInventoryTypeLabel(inventory); + } + function buildStatusIndicators(inventory){ inventory.launch_class = ""; if (inventory.has_inventory_sources) { @@ -71,6 +76,10 @@ function InventoriesList($scope, $rootScope, $location, } } + function buildInventoryTypeLabel(inventory) { + inventory.kind_label = inventory.kind === '' ? 'Inventory' : (inventory.kind === 'smart' ? 'Smart Inventory': 'Inventory'); + } + function ellipsis(a) { if (a.length > 20) { return a.substr(0,20) + '...'; @@ -249,12 +258,13 @@ function InventoriesList($scope, $rootScope, $location, }; - $scope.addInventory = function () { - $state.go('inventories.add'); - }; - - $scope.editInventory = function (id) { - $state.go('inventories.edit', {inventory_id: id}); + $scope.editInventory = function (inventory) { + if(inventory.kind && inventory.kind === 'smart') { + $state.go('inventories.editSmartInventory', {smartinventory_id: inventory.id}); + } + else { + $state.go('inventories.edit', {inventory_id: inventory.id}); + } }; $scope.manageInventory = function(id){ diff --git a/awx/ui/client/src/inventories/main.js b/awx/ui/client/src/inventories/main.js index b8d66df381..afade0c8a7 100644 --- a/awx/ui/client/src/inventories/main.js +++ b/awx/ui/client/src/inventories/main.js @@ -10,28 +10,25 @@ import group from './groups/main'; import sources from './sources/main'; import relatedHost from './related-hosts/main'; import inventoryCompletedJobs from './completed_jobs/main'; -import inventoryAdd from './add/main'; -import inventoryEdit from './edit/main'; import inventoryList from './list/main'; import hostGroups from './host-groups/main'; import { templateUrl } from '../shared/template-url/template-url.factory'; import { N_ } from '../i18n'; import InventoryList from './inventory.list'; -import InventoryForm from './inventory.form'; import InventoryManageService from './inventory-manage.service'; import adHocRoute from './adhoc/adhoc.route'; import ansibleFacts from './ansible_facts/main'; import insights from './insights/main'; import { copyMoveGroupRoute, copyMoveHostRoute } from './copy-move/copy-move.route'; import copyMove from './copy-move/main'; -import inventoryCompletedJobsRoute from './completed_jobs/completed_jobs.route'; +import completedJobsRoute from './completed_jobs/completed_jobs.route'; import inventorySourceEditRoute from './sources/edit/sources-edit.route'; import inventorySourceAddRoute from './sources/add/sources-add.route'; import inventorySourceListRoute from './sources/list/sources-list.route'; import inventorySourceListScheduleRoute from './sources/list/schedule/sources-schedule.route'; import inventorySourceListScheduleAddRoute from './sources/list/schedule/sources-schedule-add.route'; import inventorySourceListScheduleEditRoute from './sources/list/schedule/sources-schedule-edit.route'; -import inventoryAdhocCredential from './adhoc/adhoc-credential.route'; +import adhocCredentialRoute from './adhoc/adhoc-credential.route'; import inventoryGroupsList from './groups/list/groups-list.route'; import inventoryGroupsAdd from './groups/add/groups-add.route'; import inventoryGroupsEdit from './groups/edit/groups-edit.route'; @@ -39,6 +36,7 @@ import nestedGroups from './groups/nested-groups/nested-groups.route'; import nestedGroupsAdd from './groups/nested-groups/nested-groups-add.route'; import nestedHosts from './groups/nested-hosts/nested-hosts.route'; import inventoryHosts from './related-hosts/related-host.route'; +import smartInventoryHosts from './smart-inventory/smart-inventory-hosts.route'; import inventoriesList from './inventories.route'; import inventoryHostsAdd from './related-hosts/add/host-add.route'; import inventoryHostsEdit from './related-hosts/edit/host-edit.route'; @@ -50,6 +48,8 @@ import hostGroupsRoute from './host-groups/host-groups.route'; import hostGroupsAssociateRoute from './host-groups/host-groups-associate/host-groups-associate.route'; import inventorySourcesCredentialRoute from './sources/lookup/sources-lookup-credential.route'; import inventorySourcesInventoryScriptRoute from './sources/lookup/sources-lookup-inventory-script.route'; +import SmartInventory from './smart-inventory/main'; +import StandardInventory from './standard/main'; export default angular.module('inventory', [ @@ -59,15 +59,14 @@ angular.module('inventory', [ sources.name, relatedHost.name, inventoryCompletedJobs.name, - inventoryAdd.name, - inventoryEdit.name, inventoryList.name, ansibleFacts.name, insights.name, copyMove.name, - hostGroups.name + hostGroups.name, + SmartInventory.name, + StandardInventory.name ]) - .factory('InventoryForm', InventoryForm) .factory('InventoryList', InventoryList) .service('InventoryManageService', InventoryManageService) .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', @@ -79,7 +78,7 @@ angular.module('inventory', [ let basicInventoryAdd = stateDefinitions.generateTree({ name: 'inventories.add', // top-most node in the generated tree (will replace this state definition) - url: '/basic_inventory/add', + url: '/standard_inventory/add', modes: ['add'], form: 'InventoryForm', controllers: { @@ -89,7 +88,7 @@ angular.module('inventory', [ let basicInventoryEdit = stateDefinitions.generateTree({ name: 'inventories.edit', - url: '/basic_inventory/:inventory_id', + url: '/standard_inventory/:inventory_id', modes: ['edit'], form: 'InventoryForm', controllers: { @@ -102,9 +101,9 @@ angular.module('inventory', [ let smartInventoryAdd = stateDefinitions.generateTree({ name: 'inventories.addSmartInventory', // top-most node in the generated tree (will replace this state definition) - url: '/smart_inventory/add?hostfilter', + url: '/smart/add?hostfilter', modes: ['add'], - form: 'SmartInventoryForm', + form: 'smartInventoryForm', controllers: { add: 'SmartInventoryAddController' } @@ -112,11 +111,14 @@ angular.module('inventory', [ let smartInventoryEdit = stateDefinitions.generateTree({ name: 'inventories.editSmartInventory', - url: '/smart_inventory/:inventory_id', + url: '/smart/:smartinventory_id', modes: ['edit'], - form: 'SmartInventoryForm', + form: 'smartInventoryForm', controllers: { edit: 'SmartInventoryEditController' + }, + breadcrumbs: { + edit: '{{breadcrumb.inventory_name}}' } }); @@ -169,6 +171,24 @@ angular.module('inventory', [ editSourceInventoryScript.name = 'inventories.edit.inventory_sources.edit.inventory_script'; editSourceInventoryScript.url = '/inventory_script'; + let inventoryCompletedJobsRoute = _.cloneDeep(completedJobsRoute); + inventoryCompletedJobsRoute.name = 'inventories.edit.completed_jobs'; + + let smartInventoryCompletedJobsRoute = _.cloneDeep(completedJobsRoute); + smartInventoryCompletedJobsRoute.name = 'inventories.editSmartInventory.completed_jobs'; + + let inventoryAdhocRoute = _.cloneDeep(adHocRoute); + inventoryAdhocRoute.name = 'inventories.edit.adhoc'; + + let smartInventoryAdhocRoute = _.cloneDeep(adHocRoute); + smartInventoryAdhocRoute.name = 'inventories.editSmartInventory.adhoc'; + + let inventoryAdhocCredential = _.cloneDeep(adhocCredentialRoute); + inventoryAdhocCredential.name = 'inventories.edit.adhoc.credential'; + + let smartInventoryAdhocCredential = _.cloneDeep(adhocCredentialRoute); + smartInventoryAdhocCredential.name = 'inventories.editSmartInventory.adhoc.credential'; + return Promise.all([ basicInventoryAdd, basicInventoryEdit, @@ -180,8 +200,10 @@ angular.module('inventory', [ return result.concat(definition.states); }, [ stateExtender.buildDefinition(inventoriesList), - stateExtender.buildDefinition(adHocRoute), + stateExtender.buildDefinition(inventoryAdhocRoute), + stateExtender.buildDefinition(smartInventoryAdhocRoute), stateExtender.buildDefinition(inventoryAdhocCredential), + stateExtender.buildDefinition(smartInventoryAdhocCredential), stateExtender.buildDefinition(inventorySourceListScheduleRoute), stateExtender.buildDefinition(inventorySourceListScheduleAddRoute), stateExtender.buildDefinition(inventorySourceListScheduleEditRoute), @@ -201,6 +223,7 @@ angular.module('inventory', [ stateExtender.buildDefinition(nestedHostsEdit), stateExtender.buildDefinition(inventoryGroupsEditNestedHostsEditGroups), stateExtender.buildDefinition(inventoryHosts), + stateExtender.buildDefinition(smartInventoryHosts), stateExtender.buildDefinition(inventoryHostsAdd), stateExtender.buildDefinition(inventoryHostsEdit), stateExtender.buildDefinition(inventoryHostsEditGroups), @@ -208,6 +231,7 @@ angular.module('inventory', [ stateExtender.buildDefinition(inventorySourceAddRoute), stateExtender.buildDefinition(inventorySourceEditRoute), stateExtender.buildDefinition(inventoryCompletedJobsRoute), + stateExtender.buildDefinition(smartInventoryCompletedJobsRoute), stateExtender.buildDefinition(addSourceCredential), stateExtender.buildDefinition(addSourceInventoryScript), stateExtender.buildDefinition(editSourceCredential), @@ -225,7 +249,6 @@ angular.module('inventory', [ list: 'HostsList', form: 'HostsForm', controllers: { - list: 'HostListController', edit: 'HostEditController' }, breadcrumbs: { @@ -245,23 +268,14 @@ angular.module('inventory', [ ] } }, - ncyBreadcrumb: { - label: N_('HOSTS') - }, views: { '@': { - templateUrl: templateUrl('inventories/inventories') - }, - 'list@hosts': { - templateProvider: function(HostsList, generateList) { - let html = generateList.build({ - list: HostsList, - mode: 'edit' - }); - return html; - }, + templateUrl: templateUrl('inventories/hosts/hosts'), controller: 'HostListController' } + }, + ncyBreadcrumb: { + label: N_('HOSTS') } }); diff --git a/awx/ui/client/src/inventories/related-hosts/edit/host-edit.controller.js b/awx/ui/client/src/inventories/related-hosts/edit/host-edit.controller.js index fa793fc097..2dbe122bb5 100644 --- a/awx/ui/client/src/inventories/related-hosts/edit/host-edit.controller.js +++ b/awx/ui/client/src/inventories/related-hosts/edit/host-edit.controller.js @@ -5,8 +5,8 @@ *************************************************/ export default - ['$scope', '$state', '$stateParams', 'DashboardHostsForm', 'GenerateForm', 'ParseTypeChange', 'DashboardHostService', 'host', '$rootScope', - function($scope, $state, $stateParams, DashboardHostsForm, GenerateForm, ParseTypeChange, DashboardHostService, host, $rootScope){ + ['$scope', '$state', '$stateParams', 'GenerateForm', 'ParseTypeChange', 'HostManageService', 'host', '$rootScope', + function($scope, $state, $stateParams, GenerateForm, ParseTypeChange, HostManageService, host, $rootScope){ $scope.parseType = 'yaml'; $scope.formCancel = function(){ $state.go('^', null, {reload: true}); @@ -28,8 +28,8 @@ description: $scope.description, enabled: $scope.host.enabled }; - DashboardHostService.putHost(host).then(function(){ - $state.go('^', null, {reload: true}); + HostManageService.put(host).then(function(){ + $state.go('.', null, {reload: true}); }); }; diff --git a/awx/ui/client/src/inventories/related-hosts/list/host-list.controller.js b/awx/ui/client/src/inventories/related-hosts/list/host-list.controller.js index f2f960ceea..6a9b41bbcb 100644 --- a/awx/ui/client/src/inventories/related-hosts/list/host-list.controller.js +++ b/awx/ui/client/src/inventories/related-hosts/list/host-list.controller.js @@ -5,14 +5,14 @@ *************************************************/ // import HostManageService from './../hosts/host.service'; -export default ['$scope', 'RelatedHostsListDefinition', '$rootScope', 'GetBasePath', +export default ['$scope', 'ListDefinition', '$rootScope', 'GetBasePath', 'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait', 'HostManageService', 'SetStatus', - function($scope, RelatedHostsListDefinition, $rootScope, GetBasePath, + function($scope, ListDefinition, $rootScope, GetBasePath, rbacUiControlService, Dataset, $state, $filter, Prompt, Wait, HostManageService, SetStatus) { - let list = RelatedHostsListDefinition; + let list = ListDefinition; init(); @@ -42,22 +42,17 @@ export default ['$scope', 'RelatedHostsListDefinition', '$rootScope', 'GetBasePa }); $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) { - if(toState.name === 'hosts.addSmartInventory') { - $scope.enableSmartInventoryButton = false; + if(toParams && toParams.host_search) { + let hasMoreThanDefaultKeys = false; + angular.forEach(toParams.host_search, function(value, key) { + if(key !== 'order_by' && key !== 'page_size') { + hasMoreThanDefaultKeys = true; + } + }); + $scope.enableSmartInventoryButton = hasMoreThanDefaultKeys ? true : false; } else { - if(toParams && toParams.host_search) { - let hasMoreThanDefaultKeys = false; - angular.forEach(toParams.host_search, function(value, key) { - if(key !== 'order_by' && key !== 'page_size') { - hasMoreThanDefaultKeys = true; - } - }); - $scope.enableSmartInventoryButton = hasMoreThanDefaultKeys ? true : false; - } - else { - $scope.enableSmartInventoryButton = false; - } + $scope.enableSmartInventoryButton = false; } }); @@ -142,7 +137,7 @@ export default ['$scope', 'RelatedHostsListDefinition', '$rootScope', 'GetBasePa $scope.systemTracking = function(){ var hostIds = _.map($scope.hostsSelected, (host) => host.id); $state.go('systemTracking', { - inventoryId: $state.params.inventory_id, + inventoryId: $state.params.inventory_id ? $state.params.inventory_id : $state.params.smartinventory_id, hosts: $scope.hostsSelected, hostIds: hostIds }); diff --git a/awx/ui/client/src/inventories/related-hosts/related-host.list.js b/awx/ui/client/src/inventories/related-hosts/related-host.list.js index 3a83afc195..c806fbaa58 100644 --- a/awx/ui/client/src/inventories/related-hosts/related-host.list.js +++ b/awx/ui/client/src/inventories/related-hosts/related-host.list.js @@ -109,14 +109,6 @@ export default { 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()", diff --git a/awx/ui/client/src/inventories/related-hosts/related-host.route.js b/awx/ui/client/src/inventories/related-hosts/related-host.route.js index a9bb70695d..9ffb1e5f5e 100644 --- a/awx/ui/client/src/inventories/related-hosts/related-host.route.js +++ b/awx/ui/client/src/inventories/related-hosts/related-host.route.js @@ -19,8 +19,8 @@ export default { }, views: { 'related': { - templateProvider: function(RelatedHostsListDefinition, generateList, $stateParams, GetBasePath) { - let list = _.cloneDeep(RelatedHostsListDefinition); + templateProvider: function(ListDefinition, generateList, $stateParams, GetBasePath) { + let list = _.cloneDeep(ListDefinition); if($stateParams && $stateParams.group) { list.basePath = GetBasePath('groups') + _.last($stateParams.group) + '/all_hosts'; } @@ -38,7 +38,12 @@ export default { } }, resolve: { - Dataset: ['RelatedHostsListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', + ListDefinition: ['RelatedHostsListDefinition', '$stateParams', 'GetBasePath', (RelatedHostsListDefinition, $stateParams, GetBasePath) => { + let list = _.cloneDeep(RelatedHostsListDefinition); + list.basePath = GetBasePath('inventory') + $stateParams.inventory_id + '/hosts'; + 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; @@ -58,7 +63,7 @@ export default { // root context - provide all hosts in an inventory InventoryManageService.rootHostsUrl($stateParams.inventory_id); }], - hostsDataset: ['RelatedHostsListDefinition', 'QuerySet', '$stateParams', 'hostsUrl', (list, qs, $stateParams, hostsUrl) => { + hostsDataset: ['ListDefinition', 'QuerySet', '$stateParams', 'hostsUrl', (list, qs, $stateParams, hostsUrl) => { let path = hostsUrl; return qs.search(path, $stateParams[`${list.iterator}_search`]); }], diff --git a/awx/ui/client/src/inventories/hosts/smart-inventory/add/main.js b/awx/ui/client/src/inventories/smart-inventory/add/main.js similarity index 100% rename from awx/ui/client/src/inventories/hosts/smart-inventory/add/main.js rename to awx/ui/client/src/inventories/smart-inventory/add/main.js diff --git a/awx/ui/client/src/inventories/hosts/smart-inventory/add/smart-inventory-add.controller.js b/awx/ui/client/src/inventories/smart-inventory/add/smart-inventory-add.controller.js similarity index 69% rename from awx/ui/client/src/inventories/hosts/smart-inventory/add/smart-inventory-add.controller.js rename to awx/ui/client/src/inventories/smart-inventory/add/smart-inventory-add.controller.js index 32fff4da0a..9f044877c5 100644 --- a/awx/ui/client/src/inventories/hosts/smart-inventory/add/smart-inventory-add.controller.js +++ b/awx/ui/client/src/inventories/smart-inventory/add/smart-inventory-add.controller.js @@ -11,7 +11,7 @@ */ function SmartInventoryAdd($scope, $location, - GenerateForm, SmartInventoryForm, rbacUiControlService, Rest, Alert, ProcessErrors, + GenerateForm, smartInventoryForm, rbacUiControlService, Rest, Alert, ProcessErrors, ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON, $state) { @@ -34,7 +34,7 @@ function SmartInventoryAdd($scope, $location, // Inject dynamic view var defaultUrl = GetBasePath('inventory'), - form = SmartInventoryForm; + form = smartInventoryForm; init(); @@ -49,37 +49,39 @@ function SmartInventoryAdd($scope, $location, $scope.parseType = 'yaml'; ParseTypeChange({ scope: $scope, - variable: 'variables', + variable: 'smartinventory_variables', parse_variable: 'parseType', - field_id: 'smartinventory_variables' + field_id: 'smartinventory_smartinventory_variables' }); - $scope.dynamic_hosts = $state.params.hostfilter ? JSON.parse($state.params.hostfilter) : ''; + $scope.smart_hosts = $state.params.hostfilter ? JSON.parse($state.params.hostfilter) : ''; } // Save $scope.formSave = function() { Wait('start'); try { - var fld, json_data, data; + let fld, data = {}; - json_data = ToJSON($scope.parseType, $scope.variables, true); - - data = {}; for (fld in form.fields) { - if (form.fields[fld].realName) { - data[form.fields[fld].realName] = $scope[fld]; - } else { - data[fld] = $scope[fld]; - } + data[fld] = $scope[fld]; } + data.variables = ToJSON($scope.parseType, $scope.smartinventory_variables, true); + + let decodedHostFilter = decodeURIComponent($scope.smart_hosts.host_filter); + decodedHostFilter = decodedHostFilter.replace(/__icontains_DEFAULT/g, "__icontains"); + decodedHostFilter = decodedHostFilter.replace(/__search_DEFAULT/g, "__search"); + data.host_filter = decodedHostFilter; + + data.kind = "smart"; + Rest.setUrl(defaultUrl); Rest.post(data) .success(function(data) { var inventory_id = data.id; Wait('stop'); - $location.path('/inventories/' + inventory_id); + $state.go('inventories.editSmartInventory', {smartinventory_id: inventory_id}, {reload: true}); }) .error(function(data, status) { ProcessErrors($scope, data, status, form, { @@ -95,12 +97,12 @@ function SmartInventoryAdd($scope, $location, }; $scope.formCancel = function() { - $state.go('hosts'); + $state.go('inventories'); }; } export default ['$scope', '$location', - 'GenerateForm', 'SmartInventoryForm', 'rbacUiControlService', 'Rest', 'Alert', + 'GenerateForm', 'smartInventoryForm', 'rbacUiControlService', 'Rest', 'Alert', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', '$state', SmartInventoryAdd ]; diff --git a/awx/ui/client/src/inventories/hosts/smart-inventory/edit/main.js b/awx/ui/client/src/inventories/smart-inventory/edit/main.js similarity index 100% rename from awx/ui/client/src/inventories/hosts/smart-inventory/edit/main.js rename to awx/ui/client/src/inventories/smart-inventory/edit/main.js diff --git a/awx/ui/client/src/inventories/smart-inventory/edit/smart-inventory-edit.controller.js b/awx/ui/client/src/inventories/smart-inventory/edit/smart-inventory-edit.controller.js new file mode 100644 index 0000000000..637110dd0e --- /dev/null +++ b/awx/ui/client/src/inventories/smart-inventory/edit/smart-inventory-edit.controller.js @@ -0,0 +1,103 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +function SmartInventoryEdit($scope, $location, + $stateParams, InventoryForm, Rest, ProcessErrors, + ClearScope, GetBasePath, ParseTypeChange, Wait, ToJSON, + ParseVariableString, $state, OrgAdminLookup, resourceData, $rootScope) { + + // Inject dynamic view + var defaultUrl = GetBasePath('inventory'), + form = InventoryForm, + inventory_id = $stateParams.smartinventory_id, + inventoryData = resourceData.data; + + ClearScope(); + init(); + + function init() { + ClearScope(); + form.formLabelSize = null; + form.formFieldSize = null; + $scope.inventory_id = inventory_id; + + $scope = angular.extend($scope, inventoryData); + + $scope.smartinventory_variables = inventoryData.variables === null || inventoryData.variables === '' ? '---' : ParseVariableString(inventoryData.variables); + $scope.organization_name = inventoryData.summary_fields.organization.name; + + $scope.$watch('inventory_obj.summary_fields.user_capabilities.edit', function(val) { + if (val === false) { + $scope.canAdd = false; + } + }); + + $scope.parseType = 'yaml'; + + $rootScope.$on('$stateChangeSuccess', function(event, toState) { + if(toState.name === 'inventories.editSmartInventory') { + ParseTypeChange({ + scope: $scope, + variable: 'smartinventory_variables', + parse_variable: 'parseType', + field_id: 'smartinventory_smartinventory_variables' + }); + } + }); + + OrgAdminLookup.checkForAdminAccess({organization: inventoryData.organization}) + .then(function(canEditOrg){ + $scope.canEditOrg = canEditOrg; + }); + + $scope.inventory_obj = inventoryData; + $rootScope.breadcrumb.inventory_name = inventoryData.name; + + $scope.smart_hosts = { + host_filter: encodeURIComponent($scope.host_filter) + }; + } + + // Save + $scope.formSave = function() { + Wait('start'); + + let fld, data = {}; + + for (fld in form.fields) { + data[fld] = $scope[fld]; + } + + data.variables = ToJSON($scope.parseType, $scope.smartinventory_variables, true); + data.host_filter = decodeURIComponent($scope.smart_hosts.host_filter); + data.kind = "smart"; + + Rest.setUrl(defaultUrl + inventory_id + '/'); + Rest.put(data) + .success(function() { + Wait('stop'); + $state.go($state.current, {}, { reload: true }); + }) + .error(function(data, status) { + ProcessErrors($scope, data, status, form, { + hdr: 'Error!', + msg: 'Failed to update inventory. PUT returned status: ' + status + }); + }); + }; + + $scope.formCancel = function() { + $state.go('inventories'); + }; + +} + +export default [ '$scope', '$location', + '$stateParams', 'InventoryForm', 'Rest', + 'ProcessErrors', 'ClearScope', 'GetBasePath', 'ParseTypeChange', 'Wait', + 'ToJSON', 'ParseVariableString', + '$state', 'OrgAdminLookup', 'resourceData', '$rootScope', SmartInventoryEdit +]; diff --git a/awx/ui/client/src/inventories/hosts/smart-inventory/main.js b/awx/ui/client/src/inventories/smart-inventory/main.js similarity index 51% rename from awx/ui/client/src/inventories/hosts/smart-inventory/main.js rename to awx/ui/client/src/inventories/smart-inventory/main.js index a560ac0459..9d71393069 100644 --- a/awx/ui/client/src/inventories/hosts/smart-inventory/main.js +++ b/awx/ui/client/src/inventories/smart-inventory/main.js @@ -6,15 +6,15 @@ import smartInventoryAdd from './add/main'; import smartInventoryEdit from './edit/main'; - import SmartInventoryForm from './smart-inventory.form'; - import dynamicInventoryHostFilter from './dynamic-inventory-host-filter/dynamic-inventory-host-filter.directive'; - import hostFilterModal from './dynamic-inventory-host-filter/host-filter-modal/host-filter-modal.directive'; + import smartInventoryForm from './smart-inventory.form'; + import smartInventoryHostFilter from './smart-inventory-host-filter/smart-inventory-host-filter.directive'; + import hostFilterModal from './smart-inventory-host-filter/host-filter-modal/host-filter-modal.directive'; export default angular.module('smartInventory', [ smartInventoryAdd.name, smartInventoryEdit.name ]) - .factory('SmartInventoryForm', SmartInventoryForm) - .directive('dynamicInventoryHostFilter', dynamicInventoryHostFilter) + .factory('smartInventoryForm', smartInventoryForm) + .directive('smartInventoryHostFilter', smartInventoryHostFilter) .directive('hostFilterModal', hostFilterModal); diff --git a/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal.directive.js b/awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.directive.js similarity index 92% rename from awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal.directive.js rename to awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.directive.js index 6a29ec99a2..b9e03ba185 100644 --- a/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal.directive.js +++ b/awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.directive.js @@ -4,7 +4,7 @@ export default ['templateUrl', function(templateUrl) { scope: { hostFilter: '=' }, - templateUrl: templateUrl('inventories/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal'), + templateUrl: templateUrl('inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal'), link: function(scope, element) { $('#host-filter-modal').on('hidden.bs.modal', function () { @@ -44,7 +44,7 @@ export default ['templateUrl', function(templateUrl) { let hostList = _.cloneDeep(HostsList); delete hostList.fields.toggleHost; delete hostList.fields.active_failures; - delete hostList.fields.inventory_name; + delete hostList.fields.inventory; let html = GenerateList.build({ list: hostList, input_type: 'foobar', diff --git a/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html b/awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html similarity index 100% rename from awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html rename to awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html diff --git a/awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.controller.js b/awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.controller.js new file mode 100644 index 0000000000..73b20ada75 --- /dev/null +++ b/awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.controller.js @@ -0,0 +1,32 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default ['$scope', 'QuerySet', + function($scope, qs) { + $scope.hostFilterTags = []; + + $scope.$watch('hostFilter', function(){ + $scope.hostFilterTags = []; + + if($scope.hostFilter && $scope.hostFilter !== '') { + let hostFilterCopy = angular.copy($scope.hostFilter); + + let searchParam = hostFilterCopy.host_filter.split('%20and%20'); + delete hostFilterCopy.host_filter; + + $.each(searchParam, function(index, param) { + let paramParts = decodeURIComponent(param).split(/=(.+)/); + paramParts[0] = paramParts[0].replace(/__icontains(_DEFAULT)?/g, ""); + paramParts[0] = paramParts[0].replace(/__search(_DEFAULT)?/g, ""); + let reconstructedSearchString = qs.decodeParam(paramParts[1], paramParts[0]); + $scope.hostFilterTags.push(reconstructedSearchString); + }); + + $scope.hostFilterTags = $scope.hostFilterTags.concat(qs.stripDefaultParams(hostFilterCopy)); + } + }); + } +]; diff --git a/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.directive.js b/awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.directive.js similarity index 68% rename from awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.directive.js rename to awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.directive.js index 7d336abae7..1fe3abeb37 100644 --- a/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.directive.js +++ b/awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.directive.js @@ -4,7 +4,7 @@ * All Rights Reserved *************************************************/ -import dynamicInventoryHostFilterController from './dynamic-inventory-host-filter.controller'; +import smartInventoryHostFilterController from './smart-inventory-host-filter.controller'; export default ['templateUrl', '$compile', function(templateUrl, $compile) { @@ -13,8 +13,8 @@ export default ['templateUrl', '$compile', hostFilter: '=' }, restrict: 'E', - templateUrl: templateUrl('inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter'), - controller: dynamicInventoryHostFilterController, + templateUrl: templateUrl('inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter'), + controller: smartInventoryHostFilterController, link: function(scope) { scope.openHostFilterModal = function() { $('#content-container').append($compile('')(scope)); diff --git a/awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.partial.html b/awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.partial.html new file mode 100644 index 0000000000..daf7a34cc6 --- /dev/null +++ b/awx/ui/client/src/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.partial.html @@ -0,0 +1,12 @@ +
+ + + + + + {{tag}} + + +
diff --git a/awx/ui/client/src/inventories/smart-inventory/smart-inventory-hosts.route.js b/awx/ui/client/src/inventories/smart-inventory/smart-inventory-hosts.route.js new file mode 100644 index 0000000000..c6b0c01f28 --- /dev/null +++ b/awx/ui/client/src/inventories/smart-inventory/smart-inventory-hosts.route.js @@ -0,0 +1,63 @@ +import { N_ } from '../../i18n'; + +export default { + name: "inventories.editSmartInventory.hosts", + url: "/hosts?{host_search:queryset}", + params: { + host_search: { + value: { + page_size: "20", + order_by: "name" + }, + dynamic: true, + squash:"" + } + }, + ncyBreadcrumb: { + label: N_("HOSTS") + }, + views: { + 'related': { + templateProvider: function(ListDefinition, generateList) { + let list = _.cloneDeep(ListDefinition); + let html = generateList.build({ + list: list, + mode: 'edit' + }); + return html; + }, + controller: 'RelatedHostListController' + } + }, + resolve: { + ListDefinition: ['RelatedHostsListDefinition', '$stateParams', 'GetBasePath', (RelatedHostsListDefinition, $stateParams, GetBasePath) => { + let list = _.cloneDeep(RelatedHostsListDefinition); + list.basePath = GetBasePath('inventory') + $stateParams.smartinventory_id + '/hosts'; + delete list.actions.create; + 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 }); + } + return qs.search(path, $stateParams[`${list.iterator}_search`]); + } + ], + hostsUrl: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) { + return InventoryManageService.rootHostsUrl($stateParams.smartinventory_id); + }], + hostsDataset: ['ListDefinition', 'QuerySet', '$stateParams', 'hostsUrl', (list, qs, $stateParams, hostsUrl) => { + let path = hostsUrl; + return qs.search(path, $stateParams[`${list.iterator}_search`]); + }], + inventoryData: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) { + return InventoryManageService.getInventory($stateParams.smartinventory_id).then(res => res.data); + }] + } +}; diff --git a/awx/ui/client/src/inventories/smart-inventory/smart-inventory.form.js b/awx/ui/client/src/inventories/smart-inventory/smart-inventory.form.js new file mode 100644 index 0000000000..de06dc4a6e --- /dev/null +++ b/awx/ui/client/src/inventories/smart-inventory/smart-inventory.form.js @@ -0,0 +1,169 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +export default ['i18n', 'InventoryCompletedJobsList', function(i18n, InventoryCompletedJobsList) { + + var completed_jobs_object = { + name: 'completed_jobs', + index: false, + basePath: "unified_jobs", + title: i18n._('Completed Jobs'), + iterator: 'completed_job', + generateList: true, + skipGenerator: true, + search: { + "or__job__inventory": '' + }, + ngClick: "$state.go('inventories.editSmartInventory.completed_jobs')" + }; + + let clone = _.clone(InventoryCompletedJobsList); + completed_jobs_object = angular.extend(clone, completed_jobs_object); + + return { + + addTitle: i18n._('NEW SMART INVENTORY'), + editTitle: '{{ name }}', + name: 'smartinventory', + basePath: 'inventory', + breadcrumbName: 'SMART INVENTORY', + stateTree: 'inventories', + activeEditState: 'inventories.editSmartInventory', + detailsClick: "$state.go('inventories.editSmartInventory')", + + fields: { + name: { + label: i18n._('Name'), + type: 'text', + required: true, + capitalize: false, + ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' + }, + 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" + }, + ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd) || !canEditOrg', + awLookupWhen: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd) && canEditOrg' + }, + smart_hosts: { + label: i18n._('Smart Hosts'), + type: 'custom', + control: '', + basePath: 'hosts', + list: 'HostsList', + sourceModel: 'host', + sourceField: 'name', + required: true, + class: 'Form-formGroup--fullWidth' + }, + smartinventory_variables: { + label: i18n._('Variables'), + type: 'textarea', + class: 'Form-formGroup--fullWidth', + 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" + + "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + + "YAML:
\n" + + "
---
somevar: somevalue
password: magic
\n" + + '

' + i18n.sprintf(i18n._('View JSON examples at %s'), 'www.json.org') + '

' + + '

' + i18n.sprintf(i18n._('View YAML examples at %s'), 'docs.ansible.com') + '

', + dataTitle: i18n._('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()', + ngShow: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' + }, + save: { + ngClick: 'formSave()', + ngDisabled: true, + ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' + } + }, + related: { + permissions: { + name: 'permissions', + awToolTip: i18n._('Please save before assigning permissions'), + dataPlacement: 'top', + basePath: 'api/v1/inventories/{{$stateParams.smartinventory_id}}/access_list/', + type: 'collection', + title: i18n._('Permissions'), + iterator: 'permission', + index: false, + open: false, + search: { + order_by: 'username' + }, + actions: { + add: { + label: i18n._('Add'), + ngClick: "$state.go('.add')", + awToolTip: i18n._('Add a permission'), + actionClass: 'btn List-buttonSubmit', + buttonContent: '+ ADD', + ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' + + } + }, + fields: { + username: { + key: true, + label: i18n._('User'), + linkBase: 'users', + class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4' + }, + 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', + } + }, + ngClick: "$state.go('inventories.editSmartInventory.permissions');" + }, + hosts: { + name: 'hosts', + include: "RelatedHostsListDefinition", + title: i18n._('Hosts'), + iterator: 'host', + ngClick: "$state.go('inventories.editSmartInventory.hosts');", + skipGenerator: true + }, + completed_jobs: completed_jobs_object + } + + }; + }]; diff --git a/awx/ui/client/src/inventories/sources/list/sources-list.controller.js b/awx/ui/client/src/inventories/sources/list/sources-list.controller.js index 0e09051f16..d85175754a 100644 --- a/awx/ui/client/src/inventories/sources/list/sources-list.controller.js +++ b/awx/ui/client/src/inventories/sources/list/sources-list.controller.js @@ -95,7 +95,7 @@ $state.go('inventories.edit.inventory_sources.edit', {inventory_source_id: id}); }; $scope.deleteSource = function(inventory_source){ - var body = '
Are you sure you want to permanently delete the inventory source below from the inventory? Hosts generated by this inventory source will also be deleted.
' + $filter('sanitize')(inventory_source.name) + '
'; + var body = '
Are you sure you want to permanently delete the inventory source below from the inventory?
' + $filter('sanitize')(inventory_source.name) + '
'; var action = function(){ delete $rootScope.promptActionBtnClass; Wait('start'); diff --git a/awx/ui/client/src/inventories/add/inventory-add.controller.js b/awx/ui/client/src/inventories/standard/add/inventory-add.controller.js similarity index 100% rename from awx/ui/client/src/inventories/add/inventory-add.controller.js rename to awx/ui/client/src/inventories/standard/add/inventory-add.controller.js diff --git a/awx/ui/client/src/inventories/add/main.js b/awx/ui/client/src/inventories/standard/add/main.js similarity index 100% rename from awx/ui/client/src/inventories/add/main.js rename to awx/ui/client/src/inventories/standard/add/main.js diff --git a/awx/ui/client/src/inventories/edit/inventory-edit.controller.js b/awx/ui/client/src/inventories/standard/edit/inventory-edit.controller.js similarity index 99% rename from awx/ui/client/src/inventories/edit/inventory-edit.controller.js rename to awx/ui/client/src/inventories/standard/edit/inventory-edit.controller.js index 20c039216d..205b483779 100644 --- a/awx/ui/client/src/inventories/edit/inventory-edit.controller.js +++ b/awx/ui/client/src/inventories/standard/edit/inventory-edit.controller.js @@ -30,9 +30,7 @@ function InventoriesEdit($scope, $location, $scope = angular.extend($scope, inventoryData); $scope.organization_name = inventoryData.summary_fields.organization.name; - $scope.inventory_variables = inventoryData.variables === null || inventoryData.variables === '' ? '---' : ParseVariableString(inventoryData.variables); - $scope.parseType = 'yaml'; $rootScope.$on('$stateChangeSuccess', function(event, toState) { diff --git a/awx/ui/client/src/inventories/edit/main.js b/awx/ui/client/src/inventories/standard/edit/main.js similarity index 100% rename from awx/ui/client/src/inventories/edit/main.js rename to awx/ui/client/src/inventories/standard/edit/main.js diff --git a/awx/ui/client/src/inventories/inventory.form.js b/awx/ui/client/src/inventories/standard/inventory.form.js similarity index 99% rename from awx/ui/client/src/inventories/inventory.form.js rename to awx/ui/client/src/inventories/standard/inventory.form.js index 08e3d8df48..a49af35a00 100644 --- a/awx/ui/client/src/inventories/inventory.form.js +++ b/awx/ui/client/src/inventories/standard/inventory.form.js @@ -31,7 +31,7 @@ function(i18n, InventoryCompletedJobsList) { return { - addTitle: i18n._('NEW INVENTORY'), + addTitle: i18n._('NEW STANDARD INVENTORY'), editTitle: '{{ inventory_name }}', name: 'inventory', basePath: 'inventory', diff --git a/awx/ui/client/src/inventories/standard/main.js b/awx/ui/client/src/inventories/standard/main.js new file mode 100644 index 0000000000..1b2f8d8be5 --- /dev/null +++ b/awx/ui/client/src/inventories/standard/main.js @@ -0,0 +1,16 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + import inventoryAdd from './add/main'; + import inventoryEdit from './edit/main'; + import InventoryForm from './inventory.form'; + +export default +angular.module('standardInventory', [ + inventoryAdd.name, + inventoryEdit.name + ]) + .factory('InventoryForm', InventoryForm); diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js index 6e48f7ea2b..973db9b6d8 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -55,6 +55,14 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy return null; } } + else if(key === 'inventory') { + if($scope.job.summary_fields.inventory && $scope.job.summary_fields.inventory.kind && $scope.job.summary_fields.inventory.kind === 'smart') { + return '/#/inventories/smart_inventory/' + $scope.job.summary_fields.inventory.id; + } + else { + return '/#/inventories/standard_inventory/' + $scope.job.summary_fields.inventory.id; + } + } else { if ($scope.job.related[key]) { return '/#/' + $scope.job.related[key] diff --git a/awx/ui/client/src/scheduler/schedulerList.controller.js b/awx/ui/client/src/scheduler/schedulerList.controller.js index 819615bcb6..4604a04778 100644 --- a/awx/ui/client/src/scheduler/schedulerList.controller.js +++ b/awx/ui/client/src/scheduler/schedulerList.controller.js @@ -163,9 +163,9 @@ export default [ Rest.setUrl(schedule.related.unified_job_template); Rest.get().then( (res) => { deferred.resolve({ - name: 'inventoryManage.editGroup.schedules.edit', + name: 'inventories.edit.inventory_sources.edit.schedules.edit', params: { - group_id: res.data.group, + inventory_source_id: res.data.id, inventory_id: res.data.inventory, schedule_id: schedule.id, } @@ -198,23 +198,8 @@ export default [ } function routeToScheduleForm(schedule){ - - buildStateMap(schedule).then( (state) =>{ - $state.go(state.name, state.params).catch((err) =>{ - // stateDefinition.lazyLoad'd state name matcher is not configured to match inventoryManage.* state names - // However, the stateDefinition.lazyLoad url matcher will load the correct tree. - // Expected error: - // Transition rejection error - // type: 4 // SUPERSEDED = 2, ABORTED = 3, INVALID = 4, IGNORED = 5, ERROR = 6 - // detail : "Could not resolve 'inventoryManage.editGroup.schedules.edit' from state 'jobs.schedules'" - // message: "This transition is invalid" - if (err.type === 4 && err.detail.includes('inventoryManage.editGroup.schedules.edit')){ - $location.path(`/inventories/${state.params.inventory_id}/manage/edit-group/${state.params.group_id}/schedules/${state.params.schedule_id}`); - } - else { - throw err; - } - }); + buildStateMap(schedule).then((state) =>{ + $state.go(state.name, state.params); }); } }; diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index 5206f8221e..b247255dc5 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -1464,6 +1464,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html+= "{{external_account}}"; } + if(this.form.name === "smartinventory"){ + html+= "" + i18n._("Smart Inventory") + ""; + } html += "\n"; html += "
"; if(this.form.headerFields){ diff --git a/awx/ui/client/src/shared/list-generator/list-generator.factory.js b/awx/ui/client/src/shared/list-generator/list-generator.factory.js index eade5fe6da..d9f2a1f9b2 100644 --- a/awx/ui/client/src/shared/list-generator/list-generator.factory.js +++ b/awx/ui/client/src/shared/list-generator/list-generator.factory.js @@ -198,15 +198,13 @@ export default ['$compile', 'Attr', 'Icon', list.searchSize = 'col-lg-7 col-md-12 col-sm-12 col-xs-12'; } if (options.showSearch === undefined || options.showSearch === true) { - let nonstandardSearchParam = list.nonstandardSearchParam && list.nonstandardSearchParam.param ? list.nonstandardSearchParam.param : undefined; - let nonstandardSearchParamRoot = list.nonstandardSearchParam && list.nonstandardSearchParam.root ? list.nonstandardSearchParam.root : undefined; + let singleSearchParam = list.singleSearchParam && list.singleSearchParam.param ? `single-search-param="${list.singleSearchParam.param}"` : ''; html += `
{ - if(step && step.params && step.params.hasOwnProperty(`${$scope.iterator}_search`)){ - return step.params.hasOwnProperty(`${$scope.iterator}_search`); - } - }).params[`${$scope.iterator}_search`].config.value; - } - - if($scope.querySet) { - queryset = _.cloneDeep($scope.querySet); - } - else { - queryset = $state.params[`${$scope.iterator}_search`]; - } - - // build $scope.tags from $stateParams.QuerySet, build fieldset key init(); function init() { + + if($scope.defaultParams) { + defaults = $scope.defaultParams; + } + else { + // steps through the current tree of $state configurations, grabs default search params + defaults = _.find($state.$current.path, (step) => { + if(step && step.params && step.params.hasOwnProperty(`${$scope.iterator}_search`)){ + return step.params.hasOwnProperty(`${$scope.iterator}_search`); + } + }).params[`${$scope.iterator}_search`].config.value; + } + + if($scope.querySet) { + queryset = _.cloneDeep($scope.querySet); + } + else { + queryset = $state.params[`${$scope.iterator}_search`]; + } + path = GetBasePath($scope.basePath) || $scope.basePath; - $scope.searchTags = qs.stripDefaultParams(queryset, defaults); + generateSearchTags(); qs.initFieldset(path, $scope.djangoModel).then((data) => { $scope.models = data.models; $scope.options = data.options.data; @@ -69,7 +69,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' }); $scope.searchTerm = null; - $scope.searchTags = qs.stripDefaultParams(queryset, defaults); + generateSearchTags(); } } }); @@ -86,7 +86,53 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' }); } - function setDefaults(term) { + function generateSearchTags() { + $scope.searchTags = []; + + let querysetCopy = angular.copy(queryset); + + if($scope.singleSearchParam && querysetCopy[$scope.singleSearchParam]) { + let searchParam = querysetCopy[$scope.singleSearchParam].split('%20and%20'); + delete querysetCopy[$scope.singleSearchParam]; + + $.each(searchParam, function(index, param) { + let paramParts = decodeURIComponent(param).split(/=(.+)/); + let reconstructedSearchString = qs.decodeParam(paramParts[1], paramParts[0]); + $scope.searchTags.push(reconstructedSearchString); + }); + } + + $scope.searchTags = $scope.searchTags.concat(qs.stripDefaultParams(querysetCopy, defaults)); + } + + function revertSearch(queryToBeRestored) { + queryset = queryToBeRestored; + // https://ui-router.github.io/docs/latest/interfaces/params.paramdeclaration.html#dynamic + // This transition will not reload controllers/resolves/views + // but will register new $stateParams[$scope.iterator + '_search'] terms + if(!$scope.querySet) { + $state.go('.', { + [$scope.iterator + '_search']: queryset }, {notify: false}); + } + qs.search(path, queryset).then((res) => { + if($scope.querySet) { + $scope.querySet = queryset; + } + $scope.dataset = res.data; + $scope.collection = res.data.results; + }); + + $scope.searchTerm = null; + + generateSearchTags(); + } + + function searchWithoutKey(term) { + if($scope.singleSearchParam) { + return { + [$scope.singleSearchParam]: encodeURIComponent("search=" + term) + }; + } return { search: encodeURIComponent(term) }; @@ -96,100 +142,8 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' $scope.showKeyPane = !$scope.showKeyPane; }; - $scope.clearAll = function(){ - let cleared = _.cloneDeep(defaults); - delete cleared.page; - queryset = cleared; - if(!$scope.querySet) { - $state.go('.', {[$scope.iterator + '_search']: queryset}, {notify: false}); - } - qs.search(path, queryset).then((res) => { - if($scope.querySet) { - $scope.querySet = queryset; - } - $scope.dataset = res.data; - $scope.collection = res.data.results; - }); - $scope.searchTags = qs.stripDefaultParams(queryset, defaults); - }; - - // remove tag, merge new queryset, $state.go - $scope.remove = function(index) { - let tagToRemove = $scope.searchTags.splice(index, 1)[0], - termParts = SmartSearchService.splitTermIntoParts(tagToRemove), - removed; - - let removeFromQuerySet = function(set) { - _.each(removed, (value, key) => { - if (Array.isArray(set[key])){ - _.remove(set[key], (item) => item === value); - // If the array is now empty, remove that key - if(set[key].length === 0) { - delete set[key]; - } - } - else { - delete set[key]; - } - }); - }; - - if (termParts.length === 1) { - removed = setDefaults(tagToRemove); - } - else { - let root = termParts[0].split(".")[0].replace(/^-/, ''); - let encodeParams = { - term: tagToRemove - }; - if($scope.models[$scope.list.name]) { - if(_.has($scope.models[$scope.list.name].base, root)) { - if($scope.models[$scope.list.name].base[root].type && $scope.models[$scope.list.name].base[root].type === 'field') { - encodeParams.relatedSearchTerm = true; - } - else { - encodeParams.searchTerm = true; - } - removed = qs.encodeParam(encodeParams); - } - else if(_.contains($scope.models[$scope.list.name].related, root)) { - encodeParams.relatedSearchTerm = true; - removed = qs.encodeParam(encodeParams); - } - else if($scope.nonstandardSearchParam && $scope.nonstandardSearchParamRoot && root === $scope.nonstandardSearchParamRoot) { - removed = qs.encodeParam(encodeParams); - } - else { - removed = setDefaults(termParts[termParts.length-1]); - } - } - else { - removed = setDefaults(termParts[termParts.length-1]); - } - } - removeFromQuerySet(queryset); - if(!$scope.querySet) { - $state.go('.', { - [$scope.iterator + '_search']: queryset }, {notify: false}).then(function(){ - // ISSUE: for some reason deleting a tag from a list in a modal does not - // remove the param from $stateParams. Here we'll manually check to make sure - // that that happened and remove it if it didn't. - - removeFromQuerySet($stateParams[`${$scope.iterator}_search`]); - }); - } - qs.search(path, queryset).then((res) => { - if($scope.querySet) { - $scope.querySet = queryset; - } - $scope.dataset = res.data; - $scope.collection = res.data.results; - }); - $scope.searchTags = qs.stripDefaultParams(queryset, defaults); - }; - // add a search tag, merge new queryset, $state.go() - $scope.add = function(terms) { + $scope.addTerm = function(terms) { let params = {}, origQueryset = _.clone(queryset); @@ -209,40 +163,53 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' } else { if(a) { - return [a,b]; + if($scope.singleSearchParam) { + return a + "%20and%20" + b; + } + else { + return [a,b]; + } } } } - // if only a value is provided, search using default keys - if (termParts.length === 1) { - params = _.merge(params, setDefaults(term), combineSameSearches); - } else { - // Figure out if this is a search term - let root = termParts[0].split(".")[0].replace(/^-/, ''); - if(_.has($scope.models[$scope.list.name].base, root)) { - if($scope.models[$scope.list.name].base[root].type && $scope.models[$scope.list.name].base[root].type === 'field') { + if($scope.singleSearchParam) { + if (termParts.length === 1) { + params = _.merge(params, searchWithoutKey(term), combineSameSearches); + } + else { + params = _.merge(params, qs.encodeParam({term: term, searchTerm: true, singleSearchParam: $scope.singleSearchParam ? $scope.singleSearchParam : false}), combineSameSearches); + } + } + + else { + // if only a value is provided, search using default keys + if (termParts.length === 1) { + params = _.merge(params, searchWithoutKey(term), combineSameSearches); + } else { + // Figure out if this is a search term + let root = termParts[0].split(".")[0].replace(/^-/, ''); + if(_.has($scope.models[$scope.list.name].base, root)) { + if($scope.models[$scope.list.name].base[root].type && $scope.models[$scope.list.name].base[root].type === 'field') { + params = _.merge(params, qs.encodeParam({term: term, relatedSearchTerm: true}), combineSameSearches); + } + else { + params = _.merge(params, qs.encodeParam({term: term, searchTerm: true}), combineSameSearches); + } + } + // The related fields need to also be checked for related searches. + // The related fields for the search are retrieved from the API + // options endpoint, and are stored in the $scope.model. FYI, the + // Django search model is what sets the related fields on the model. + else if(_.contains($scope.models[$scope.list.name].related, root)) { params = _.merge(params, qs.encodeParam({term: term, relatedSearchTerm: true}), combineSameSearches); } + // Its not a search term or a related search term - treat it as a string else { - params = _.merge(params, qs.encodeParam({term: term, searchTerm: true}), combineSameSearches); + params = _.merge(params, searchWithoutKey(term), combineSameSearches); } - } - // The related fields need to also be checked for related searches. - // The related fields for the search are retrieved from the API - // options endpoint, and are stored in the $scope.model. FYI, the - // Django search model is what sets the related fields on the model. - else if(_.contains($scope.models[$scope.list.name].related, root)) { - params = _.merge(params, qs.encodeParam({term: term, relatedSearchTerm: true}), combineSameSearches); - } - else if($scope.nonstandardSearchParam && $scope.nonstandardSearchParamRoot && root === $scope.nonstandardSearchParamRoot) { - params = _.merge(params, qs.encodeParam({term: term, searchTerm: true}), combineSameSearches); - } - // Its not a search term or a related search term - treat it as a string - else { - params = _.merge(params, setDefaults(term), combineSameSearches); - } + } } }); @@ -254,6 +221,14 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' return object[key]; } else { + if($scope.singleSearchParam) { + if(!object[key]) { + return sourceValue; + } + else { + return object[key] + "%20and%20" + sourceValue; + } + } // Start the array of keys return [object[key], sourceValue]; } @@ -287,22 +262,90 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' $scope.collection = res.data.results; }) .catch(function() { - $scope.revertSearch(origQueryset); + revertSearch(origQueryset); }); $scope.searchTerm = null; - $scope.searchTags = qs.stripDefaultParams(queryset, defaults); + + generateSearchTags(); } }; - $scope.revertSearch = function(queryToBeRestored) { - queryset = queryToBeRestored; - // https://ui-router.github.io/docs/latest/interfaces/params.paramdeclaration.html#dynamic - // This transition will not reload controllers/resolves/views - // but will register new $stateParams[$scope.iterator + '_search'] terms + // remove tag, merge new queryset, $state.go + $scope.removeTerm = function(index) { + let tagToRemove = $scope.searchTags.splice(index, 1)[0], + termParts = SmartSearchService.splitTermIntoParts(tagToRemove), + removed; + + let removeFromQuerySet = function(set) { + _.each(removed, (value, key) => { + if (Array.isArray(set[key])){ + _.remove(set[key], (item) => item === value); + // If the array is now empty, remove that key + if(set[key].length === 0) { + delete set[key]; + } + } + else { + if($scope.singleSearchParam && set[$scope.singleSearchParam] && set[$scope.singleSearchParam].includes("%20and%20")) { + let searchParamParts = set[$scope.singleSearchParam].split("%20and%20"); + var index = searchParamParts.indexOf(value); + if (index !== -1) { + searchParamParts.splice(index, 1); + } + set[$scope.singleSearchParam] = searchParamParts.join("%20and%20"); + } + else { + delete set[key]; + } + } + }); + }; + + if (termParts.length === 1) { + removed = searchWithoutKey(tagToRemove); + } + else { + let root = termParts[0].split(".")[0].replace(/^-/, ''); + let encodeParams = { + term: tagToRemove, + singleSearchParam: $scope.singleSearchParam ? $scope.singleSearchParam : false + }; + if($scope.models[$scope.list.name]) { + if($scope.singleSearchParam) { + removed = qs.encodeParam(encodeParams); + } + else if(_.has($scope.models[$scope.list.name].base, root)) { + if($scope.models[$scope.list.name].base[root].type && $scope.models[$scope.list.name].base[root].type === 'field') { + encodeParams.relatedSearchTerm = true; + } + else { + encodeParams.searchTerm = true; + } + removed = qs.encodeParam(encodeParams); + } + else if(_.contains($scope.models[$scope.list.name].related, root)) { + encodeParams.relatedSearchTerm = true; + removed = qs.encodeParam(encodeParams); + } + else { + removed = searchWithoutKey(termParts[termParts.length-1]); + } + } + else { + removed = searchWithoutKey(termParts[termParts.length-1]); + } + } + removeFromQuerySet(queryset); if(!$scope.querySet) { $state.go('.', { - [$scope.iterator + '_search']: queryset }, {notify: false}); + [$scope.iterator + '_search']: queryset }, {notify: false}).then(function(){ + // ISSUE: for some reason deleting a tag from a list in a modal does not + // remove the param from $stateParams. Here we'll manually check to make sure + // that that happened and remove it if it didn't. + + removeFromQuerySet($stateParams[`${$scope.iterator}_search`]); + }); } qs.search(path, queryset).then((res) => { if($scope.querySet) { @@ -312,7 +355,23 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' $scope.collection = res.data.results; }); - $scope.searchTerm = null; + generateSearchTags(); + }; + + $scope.clearAllTerms = function(){ + let cleared = _.cloneDeep(defaults); + delete cleared.page; + queryset = cleared; + if(!$scope.querySet) { + $state.go('.', {[$scope.iterator + '_search']: queryset}, {notify: false}); + } + qs.search(path, queryset).then((res) => { + if($scope.querySet) { + $scope.querySet = queryset; + } + $scope.dataset = res.data; + $scope.collection = res.data.results; + }); $scope.searchTags = qs.stripDefaultParams(queryset, defaults); }; } diff --git a/awx/ui/client/src/shared/smart-search/smart-search.directive.js b/awx/ui/client/src/shared/smart-search/smart-search.directive.js index 193d772ed7..30599a1b3f 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.directive.js +++ b/awx/ui/client/src/shared/smart-search/smart-search.directive.js @@ -18,8 +18,7 @@ export default ['templateUrl', disableSearch: '=', defaultParams: '=', querySet: '=', - nonstandardSearchParam: '@', - nonstandardSearchParamRoot: '@' + singleSearchParam: '@' }, controller: 'SmartSearchController', templateUrl: templateUrl('shared/smart-search/smart-search') diff --git a/awx/ui/client/src/shared/smart-search/smart-search.partial.html b/awx/ui/client/src/shared/smart-search/smart-search.partial.html index 9f589a9d1d..f8a26de003 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.partial.html +++ b/awx/ui/client/src/shared/smart-search/smart-search.partial.html @@ -3,11 +3,11 @@
- + -
+
@@ -20,14 +20,14 @@
-
+
{{tag}}
- CLEAR ALL + CLEAR ALL
diff --git a/awx/ui/client/src/standard-out/standard-out.controller.js b/awx/ui/client/src/standard-out/standard-out.controller.js index b3ad1ba2ae..4206ffad15 100644 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ b/awx/ui/client/src/standard-out/standard-out.controller.js @@ -54,7 +54,17 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams, $scope.project_name = (data.summary_fields.project) ? data.summary_fields.project.name : ''; $scope.inventory_name = (data.summary_fields.inventory) ? data.summary_fields.inventory.name : ''; $scope.job_template_url = '/#/templates/' + data.unified_job_template; - $scope.inventory_url = ($scope.inventory_name && data.inventory) ? '/#/inventories/' + data.inventory : ''; + if($scope.inventory_name && data.inventory && data.summary_fields.inventory && data.summary_fields.inventory.kind) { + if(data.summary_fields.inventory.kind === '') { + $scope.inventory_url = '/#/inventories/standard_inventory' + data.inventory; + } + else if(data.summary_fields.inventory.kind === 'smart') { + $scope.inventory_url = '/#/inventories/smart_inventory' + data.inventory; + } + } + else { + $scope.inventory_url = ''; + } $scope.project_url = ($scope.project_name && data.project) ? '/#/projects/' + data.project : ''; $scope.credential_name = (data.summary_fields.credential) ? data.summary_fields.credential.name : ''; $scope.credential_url = (data.credential) ? '/#/credentials/' + data.credential : ''; diff --git a/awx/ui/client/src/templates/labels/labelsList.block.less b/awx/ui/client/src/templates/labels/labelsList.block.less index b384579314..58e020ece8 100644 --- a/awx/ui/client/src/templates/labels/labelsList.block.less +++ b/awx/ui/client/src/templates/labels/labelsList.block.less @@ -8,7 +8,7 @@ } -.LabelList-tagContainer, +.LabelList-tagContainer, .LabelList-seeMoreLess { display: flex; max-width: 200px; @@ -23,6 +23,7 @@ background-color: @default-list-header-bg; margin-right: 5px; max-width: 100%; + display: inline-block; } .LabelList-seeMoreLess { @@ -89,4 +90,4 @@ .List-tableCell.col-md-4 { padding-left: 40px; } -} \ No newline at end of file +} diff --git a/awx/ui/tests/spec/job-results/job-results.controller-test.js b/awx/ui/tests/spec/job-results/job-results.controller-test.js index e9a1629143..971863577f 100644 --- a/awx/ui/tests/spec/job-results/job-results.controller-test.js +++ b/awx/ui/tests/spec/job-results/job-results.controller-test.js @@ -10,9 +10,15 @@ describe('Controller: jobResultsController', () => { statusSocket = function() { var fn = function() {}; return fn; - } + }; jobData = { - related: {} + related: {}, + summary_fields: { + inventory: { + id: null, + kind: '' + } + } }; jobDataOptions = { actions: { @@ -85,7 +91,7 @@ describe('Controller: jobResultsController', () => { $provide.value('ParseVariableString', ParseVariableString); $provide.value('jobResultsService', jobResultsService); $provide.value('eventQueue', eventQueue); - $provide.value('Dataset', Dataset) + $provide.value('Dataset', Dataset); $provide.value('Rest', Rest); $provide.value('$state', $state); $provide.value('QuerySet', QuerySet); @@ -140,7 +146,7 @@ describe('Controller: jobResultsController', () => { eventQueue.populate.and .returnValue(populateResolve); - jobResultsService.getJobData = function(blah) { + jobResultsService.getJobData = function() { var deferred = $q.defer(); deferred.resolve({}); return deferred.promise; @@ -200,12 +206,17 @@ describe('Controller: jobResultsController', () => { "network_credential": "api/v1/credentials/14", }; + jobData.summary_fields.inventory = { + id: 12, + kind: '' + }; + bootstrapTest(); }); it('should transform related links and set to scope var', () => { expect($scope.created_by_link).toBe('/#/users/12'); - expect($scope.inventory_link).toBe('/#/inventories/12'); + expect($scope.inventory_link).toBe('/#/inventories/standard_inventory/12'); expect($scope.project_link).toBe('/#/projects/12'); expect($scope.machine_credential_link).toBe('/#/credentials/12'); expect($scope.cloud_credential_link).toBe('/#/credentials/13');