1
0
mirror of https://github.com/ansible/awx.git synced 2024-10-31 06:51:10 +03:00

Dynamic inventory add form work

This commit is contained in:
Michael Abashian 2017-04-18 12:20:58 -04:00 committed by Jared Tabor
parent 62fafc9870
commit cc80cd8549
13 changed files with 216 additions and 156 deletions

View File

@ -53,6 +53,8 @@ function SmartInventoryAdd($scope, $location,
parse_variable: 'parseType', parse_variable: 'parseType',
field_id: 'smartinventory_variables' field_id: 'smartinventory_variables'
}); });
$scope.dynamic_hosts = $state.params.hostfilter ? JSON.parse($state.params.hostfilter) : '';
} }
// Save // Save

View File

@ -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);
});
}
];

View File

@ -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('<host-filter-modal host-filter="hostFilter"></host-filter-modal>')(scope));
};
}
};
}
];

View File

@ -0,0 +1,12 @@
<div class="input-group Form-mixedInputGroup">
<span class="input-group-btn">
<button type="button" class="Form-lookupButton btn btn-default" ng-click="openHostFilterModal()">
<i class="fa fa-search"></i>
</button>
</span>
<span class="form-control Form-textInput input-medium lookup" style="padding: 4px 6px;">
<span class="LabelList-tag" ng-repeat="tag in hostFilterTags">
<span class="LabelList-name">{{tag}}</span>
</span>
</span>
</div>

View File

@ -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();
};
}]
};
}];

View File

@ -0,0 +1,21 @@
<div id="host-filter-modal" class="Lookup modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header Form-header">
<div class="Form-title Form-title--uppercase">DYNAMIC HOSTS</div>
<!-- optional: transclude header fields -->
<div class="Form-header--fields"></div>
<div class="Form-exitHolder">
<button type="button" class="Form-exit" ng-click="cancelForm()">
<i class="fa fa-times-circle"></i>
</button>
</div>
</div>
<div class="modal-body" id="foobar"></div>
<div class="modal-footer">
<button type="button" ng-click="cancelForm()" class="Lookup-cancel btn btn-default">CANCEL</button>
<button type="button" ng-click="saveForm()" class="Lookup-save btn btn-primary">SAVE</button>
</div>
</div>
</div>
</div>

View File

@ -7,10 +7,14 @@
import smartInventoryAdd from './add/main'; import smartInventoryAdd from './add/main';
import smartInventoryEdit from './edit/main'; import smartInventoryEdit from './edit/main';
import SmartInventoryForm from './smart-inventory.form'; 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 export default
angular.module('smartInventory', [ angular.module('smartInventory', [
smartInventoryAdd.name, smartInventoryAdd.name,
smartInventoryEdit.name smartInventoryEdit.name
]) ])
.factory('SmartInventoryForm', SmartInventoryForm); .factory('SmartInventoryForm', SmartInventoryForm)
.directive('dynamicInventoryHostFilter', dynamicInventoryHostFilter)
.directive('hostFilterModal', hostFilterModal);

View File

@ -43,6 +43,17 @@ export default ['i18n', 'buildHostListState', function(i18n, buildHostListState)
ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd) || !canEditOrg', ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd) || !canEditOrg',
awLookupWhen: '(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: '<dynamic-inventory-host-filter host-filter="dynamic_hosts"></dynamic-inventory-host-filter>',
basePath: 'hosts',
list: 'HostsList',
sourceModel: 'host',
sourceField: 'name',
required: true
// TODO: add required, ngDisabled, awLookupWhen (?)
},
variables: { variables: {
label: i18n._('Variables'), label: i18n._('Variables'),
type: 'textarea', type: 'textarea',

View File

@ -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 `<lookup-modal>${list_html}</lookup-modal>`;
//
// }
// }
// },
// 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({ $stateProvider.state({
name: 'hosts', name: 'hosts',
url: '/hosts', url: '/hosts',

View File

@ -316,7 +316,11 @@ export default ['$compile', 'Attr', 'Icon',
if (options.mode === 'lookup') { 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 if (options.input_type === "radio") { //added by JT so that lookup forms can be either radio inputs or check box inputs
innerTable += `<td class="List-tableCell"> <input type="radio" ng-model="${list.iterator}.checked" ng-value="1" ng-false-value="0" name="check_${list.iterator}_{{${list.iterator}.id}}" ng-click="toggle_row(${list.iterator}.id)"></td>`; innerTable += `<td class="List-tableCell"> <input type="radio" ng-model="${list.iterator}.checked" ng-value="1" ng-false-value="0" name="check_${list.iterator}_{{${list.iterator}.id}}" ng-click="toggle_row(${list.iterator}.id)"></td>`;
} else { // its assumed that options.input_type = checkbox }
else if (options.input_type === "foobar") {
}
else { // its assumed that options.input_type = checkbox
innerTable += "<td class=\"List-tableCell select-column List-staticColumn--smallStatus\"><input type=\"checkbox\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" + innerTable += "<td class=\"List-tableCell select-column List-staticColumn--smallStatus\"><input type=\"checkbox\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" +
list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ".id, true)\" ng-true-value=\"1\" " + list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ".id, true)\" ng-true-value=\"1\" " +
"ng-false-value=\"0\" id=\"check_" + list.iterator + "_{{" + list.iterator + ".id}}\" /></td>"; "ng-false-value=\"0\" id=\"check_" + list.iterator + "_{{" + list.iterator + ".id}}\" /></td>";
@ -483,7 +487,7 @@ export default ['$compile', 'Attr', 'Icon',
if (list.multiSelect) { if (list.multiSelect) {
html += buildSelectAll().prop('outerHTML'); html += buildSelectAll().prop('outerHTML');
} else if (options.mode === 'lookup') { } else if (options.mode === 'lookup' && options.input_type !== 'foobar') {
html += "<th class=\"List-tableHeader select-column List-staticColumn--smallStatus\"></th>"; html += "<th class=\"List-tableHeader select-column List-staticColumn--smallStatus\"></th>";
} }

View File

@ -274,6 +274,32 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear
hdr: 'Error!', hdr: 'Error!',
msg: `Invalid search term entered. GET returned: ${status}` 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();
}
} }
}; };
} }

View File

@ -22,7 +22,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
queryset = _.cloneDeep($scope.querySet); queryset = _.cloneDeep($scope.querySet);
} }
else { else {
queryset = $stateParams[`${$scope.iterator}_search`]; queryset = $state.params[`${$scope.iterator}_search`];
} }
// build $scope.tags from $stateParams.QuerySet, build fieldset key // build $scope.tags from $stateParams.QuerySet, build fieldset key
@ -30,7 +30,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
function init() { function init() {
path = GetBasePath($scope.basePath) || $scope.basePath; 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) => { qs.initFieldset(path, $scope.djangoModel).then((data) => {
$scope.models = data.models; $scope.models = data.models;
$scope.options = data.options.data; $scope.options = data.options.data;
@ -69,7 +69,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
}); });
$scope.searchTerm = null; $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) { function setDefaults(term) {
if ($scope.list.defaultSearchParams) { if ($scope.list.defaultSearchParams) {
return $scope.list.defaultSearchParams(encodeURIComponent(term)); return $scope.list.defaultSearchParams(encodeURIComponent(term));
@ -136,7 +114,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
$scope.dataset = res.data; $scope.dataset = res.data;
$scope.collection = res.data.results; $scope.collection = res.data.results;
}); });
$scope.searchTags = stripDefaultParams(queryset); $scope.searchTags = qs.stripDefaultParams(queryset, defaults);
}; };
// remove tag, merge new queryset, $state.go // remove tag, merge new queryset, $state.go
@ -208,7 +186,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
$scope.dataset = res.data; $scope.dataset = res.data;
$scope.collection = res.data.results; $scope.collection = res.data.results;
}); });
$scope.searchTags = stripDefaultParams(queryset); $scope.searchTags = qs.stripDefaultParams(queryset, defaults);
}; };
// add a search tag, merge new queryset, $state.go() // add a search tag, merge new queryset, $state.go()
@ -311,7 +289,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
}); });
$scope.searchTerm = null; $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.searchTerm = null;
$scope.searchTags = stripDefaultParams(queryset); $scope.searchTags = qs.stripDefaultParams(queryset, defaults);
}; };
} }
]; ];

View File

@ -699,6 +699,11 @@ function($injector, $stateExtender, $log, i18n) {
organization: null organization: null
}; };
} }
else if(field.sourceModel === 'host') {
params = {
page_size: '5'
};
}
else { else {
params = { params = {
page_size: '5', page_size: '5',