From cc80cd854995e5e42721979eb00d5b152e2de634 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Tue, 18 Apr 2017 12:20:58 -0400 Subject: [PATCH] Dynamic inventory add form work --- .../add/smart-inventory-add.controller.js | 2 + ...ynamic-inventory-host-filter.controller.js | 15 +++ ...dynamic-inventory-host-filter.directive.js | 25 ++++ ...dynamic-inventory-host-filter.partial.html | 12 ++ .../host-filter-modal.directive.js | 81 ++++++++++++ .../host-filter-modal.partial.html | 21 +++ .../inventories/hosts/smart-inventory/main.js | 6 +- .../smart-inventory/smart-inventory.form.js | 11 ++ awx/ui/client/src/inventories/main.js | 124 ------------------ .../list-generator/list-generator.factory.js | 8 +- .../shared/smart-search/queryset.service.js | 26 ++++ .../smart-search/smart-search.controller.js | 36 +---- .../src/shared/stateDefinitions.factory.js | 5 + 13 files changed, 216 insertions(+), 156 deletions(-) create mode 100644 awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.controller.js create mode 100644 awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.directive.js create mode 100644 awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.partial.html create mode 100644 awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal.directive.js create mode 100644 awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html diff --git a/awx/ui/client/src/inventories/hosts/smart-inventory/add/smart-inventory-add.controller.js b/awx/ui/client/src/inventories/hosts/smart-inventory/add/smart-inventory-add.controller.js index 5ccac44ddc..32fff4da0a 100644 --- a/awx/ui/client/src/inventories/hosts/smart-inventory/add/smart-inventory-add.controller.js +++ b/awx/ui/client/src/inventories/hosts/smart-inventory/add/smart-inventory-add.controller.js @@ -53,6 +53,8 @@ function SmartInventoryAdd($scope, $location, parse_variable: 'parseType', field_id: 'smartinventory_variables' }); + + $scope.dynamic_hosts = $state.params.hostfilter ? JSON.parse($state.params.hostfilter) : ''; } // Save 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 new file mode 100644 index 0000000000..6a96c0d74c --- /dev/null +++ b/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.controller.js @@ -0,0 +1,15 @@ +/************************************************* + * 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.directive.js b/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.directive.js new file mode 100644 index 0000000000..7d336abae7 --- /dev/null +++ b/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.directive.js @@ -0,0 +1,25 @@ +/************************************************* + * Copyright (c) 2017 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + +import dynamicInventoryHostFilterController from './dynamic-inventory-host-filter.controller'; + +export default ['templateUrl', '$compile', + function(templateUrl, $compile) { + return { + scope: { + hostFilter: '=' + }, + restrict: 'E', + templateUrl: templateUrl('inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter'), + controller: dynamicInventoryHostFilterController, + link: function(scope) { + scope.openHostFilterModal = function() { + $('#content-container').append($compile('')(scope)); + }; + } + }; + } +]; 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 new file mode 100644 index 0000000000..6b3546d2b9 --- /dev/null +++ b/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/dynamic-inventory-host-filter.partial.html @@ -0,0 +1,12 @@ +
+ + + + + + {{tag}} + + +
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/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal.directive.js new file mode 100644 index 0000000000..6a29ec99a2 --- /dev/null +++ b/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal.directive.js @@ -0,0 +1,81 @@ +export default ['templateUrl', function(templateUrl) { + return { + restrict: 'E', + scope: { + hostFilter: '=' + }, + templateUrl: templateUrl('inventories/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal'), + link: function(scope, element) { + + $('#host-filter-modal').on('hidden.bs.modal', function () { + $('#host-filter-modal').off('hidden.bs.modal'); + $(element).remove(); + }); + + scope.showModal = function() { + $('#host-filter-modal').modal('show'); + }; + + scope.destroyModal = function() { + $('#host-filter-modal').modal('hide'); + }; + + }, + controller: ['$scope', 'QuerySet', 'GetBasePath', 'HostsList', '$compile', 'generateList', function($scope, qs, GetBasePath, HostsList, $compile, GenerateList) { + + function init() { + + $scope.host_default_params = { + order_by: 'name', + page_size: 5 + }; + + $scope.host_queryset = _.merge({ + order_by: 'name', + page_size: 5 + }, $scope.hostFilter ? $scope.hostFilter : {}); + + // Fire off the initial search + qs.search(GetBasePath('hosts'), $scope.host_queryset) + .then(res => { + $scope.host_dataset = res.data; + $scope.hosts = $scope.host_dataset.results; + + let hostList = _.cloneDeep(HostsList); + delete hostList.fields.toggleHost; + delete hostList.fields.active_failures; + delete hostList.fields.inventory_name; + let html = GenerateList.build({ + list: hostList, + input_type: 'foobar', + mode: 'lookup' + }); + + $scope.list = hostList; + + $('#foobar').append($compile(html)($scope)); + + $scope.showModal(); + }); + } + + init(); + + $scope.cancelForm = function() { + $scope.destroyModal(); + }; + + $scope.saveForm = function() { + // Strip defaults out of the state params copy + angular.forEach(Object.keys($scope.host_default_params), function(value) { + delete $scope.host_queryset[value]; + }); + + $scope.hostFilter = angular.copy($scope.host_queryset); + + $scope.destroyModal(); + }; + + }] + }; +}]; 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/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html new file mode 100644 index 0000000000..3f5a33e48d --- /dev/null +++ b/awx/ui/client/src/inventories/hosts/smart-inventory/dynamic-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html @@ -0,0 +1,21 @@ + diff --git a/awx/ui/client/src/inventories/hosts/smart-inventory/main.js b/awx/ui/client/src/inventories/hosts/smart-inventory/main.js index 7fa368df70..a560ac0459 100644 --- a/awx/ui/client/src/inventories/hosts/smart-inventory/main.js +++ b/awx/ui/client/src/inventories/hosts/smart-inventory/main.js @@ -7,10 +7,14 @@ 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'; export default angular.module('smartInventory', [ smartInventoryAdd.name, smartInventoryEdit.name ]) - .factory('SmartInventoryForm', SmartInventoryForm); + .factory('SmartInventoryForm', SmartInventoryForm) + .directive('dynamicInventoryHostFilter', dynamicInventoryHostFilter) + .directive('hostFilterModal', hostFilterModal); 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 index a8055d8838..7be1881920 100644 --- 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 @@ -43,6 +43,17 @@ export default ['i18n', 'buildHostListState', function(i18n, buildHostListState) 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', diff --git a/awx/ui/client/src/inventories/main.js b/awx/ui/client/src/inventories/main.js index 0e3532bd2e..c9de579972 100644 --- a/awx/ui/client/src/inventories/main.js +++ b/awx/ui/client/src/inventories/main.js @@ -124,130 +124,6 @@ angular.module('inventory', [ } - // function generateInventoryStates() { - // - // let smartInventoryAdd = { - // name: 'inventories.addSmartInventory', - // url: '/smartinventory', - // form: 'SmartInventoryForm', - // ncyBreadcrumb: { - // label: "CREATE SMART INVENTORY" - // }, - // views: { - // 'form@inventories': { - // templateProvider: function(SmartInventoryForm, GenerateForm) { - // return GenerateForm.buildHTML(SmartInventoryForm, { - // mode: 'add', - // related: false - // }); - // }, - // controller: 'SmartInventoryAddController' - // } - // } - // }; - // - // let smartInventoryAddOrgLookup = { - // searchPrefix: 'organization', - // name: 'inventories.addSmartInventory.organization', - // url: '/organization', - // data: { - // formChildState: true - // }, - // params: { - // organization_search: { - // value: { - // page_size: '5' - // }, - // squash: true, - // dynamic: true - // } - // }, - // ncyBreadcrumb: { - // skip: true - // }, - // views: { - // 'related': { - // templateProvider: function(ListDefinition, generateList) { - // let list_html = generateList.build({ - // mode: 'lookup', - // list: ListDefinition, - // input_type: 'radio' - // }); - // return `${list_html}`; - // - // } - // } - // }, - // resolve: { - // ListDefinition: ['OrganizationList', function(OrganizationList) { - // let list = _.cloneDeep(OrganizationList); - // list.lookupConfirmText = 'SELECT'; - // return list; - // }], - // Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', - // (list, qs, $stateParams, GetBasePath) => { - // let path = GetBasePath(list.name) || GetBasePath(list.basePath); - // return qs.search(path, $stateParams[`${list.iterator}_search`]); - // } - // ] - // }, - // onExit: function($state) { - // if ($state.transition) { - // $('#form-modal').modal('hide'); - // $('.modal-backdrop').remove(); - // $('body').removeClass('modal-open'); - // } - // }, - // }; - // - // let inventories = stateDefinitions.generateTree({ - // parent: 'inventories', // top-most node in the generated tree (will replace this state definition) - // modes: ['add', 'edit'], - // list: 'InventoryList', - // form: 'InventoryForm', - // controllers: { - // list: 'InventoryListController', - // add: 'InventoryAddController', - // edit: 'InventoryEditController' - // }, - // urls: { - // list: '/inventories' - // }, - // ncyBreadcrumb: { - // label: N_('INVENTORIES') - // }, - // views: { - // '@': { - // templateUrl: templateUrl('inventories/inventories') - // }, - // 'list@inventories': { - // templateProvider: function(InventoryList, generateList) { - // let html = generateList.build({ - // list: InventoryList, - // mode: 'edit' - // }); - // return html; - // }, - // controller: 'InventoryListController' - // } - // } - // }); - // - // return Promise.all([ - // inventories - // ]).then((generated) => { - // return { - // states: _.reduce(generated, (result, definition) => { - // return result.concat(definition.states); - // }, [ - // stateExtender.buildDefinition(smartInventoryAdd), - // stateExtender.buildDefinition(smartInventoryAddOrgLookup) - // ]) - // }; - // }); - // - // } - $stateProvider.state({ name: 'hosts', url: '/hosts', 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 ac2802d6f9..78eb66f5b7 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 @@ -316,7 +316,11 @@ export default ['$compile', 'Attr', 'Icon', if (options.mode === 'lookup') { if (options.input_type === "radio") { //added by JT so that lookup forms can be either radio inputs or check box inputs innerTable += ` `; - } else { // its assumed that options.input_type = checkbox + } + else if (options.input_type === "foobar") { + + } + else { // its assumed that options.input_type = checkbox innerTable += ""; @@ -483,7 +487,7 @@ export default ['$compile', 'Attr', 'Icon', if (list.multiSelect) { html += buildSelectAll().prop('outerHTML'); - } else if (options.mode === 'lookup') { + } else if (options.mode === 'lookup' && options.input_type !== 'foobar') { html += ""; } diff --git a/awx/ui/client/src/shared/smart-search/queryset.service.js b/awx/ui/client/src/shared/smart-search/queryset.service.js index c2800f0bf2..f647b48dd4 100644 --- a/awx/ui/client/src/shared/smart-search/queryset.service.js +++ b/awx/ui/client/src/shared/smart-search/queryset.service.js @@ -274,6 +274,32 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear hdr: 'Error!', msg: `Invalid search term entered. GET returned: ${status}` }); + }, + // Removes state definition defaults and pagination terms + stripDefaultParams(params, defaults) { + if(defaults) { + let stripped =_.pick(params, (value, key) => { + // setting the default value of a term to null in a state definition is a very explicit way to ensure it will NEVER generate a search tag, even with a non-default value + return defaults[key] !== value && key !== 'order_by' && key !== 'page' && key !== 'page_size' && defaults[key] !== null; + }); + let strippedCopy = _.cloneDeep(stripped); + if(_.keys(_.pick(defaults, _.keys(strippedCopy))).length > 0){ + for (var key in strippedCopy) { + if (strippedCopy.hasOwnProperty(key)) { + let value = strippedCopy[key]; + if(_.isArray(value)){ + let index = _.indexOf(value, defaults[key]); + value = value.splice(index, 1)[0]; + } + } + } + stripped = strippedCopy; + } + return _(strippedCopy).map(this.decodeParam).flatten().value(); + } + else { + return _(params).map(this.decodeParam).flatten().value(); + } } }; } diff --git a/awx/ui/client/src/shared/smart-search/smart-search.controller.js b/awx/ui/client/src/shared/smart-search/smart-search.controller.js index e008a0db78..a6211f2e56 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.controller.js +++ b/awx/ui/client/src/shared/smart-search/smart-search.controller.js @@ -22,7 +22,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' queryset = _.cloneDeep($scope.querySet); } else { - queryset = $stateParams[`${$scope.iterator}_search`]; + queryset = $state.params[`${$scope.iterator}_search`]; } // build $scope.tags from $stateParams.QuerySet, build fieldset key @@ -30,7 +30,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' function init() { path = GetBasePath($scope.basePath) || $scope.basePath; - $scope.searchTags = stripDefaultParams($state.params[`${$scope.iterator}_search`]); + $scope.searchTags = qs.stripDefaultParams(queryset, defaults); 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 = stripDefaultParams(queryset); + $scope.searchTags = qs.stripDefaultParams(queryset, defaults); } } }); @@ -86,28 +86,6 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' }); } - // Removes state definition defaults and pagination terms - function stripDefaultParams(params) { - let stripped =_.pick(params, (value, key) => { - // setting the default value of a term to null in a state definition is a very explicit way to ensure it will NEVER generate a search tag, even with a non-default value - return defaults[key] !== value && key !== 'order_by' && key !== 'page' && key !== 'page_size' && defaults[key] !== null; - }); - let strippedCopy = _.cloneDeep(stripped); - if(_.keys(_.pick(defaults, _.keys(strippedCopy))).length > 0){ - for (var key in strippedCopy) { - if (strippedCopy.hasOwnProperty(key)) { - let value = strippedCopy[key]; - if(_.isArray(value)){ - let index = _.indexOf(value, defaults[key]); - value = value.splice(index, 1)[0]; - } - } - } - stripped = strippedCopy; - } - return _(strippedCopy).map(qs.decodeParam).flatten().value(); - } - function setDefaults(term) { if ($scope.list.defaultSearchParams) { return $scope.list.defaultSearchParams(encodeURIComponent(term)); @@ -136,7 +114,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' $scope.dataset = res.data; $scope.collection = res.data.results; }); - $scope.searchTags = stripDefaultParams(queryset); + $scope.searchTags = qs.stripDefaultParams(queryset, defaults); }; // remove tag, merge new queryset, $state.go @@ -208,7 +186,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' $scope.dataset = res.data; $scope.collection = res.data.results; }); - $scope.searchTags = stripDefaultParams(queryset); + $scope.searchTags = qs.stripDefaultParams(queryset, defaults); }; // add a search tag, merge new queryset, $state.go() @@ -311,7 +289,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' }); $scope.searchTerm = null; - $scope.searchTags = stripDefaultParams(queryset); + $scope.searchTags = qs.stripDefaultParams(queryset, defaults); } }; @@ -333,7 +311,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', ' }); $scope.searchTerm = null; - $scope.searchTags = stripDefaultParams(queryset); + $scope.searchTags = qs.stripDefaultParams(queryset, defaults); }; } ]; diff --git a/awx/ui/client/src/shared/stateDefinitions.factory.js b/awx/ui/client/src/shared/stateDefinitions.factory.js index a5e9797f38..b131ff4c95 100644 --- a/awx/ui/client/src/shared/stateDefinitions.factory.js +++ b/awx/ui/client/src/shared/stateDefinitions.factory.js @@ -699,6 +699,11 @@ function($injector, $stateExtender, $log, i18n) { organization: null }; } + else if(field.sourceModel === 'host') { + params = { + page_size: '5' + }; + } else { params = { page_size: '5',