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',