mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 19:10:07 -03:30
Adding first pass at inventory sources CRUD workflow
and breaking out sources info from groups and simplifying groups workflows to disclude source info
This commit is contained in:
parent
a9ea639d9b
commit
c22e4a5023
@ -31,195 +31,32 @@ export default ['$state', '$stateParams', '$scope', 'GroupForm',
|
||||
field_id: 'group_variables',
|
||||
variable: 'variables',
|
||||
});
|
||||
initSources();
|
||||
}
|
||||
|
||||
$scope.lookupCredential = function(){
|
||||
let kind = ($scope.source.value === "ec2") ? "aws" : $scope.source.value;
|
||||
$state.go('.credential', {
|
||||
credential_search: {
|
||||
kind: kind,
|
||||
page_size: '5',
|
||||
page: '1'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.formCancel = function() {
|
||||
$state.go('^');
|
||||
};
|
||||
|
||||
$scope.formSave = function() {
|
||||
var params, source, json_data;
|
||||
var json_data;
|
||||
json_data = ToJSON($scope.parseType, $scope.variables, true);
|
||||
// group fields
|
||||
|
||||
var group = {
|
||||
variables: json_data,
|
||||
name: $scope.name,
|
||||
description: $scope.description,
|
||||
inventory: inventoryData.id
|
||||
};
|
||||
if ($scope.source) {
|
||||
// inventory_source fields
|
||||
params = {
|
||||
instance_filters: $scope.instance_filters,
|
||||
source_vars: $scope[$scope.source.value + '_variables'] === '---' || $scope[$scope.source.value + '_variables'] === '{}' ? null : $scope[$scope.source.value + '_variables'],
|
||||
source_script: $scope.inventory_script,
|
||||
source: $scope.source.value,
|
||||
credential: $scope.credential,
|
||||
overwrite: $scope.overwrite,
|
||||
overwrite_vars: $scope.overwrite_vars,
|
||||
update_on_launch: $scope.update_on_launch,
|
||||
update_cache_timeout: $scope.update_cache_timeout || 0,
|
||||
// comma-delimited strings
|
||||
group_by: _.map($scope.group_by, 'value').join(','),
|
||||
source_regions: _.map($scope.source_regions, 'value').join(',')
|
||||
};
|
||||
source = $scope.source.value;
|
||||
} else {
|
||||
source = null;
|
||||
}
|
||||
switch (source) {
|
||||
// no inventory source set, just create a new group
|
||||
// '' is the value supplied for Manual source type
|
||||
case null || '':
|
||||
GroupManageService.post(group).then(res => {
|
||||
// associate
|
||||
if ($stateParams.group) {
|
||||
return GroupManageService.associateGroup(res.data, _.last($stateParams.group))
|
||||
.then(() => $state.go('^', null, { reload: true }));
|
||||
} else {
|
||||
$state.go('^', null, { reload: true });
|
||||
}
|
||||
});
|
||||
break;
|
||||
// create a new group and create/associate an inventory source
|
||||
// equal to case 'rax' || 'ec2' || 'azure' || 'azure_rm' || 'vmware' || 'satellite6' || 'cloudforms' || 'openstack' || 'custom'
|
||||
default:
|
||||
GroupManageService.post(group)
|
||||
// associate to group
|
||||
.then(res => {
|
||||
if ($stateParams.group) {
|
||||
GroupManageService.associateGroup(res.data, _.last($stateParams.group));
|
||||
return res;
|
||||
} else {
|
||||
return res; }
|
||||
// pass the original POST response and not the association response
|
||||
})
|
||||
.then(res => GroupManageService.putInventorySource(
|
||||
// put the received group ID into inventory source payload
|
||||
// and pass the related endpoint
|
||||
_.assign(params, { group: res.data.id }), res.data.related.inventory_source))
|
||||
.then(res => $state.go('inventoryManage.editGroup', { group_id: res.data.group }, { reload: true }));
|
||||
break;
|
||||
}
|
||||
|
||||
GroupManageService.post(group).then(res => {
|
||||
if ($stateParams.group) {
|
||||
return GroupManageService.associateGroup(res.data, _.last($stateParams.group))
|
||||
.then(() => $state.go('^', null, { reload: true }));
|
||||
} else {
|
||||
$state.go('inventoryManage.editGroup', { group_id: res.data.group }, { reload: true });
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
$scope.sourceChange = function(source) {
|
||||
source = source.value;
|
||||
if (source === 'custom'){
|
||||
$scope.credentialBasePath = GetBasePath('inventory_script');
|
||||
}
|
||||
// equal to case 'ec2' || 'rax' || 'azure' || 'azure_rm' || 'vmware' || 'satellite6' || 'cloudforms' || 'openstack'
|
||||
else{
|
||||
$scope.credentialBasePath = (source === 'ec2') ? GetBasePath('credentials') + '?kind=aws' : GetBasePath('credentials') + (source === '' ? '' : '?kind=' + (source));
|
||||
}
|
||||
if (source === 'ec2' || source === 'custom' || source === 'vmware' || source === 'openstack') {
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
field_id: source + '_variables',
|
||||
variable: source + '_variables',
|
||||
parse_variable: 'envParseType'
|
||||
});
|
||||
}
|
||||
|
||||
// reset fields
|
||||
$scope.group_by_choices = source === 'ec2' ? $scope.ec2_group_by : null;
|
||||
// azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint
|
||||
$scope.source_region_choices = source === 'azure_rm' ? $scope.azure_regions : $scope[source + '_regions'];
|
||||
$scope.cloudCredentialRequired = source !== '' && source !== 'custom' && source !== 'ec2' ? true : false;
|
||||
$scope.group_by = null;
|
||||
$scope.source_regions = null;
|
||||
$scope.credential = null;
|
||||
$scope.credential_name = null;
|
||||
initRegionSelect();
|
||||
};
|
||||
// region / source options callback
|
||||
$scope.$on('choicesReadyGroup', function() {
|
||||
initRegionSelect();
|
||||
});
|
||||
|
||||
$scope.$on('sourceTypeOptionsReady', function() {
|
||||
initSourceSelect();
|
||||
});
|
||||
|
||||
function initRegionSelect(){
|
||||
CreateSelect2({
|
||||
element: '#group_source_regions',
|
||||
multiple: true
|
||||
});
|
||||
CreateSelect2({
|
||||
element: '#group_group_by',
|
||||
multiple: true
|
||||
});
|
||||
}
|
||||
function initSourceSelect(){
|
||||
CreateSelect2({
|
||||
element: '#group_source',
|
||||
multiple: false
|
||||
});
|
||||
}
|
||||
|
||||
function initSources(){
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'rax_regions',
|
||||
choice_name: 'rax_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'ec2_regions',
|
||||
choice_name: 'ec2_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'gce_regions',
|
||||
choice_name: 'gce_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'azure_regions',
|
||||
choice_name: 'azure_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
|
||||
// Load options for group_by
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'group_by',
|
||||
variable: 'ec2_group_by',
|
||||
choice_name: 'ec2_group_by_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
GetSourceTypeOptions({
|
||||
scope: $scope,
|
||||
variable: 'source_type_options',
|
||||
//callback: 'sourceTypeOptionsReady' this callback is hard-coded into GetSourceTypeOptions(), included for ref
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
*************************************************/
|
||||
|
||||
export default ['$state', '$stateParams', '$scope', 'ParseVariableString', 'rbacUiControlService', 'ToJSON',
|
||||
'ParseTypeChange', 'GroupManageService', 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', 'groupData', 'inventorySourceData',
|
||||
'ParseTypeChange', 'GroupManageService', 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', 'groupData',
|
||||
function($state, $stateParams, $scope, ParseVariableString, rbacUiControlService, ToJSON,
|
||||
ParseTypeChange, GroupManageService, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions, groupData, inventorySourceData) {
|
||||
ParseTypeChange, GroupManageService, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions, groupData) {
|
||||
|
||||
init();
|
||||
|
||||
@ -16,17 +16,8 @@ export default ['$state', '$stateParams', '$scope', 'ParseVariableString', 'rbac
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
// instantiate expected $scope values from inventorySourceData & groupData
|
||||
_.assign($scope, { credential: inventorySourceData.credential }, { overwrite: inventorySourceData.overwrite }, { overwrite_vars: inventorySourceData.overwrite_vars }, { update_on_launch: inventorySourceData.update_on_launch }, { update_cache_timeout: inventorySourceData.update_cache_timeout }, { instance_filters: inventorySourceData.instance_filters }, { inventory_script: inventorySourceData.source_script });
|
||||
if (inventorySourceData.credential) {
|
||||
$scope.credential_name = inventorySourceData.summary_fields.credential.name;
|
||||
}
|
||||
$scope = angular.extend($scope, groupData);
|
||||
|
||||
// display custom inventory_script name
|
||||
if (inventorySourceData.source === 'custom') {
|
||||
$scope.inventory_script_name = inventorySourceData.summary_fields.source_script.name;
|
||||
}
|
||||
$scope = angular.extend($scope, groupData);
|
||||
|
||||
$scope.$watch('summary_fields.user_capabilities.edit', function(val) {
|
||||
$scope.canAdd = val;
|
||||
@ -43,36 +34,14 @@ export default ['$state', '$stateParams', '$scope', 'ParseVariableString', 'rbac
|
||||
variable: 'variables',
|
||||
});
|
||||
|
||||
initSources();
|
||||
}
|
||||
|
||||
var initRegionSelect = function() {
|
||||
CreateSelect2({
|
||||
element: '#group_source_regions',
|
||||
multiple: true
|
||||
});
|
||||
CreateSelect2({
|
||||
element: '#group_group_by',
|
||||
multiple: true
|
||||
});
|
||||
};
|
||||
|
||||
$scope.lookupCredential = function(){
|
||||
let kind = ($scope.source.value === "ec2") ? "aws" : $scope.source.value;
|
||||
$state.go('.credential', {
|
||||
credential_search: {
|
||||
kind: kind,
|
||||
page_size: '5',
|
||||
page: '1'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.formCancel = function() {
|
||||
$state.go('^');
|
||||
};
|
||||
|
||||
$scope.formSave = function() {
|
||||
var params, source, json_data;
|
||||
var json_data;
|
||||
json_data = ToJSON($scope.parseType, $scope.variables, true);
|
||||
// group fields
|
||||
var group = {
|
||||
@ -82,166 +51,8 @@ export default ['$state', '$stateParams', '$scope', 'ParseVariableString', 'rbac
|
||||
inventory: $scope.inventory,
|
||||
id: groupData.id
|
||||
};
|
||||
if ($scope.source) {
|
||||
// inventory_source fields
|
||||
params = {
|
||||
group: groupData.id,
|
||||
source: $scope.source.value,
|
||||
credential: $scope.credential,
|
||||
overwrite: $scope.overwrite,
|
||||
overwrite_vars: $scope.overwrite_vars,
|
||||
source_script: $scope.inventory_script,
|
||||
update_on_launch: $scope.update_on_launch,
|
||||
update_cache_timeout: $scope.update_cache_timeout || 0,
|
||||
// comma-delimited strings
|
||||
group_by: _.map($scope.group_by, 'value').join(','),
|
||||
source_regions: _.map($scope.source_regions, 'value').join(','),
|
||||
instance_filters: $scope.instance_filters,
|
||||
source_vars: $scope[$scope.source.value + '_variables'] === '---' || $scope[$scope.source.value + '_variables'] === '{}' ? null : $scope[$scope.source.value + '_variables']
|
||||
};
|
||||
source = $scope.source.value;
|
||||
} else {
|
||||
source = null;
|
||||
}
|
||||
switch (source) {
|
||||
// no inventory source set, just create a new group
|
||||
// '' is the value supplied for Manual source type
|
||||
case null || '':
|
||||
GroupManageService.put(group).then(() => $state.go($state.current, null, { reload: true }));
|
||||
break;
|
||||
// create a new group and create/associate an inventory source
|
||||
// equal to case 'rax' || 'ec2' || 'azure' || 'azure_rm' || 'vmware' || 'satellite6' || 'cloudforms' || 'openstack' || 'custom'
|
||||
default:
|
||||
GroupManageService.put(group)
|
||||
.then(() => GroupManageService.putInventorySource(params, groupData.related.inventory_source))
|
||||
.then(() => $state.go($state.current, null, { reload: true }));
|
||||
break;
|
||||
}
|
||||
GroupManageService.put(group).then(() => $state.go($state.current, null, { reload: true }));
|
||||
};
|
||||
|
||||
$scope.sourceChange = function(source) {
|
||||
$scope.source = source;
|
||||
if (source.value === 'ec2' || source.value === 'custom' ||
|
||||
source.value === 'vmware' || source.value === 'openstack') {
|
||||
$scope[source.value + '_variables'] = $scope[source.value + '_variables'] === (null || undefined) ? '---' : $scope[source.value + '_variables'];
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
field_id: source.value + '_variables',
|
||||
variable: source.value + '_variables',
|
||||
parse_variable: 'envParseType',
|
||||
});
|
||||
}
|
||||
// reset fields
|
||||
// azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint
|
||||
$scope.source_region_choices = source.value === 'azure_rm' ? $scope.azure_regions : $scope[source.value + '_regions'];
|
||||
$scope.cloudCredentialRequired = source.value !== '' && source.value !== 'custom' && source.value !== 'ec2' ? true : false;
|
||||
$scope.group_by = null;
|
||||
$scope.source_regions = null;
|
||||
$scope.credential = null;
|
||||
$scope.credential_name = null;
|
||||
initRegionSelect();
|
||||
};
|
||||
|
||||
function initSourceSelect() {
|
||||
$scope.source = _.find($scope.source_type_options, { value: inventorySourceData.source });
|
||||
CreateSelect2({
|
||||
element: '#group_source',
|
||||
multiple: false
|
||||
});
|
||||
// After the source is set, conditional fields will be visible
|
||||
// CodeMirror is buggy if you instantiate it in a not-visible element
|
||||
// So we initialize it here instead of the init() routine
|
||||
if (inventorySourceData.source === 'ec2' || inventorySourceData.source === 'openstack' ||
|
||||
inventorySourceData.source === 'custom' || inventorySourceData.source === 'vmware') {
|
||||
$scope[inventorySourceData.source + '_variables'] = inventorySourceData.source_vars === null || inventorySourceData.source_vars === '' ? '---' : ParseVariableString(inventorySourceData.source_vars);
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
field_id: inventorySourceData.source + '_variables',
|
||||
variable: inventorySourceData.source + '_variables',
|
||||
parse_variable: 'envParseType',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initRegionData() {
|
||||
var source = $scope.source.value === 'azure_rm' ? 'azure' : $scope.source.value;
|
||||
var regions = inventorySourceData.source_regions.split(',');
|
||||
// azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint
|
||||
$scope.source_region_choices = $scope[source + '_regions'];
|
||||
|
||||
// the API stores azure regions as all-lowercase strings - but the azure regions received from OPTIONS are Snake_Cased
|
||||
if (source === 'azure') {
|
||||
$scope.source_regions = _.map(regions, (region) => _.find($scope[source + '_regions'], (o) => o.value.toLowerCase() === region));
|
||||
}
|
||||
// all other regions are 1-1
|
||||
else {
|
||||
$scope.source_regions = _.map(regions, (region) => _.find($scope[source + '_regions'], (o) => o.value === region));
|
||||
}
|
||||
$scope.group_by_choices = source === 'ec2' ? $scope.ec2_group_by : null;
|
||||
if (source === 'ec2') {
|
||||
var group_by = inventorySourceData.group_by.split(',');
|
||||
$scope.group_by = _.map(group_by, (item) => _.find($scope.ec2_group_by, { value: item }));
|
||||
}
|
||||
initRegionSelect();
|
||||
}
|
||||
|
||||
function initSources() {
|
||||
GetSourceTypeOptions({
|
||||
scope: $scope,
|
||||
variable: 'source_type_options',
|
||||
//callback: 'sourceTypeOptionsReady' this callback is hard-coded into GetSourceTypeOptions(), included for ref
|
||||
});
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'rax_regions',
|
||||
choice_name: 'rax_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'ec2_regions',
|
||||
choice_name: 'ec2_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'gce_regions',
|
||||
choice_name: 'gce_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'azure_regions',
|
||||
choice_name: 'azure_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'group_by',
|
||||
variable: 'ec2_group_by',
|
||||
choice_name: 'ec2_group_by_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
}
|
||||
|
||||
// region / source options callback
|
||||
$scope.$on('choicesReadyGroup', function() {
|
||||
if (angular.isObject($scope.source)) {
|
||||
initRegionData();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('sourceTypeOptionsReady', function() {
|
||||
initSourceSelect();
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
@ -11,329 +11,69 @@
|
||||
*/
|
||||
|
||||
export default {
|
||||
addTitle: 'CREATE GROUP',
|
||||
editTitle: '{{ name }}',
|
||||
showTitle: true,
|
||||
name: 'group',
|
||||
basePath: 'groups',
|
||||
parent: 'inventories.edit.groups',
|
||||
// the parent node this generated state definition tree expects to attach to
|
||||
stateTree: 'inventories',
|
||||
// form generator inspects the current state name to determine whether or not to set an active (.is-selected) class on a form tab
|
||||
// this setting is optional on most forms, except where the form's edit state name is not parentStateName.edit
|
||||
activeEditState: 'inventories.edit.groups.editGroup',
|
||||
detailsClick: "$state.go('inventories.edit.groups.editGroup')",
|
||||
well: false,
|
||||
fields: {
|
||||
name: {
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
required: true,
|
||||
tab: 'properties'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
tab: 'properties'
|
||||
},
|
||||
variables: {
|
||||
label: 'Variables',
|
||||
type: 'textarea',
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
rows: 6,
|
||||
'default': '---',
|
||||
dataTitle: 'Group Variables',
|
||||
dataPlacement: 'right',
|
||||
parseTypeName: 'parseType',
|
||||
awPopOver: "<p>Variables defined here apply to all child groups and hosts.</p>" +
|
||||
"<p>Enter variables using either JSON or YAML syntax. Use the " +
|
||||
"radio button to toggle between the two.</p>" +
|
||||
"JSON:<br />\n" +
|
||||
"<blockquote>{<br />  \"somevar\": \"somevalue\",<br /> \"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||
"YAML:<br />\n" +
|
||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
|
||||
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
|
||||
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
|
||||
dataContainer: 'body',
|
||||
tab: 'properties'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
addTitle: 'CREATE GROUP',
|
||||
editTitle: '{{ name }}',
|
||||
showTitle: true,
|
||||
name: 'group',
|
||||
basePath: 'groups',
|
||||
parent: 'inventories.edit.groups',
|
||||
// the parent node this generated state definition tree expects to attach to
|
||||
stateTree: 'inventories',
|
||||
// form generator inspects the current state name to determine whether or not to set an active (.is-selected) class on a form tab
|
||||
// this setting is optional on most forms, except where the form's edit state name is not parentStateName.edit
|
||||
activeEditState: 'inventories.edit.groups.editGroup',
|
||||
detailsClick: "$state.go('inventories.edit.groups.editGroup')",
|
||||
well: false,
|
||||
fields: {
|
||||
name: {
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
required: true,
|
||||
tab: 'properties'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
tab: 'properties'
|
||||
},
|
||||
variables: {
|
||||
label: 'Variables',
|
||||
type: 'textarea',
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
rows: 6,
|
||||
'default': '---',
|
||||
dataTitle: 'Group Variables',
|
||||
dataPlacement: 'right',
|
||||
parseTypeName: 'parseType',
|
||||
awPopOver: "<p>Variables defined here apply to all child groups and hosts.</p>" +
|
||||
"<p>Enter variables using either JSON or YAML syntax. Use the " +
|
||||
"radio button to toggle between the two.</p>" +
|
||||
"JSON:<br />\n" +
|
||||
"<blockquote>{<br />  \"somevar\": \"somevalue\",<br /> \"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||
"YAML:<br />\n" +
|
||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
|
||||
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
|
||||
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
|
||||
dataContainer: 'body',
|
||||
tab: 'properties'
|
||||
},
|
||||
source: {
|
||||
label: 'Source',
|
||||
type: 'select',
|
||||
ngOptions: 'source.label for source in source_type_options track by source.value',
|
||||
ngChange: 'sourceChange(source)',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
ngModel: 'source'
|
||||
},
|
||||
credential: {
|
||||
// initializes a default value for this search param
|
||||
// search params with default values set will not generate user-interactable search tags
|
||||
search: {
|
||||
kind: null
|
||||
},
|
||||
label: 'Cloud Credential',
|
||||
type: 'lookup',
|
||||
list: 'CredentialList',
|
||||
basePath: 'credentials',
|
||||
ngShow: "source && source.value !== '' && source.value !== 'custom'",
|
||||
sourceModel: 'credential',
|
||||
sourceField: 'name',
|
||||
ngClick: 'lookupCredential()',
|
||||
awRequiredWhen: {
|
||||
reqExpression: "cloudCredentialRequired",
|
||||
init: "false"
|
||||
},
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
watchBasePath: "credentialBasePath"
|
||||
},
|
||||
source_regions: {
|
||||
label: 'Regions',
|
||||
type: 'select',
|
||||
ngOptions: 'source.label for source in source_region_choices track by source.value',
|
||||
multiSelect: true,
|
||||
ngShow: "source && (source.value == 'rax' || source.value == 'ec2' || source.value == 'gce' || source.value == 'azure' || source.value == 'azure_rm')",
|
||||
|
||||
|
||||
dataTitle: 'Source Regions',
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Click on the regions field to see a list of regions for your cloud provider. You can select multiple regions, " +
|
||||
"or choose <em>All</em> to include all regions. Tower will only be updated with Hosts associated with the selected regions." +
|
||||
"</p>",
|
||||
dataContainer: 'body',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
instance_filters: {
|
||||
label: 'Instance Filters',
|
||||
type: 'text',
|
||||
ngShow: "source && source.value == 'ec2'",
|
||||
dataTitle: 'Instance Filters',
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Provide a comma-separated list of filter expressions. " +
|
||||
"Hosts are imported to Tower when <em>ANY</em> of the filters match.</p>" +
|
||||
"Limit to hosts having a tag:<br />\n" +
|
||||
"<blockquote>tag-key=TowerManaged</blockquote>\n" +
|
||||
"Limit to hosts using either key pair:<br />\n" +
|
||||
"<blockquote>key-name=staging, key-name=production</blockquote>\n" +
|
||||
"Limit to hosts where the Name tag begins with <em>test</em>:<br />\n" +
|
||||
"<blockquote>tag:Name=test*</blockquote>\n" +
|
||||
"<p>View the <a href=\"http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html\" target=\"_blank\">Describe Instances documentation</a> " +
|
||||
"for a complete list of supported filters.</p>",
|
||||
dataContainer: 'body',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
group_by: {
|
||||
label: 'Only Group By',
|
||||
type: 'select',
|
||||
ngShow: "source && source.value == 'ec2'",
|
||||
ngOptions: 'source.label for source in group_by_choices track by source.value',
|
||||
multiSelect: true,
|
||||
dataTitle: 'Only Group By',
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Select which groups to create automatically. " +
|
||||
"Tower will create group names similar to the following examples based on the options selected:</p><ul>" +
|
||||
"<li>Availability Zone: <strong>zones » us-east-1b</strong></li>" +
|
||||
"<li>Image ID: <strong>images » ami-b007ab1e</strong></li>" +
|
||||
"<li>Instance ID: <strong>instances » i-ca11ab1e</strong></li>" +
|
||||
"<li>Instance Type: <strong>types » type_m1_medium</strong></li>" +
|
||||
"<li>Key Name: <strong>keys » key_testing</strong></li>" +
|
||||
"<li>Region: <strong>regions » us-east-1</strong></li>" +
|
||||
"<li>Security Group: <strong>security_groups » security_group_default</strong></li>" +
|
||||
"<li>Tags: <strong>tags » tag_Name » tag_Name_host1</strong></li>" +
|
||||
"<li>VPC ID: <strong>vpcs » vpc-5ca1ab1e</strong></li>" +
|
||||
"<li>Tag None: <strong>tags » tag_none</strong></li>" +
|
||||
"</ul><p>If blank, all groups above are created except <em>Instance ID</em>.</p>",
|
||||
dataContainer: 'body',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
inventory_script: {
|
||||
label : "Custom Inventory Script",
|
||||
type: 'lookup',
|
||||
basePath: 'inventory_scripts',
|
||||
list: 'InventoryScriptsList',
|
||||
ngShow: "source && source.value === 'custom'",
|
||||
sourceModel: 'inventory_script',
|
||||
sourceField: 'name',
|
||||
awRequiredWhen: {
|
||||
reqExpression: "source && source.value === 'custom'",
|
||||
init: "false"
|
||||
},
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
},
|
||||
custom_variables: {
|
||||
id: 'custom_variables',
|
||||
label: 'Environment Variables', //"{{vars_label}}" ,
|
||||
ngShow: "source && source.value=='custom' ",
|
||||
type: 'textarea',
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
rows: 6,
|
||||
'default': '---',
|
||||
parseTypeName: 'envParseType',
|
||||
dataTitle: "Environment Variables",
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Provide environment variables to pass to the custom inventory script.</p>" +
|
||||
"<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
|
||||
"JSON:<br />\n" +
|
||||
"<blockquote>{<br /> \"somevar\": \"somevalue\",<br /> \"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||
"YAML:<br />\n" +
|
||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
|
||||
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
|
||||
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
|
||||
dataContainer: 'body'
|
||||
},
|
||||
ec2_variables: {
|
||||
id: 'ec2_variables',
|
||||
label: 'Source Variables', //"{{vars_label}}" ,
|
||||
ngShow: "source && source.value == 'ec2'",
|
||||
type: 'textarea',
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
rows: 6,
|
||||
'default': '---',
|
||||
parseTypeName: 'envParseType',
|
||||
dataTitle: "Source Variables",
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Override variables found in ec2.ini and used by the inventory update script. For a detailed description of these variables " +
|
||||
"<a href=\"https://github.com/ansible/ansible/blob/devel/contrib/inventory/ec2.ini\" target=\"_blank\">" +
|
||||
"view ec2.ini in the Ansible github repo.</a></p>" +
|
||||
"<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
|
||||
"JSON:<br />\n" +
|
||||
"<blockquote>{<br /> \"somevar\": \"somevalue\",<br /> \"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||
"YAML:<br />\n" +
|
||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
|
||||
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
|
||||
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
|
||||
dataContainer: 'body'
|
||||
},
|
||||
vmware_variables: {
|
||||
id: 'vmware_variables',
|
||||
label: 'Source Variables', //"{{vars_label}}" ,
|
||||
ngShow: "source && source.value == 'vmware'",
|
||||
type: 'textarea',
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
rows: 6,
|
||||
'default': '---',
|
||||
parseTypeName: 'envParseType',
|
||||
dataTitle: "Source Variables",
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Override variables found in vmware.ini and used by the inventory update script. For a detailed description of these variables " +
|
||||
"<a href=\"https://github.com/ansible/ansible/blob/devel/contrib/inventory/vmware_inventory.ini\" target=\"_blank\">" +
|
||||
"view vmware_inventory.ini in the Ansible github repo.</a></p>" +
|
||||
"<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
|
||||
"JSON:<br />\n" +
|
||||
"<blockquote>{<br /> \"somevar\": \"somevalue\",<br /> \"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||
"YAML:<br />\n" +
|
||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
|
||||
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
|
||||
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
|
||||
dataContainer: 'body'
|
||||
},
|
||||
openstack_variables: {
|
||||
id: 'openstack_variables',
|
||||
label: 'Source Variables', //"{{vars_label}}" ,
|
||||
ngShow: "source && source.value == 'openstack'",
|
||||
type: 'textarea',
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
rows: 6,
|
||||
'default': '---',
|
||||
parseTypeName: 'envParseType',
|
||||
dataTitle: "Source Variables",
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Override variables found in openstack.yml and used by the inventory update script. For an example variable configuration " +
|
||||
"<a href=\"https://github.com/ansible/ansible/blob/devel/contrib/inventory/openstack.yml\" target=\"_blank\">" +
|
||||
"view openstack.yml in the Ansible github repo.</a></p>" +
|
||||
"<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
|
||||
"JSON:<br />\n" +
|
||||
"<blockquote>{<br /> \"somevar\": \"somevalue\",<br /> \"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||
"YAML:<br />\n" +
|
||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
|
||||
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
|
||||
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
|
||||
dataContainer: 'body'
|
||||
},
|
||||
checkbox_group: {
|
||||
label: 'Update Options',
|
||||
type: 'checkbox_group',
|
||||
ngShow: "source && (source.value !== '' && source.value !== null)",
|
||||
class: 'Form-checkbox--stacked',
|
||||
fields: [{
|
||||
name: 'overwrite',
|
||||
label: 'Overwrite',
|
||||
type: 'checkbox',
|
||||
ngShow: "source.value !== '' && source.value !== null",
|
||||
|
||||
|
||||
awPopOver: '<p>If checked, all child groups and hosts not found on the external source will be deleted from ' +
|
||||
'the local inventory.</p><p>When not checked, local child hosts and groups not found on the external source will ' +
|
||||
'remain untouched by the inventory update process.</p>',
|
||||
dataTitle: 'Overwrite',
|
||||
dataContainer: 'body',
|
||||
dataPlacement: 'right',
|
||||
labelClass: 'checkbox-options',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}, {
|
||||
name: 'overwrite_vars',
|
||||
label: 'Overwrite Variables',
|
||||
type: 'checkbox',
|
||||
ngShow: "source.value !== '' && source.value !== null",
|
||||
|
||||
|
||||
awPopOver: '<p>If checked, all variables for child groups and hosts will be removed and replaced by those ' +
|
||||
'found on the external source.</p><p>When not checked, a merge will be performed, combining local variables with ' +
|
||||
'those found on the external source.</p>',
|
||||
dataTitle: 'Overwrite Variables',
|
||||
dataContainer: 'body',
|
||||
dataPlacement: 'right',
|
||||
labelClass: 'checkbox-options',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}, {
|
||||
name: 'update_on_launch',
|
||||
label: 'Update on Launch',
|
||||
type: 'checkbox',
|
||||
ngShow: "source.value !== '' && source.value !== null",
|
||||
awPopOver: '<p>Each time a job runs using this inventory, refresh the inventory from the selected source before ' +
|
||||
'executing job tasks.</p>',
|
||||
dataTitle: 'Update on Launch',
|
||||
dataContainer: 'body',
|
||||
dataPlacement: 'right',
|
||||
labelClass: 'checkbox-options',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}]
|
||||
},
|
||||
update_cache_timeout: {
|
||||
label: "Cache Timeout <span class=\"small-text\"> (seconds)</span>",
|
||||
id: 'source-cache-timeout',
|
||||
type: 'number',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
integer: true,
|
||||
min: 0,
|
||||
ngShow: "source && source.value !== '' && update_on_launch",
|
||||
spinner: true,
|
||||
"default": 0,
|
||||
awPopOver: '<p>Time in seconds to consider an inventory sync to be current. During job runs and callbacks the task system will ' +
|
||||
'evaluate the timestamp of the latest sync. If it is older than Cache Timeout, it is not considered current, ' +
|
||||
'and a new inventory sync will be performed.</p>',
|
||||
dataTitle: 'Cache Timeout',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body"
|
||||
}
|
||||
},
|
||||
|
||||
buttons: {
|
||||
cancel: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
close: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
save: {
|
||||
ngClick: 'formSave()',
|
||||
ngDisabled: true,
|
||||
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
}
|
||||
};
|
||||
buttons: {
|
||||
cancel: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
close: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
save: {
|
||||
ngClick: 'formSave()',
|
||||
ngDisabled: true,
|
||||
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -17,19 +17,6 @@ export default {
|
||||
basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/root_groups/',
|
||||
|
||||
fields: {
|
||||
sync_status: {
|
||||
label: '',
|
||||
nosort: true,
|
||||
mode: 'all',
|
||||
iconOnly: true,
|
||||
ngClick: 'viewUpdateStatus(group.id)',
|
||||
awToolTip: "{{ group.status_tooltip }}",
|
||||
dataTipWatch: "group.status_tooltip",
|
||||
icon: "{{ 'fa icon-cloud-' + group.status_class }}",
|
||||
ngClass: "group.status_class",
|
||||
dataPlacement: "top",
|
||||
columnClass: 'status-column List-staticColumn--smallStatus'
|
||||
},
|
||||
failed_hosts: {
|
||||
label: '',
|
||||
nosort: true,
|
||||
|
||||
@ -50,13 +50,8 @@
|
||||
group = {};
|
||||
}
|
||||
|
||||
let group_status, hosts_status;
|
||||
let hosts_status;
|
||||
|
||||
group_status = GetSyncStatusMsg({
|
||||
status: group.summary_fields.inventory_source.status,
|
||||
has_inventory_sources: group.has_inventory_sources,
|
||||
source: ( (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null )
|
||||
});
|
||||
hosts_status = GetHostsStatusMsg({
|
||||
active_failures: group.hosts_with_active_failures,
|
||||
total_hosts: group.total_hosts,
|
||||
@ -64,15 +59,8 @@
|
||||
group_id: group.id
|
||||
});
|
||||
_.assign(group,
|
||||
{status_class: group_status.class},
|
||||
{status_tooltip: group_status.tooltip},
|
||||
{launch_tooltip: group_status.launch_tip},
|
||||
{launch_class: group_status.launch_class},
|
||||
{group_schedule_tooltip: group_status.schedule_tip},
|
||||
{hosts_status_tip: hosts_status.tooltip},
|
||||
{hosts_status_class: hosts_status.class},
|
||||
{source: group.summary_fields.inventory_source ? group.summary_fields.inventory_source.source : null},
|
||||
{status: group.summary_fields.inventory_source ? group.summary_fields.inventory_source.status : null});
|
||||
{hosts_status_class: hosts_status.class});
|
||||
}
|
||||
|
||||
$scope.groupSelect = function(id){
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<div class="tab-pane" id="inventories-panel">
|
||||
<div ui-view="groupForm"></div>
|
||||
<div ui-view="hostForm"></div>
|
||||
<div ui-view="sourcesForm"></div>
|
||||
<div ui-view="form"></div>
|
||||
<div ng-cloak id="htmlTemplate" class="Panel">
|
||||
<div class="row Form-tabRow">
|
||||
|
||||
@ -12,9 +12,11 @@
|
||||
|
||||
export default ['i18n', 'buildGroupsListState', 'buildGroupsAddState',
|
||||
'buildGroupsEditState', 'buildHostListState', 'buildHostAddState',
|
||||
'buildHostEditState',
|
||||
'buildHostEditState', 'buildSourcesListState', 'buildSourcesAddState',
|
||||
'buildSourcesEditState',
|
||||
function(i18n, buildGroupsListState, buildGroupsAddState, buildGroupsEditState,
|
||||
buildHostListState, buildHostAddState, buildHostEditState) {
|
||||
buildHostListState, buildHostAddState, buildHostEditState,
|
||||
buildSourcesListState, buildSourcesAddState,buildSourcesEditState) {
|
||||
return {
|
||||
|
||||
addTitle: i18n._('NEW INVENTORY'),
|
||||
@ -155,35 +157,13 @@ function(i18n, buildGroupsListState, buildGroupsAddState, buildGroupsEditState,
|
||||
},
|
||||
inventory_sources: {
|
||||
name: 'inventory_sources',
|
||||
// awToolTip: i18n._('Please save before assigning permissions'),
|
||||
// dataPlacement: 'top',
|
||||
basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/inventory_sources/',
|
||||
type: 'collection',
|
||||
include: "SourcesListDefinition",
|
||||
includeForm: "SourcesFormDefinition",
|
||||
title: i18n._('Sources'),
|
||||
iterator: 'inventory_source',
|
||||
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'
|
||||
}
|
||||
}
|
||||
listState: buildSourcesListState,
|
||||
addState: buildSourcesAddState,
|
||||
editState: buildSourcesEditState
|
||||
},
|
||||
//this is a placeholder for when we're ready for completed jobs
|
||||
completed_jobs: {
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
import host from './hosts/main';
|
||||
import group from './groups/main';
|
||||
import sources from './sources/main';
|
||||
import relatedHost from './related-hosts/main';
|
||||
import inventoryAdd from './add/main';
|
||||
import inventoryEdit from './edit/main';
|
||||
@ -19,6 +20,7 @@ export default
|
||||
angular.module('inventory', [
|
||||
host.name,
|
||||
group.name,
|
||||
sources.name,
|
||||
relatedHost.name,
|
||||
inventoryAdd.name,
|
||||
inventoryEdit.name,
|
||||
|
||||
@ -1,141 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import { templateUrl } from '../../shared/template-url/template-url.factory';
|
||||
import InventoriesManage from './inventory-manage.controller';
|
||||
import BreadcrumbsController from './breadcrumbs/breadcrumbs.controller';
|
||||
import HostsListController from './hosts/hosts-list.controller';
|
||||
import GroupsListController from './groups/groups-list.controller';
|
||||
|
||||
export default {
|
||||
name: 'inventoryManage',
|
||||
data: {
|
||||
socket: {
|
||||
"groups": {
|
||||
"jobs": ["status_changed"]
|
||||
}
|
||||
}
|
||||
},
|
||||
// instead of a single 'searchPrefix' attribute, provide hard-coded search params
|
||||
url: '/inventories/:inventory_id/manage?{group:int}{group_search:queryset}{host_search:queryset}',
|
||||
params: {
|
||||
group: {
|
||||
array: true
|
||||
},
|
||||
group_search: {
|
||||
value: {
|
||||
page_size: '20',
|
||||
page: '1',
|
||||
order_by: 'name',
|
||||
},
|
||||
squash: true,
|
||||
dynamic: true
|
||||
},
|
||||
host_search: {
|
||||
value: {
|
||||
page_size: '20',
|
||||
page: '1',
|
||||
order_by: 'name',
|
||||
},
|
||||
squash: true,
|
||||
dynamic: true
|
||||
}
|
||||
},
|
||||
ncyBreadcrumb: {
|
||||
skip: true // Never display this state in ncy-breadcrumb.
|
||||
},
|
||||
// enforce uniqueness in group param
|
||||
onEnter: function($stateParams) {
|
||||
$stateParams.group = _.uniq($stateParams.group);
|
||||
},
|
||||
resolve: {
|
||||
groupsUrl: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) {
|
||||
return $stateParams.group && $stateParams.group.length > 0 ?
|
||||
// nested context - provide this node's children
|
||||
InventoryManageService.childGroupsUrl(_.last($stateParams.group)) :
|
||||
// root context - provide root nodes
|
||||
InventoryManageService.rootGroupsUrl($stateParams.inventory_id);
|
||||
}],
|
||||
hostsUrl: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) {
|
||||
return $stateParams.group && $stateParams.group.length > 0 ?
|
||||
// nested context - provide all hosts managed by nodes
|
||||
InventoryManageService.childHostsUrl(_.last($stateParams.group)) :
|
||||
// root context - provide all hosts in an inventory
|
||||
InventoryManageService.rootHostsUrl($stateParams.inventory_id);
|
||||
}],
|
||||
inventoryData: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) {
|
||||
return InventoryManageService.getInventory($stateParams.inventory_id).then(res => res.data);
|
||||
}],
|
||||
breadCrumbData: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) {
|
||||
return $stateParams.group && $stateParams.group.length > 0 ?
|
||||
// nested context - provide breadcrumb data
|
||||
InventoryManageService.getBreadcrumbs($stateParams.group).then(res => res.data.results) :
|
||||
// root context
|
||||
false;
|
||||
}],
|
||||
groupsDataset: ['InventoryGroups', 'QuerySet', '$stateParams', 'groupsUrl', (list, qs, $stateParams, groupsUrl) => {
|
||||
let path = groupsUrl;
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}],
|
||||
hostsDataset: ['InventoryHosts', 'QuerySet', '$stateParams', 'hostsUrl', (list, qs, $stateParams, hostsUrl) => {
|
||||
let path = hostsUrl;
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}]
|
||||
},
|
||||
views: {
|
||||
// target the ui-view with name "groupBreadcrumbs" at the root view
|
||||
'groupBreadcrumbs@': {
|
||||
controller: BreadcrumbsController,
|
||||
templateUrl: templateUrl('inventories/manage/breadcrumbs/breadcrumbs')
|
||||
},
|
||||
// target the un-named ui-view @ root level
|
||||
'@': {
|
||||
templateUrl: templateUrl('inventories/manage/inventory-manage'),
|
||||
controller: InventoriesManage
|
||||
},
|
||||
// target ui-views with name@inventoryManage state
|
||||
'groupsList@inventoryManage': {
|
||||
templateProvider: function(InventoryGroups, generateList, $templateRequest, $stateParams, GetBasePath) {
|
||||
let list = _.cloneDeep(InventoryGroups);
|
||||
if($stateParams && $stateParams.group) {
|
||||
list.basePath = GetBasePath('groups') + _.last($stateParams.group) + '/children';
|
||||
}
|
||||
else {
|
||||
//reaches here if the user is on the root level group
|
||||
list.basePath = GetBasePath('inventory') + $stateParams.inventory_id + '/root_groups';
|
||||
}
|
||||
let html = generateList.build({
|
||||
list: list,
|
||||
mode: 'edit'
|
||||
});
|
||||
html = generateList.wrapPanel(html);
|
||||
// Include the custom group delete modal template
|
||||
return $templateRequest(templateUrl('inventories/manage/groups/groups-list')).then((template) => {
|
||||
return html.concat(template);
|
||||
});
|
||||
},
|
||||
controller: GroupsListController
|
||||
},
|
||||
'hostsList@inventoryManage': {
|
||||
templateProvider: function(InventoryHosts, generateList, $stateParams, GetBasePath) {
|
||||
let list = _.cloneDeep(InventoryHosts);
|
||||
if($stateParams && $stateParams.group) {
|
||||
list.basePath = GetBasePath('groups') + _.last($stateParams.group) + '/all_hosts';
|
||||
}
|
||||
else {
|
||||
//reaches here if the user is on the root level group
|
||||
list.basePath = GetBasePath('inventory') + $stateParams.inventory_id + '/hosts';
|
||||
}
|
||||
let html = generateList.build({
|
||||
list: list,
|
||||
mode: 'edit'
|
||||
});
|
||||
return generateList.wrapPanel(html);
|
||||
},
|
||||
controller: HostsListController
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1,60 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default
|
||||
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait',
|
||||
function($rootScope, Rest, GetBasePath, ProcessErrors, Wait){
|
||||
return {
|
||||
// cute abstractions via fn.bind()
|
||||
url: function(){
|
||||
return '';
|
||||
},
|
||||
error: function(data, status) {
|
||||
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Call to ' + this.url + '. GET returned: ' + status });
|
||||
},
|
||||
success: function(data){
|
||||
return data;
|
||||
},
|
||||
// data getters
|
||||
getInventory: function(id){
|
||||
Wait('start');
|
||||
this.url = GetBasePath('inventory') + id;
|
||||
Rest.setUrl(this.url);
|
||||
return Rest.get()
|
||||
.success(this.success.bind(this))
|
||||
.error(this.error.bind(this))
|
||||
.finally(Wait('stop'));
|
||||
},
|
||||
getBreadcrumbs: function(groups){
|
||||
Wait('start');
|
||||
this.url = GetBasePath('groups') + '?' + _.map(groups, function(item){
|
||||
return '&or__id=' + item;
|
||||
}).join('');
|
||||
Rest.setUrl(this.url);
|
||||
return Rest.get()
|
||||
.success(this.success.bind(this))
|
||||
.error(this.error.bind(this))
|
||||
.finally(Wait('stop'));
|
||||
},
|
||||
rootHostsUrl: function(id){
|
||||
var url = GetBasePath('inventory') + id + '/hosts';
|
||||
return url;
|
||||
},
|
||||
childHostsUrl: function(id){
|
||||
var url = GetBasePath('groups') + id + '/all_hosts';
|
||||
return url;
|
||||
},
|
||||
childGroupsUrl: function(id){
|
||||
var url = GetBasePath('groups') + id + '/children';
|
||||
return url;
|
||||
},
|
||||
rootGroupsUrl: function(id){
|
||||
var url = GetBasePath('inventory') + id+ '/root_groups';
|
||||
return url;
|
||||
}
|
||||
};
|
||||
}];
|
||||
@ -0,0 +1,46 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import SourcesAddController from './sources-add.controller';
|
||||
|
||||
export default ['$stateExtender', 'templateUrl', '$injector',
|
||||
function($stateExtender, templateUrl, $injector){
|
||||
var val = function(field, formStateDefinition, params) {
|
||||
let state,
|
||||
list = field.include ? $injector.get(field.include) : field,
|
||||
breadcrumbLabel = (field.iterator.replace('_', ' ') + 's').toUpperCase(),
|
||||
stateConfig = {
|
||||
name: `${formStateDefinition.name}.${list.iterator}s.add`,
|
||||
url: `/add`,
|
||||
ncyBreadcrumb: {
|
||||
parent: `${formStateDefinition.name}`,
|
||||
label: `${breadcrumbLabel}`
|
||||
},
|
||||
views: {
|
||||
'sourcesForm@inventories': {
|
||||
templateProvider: function(GenerateForm, SourcesFormDefinition) {
|
||||
let form = SourcesFormDefinition;
|
||||
return GenerateForm.buildHTML(form, {
|
||||
mode: 'add',
|
||||
related: false
|
||||
});
|
||||
},
|
||||
controller: SourcesAddController
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
'FormDefinition': [params.form, function(definition) {
|
||||
return definition;
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
state = $stateExtender.buildDefinition(stateConfig);
|
||||
return state;
|
||||
};
|
||||
return val;
|
||||
}
|
||||
];
|
||||
13
awx/ui/client/src/inventories/sources/add/main.js
Normal file
13
awx/ui/client/src/inventories/sources/add/main.js
Normal file
@ -0,0 +1,13 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import buildSourcesAddState from './build-sources-add-state.factory';
|
||||
import controller from './sources-add.controller';
|
||||
|
||||
export default
|
||||
angular.module('sourcesAdd', [])
|
||||
.factory('buildSourcesAddState', buildSourcesAddState)
|
||||
.controller('SourcesAddController', controller);
|
||||
@ -0,0 +1,225 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['$state', '$stateParams', '$scope', 'SourcesFormDefinition',
|
||||
'ParseTypeChange', 'GenerateForm', 'inventoryData', 'GroupManageService',
|
||||
'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions',
|
||||
'rbacUiControlService', 'ToJSON', 'SourcesService',
|
||||
function($state, $stateParams, $scope, SourcesFormDefinition, ParseTypeChange,
|
||||
GenerateForm, inventoryData, GroupManageService, GetChoices,
|
||||
GetBasePath, CreateSelect2, GetSourceTypeOptions, rbacUiControlService,
|
||||
ToJSON, SourcesService) {
|
||||
|
||||
let form = SourcesFormDefinition;
|
||||
init();
|
||||
|
||||
function init() {
|
||||
// apply form definition's default field values
|
||||
GenerateForm.applyDefaults(form, $scope);
|
||||
|
||||
rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/inventory_sources")
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
$scope.parseType = 'yaml';
|
||||
$scope.envParseType = 'yaml';
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
field_id: 'inventory_source_variables',
|
||||
variable: 'variables',
|
||||
});
|
||||
initSources();
|
||||
}
|
||||
|
||||
$scope.lookupCredential = function(){
|
||||
let kind = ($scope.source.value === "ec2") ? "aws" : $scope.source.value;
|
||||
$state.go('.credential', {
|
||||
credential_search: {
|
||||
kind: kind,
|
||||
page_size: '5',
|
||||
page: '1'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.formCancel = function() {
|
||||
$state.go('^');
|
||||
};
|
||||
|
||||
$scope.formSave = function() {
|
||||
var params, source, json_data;
|
||||
json_data = ToJSON($scope.parseType, $scope.variables, true);
|
||||
|
||||
if ($scope.source) {
|
||||
params = {
|
||||
name: $scope.name,
|
||||
description: $scope.description,
|
||||
inventory: inventoryData.id,
|
||||
instance_filters: $scope.instance_filters,
|
||||
source_vars: $scope[$scope.source.value + '_variables'] === '---' || $scope[$scope.source.value + '_variables'] === '{}' ? null : $scope[$scope.source.value + '_variables'],
|
||||
source_script: $scope.inventory_script,
|
||||
source: $scope.source.value,
|
||||
credential: $scope.credential,
|
||||
overwrite: $scope.overwrite,
|
||||
overwrite_vars: $scope.overwrite_vars,
|
||||
update_on_launch: $scope.update_on_launch,
|
||||
update_cache_timeout: $scope.update_cache_timeout || 0,
|
||||
variables: json_data,
|
||||
// comma-delimited strings
|
||||
group_by: _.map($scope.group_by, 'value').join(','),
|
||||
source_regions: _.map($scope.source_regions, 'value').join(',')
|
||||
};
|
||||
source = $scope.source.value;
|
||||
} else {
|
||||
source = null;
|
||||
}
|
||||
// switch (source) {
|
||||
// // no inventory source set, just create a new group
|
||||
// // '' is the value supplied for Manual source type
|
||||
// case null || '':
|
||||
// GroupManageService.post(group).then(res => {
|
||||
// // associate
|
||||
// if ($stateParams.group) {
|
||||
// return GroupManageService.associateGroup(res.data, _.last($stateParams.group))
|
||||
// .then(() => $state.go('^', null, { reload: true }));
|
||||
// } else {
|
||||
// $state.go('^', null, { reload: true });
|
||||
// }
|
||||
// });
|
||||
// break;
|
||||
// // create a new group and create/associate an inventory source
|
||||
// // equal to case 'rax' || 'ec2' || 'azure' || 'azure_rm' || 'vmware' || 'satellite6' || 'cloudforms' || 'openstack' || 'custom'
|
||||
// default:
|
||||
// GroupManageService.post(group)
|
||||
// // associate to group
|
||||
// .then(res => {
|
||||
// if ($stateParams.group) {
|
||||
// GroupManageService.associateGroup(res.data, _.last($stateParams.group));
|
||||
// return res;
|
||||
// } else {
|
||||
// return res; }
|
||||
// // pass the original POST response and not the association response
|
||||
// })
|
||||
// .then(res => GroupManageService.putInventorySource(
|
||||
// // put the received group ID into inventory source payload
|
||||
// // and pass the related endpoint
|
||||
// _.assign(params, { group: res.data.id }), res.data.related.inventory_source))
|
||||
// .then(res => $state.go('inventoryManage.editGroup', { group_id: res.data.group }, { reload: true }));
|
||||
SourcesService.post({params}).then(function(){
|
||||
$state.go('.', null, {reload: true});
|
||||
});
|
||||
// break;
|
||||
// }
|
||||
};
|
||||
$scope.sourceChange = function(source) {
|
||||
source = source.value;
|
||||
if (source === 'custom'){
|
||||
$scope.credentialBasePath = GetBasePath('inventory_script');
|
||||
}
|
||||
// equal to case 'ec2' || 'rax' || 'azure' || 'azure_rm' || 'vmware' || 'satellite6' || 'cloudforms' || 'openstack'
|
||||
else{
|
||||
$scope.credentialBasePath = (source === 'ec2') ? GetBasePath('credentials') + '?kind=aws' : GetBasePath('credentials') + (source === '' ? '' : '?kind=' + (source));
|
||||
}
|
||||
if (source === 'ec2' || source === 'custom' || source === 'vmware' || source === 'openstack') {
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
field_id: source + '_variables',
|
||||
variable: source + '_variables',
|
||||
parse_variable: 'envParseType'
|
||||
});
|
||||
}
|
||||
|
||||
// reset fields
|
||||
$scope.group_by_choices = source === 'ec2' ? $scope.ec2_group_by : null;
|
||||
// azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint
|
||||
$scope.source_region_choices = source === 'azure_rm' ? $scope.azure_regions : $scope[source + '_regions'];
|
||||
$scope.cloudCredentialRequired = source !== '' && source !== 'custom' && source !== 'ec2' ? true : false;
|
||||
$scope.group_by = null;
|
||||
$scope.source_regions = null;
|
||||
$scope.credential = null;
|
||||
$scope.credential_name = null;
|
||||
initRegionSelect();
|
||||
};
|
||||
// region / source options callback
|
||||
$scope.$on('choicesReadyGroup', function() {
|
||||
initRegionSelect();
|
||||
});
|
||||
|
||||
$scope.$on('sourceTypeOptionsReady', function() {
|
||||
initSourceSelect();
|
||||
});
|
||||
|
||||
function initRegionSelect(){
|
||||
CreateSelect2({
|
||||
element: '#inventory_source_source_regions',
|
||||
multiple: true
|
||||
});
|
||||
CreateSelect2({
|
||||
element: '#inventory_source_group_by',
|
||||
multiple: true
|
||||
});
|
||||
}
|
||||
function initSourceSelect(){
|
||||
CreateSelect2({
|
||||
element: '#inventory_source_source',
|
||||
multiple: false
|
||||
});
|
||||
}
|
||||
|
||||
function initSources(){
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'rax_regions',
|
||||
choice_name: 'rax_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'ec2_regions',
|
||||
choice_name: 'ec2_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'gce_regions',
|
||||
choice_name: 'gce_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'azure_regions',
|
||||
choice_name: 'azure_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
|
||||
// Load options for group_by
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'group_by',
|
||||
variable: 'ec2_group_by',
|
||||
choice_name: 'ec2_group_by_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
GetSourceTypeOptions({
|
||||
scope: $scope,
|
||||
variable: 'source_type_options',
|
||||
//callback: 'sourceTypeOptionsReady' this callback is hard-coded into GetSourceTypeOptions(), included for ref
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
@ -0,0 +1,49 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import SourcesEditController from './sources-edit.controller';
|
||||
|
||||
export default ['$stateExtender', 'templateUrl', '$injector',
|
||||
function($stateExtender, templateUrl, $injector){
|
||||
var val = function(field, formStateDefinition, params) {
|
||||
let state,
|
||||
list = field.include ? $injector.get(field.include) : field,
|
||||
breadcrumbLabel = (field.iterator.replace('_', ' ') + 's').toUpperCase(),
|
||||
stateConfig = {
|
||||
name: `${formStateDefinition.name}.${list.iterator}s.edit`,
|
||||
url: `/edit/:source_id`,
|
||||
ncyBreadcrumb: {
|
||||
parent: `${formStateDefinition.name}`,
|
||||
label: `${breadcrumbLabel}`
|
||||
},
|
||||
views: {
|
||||
'groupForm@inventories': {
|
||||
templateProvider: function(GenerateForm, SourcesFormDefinition) {
|
||||
let form = SourcesFormDefinition;
|
||||
return GenerateForm.buildHTML(form, {
|
||||
mode: 'edit',
|
||||
related: false
|
||||
});
|
||||
},
|
||||
controller: SourcesEditController
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
'FormDefinition': [params.form, function(definition) {
|
||||
return definition;
|
||||
}],
|
||||
inventorySourceData: ['$stateParams', 'SourcesService', function($stateParams, SourcesService) {
|
||||
return SourcesService.get({id: $stateParams.source_id }).then(res => res.data.results[0]);
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
state = $stateExtender.buildDefinition(stateConfig);
|
||||
return state;
|
||||
};
|
||||
return val;
|
||||
}
|
||||
];
|
||||
13
awx/ui/client/src/inventories/sources/edit/main.js
Normal file
13
awx/ui/client/src/inventories/sources/edit/main.js
Normal file
@ -0,0 +1,13 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import buildSourcesEditState from './build-sources-edit-state.factory';
|
||||
import controller from './sources-edit.controller';
|
||||
|
||||
export default
|
||||
angular.module('sourcesEdit', [])
|
||||
.factory('buildSourcesEditState', buildSourcesEditState)
|
||||
.controller('SourcesEditController', controller);
|
||||
@ -0,0 +1,248 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['$state', '$stateParams', '$scope', 'ParseVariableString',
|
||||
'rbacUiControlService', 'ToJSON', 'ParseTypeChange', 'GroupManageService',
|
||||
'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions',
|
||||
'inventorySourceData', 'SourcesService',
|
||||
function($state, $stateParams, $scope, ParseVariableString,
|
||||
rbacUiControlService, ToJSON,ParseTypeChange, GroupManageService,
|
||||
GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions,
|
||||
inventorySourceData, SourcesService) {
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/inventory_sources")
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
// instantiate expected $scope values from inventorySourceData
|
||||
_.assign($scope, { credential: inventorySourceData.credential }, { overwrite: inventorySourceData.overwrite }, { overwrite_vars: inventorySourceData.overwrite_vars }, { update_on_launch: inventorySourceData.update_on_launch }, { update_cache_timeout: inventorySourceData.update_cache_timeout }, { instance_filters: inventorySourceData.instance_filters }, { inventory_script: inventorySourceData.source_script });
|
||||
if (inventorySourceData.credential) {
|
||||
$scope.credential_name = inventorySourceData.summary_fields.credential.name;
|
||||
}
|
||||
|
||||
// display custom inventory_script name
|
||||
if (inventorySourceData.source === 'custom') {
|
||||
$scope.inventory_script_name = inventorySourceData.summary_fields.source_script.name;
|
||||
}
|
||||
$scope = angular.extend($scope, inventorySourceData);
|
||||
|
||||
$scope.$watch('summary_fields.user_capabilities.edit', function(val) {
|
||||
$scope.canAdd = val;
|
||||
});
|
||||
|
||||
// init codemirror(s)
|
||||
$scope.variables = $scope.variables === null || $scope.variables === '' ? '---' : ParseVariableString($scope.variables);
|
||||
$scope.parseType = 'yaml';
|
||||
$scope.envParseType = 'yaml';
|
||||
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
field_id: 'inventory_source_variables',
|
||||
variable: 'variables',
|
||||
});
|
||||
|
||||
initSources();
|
||||
}
|
||||
|
||||
var initRegionSelect = function() {
|
||||
CreateSelect2({
|
||||
element: '#inventory_source_source_regions',
|
||||
multiple: true
|
||||
});
|
||||
CreateSelect2({
|
||||
element: '#inventory_source_group_by',
|
||||
multiple: true
|
||||
});
|
||||
};
|
||||
|
||||
$scope.lookupCredential = function(){
|
||||
let kind = ($scope.source.value === "ec2") ? "aws" : $scope.source.value;
|
||||
$state.go('.credential', {
|
||||
credential_search: {
|
||||
kind: kind,
|
||||
page_size: '5',
|
||||
page: '1'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.formCancel = function() {
|
||||
$state.go('^');
|
||||
};
|
||||
$scope.formSave = function() {
|
||||
var params, source, json_data;
|
||||
json_data = ToJSON($scope.parseType, $scope.variables, true);
|
||||
|
||||
if ($scope.source) {
|
||||
// inventory_source fields
|
||||
params = {
|
||||
id: $scope.id,
|
||||
variables: json_data,
|
||||
name: $scope.name,
|
||||
description: $scope.description,
|
||||
inventory: $scope.inventory,
|
||||
source: $scope.source.value,
|
||||
credential: $scope.credential,
|
||||
overwrite: $scope.overwrite,
|
||||
overwrite_vars: $scope.overwrite_vars,
|
||||
source_script: $scope.inventory_script,
|
||||
update_on_launch: $scope.update_on_launch,
|
||||
update_cache_timeout: $scope.update_cache_timeout || 0,
|
||||
// comma-delimited strings
|
||||
group_by: _.map($scope.group_by, 'value').join(','),
|
||||
source_regions: _.map($scope.source_regions, 'value').join(','),
|
||||
instance_filters: $scope.instance_filters,
|
||||
source_vars: $scope[$scope.source.value + '_variables'] === '---' || $scope[$scope.source.value + '_variables'] === '{}' ? null : $scope[$scope.source.value + '_variables']
|
||||
};
|
||||
source = $scope.source.value;
|
||||
} else {
|
||||
source = null;
|
||||
}
|
||||
// switch (source) {
|
||||
// no inventory source set, just create a new group
|
||||
// '' is the value supplied for Manual source type
|
||||
// case null || '':
|
||||
SourcesService.put(params).then(() => $state.go($state.current, null, { reload: true }));
|
||||
// break;
|
||||
// // create a new group and create/associate an inventory source
|
||||
// // equal to case 'rax' || 'ec2' || 'azure' || 'azure_rm' || 'vmware' || 'satellite6' || 'cloudforms' || 'openstack' || 'custom'
|
||||
// default:
|
||||
// GroupManageService.put(group)
|
||||
// .then(() => GroupManageService.putInventorySource(params, groupData.related.inventory_source))
|
||||
// .then(() => $state.go($state.current, null, { reload: true }));
|
||||
// break;
|
||||
// }
|
||||
};
|
||||
|
||||
$scope.sourceChange = function(source) {
|
||||
$scope.source = source;
|
||||
if (source.value === 'ec2' || source.value === 'custom' ||
|
||||
source.value === 'vmware' || source.value === 'openstack') {
|
||||
$scope[source.value + '_variables'] = $scope[source.value + '_variables'] === (null || undefined) ? '---' : $scope[source.value + '_variables'];
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
field_id: source.value + '_variables',
|
||||
variable: source.value + '_variables',
|
||||
parse_variable: 'envParseType',
|
||||
});
|
||||
}
|
||||
// reset fields
|
||||
// azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint
|
||||
$scope.source_region_choices = source.value === 'azure_rm' ? $scope.azure_regions : $scope[source.value + '_regions'];
|
||||
$scope.cloudCredentialRequired = source.value !== '' && source.value !== 'custom' && source.value !== 'ec2' ? true : false;
|
||||
$scope.group_by = null;
|
||||
$scope.source_regions = null;
|
||||
$scope.credential = null;
|
||||
$scope.credential_name = null;
|
||||
initRegionSelect();
|
||||
};
|
||||
|
||||
function initSourceSelect() {
|
||||
$scope.source = _.find($scope.source_type_options, { value: inventorySourceData.source });
|
||||
CreateSelect2({
|
||||
element: '#inventory_source_source',
|
||||
multiple: false
|
||||
});
|
||||
// After the source is set, conditional fields will be visible
|
||||
// CodeMirror is buggy if you instantiate it in a not-visible element
|
||||
// So we initialize it here instead of the init() routine
|
||||
if (inventorySourceData.source === 'ec2' || inventorySourceData.source === 'openstack' ||
|
||||
inventorySourceData.source === 'custom' || inventorySourceData.source === 'vmware') {
|
||||
$scope[inventorySourceData.source + '_variables'] = inventorySourceData.source_vars === null || inventorySourceData.source_vars === '' ? '---' : ParseVariableString(inventorySourceData.source_vars);
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
field_id: inventorySourceData.source + '_variables',
|
||||
variable: inventorySourceData.source + '_variables',
|
||||
parse_variable: 'envParseType',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initRegionData() {
|
||||
var source = $scope.source.value === 'azure_rm' ? 'azure' : $scope.source.value;
|
||||
var regions = inventorySourceData.source_regions.split(',');
|
||||
// azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint
|
||||
$scope.source_region_choices = $scope[source + '_regions'];
|
||||
|
||||
// the API stores azure regions as all-lowercase strings - but the azure regions received from OPTIONS are Snake_Cased
|
||||
if (source === 'azure') {
|
||||
$scope.source_regions = _.map(regions, (region) => _.find($scope[source + '_regions'], (o) => o.value.toLowerCase() === region));
|
||||
}
|
||||
// all other regions are 1-1
|
||||
else {
|
||||
$scope.source_regions = _.map(regions, (region) => _.find($scope[source + '_regions'], (o) => o.value === region));
|
||||
}
|
||||
$scope.group_by_choices = source === 'ec2' ? $scope.ec2_group_by : null;
|
||||
if (source === 'ec2') {
|
||||
var group_by = inventorySourceData.group_by.split(',');
|
||||
$scope.group_by = _.map(group_by, (item) => _.find($scope.ec2_group_by, { value: item }));
|
||||
}
|
||||
initRegionSelect();
|
||||
}
|
||||
|
||||
function initSources() {
|
||||
GetSourceTypeOptions({
|
||||
scope: $scope,
|
||||
variable: 'source_type_options',
|
||||
//callback: 'sourceTypeOptionsReady' this callback is hard-coded into GetSourceTypeOptions(), included for ref
|
||||
});
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'rax_regions',
|
||||
choice_name: 'rax_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'ec2_regions',
|
||||
choice_name: 'ec2_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'gce_regions',
|
||||
choice_name: 'gce_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'source_regions',
|
||||
variable: 'azure_regions',
|
||||
choice_name: 'azure_region_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
GetChoices({
|
||||
scope: $scope,
|
||||
url: GetBasePath('inventory_sources'),
|
||||
field: 'group_by',
|
||||
variable: 'ec2_group_by',
|
||||
choice_name: 'ec2_group_by_choices',
|
||||
callback: 'choicesReadyGroup'
|
||||
});
|
||||
}
|
||||
|
||||
// region / source options callback
|
||||
$scope.$on('choicesReadyGroup', function() {
|
||||
if (angular.isObject($scope.source)) {
|
||||
initRegionData();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('sourceTypeOptionsReady', function() {
|
||||
initSourceSelect();
|
||||
});
|
||||
}
|
||||
];
|
||||
@ -0,0 +1,33 @@
|
||||
export default
|
||||
function GetHostsStatusMsg() {
|
||||
return function(params) {
|
||||
var active_failures = params.active_failures,
|
||||
total_hosts = params.total_hosts,
|
||||
tip, failures, html_class;
|
||||
|
||||
// Return values for use on host status indicator
|
||||
|
||||
if (active_failures > 0) {
|
||||
tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. ' + active_failures + ' with failed jobs.';
|
||||
html_class = 'error';
|
||||
failures = true;
|
||||
} else {
|
||||
failures = false;
|
||||
if (total_hosts === 0) {
|
||||
// no hosts
|
||||
tip = "Contains 0 hosts.";
|
||||
html_class = 'none';
|
||||
} else {
|
||||
// many hosts with 0 failures
|
||||
tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. No job failures';
|
||||
html_class = 'success';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tooltip: tip,
|
||||
failures: failures,
|
||||
'class': html_class
|
||||
};
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
export default
|
||||
function GetSourceTypeOptions(Rest, ProcessErrors, GetBasePath) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
variable = params.variable;
|
||||
|
||||
if (scope[variable] === undefined) {
|
||||
scope[variable] = [];
|
||||
Rest.setUrl(GetBasePath('inventory_sources'));
|
||||
Rest.options()
|
||||
.success(function (data) {
|
||||
var i, choices = data.actions.GET.source.choices;
|
||||
for (i = 0; i < choices.length; i++) {
|
||||
if (choices[i][0] !== 'file') {
|
||||
scope[variable].push({
|
||||
label: choices[i][1],
|
||||
value: choices[i][0]
|
||||
});
|
||||
}
|
||||
}
|
||||
scope.cloudCredentialRequired = false;
|
||||
scope.$emit('sourceTypeOptionsReady');
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to retrieve options for inventory_sources.source. OPTIONS status: ' + status
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
GetSourceTypeOptions.$inject =
|
||||
[ 'Rest',
|
||||
'ProcessErrors',
|
||||
'GetBasePath'
|
||||
];
|
||||
@ -0,0 +1,77 @@
|
||||
export default
|
||||
function GetSyncStatusMsg(Empty) {
|
||||
return function(params) {
|
||||
var status = params.status,
|
||||
source = params.source,
|
||||
has_inventory_sources = params.has_inventory_sources,
|
||||
launch_class = '',
|
||||
launch_tip = 'Start sync process',
|
||||
schedule_tip = 'Schedule future inventory syncs',
|
||||
stat, stat_class, status_tip;
|
||||
|
||||
stat = status;
|
||||
stat_class = stat;
|
||||
|
||||
switch (status) {
|
||||
case 'never updated':
|
||||
stat = 'never';
|
||||
stat_class = 'na';
|
||||
status_tip = 'Sync not performed. Click <i class="fa fa-refresh"></i> to start it now.';
|
||||
break;
|
||||
case 'none':
|
||||
case 'ok':
|
||||
case '':
|
||||
launch_class = 'btn-disabled';
|
||||
stat = 'n/a';
|
||||
stat_class = 'na';
|
||||
status_tip = 'Cloud source not configured. Click <i class="fa fa-pencil"></i> to update.';
|
||||
launch_tip = 'Cloud source not configured.';
|
||||
break;
|
||||
case 'canceled':
|
||||
status_tip = 'Sync canceled. Click to view log.';
|
||||
break;
|
||||
case 'failed':
|
||||
status_tip = 'Sync failed. Click to view log.';
|
||||
break;
|
||||
case 'successful':
|
||||
status_tip = 'Sync completed. Click to view log.';
|
||||
break;
|
||||
case 'pending':
|
||||
status_tip = 'Sync pending.';
|
||||
launch_class = "btn-disabled";
|
||||
launch_tip = "Sync pending";
|
||||
break;
|
||||
case 'updating':
|
||||
case 'running':
|
||||
launch_class = "btn-disabled";
|
||||
launch_tip = "Sync running";
|
||||
status_tip = "Sync running. Click to view log.";
|
||||
break;
|
||||
}
|
||||
|
||||
if (has_inventory_sources && Empty(source)) {
|
||||
// parent has a source, therefore this group should not have a source
|
||||
launch_class = "btn-disabled";
|
||||
status_tip = 'Managed by an external cloud source.';
|
||||
launch_tip = 'Can only be updated by running a sync on the parent group.';
|
||||
}
|
||||
|
||||
if (has_inventory_sources === false && Empty(source)) {
|
||||
launch_class = 'btn-disabled';
|
||||
status_tip = 'Cloud source not configured. Click <i class="fa fa-pencil"></i> to update.';
|
||||
launch_tip = 'Cloud source not configured.';
|
||||
}
|
||||
|
||||
return {
|
||||
"class": stat_class,
|
||||
"tooltip": status_tip,
|
||||
"status": stat,
|
||||
"launch_class": launch_class,
|
||||
"launch_tip": launch_tip,
|
||||
"schedule_tip": schedule_tip
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
GetSyncStatusMsg.$inject =
|
||||
[ 'Empty' ];
|
||||
@ -0,0 +1,81 @@
|
||||
export default
|
||||
function GroupsCancelUpdate(Empty, Rest, ProcessErrors, Alert, Wait, Find) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
id = params.id,
|
||||
group = params.group;
|
||||
|
||||
if (scope.removeCancelUpdate) {
|
||||
scope.removeCancelUpdate();
|
||||
}
|
||||
scope.removeCancelUpdate = scope.$on('CancelUpdate', function (e, url) {
|
||||
// Cancel the update process
|
||||
Rest.setUrl(url);
|
||||
Rest.post()
|
||||
.success(function () {
|
||||
Wait('stop');
|
||||
//Alert('Inventory Sync Cancelled', 'Request to cancel the sync process was submitted to the task manger. ' +
|
||||
// 'Click the <i class="fa fa-refresh fa-lg"></i> button to monitor the status.', 'alert-info');
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Call to ' + url + ' failed. POST status: ' + status
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (scope.removeCheckCancel) {
|
||||
scope.removeCheckCancel();
|
||||
}
|
||||
scope.removeCheckCancel = scope.$on('CheckCancel', function (e, last_update, current_update) {
|
||||
// Check that we have access to cancelling an update
|
||||
var url = (current_update) ? current_update : last_update;
|
||||
url += 'cancel/';
|
||||
Rest.setUrl(url);
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
if (data.can_cancel) {
|
||||
scope.$emit('CancelUpdate', url);
|
||||
//} else {
|
||||
// Wait('stop');
|
||||
// Alert('Cancel Inventory Sync', 'The sync process completed. Click the <i class="fa fa-refresh fa-lg"></i> button to view ' +
|
||||
// 'the latest status.', 'alert-info');
|
||||
}
|
||||
else {
|
||||
Wait('stop');
|
||||
}
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Call to ' + url + ' failed. GET status: ' + status
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Cancel the update process
|
||||
if (Empty(group)) {
|
||||
group = Find({ list: scope.groups, key: 'id', val: id });
|
||||
scope.selected_group_id = group.id;
|
||||
}
|
||||
|
||||
if (group && (group.status === 'running' || group.status === 'pending')) {
|
||||
// We found the group, and there is a running update
|
||||
Wait('start');
|
||||
Rest.setUrl(group.related.inventory_source);
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
scope.$emit('CheckCancel', data.related.last_update, data.related.current_update);
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Call to ' + group.related.inventory_source + ' failed. GET status: ' + status
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
GroupsCancelUpdate.$inject =
|
||||
[ 'Empty', 'Rest', 'ProcessErrors',
|
||||
'Alert', 'Wait', 'Find'
|
||||
];
|
||||
@ -0,0 +1,46 @@
|
||||
export default
|
||||
function ViewUpdateStatus($state, Rest, ProcessErrors, Alert, Wait, Empty, Find) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
group_id = params.group_id,
|
||||
group = Find({ list: scope.groups, key: 'id', val: group_id });
|
||||
|
||||
if (scope.removeSourceReady) {
|
||||
scope.removeSourceReady();
|
||||
}
|
||||
scope.removeSourceReady = scope.$on('SourceReady', function(e, source) {
|
||||
|
||||
// Get the ID from the correct summary field
|
||||
var update_id = (source.summary_fields.current_update) ? source.summary_fields.current_update.id : source.summary_fields.last_update.id;
|
||||
|
||||
$state.go('inventorySyncStdout', {id: update_id});
|
||||
|
||||
});
|
||||
|
||||
if (group) {
|
||||
if (Empty(group.source)) {
|
||||
// do nothing
|
||||
} else if (Empty(group.status) || group.status === "never updated") {
|
||||
Alert('No Status Available', '<div>An inventory sync has not been performed for the selected group. Start the process by ' +
|
||||
'clicking the <i class="fa fa-refresh"></i> button.</div>', 'alert-info', null, null, null, null, true);
|
||||
} else {
|
||||
Wait('start');
|
||||
Rest.setUrl(group.related.inventory_source);
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
scope.$emit('SourceReady', data);
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to retrieve inventory source: ' + group.related.inventory_source +
|
||||
' GET returned status: ' + status });
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ViewUpdateStatus.$inject =
|
||||
[ '$state', 'Rest', 'ProcessErrors',
|
||||
'Alert', 'Wait', 'Empty', 'Find'
|
||||
];
|
||||
@ -0,0 +1,75 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
import SourcesListController from './sources-list.controller';
|
||||
export default ['SourcesListDefinition', '$stateExtender', 'templateUrl', '$injector',
|
||||
function(SourcesListDefinition, $stateExtender, templateUrl, $injector){
|
||||
var val = function(field, formStateDefinition) {
|
||||
let state,
|
||||
list = field.include ? $injector.get(field.include) : field,
|
||||
breadcrumbLabel = (field.iterator.replace('_', ' ') + 's').toUpperCase(),
|
||||
stateConfig = {
|
||||
searchPrefix: `${list.iterator}`,
|
||||
name: `${formStateDefinition.name}.${list.iterator}s`,
|
||||
url: `/${list.iterator}s`,
|
||||
ncyBreadcrumb: {
|
||||
parent: `${formStateDefinition.name}`,
|
||||
label: `${breadcrumbLabel}`
|
||||
},
|
||||
params: {
|
||||
[list.iterator + '_search']: {
|
||||
value: { order_by: field.order_by ? field.order_by : 'name' }
|
||||
},
|
||||
},
|
||||
views: {
|
||||
'related': {
|
||||
templateProvider: function(SourcesListDefinition, generateList) {
|
||||
let list = _.cloneDeep(SourcesListDefinition);
|
||||
let html = generateList.build({
|
||||
list: list,
|
||||
mode: 'edit'
|
||||
});
|
||||
// Include the custom group delete modal template
|
||||
// return $templateRequest(templateUrl('inventories/groups/list/groups-list')).then((template) => {
|
||||
// return html.concat(template);
|
||||
// });
|
||||
return html;
|
||||
},
|
||||
controller: SourcesListController
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
ListDefinition: () => {
|
||||
return list;
|
||||
},
|
||||
Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope',
|
||||
(list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => {
|
||||
// allow related list definitions to use interpolated $rootScope / $stateParams in basePath field
|
||||
let path, interpolator;
|
||||
if (GetBasePath(list.basePath)) {
|
||||
path = GetBasePath(list.basePath);
|
||||
} else {
|
||||
interpolator = $interpolate(list.basePath);
|
||||
path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams });
|
||||
}
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
}
|
||||
],
|
||||
inventoryData: ['InventoryManageService', '$stateParams', function(InventoryManageService, $stateParams) {
|
||||
return InventoryManageService.getInventory($stateParams.inventory_id).then(res => res.data);
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
state = $stateExtender.buildDefinition(stateConfig);
|
||||
// appy any default search parameters in form definition
|
||||
// if (field.search) {
|
||||
// state.params[`${field.iterator}_search`].value = _.merge(state.params[`${field.iterator}_search`].value, field.search);
|
||||
// }
|
||||
return state;
|
||||
};
|
||||
return val;
|
||||
}
|
||||
];
|
||||
13
awx/ui/client/src/inventories/sources/list/main.js
Normal file
13
awx/ui/client/src/inventories/sources/list/main.js
Normal file
@ -0,0 +1,13 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import buildSourcesListState from './build-sources-list-state.factory';
|
||||
import controller from './sources-list.controller';
|
||||
|
||||
export default
|
||||
angular.module('sourcesList', [])
|
||||
.factory('buildSourcesListState', buildSourcesListState)
|
||||
.controller('SourcesListController', controller);
|
||||
@ -0,0 +1,213 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
export default
|
||||
['$scope', '$rootScope', '$state', '$stateParams', 'SourcesListDefinition',
|
||||
'InventoryUpdate', 'GroupManageService', 'GroupsCancelUpdate',
|
||||
'ViewUpdateStatus', 'rbacUiControlService', 'GetBasePath',
|
||||
'GetSyncStatusMsg', 'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet',
|
||||
'inventoryData', '$filter', 'Prompt', 'Wait', 'SourcesService',
|
||||
function($scope, $rootScope, $state, $stateParams, SourcesListDefinition,
|
||||
InventoryUpdate, GroupManageService, GroupsCancelUpdate,
|
||||
ViewUpdateStatus, rbacUiControlService, GetBasePath, GetSyncStatusMsg,
|
||||
GetHostsStatusMsg, Dataset, Find, qs, inventoryData, $filter, Prompt,
|
||||
Wait, SourcesService){
|
||||
|
||||
let list = SourcesListDefinition;
|
||||
|
||||
init();
|
||||
|
||||
function init(){
|
||||
$scope.inventory_id = $stateParams.inventory_id;
|
||||
$scope.canAdhoc = inventoryData.summary_fields.user_capabilities.adhoc;
|
||||
$scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd(GetBasePath('inventory') + $scope.inventory_id + "/groups")
|
||||
.then(function(canAdd) {
|
||||
$scope.canAdd = canAdd;
|
||||
});
|
||||
|
||||
// Search init
|
||||
$scope.list = list;
|
||||
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
|
||||
// The ncy breadcrumb directive will look at this attribute when attempting to bind to the correct scope.
|
||||
// In this case, we don't want to incidentally bind to this scope when editing a host or a group. See:
|
||||
// https://github.com/ncuillery/angular-breadcrumb/issues/42 for a little more information on the
|
||||
// problem that this solves.
|
||||
$scope.ncyBreadcrumbIgnore = true;
|
||||
if($state.current.name === "inventoryManage.editGroup") {
|
||||
$scope.rowBeingEdited = $state.params.group_id;
|
||||
$scope.listBeingEdited = "groups";
|
||||
}
|
||||
|
||||
$scope.inventory_id = $stateParams.inventory_id;
|
||||
_.forEach($scope[list.name], buildStatusIndicators);
|
||||
|
||||
}
|
||||
|
||||
function buildStatusIndicators(inventory_source){
|
||||
if (inventory_source === undefined || inventory_source === null) {
|
||||
inventory_source = {};
|
||||
}
|
||||
|
||||
let inventory_source_status, hosts_status;
|
||||
|
||||
inventory_source_status = GetSyncStatusMsg({
|
||||
status: inventory_source.status,
|
||||
has_inventory_sources: inventory_source.has_inventory_sources,
|
||||
source: ( (inventory_source) ? inventory_source.source : null )
|
||||
});
|
||||
hosts_status = GetHostsStatusMsg({
|
||||
active_failures: inventory_source.hosts_with_active_failures,
|
||||
total_hosts: inventory_source.total_hosts,
|
||||
inventory_id: $scope.inventory_id,
|
||||
// group_id: group.id
|
||||
});
|
||||
_.assign(inventory_source,
|
||||
{status_class: inventory_source_status.class},
|
||||
{status_tooltip: inventory_source_status.tooltip},
|
||||
{launch_tooltip: inventory_source_status.launch_tip},
|
||||
{launch_class: inventory_source_status.launch_class},
|
||||
{group_schedule_tooltip: inventory_source_status.schedule_tip},
|
||||
{hosts_status_tip: hosts_status.tooltip},
|
||||
{hosts_status_class: hosts_status.class},
|
||||
{source: inventory_source ? inventory_source.source : null},
|
||||
{status: inventory_source ? inventory_source.status : null});
|
||||
}
|
||||
|
||||
$scope.groupSelect = function(id){
|
||||
var group = $stateParams.group === undefined ? [id] : _($stateParams.group).concat(id).value();
|
||||
$state.go('inventoryManage', {
|
||||
inventory_id: $stateParams.inventory_id,
|
||||
group: group,
|
||||
group_search: {
|
||||
page_size: '20',
|
||||
page: '1',
|
||||
order_by: 'name',
|
||||
}
|
||||
}, {reload: true});
|
||||
};
|
||||
$scope.createSource = function(){
|
||||
$state.go('inventories.edit.inventory_sources.add');
|
||||
};
|
||||
$scope.editSource = function(id){
|
||||
$state.go('inventories.edit.inventory_sources.edit', {source_id: id});
|
||||
};
|
||||
$scope.deleteSource = function(inventory_source){
|
||||
var body = '<div class=\"Prompt-bodyQuery\">Are you sure you want to permanently delete the inventory source below from the inventory?</div><div class=\"Prompt-bodyTarget\">' + $filter('sanitize')(inventory_source.name) + '</div>';
|
||||
var action = function(){
|
||||
delete $rootScope.promptActionBtnClass;
|
||||
Wait('start');
|
||||
SourcesService.delete(inventory_source.id).then(() => {
|
||||
$('#prompt-modal').modal('hide');
|
||||
// if (parseInt($state.params.source_id) === id) {
|
||||
// $state.go("sources", null, {reload: true});
|
||||
// } else {
|
||||
$state.go($state.current.name, null, {reload: true});
|
||||
// }
|
||||
Wait('stop');
|
||||
});
|
||||
};
|
||||
// Prompt depends on having $rootScope.promptActionBtnClass available...
|
||||
Prompt({
|
||||
hdr: 'Delete Source',
|
||||
body: body,
|
||||
action: action,
|
||||
actionText: 'DELETE',
|
||||
});
|
||||
$rootScope.promptActionBtnClass = 'Modal-errorButton';
|
||||
};
|
||||
|
||||
$scope.updateSource = function(inventory_source) {
|
||||
InventoryUpdate({
|
||||
scope: $scope,
|
||||
url: inventory_source.related.update
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$on(`ws-jobs`, function(e, data){
|
||||
var group = Find({ list: $scope.groups, key: 'id', val: data.group_id });
|
||||
|
||||
if (group === undefined || group === null) {
|
||||
group = {};
|
||||
}
|
||||
|
||||
if(data.status === 'failed' || data.status === 'successful'){
|
||||
let path;
|
||||
if($stateParams && $stateParams.group && $stateParams.group.length > 0) {
|
||||
path = GetBasePath('groups') + _.last($stateParams.group) + '/children';
|
||||
}
|
||||
else {
|
||||
//reaches here if the user is on the root level group
|
||||
path = GetBasePath('inventory') + $stateParams.inventory_id + '/root_groups';
|
||||
}
|
||||
qs.search(path, $state.params[`${list.iterator}_search`])
|
||||
.then(function(searchResponse) {
|
||||
$scope[`${list.iterator}_dataset`] = searchResponse.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
// _.forEach($scope[list.name], buildStatusIndicators);
|
||||
});
|
||||
} else {
|
||||
var status = GetSyncStatusMsg({
|
||||
status: data.status,
|
||||
has_inventory_sources: group.has_inventory_sources,
|
||||
source: group.source
|
||||
});
|
||||
group.status = data.status;
|
||||
group.status_class = status.class;
|
||||
group.status_tooltip = status.tooltip;
|
||||
group.launch_tooltip = status.launch_tip;
|
||||
group.launch_class = status.launch_class;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.cancelUpdate = function (id) {
|
||||
GroupsCancelUpdate({ scope: $scope, id: id });
|
||||
};
|
||||
$scope.viewUpdateStatus = function (id) {
|
||||
ViewUpdateStatus({
|
||||
scope: $scope,
|
||||
group_id: id
|
||||
});
|
||||
};
|
||||
$scope.showFailedHosts = function() {
|
||||
$state.go('inventoryManage', {failed: true}, {reload: true});
|
||||
};
|
||||
$scope.scheduleGroup = function(id) {
|
||||
// Add this group's id to the array of group id's so that it gets
|
||||
// added to the breadcrumb trail
|
||||
var groupsArr = $stateParams.group ? $stateParams.group : [];
|
||||
groupsArr.push(id);
|
||||
$state.go('inventoryManage.editGroup.schedules', {group_id: id, group: groupsArr}, {reload: true});
|
||||
};
|
||||
// $scope.$parent governed by InventoryManageController, for unified multiSelect options
|
||||
$scope.$on('multiSelectList.selectionChanged', (event, selection) => {
|
||||
$scope.$parent.groupsSelected = selection.length > 0 ? true : false;
|
||||
$scope.$parent.groupsSelectedItems = selection.selectedItems;
|
||||
});
|
||||
|
||||
$scope.copyMoveGroup = function(id){
|
||||
$state.go('inventoryManage.copyMoveGroup', {group_id: id, groups: $stateParams.groups});
|
||||
};
|
||||
|
||||
var cleanUpStateChangeListener = $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams) {
|
||||
if (toState.name === "inventoryManage.editGroup") {
|
||||
$scope.rowBeingEdited = toParams.group_id;
|
||||
$scope.listBeingEdited = "groups";
|
||||
}
|
||||
else {
|
||||
delete $scope.rowBeingEdited;
|
||||
delete $scope.listBeingEdited;
|
||||
}
|
||||
});
|
||||
|
||||
// Remove the listener when the scope is destroyed to avoid a memory leak
|
||||
$scope.$on('$destroy', function() {
|
||||
cleanUpStateChangeListener();
|
||||
});
|
||||
|
||||
}];
|
||||
@ -0,0 +1,79 @@
|
||||
<div class="modal fade GroupDelete" id="group-delete-modal" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content Modal-content">
|
||||
<div class="Modal-header">
|
||||
<div class="Modal-title ng-binding">
|
||||
Delete Group
|
||||
<a href="" id="awp-promote" href=""
|
||||
aw-pop-over="<dl><dt>Delete</dt><dd>Deletes groups and hosts associated with the group being deleted. If a group or host is associated with other groups, it will still exist within those groups. Otherwise, the associated groups and hosts will no longer appear in the inventory.</dd>\n<dt style='margin-top: 5px;'>Promote</dt><dd>Groups and hosts associated with the group being removed will be promoted root level. Note: groups already associated with other groups cannot be promoted.</dd></dl>\n" aw-tool-tip="Click for help"
|
||||
data-placement="right"
|
||||
data-container="body"
|
||||
data-title="Delete Group"
|
||||
class="help-link">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="Modal-exitHolder">
|
||||
<button class="close Modal-exit" data-target="#group-delete-modal" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times-circle"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Modal-body">
|
||||
<div ng-show="toDelete.total_groups > 0 || toDelete.total_hosts > 0">
|
||||
<div>
|
||||
<p class="Prompt-bodyQuery">Deleting group <em>{{ toDelete.name }}</em>.
|
||||
<span ng-show="toDelete.total_groups > 0 && toDelete.total_hosts > 0"> This group contains {{ toDelete.total_groups }} groups and {{ toDelete.total_hosts }} hosts. </span>
|
||||
<span ng-show="toDelete.total_groups == 0 && toDelete.total_hosts > 0"> This group contains {{ toDelete.total_hosts }} hosts. </span>
|
||||
<span ng-show="toDelete.total_groups > 0 && toDelete.total_hosts == 0"> This group contains {{ toDelete.total_groups }} groups. </span>
|
||||
Delete or promote the group's children?</p>
|
||||
<div style="margin: 15px auto;">
|
||||
|
||||
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts > 0">
|
||||
<label>
|
||||
<input type="radio" ng-model="deleteOption" value="promote"> Promote groups and hosts
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts > 0">
|
||||
<label>
|
||||
<input type="radio" ng-model="deleteOption" value="delete"> Delete groups and hosts
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts == 0">
|
||||
<label>
|
||||
<input type="radio" ng-model="deleteOption" value="promote"> Promote groups
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio" ng-show="toDelete.total_groups > 0 && toDelete.total_hosts == 0">
|
||||
<label>
|
||||
<input type="radio" ng-model="deleteOption" value="delete"> Delete groups
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="radio" ng-show="toDelete.total_groups == 0 && toDelete.total_hosts > 0">
|
||||
<label>
|
||||
<input type="radio" ng-model="deleteOption" value="promote"> Promote hosts
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio" ng-show="toDelete.total_groups == 0 && toDelete.total_hosts > 0">
|
||||
<label>
|
||||
<input type="radio" ng-model="deleteOption" value="delete"> Delete hosts
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div ng-show="toDelete.total_groups == 0 && toDelete.total_hosts == 0">
|
||||
<div class="Prompt-bodyQuery">Are you sure you want to permanently delete the group below from the inventory?</div>
|
||||
<div class="Prompt-bodyTarget">{{ toDelete.name }}</div>
|
||||
</div>
|
||||
<div class="Modal-footer">
|
||||
<a href="#" data-target="#group-delete-modal" data-dismiss="modal" id="prompt_cancel_btn_groups_list" class="btn Modal-defaultButton Modal-footerButton">CANCEL</a>
|
||||
<a href="" ng-class="promptActionBtnClass" ng-click="confirmDelete()" id="prompt_action_btn_groups_list" class="btn Modal-footerButton Modal-errorButton">DELETE</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
22
awx/ui/client/src/inventories/sources/main.js
Normal file
22
awx/ui/client/src/inventories/sources/main.js
Normal file
@ -0,0 +1,22 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import sourcesList from './list/main';
|
||||
import sourcesAdd from './add/main';
|
||||
import sourcesEdit from './edit/main';
|
||||
import sourcesFormDefinition from './sources.form';
|
||||
import sourcesListDefinition from './sources.list';
|
||||
import service from './sources.service';
|
||||
|
||||
export default
|
||||
angular.module('sources', [
|
||||
sourcesList.name,
|
||||
sourcesAdd.name,
|
||||
sourcesEdit.name
|
||||
])
|
||||
.value('SourcesFormDefinition', sourcesFormDefinition)
|
||||
.value('SourcesListDefinition', sourcesListDefinition)
|
||||
.service('SourcesService', service);
|
||||
337
awx/ui/client/src/inventories/sources/sources.form.js
Normal file
337
awx/ui/client/src/inventories/sources/sources.form.js
Normal file
@ -0,0 +1,337 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name forms.function:Groups
|
||||
* @description This form is for adding/editing a Group on the inventory page
|
||||
*/
|
||||
|
||||
export default {
|
||||
addTitle: 'CREATE SOURCE',
|
||||
editTitle: '{{ name }}',
|
||||
showTitle: true,
|
||||
name: 'inventory_source',
|
||||
basePath: 'inventory_sources',
|
||||
parent: 'inventories.edit.sources',
|
||||
// the parent node this generated state definition tree expects to attach to
|
||||
stateTree: 'inventories',
|
||||
// form generator inspects the current state name to determine whether or not to set an active (.is-selected) class on a form tab
|
||||
// this setting is optional on most forms, except where the form's edit state name is not parentStateName.edit
|
||||
activeEditState: 'inventories.edit.groups.editGroup',
|
||||
detailsClick: "$state.go('inventories.edit.inventory_sources.edit')",
|
||||
well: false,
|
||||
fields: {
|
||||
name: {
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
required: true,
|
||||
tab: 'properties'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
type: 'text',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
tab: 'properties'
|
||||
},
|
||||
variables: {
|
||||
label: 'Variables',
|
||||
type: 'textarea',
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
rows: 6,
|
||||
'default': '---',
|
||||
dataTitle: 'Group Variables',
|
||||
dataPlacement: 'right',
|
||||
parseTypeName: 'parseType',
|
||||
awPopOver: "<p>Variables defined here apply to all child groups and hosts.</p>" +
|
||||
"<p>Enter variables using either JSON or YAML syntax. Use the " +
|
||||
"radio button to toggle between the two.</p>" +
|
||||
"JSON:<br />\n" +
|
||||
"<blockquote>{<br />  \"somevar\": \"somevalue\",<br /> \"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||
"YAML:<br />\n" +
|
||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
|
||||
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
|
||||
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
|
||||
dataContainer: 'body',
|
||||
tab: 'properties'
|
||||
},
|
||||
source: {
|
||||
label: 'Source',
|
||||
type: 'select',
|
||||
ngOptions: 'source.label for source in source_type_options track by source.value',
|
||||
ngChange: 'sourceChange(source)',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
ngModel: 'source'
|
||||
},
|
||||
credential: {
|
||||
// initializes a default value for this search param
|
||||
// search params with default values set will not generate user-interactable search tags
|
||||
search: {
|
||||
kind: null
|
||||
},
|
||||
label: 'Cloud Credential',
|
||||
type: 'lookup',
|
||||
list: 'CredentialList',
|
||||
basePath: 'credentials',
|
||||
ngShow: "source && source.value !== '' && source.value !== 'custom'",
|
||||
sourceModel: 'credential',
|
||||
sourceField: 'name',
|
||||
ngClick: 'lookupCredential()',
|
||||
awRequiredWhen: {
|
||||
reqExpression: "cloudCredentialRequired",
|
||||
init: "false"
|
||||
},
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
watchBasePath: "credentialBasePath"
|
||||
},
|
||||
source_regions: {
|
||||
label: 'Regions',
|
||||
type: 'select',
|
||||
ngOptions: 'source.label for source in source_region_choices track by source.value',
|
||||
multiSelect: true,
|
||||
ngShow: "source && (source.value == 'rax' || source.value == 'ec2' || source.value == 'gce' || source.value == 'azure' || source.value == 'azure_rm')",
|
||||
|
||||
|
||||
dataTitle: 'Source Regions',
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Click on the regions field to see a list of regions for your cloud provider. You can select multiple regions, " +
|
||||
"or choose <em>All</em> to include all regions. Tower will only be updated with Hosts associated with the selected regions." +
|
||||
"</p>",
|
||||
dataContainer: 'body',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
instance_filters: {
|
||||
label: 'Instance Filters',
|
||||
type: 'text',
|
||||
ngShow: "source && source.value == 'ec2'",
|
||||
dataTitle: 'Instance Filters',
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Provide a comma-separated list of filter expressions. " +
|
||||
"Hosts are imported to Tower when <em>ANY</em> of the filters match.</p>" +
|
||||
"Limit to hosts having a tag:<br />\n" +
|
||||
"<blockquote>tag-key=TowerManaged</blockquote>\n" +
|
||||
"Limit to hosts using either key pair:<br />\n" +
|
||||
"<blockquote>key-name=staging, key-name=production</blockquote>\n" +
|
||||
"Limit to hosts where the Name tag begins with <em>test</em>:<br />\n" +
|
||||
"<blockquote>tag:Name=test*</blockquote>\n" +
|
||||
"<p>View the <a href=\"http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html\" target=\"_blank\">Describe Instances documentation</a> " +
|
||||
"for a complete list of supported filters.</p>",
|
||||
dataContainer: 'body',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
group_by: {
|
||||
label: 'Only Group By',
|
||||
type: 'select',
|
||||
ngShow: "source && source.value == 'ec2'",
|
||||
ngOptions: 'source.label for source in group_by_choices track by source.value',
|
||||
multiSelect: true,
|
||||
dataTitle: 'Only Group By',
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Select which groups to create automatically. " +
|
||||
"Tower will create group names similar to the following examples based on the options selected:</p><ul>" +
|
||||
"<li>Availability Zone: <strong>zones » us-east-1b</strong></li>" +
|
||||
"<li>Image ID: <strong>images » ami-b007ab1e</strong></li>" +
|
||||
"<li>Instance ID: <strong>instances » i-ca11ab1e</strong></li>" +
|
||||
"<li>Instance Type: <strong>types » type_m1_medium</strong></li>" +
|
||||
"<li>Key Name: <strong>keys » key_testing</strong></li>" +
|
||||
"<li>Region: <strong>regions » us-east-1</strong></li>" +
|
||||
"<li>Security Group: <strong>security_groups » security_group_default</strong></li>" +
|
||||
"<li>Tags: <strong>tags » tag_Name » tag_Name_host1</strong></li>" +
|
||||
"<li>VPC ID: <strong>vpcs » vpc-5ca1ab1e</strong></li>" +
|
||||
"<li>Tag None: <strong>tags » tag_none</strong></li>" +
|
||||
"</ul><p>If blank, all groups above are created except <em>Instance ID</em>.</p>",
|
||||
dataContainer: 'body',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
inventory_script: {
|
||||
label : "Custom Inventory Script",
|
||||
type: 'lookup',
|
||||
basePath: 'inventory_scripts',
|
||||
list: 'InventoryScriptsList',
|
||||
ngShow: "source && source.value === 'custom'",
|
||||
sourceModel: 'inventory_script',
|
||||
sourceField: 'name',
|
||||
awRequiredWhen: {
|
||||
reqExpression: "source && source.value === 'custom'",
|
||||
init: "false"
|
||||
},
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
},
|
||||
custom_variables: {
|
||||
id: 'custom_variables',
|
||||
label: 'Environment Variables', //"{{vars_label}}" ,
|
||||
ngShow: "source && source.value=='custom' ",
|
||||
type: 'textarea',
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
rows: 6,
|
||||
'default': '---',
|
||||
parseTypeName: 'envParseType',
|
||||
dataTitle: "Environment Variables",
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Provide environment variables to pass to the custom inventory script.</p>" +
|
||||
"<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
|
||||
"JSON:<br />\n" +
|
||||
"<blockquote>{<br /> \"somevar\": \"somevalue\",<br /> \"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||
"YAML:<br />\n" +
|
||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
|
||||
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
|
||||
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
|
||||
dataContainer: 'body'
|
||||
},
|
||||
ec2_variables: {
|
||||
id: 'ec2_variables',
|
||||
label: 'Source Variables', //"{{vars_label}}" ,
|
||||
ngShow: "source && source.value == 'ec2'",
|
||||
type: 'textarea',
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
rows: 6,
|
||||
'default': '---',
|
||||
parseTypeName: 'envParseType',
|
||||
dataTitle: "Source Variables",
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Override variables found in ec2.ini and used by the inventory update script. For a detailed description of these variables " +
|
||||
"<a href=\"https://github.com/ansible/ansible/blob/devel/contrib/inventory/ec2.ini\" target=\"_blank\">" +
|
||||
"view ec2.ini in the Ansible github repo.</a></p>" +
|
||||
"<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
|
||||
"JSON:<br />\n" +
|
||||
"<blockquote>{<br /> \"somevar\": \"somevalue\",<br /> \"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||
"YAML:<br />\n" +
|
||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
|
||||
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
|
||||
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
|
||||
dataContainer: 'body'
|
||||
},
|
||||
vmware_variables: {
|
||||
id: 'vmware_variables',
|
||||
label: 'Source Variables', //"{{vars_label}}" ,
|
||||
ngShow: "source && source.value == 'vmware'",
|
||||
type: 'textarea',
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
rows: 6,
|
||||
'default': '---',
|
||||
parseTypeName: 'envParseType',
|
||||
dataTitle: "Source Variables",
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Override variables found in vmware.ini and used by the inventory update script. For a detailed description of these variables " +
|
||||
"<a href=\"https://github.com/ansible/ansible/blob/devel/contrib/inventory/vmware_inventory.ini\" target=\"_blank\">" +
|
||||
"view vmware_inventory.ini in the Ansible github repo.</a></p>" +
|
||||
"<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
|
||||
"JSON:<br />\n" +
|
||||
"<blockquote>{<br /> \"somevar\": \"somevalue\",<br /> \"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||
"YAML:<br />\n" +
|
||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
|
||||
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
|
||||
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
|
||||
dataContainer: 'body'
|
||||
},
|
||||
openstack_variables: {
|
||||
id: 'openstack_variables',
|
||||
label: 'Source Variables', //"{{vars_label}}" ,
|
||||
ngShow: "source && source.value == 'openstack'",
|
||||
type: 'textarea',
|
||||
class: 'Form-textAreaLabel Form-formGroup--fullWidth',
|
||||
rows: 6,
|
||||
'default': '---',
|
||||
parseTypeName: 'envParseType',
|
||||
dataTitle: "Source Variables",
|
||||
dataPlacement: 'right',
|
||||
awPopOver: "<p>Override variables found in openstack.yml and used by the inventory update script. For an example variable configuration " +
|
||||
"<a href=\"https://github.com/ansible/ansible/blob/devel/contrib/inventory/openstack.yml\" target=\"_blank\">" +
|
||||
"view openstack.yml in the Ansible github repo.</a></p>" +
|
||||
"<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
|
||||
"JSON:<br />\n" +
|
||||
"<blockquote>{<br /> \"somevar\": \"somevalue\",<br /> \"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||
"YAML:<br />\n" +
|
||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
|
||||
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
|
||||
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
|
||||
dataContainer: 'body'
|
||||
},
|
||||
checkbox_group: {
|
||||
label: 'Update Options',
|
||||
type: 'checkbox_group',
|
||||
ngShow: "source && (source.value !== '' && source.value !== null)",
|
||||
class: 'Form-checkbox--stacked',
|
||||
fields: [{
|
||||
name: 'overwrite',
|
||||
label: 'Overwrite',
|
||||
type: 'checkbox',
|
||||
ngShow: "source.value !== '' && source.value !== null",
|
||||
|
||||
|
||||
awPopOver: '<p>If checked, all child groups and hosts not found on the external source will be deleted from ' +
|
||||
'the local inventory.</p><p>When not checked, local child hosts and groups not found on the external source will ' +
|
||||
'remain untouched by the inventory update process.</p>',
|
||||
dataTitle: 'Overwrite',
|
||||
dataContainer: 'body',
|
||||
dataPlacement: 'right',
|
||||
labelClass: 'checkbox-options',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}, {
|
||||
name: 'overwrite_vars',
|
||||
label: 'Overwrite Variables',
|
||||
type: 'checkbox',
|
||||
ngShow: "source.value !== '' && source.value !== null",
|
||||
|
||||
|
||||
awPopOver: '<p>If checked, all variables for child groups and hosts will be removed and replaced by those ' +
|
||||
'found on the external source.</p><p>When not checked, a merge will be performed, combining local variables with ' +
|
||||
'those found on the external source.</p>',
|
||||
dataTitle: 'Overwrite Variables',
|
||||
dataContainer: 'body',
|
||||
dataPlacement: 'right',
|
||||
labelClass: 'checkbox-options',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}, {
|
||||
name: 'update_on_launch',
|
||||
label: 'Update on Launch',
|
||||
type: 'checkbox',
|
||||
ngShow: "source.value !== '' && source.value !== null",
|
||||
awPopOver: '<p>Each time a job runs using this inventory, refresh the inventory from the selected source before ' +
|
||||
'executing job tasks.</p>',
|
||||
dataTitle: 'Update on Launch',
|
||||
dataContainer: 'body',
|
||||
dataPlacement: 'right',
|
||||
labelClass: 'checkbox-options',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}]
|
||||
},
|
||||
update_cache_timeout: {
|
||||
label: "Cache Timeout <span class=\"small-text\"> (seconds)</span>",
|
||||
id: 'source-cache-timeout',
|
||||
type: 'number',
|
||||
ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)',
|
||||
integer: true,
|
||||
min: 0,
|
||||
ngShow: "source && source.value !== '' && update_on_launch",
|
||||
spinner: true,
|
||||
"default": 0,
|
||||
awPopOver: '<p>Time in seconds to consider an inventory sync to be current. During job runs and callbacks the task system will ' +
|
||||
'evaluate the timestamp of the latest sync. If it is older than Cache Timeout, it is not considered current, ' +
|
||||
'and a new inventory sync will be performed.</p>',
|
||||
dataTitle: 'Cache Timeout',
|
||||
dataPlacement: 'right',
|
||||
dataContainer: "body"
|
||||
}
|
||||
},
|
||||
|
||||
buttons: {
|
||||
cancel: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
close: {
|
||||
ngClick: 'formCancel()',
|
||||
ngShow: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
},
|
||||
save: {
|
||||
ngClick: 'formSave()',
|
||||
ngDisabled: true,
|
||||
ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)'
|
||||
}
|
||||
}
|
||||
};
|
||||
146
awx/ui/client/src/inventories/sources/sources.list.js
Normal file
146
awx/ui/client/src/inventories/sources/sources.list.js
Normal file
@ -0,0 +1,146 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2017 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default {
|
||||
name: 'inventory_sources',
|
||||
iterator: 'inventory_source',
|
||||
editTitle: '{{ inventory_source.name }}',
|
||||
well: true,
|
||||
wellOverride: true,
|
||||
index: false,
|
||||
hover: true,
|
||||
multiSelect: true,
|
||||
trackBy: 'inventory_source.id',
|
||||
basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/inventory_sources/',
|
||||
|
||||
fields: {
|
||||
sync_status: {
|
||||
label: '',
|
||||
nosort: true,
|
||||
mode: 'all',
|
||||
iconOnly: true,
|
||||
ngClick: 'viewUpdateStatus(inventory_source.id)',
|
||||
awToolTip: "{{ inventory_source.status_tooltip }}",
|
||||
dataTipWatch: "inventory_source.status_tooltip",
|
||||
icon: "{{ 'fa icon-cloud-' + inventory_source.status_class }}",
|
||||
ngClass: "inventory_source.status_class",
|
||||
dataPlacement: "top",
|
||||
columnClass: 'status-column List-staticColumn--smallStatus'
|
||||
},
|
||||
name: {
|
||||
label: 'Sources',
|
||||
key: true,
|
||||
ngClick: "groupSelect(inventory_source.id)",
|
||||
columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6',
|
||||
class: 'InventoryManage-breakWord',
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
refresh: {
|
||||
mode: 'all',
|
||||
awToolTip: "Refresh the page",
|
||||
ngClick: "refreshGroups()",
|
||||
ngShow: "socketStatus == 'error'",
|
||||
actionClass: 'btn List-buttonDefault',
|
||||
buttonContent: 'REFRESH'
|
||||
},
|
||||
// launch: {
|
||||
// mode: 'all',
|
||||
// // $scope.$parent is governed by InventoryManageController,
|
||||
// ngDisabled: '!$parent.groupsSelected && !$parent.hostsSelected',
|
||||
// ngClick: '$parent.setAdhocPattern()',
|
||||
// awToolTip: "Select an inventory source by clicking the check box beside it. The inventory source can be a single group or host, a selection of multiple hosts, or a selection of multiple groups.",
|
||||
// dataTipWatch: "adhocCommandTooltip",
|
||||
// actionClass: 'btn List-buttonDefault',
|
||||
// buttonContent: 'RUN COMMANDS',
|
||||
// showTipWhenDisabled: true,
|
||||
// tooltipInnerClass: "Tooltip-wide",
|
||||
// ngShow: 'canAdhoc'
|
||||
// // TODO: set up a tip watcher and change text based on when
|
||||
// // things are selected/not selected. This is started and
|
||||
// // commented out in the inventory controller within the watchers.
|
||||
// // awToolTip: "{{ adhocButtonTipContents }}",
|
||||
// // dataTipWatch: "adhocButtonTipContents"
|
||||
// },
|
||||
create: {
|
||||
mode: 'all',
|
||||
ngClick: "createSource()",
|
||||
awToolTip: "Create a new source",
|
||||
actionClass: 'btn List-buttonSubmit',
|
||||
buttonContent: '+ ADD SOURCE',
|
||||
ngShow: 'canAdd',
|
||||
dataPlacement: "top",
|
||||
}
|
||||
},
|
||||
|
||||
fieldActions: {
|
||||
|
||||
columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6 text-right',
|
||||
|
||||
group_update: {
|
||||
//label: 'Sync',
|
||||
mode: 'all',
|
||||
ngClick: 'updateSource(inventory_source)',
|
||||
awToolTip: "{{ inventory_source.launch_tooltip }}",
|
||||
dataTipWatch: "inventory_source.launch_tooltip",
|
||||
ngShow: "(inventory_source.status !== 'running' && inventory_source.status " +
|
||||
"!== 'pending' && inventory_source.status !== 'updating') && inventory_source.summary_fields.user_capabilities.start",
|
||||
ngClass: "inventory_source.launch_class",
|
||||
dataPlacement: "top",
|
||||
},
|
||||
cancel: {
|
||||
//label: 'Cancel',
|
||||
mode: 'all',
|
||||
ngClick: "cancelUpdate(inventory_source.id)",
|
||||
awToolTip: "Cancel sync process",
|
||||
'class': 'red-txt',
|
||||
ngShow: "(inventory_source.status == 'running' || inventory_source.status == 'pending' " +
|
||||
"|| inventory_source.status == 'updating') && inventory_source.summary_fields.user_capabilities.start",
|
||||
dataPlacement: "top",
|
||||
iconClass: "fa fa-minus-circle"
|
||||
},
|
||||
copy: {
|
||||
mode: 'all',
|
||||
ngClick: "copyMoveSource(inventory_source.id)",
|
||||
awToolTip: 'Copy or move source',
|
||||
ngShow: "inventory_source.id > 0 && inventory_source.summary_fields.user_capabilities.copy",
|
||||
dataPlacement: "top"
|
||||
},
|
||||
schedule: {
|
||||
mode: 'all',
|
||||
ngClick: "scheduleSource(inventory_source.id)",
|
||||
awToolTip: "{{ inventory_source.group_schedule_tooltip }}",
|
||||
ngClass: "inventory_source.scm_type_class",
|
||||
dataPlacement: 'top',
|
||||
ngShow: "!(inventory_source.summary_fields.inventory_source.source === '')"
|
||||
},
|
||||
edit: {
|
||||
//label: 'Edit',
|
||||
mode: 'all',
|
||||
ngClick: "editSource(inventory_source.id)",
|
||||
awToolTip: 'Edit source',
|
||||
dataPlacement: "top",
|
||||
ngShow: "inventory_source.summary_fields.user_capabilities.edit"
|
||||
},
|
||||
view: {
|
||||
//label: 'Edit',
|
||||
mode: 'all',
|
||||
ngClick: "editSource(inventory_source.id)",
|
||||
awToolTip: 'View source',
|
||||
dataPlacement: "top",
|
||||
ngShow: "!inventory_source.summary_fields.user_capabilities.edit"
|
||||
},
|
||||
"delete": {
|
||||
//label: 'Delete',
|
||||
mode: 'all',
|
||||
ngClick: "deleteSource(inventory_source)",
|
||||
awToolTip: 'Delete source',
|
||||
dataPlacement: "top",
|
||||
ngShow: "inventory_source.summary_fields.user_capabilities.delete"
|
||||
}
|
||||
}
|
||||
};
|
||||
113
awx/ui/client/src/inventories/sources/sources.service.js
Normal file
113
awx/ui/client/src/inventories/sources/sources.service.js
Normal file
@ -0,0 +1,113 @@
|
||||
export default
|
||||
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', function($rootScope, Rest, GetBasePath, ProcessErrors, Wait){
|
||||
return {
|
||||
stringifyParams: function(params){
|
||||
return _.reduce(params, (result, value, key) => {
|
||||
return result + key + '=' + value + '&';
|
||||
}, '');
|
||||
},
|
||||
// cute abstractions via fn.bind()
|
||||
url: function(){
|
||||
return '';
|
||||
},
|
||||
error: function(data, status) {
|
||||
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Call to ' + this.url + '. GET returned: ' + status });
|
||||
},
|
||||
success: function(data){
|
||||
return data;
|
||||
},
|
||||
// HTTP methods
|
||||
get: function(params){
|
||||
Wait('start');
|
||||
this.url = GetBasePath('inventory_sources') + '?' + this.stringifyParams(params);
|
||||
Rest.setUrl(this.url);
|
||||
return Rest.get()
|
||||
.success(this.success.bind(this))
|
||||
.error(this.error.bind(this))
|
||||
.finally(Wait('stop'));
|
||||
},
|
||||
post: function(inventory_source){
|
||||
Wait('start');
|
||||
this.url = GetBasePath('inventory_sources');
|
||||
Rest.setUrl(this.url);
|
||||
return Rest.post(inventory_source)
|
||||
.success(this.success.bind(this))
|
||||
.error(this.error.bind(this))
|
||||
.finally(Wait('stop'));
|
||||
},
|
||||
put: function(inventory_source){
|
||||
Wait('start');
|
||||
this.url = GetBasePath('inventory_sources') + inventory_source.id;
|
||||
Rest.setUrl(this.url);
|
||||
return Rest.put(inventory_source)
|
||||
.success(this.success.bind(this))
|
||||
.error(this.error.bind(this))
|
||||
.finally(Wait('stop'));
|
||||
},
|
||||
delete: function(id){
|
||||
Wait('start');
|
||||
this.url = GetBasePath('inventory_sources') + id;
|
||||
Rest.setUrl(this.url);
|
||||
return Rest.destroy()
|
||||
.success(this.success.bind(this))
|
||||
.error(this.error.bind(this))
|
||||
.finally(Wait('stop'));
|
||||
},
|
||||
getCredential: function(id){
|
||||
Wait('start');
|
||||
this.url = GetBasePath('credentials') + id;
|
||||
Rest.setUrl(this.url);
|
||||
return Rest.get()
|
||||
.success(this.success.bind(this))
|
||||
.error(this.error.bind(this))
|
||||
.finally(Wait('stop'));
|
||||
},
|
||||
getInventorySource: function(params){
|
||||
Wait('start');
|
||||
this.url = GetBasePath('inventory_sources') + '?' + this.stringifyParams(params);
|
||||
Rest.setUrl(this.url);
|
||||
return Rest.get()
|
||||
.success(this.success.bind(this))
|
||||
.error(this.error.bind(this))
|
||||
.finally(Wait('stop'));
|
||||
},
|
||||
putInventorySource: function(params, url){
|
||||
Wait('start');
|
||||
this.url = url;
|
||||
Rest.setUrl(this.url);
|
||||
return Rest.put(params)
|
||||
.success(this.success.bind(this))
|
||||
.error(this.error.bind(this))
|
||||
.finally(Wait('stop'));
|
||||
},
|
||||
// these relationship setters could be consolidated, but verbosity makes the operation feel more clear @ controller level
|
||||
associateGroup: function(group, target){
|
||||
Wait('start');
|
||||
this.url = GetBasePath('groups') + target + '/children/';
|
||||
Rest.setUrl(this.url);
|
||||
return Rest.post(group)
|
||||
.success(this.success.bind(this))
|
||||
.error(this.error.bind(this))
|
||||
.finally(Wait('stop'));
|
||||
},
|
||||
disassociateGroup: function(group, parent){
|
||||
Wait('start');
|
||||
this.url = GetBasePath('groups') + parent + '/children/';
|
||||
Rest.setUrl(this.url);
|
||||
return Rest.post({id: group, disassociate: 1})
|
||||
.success(this.success.bind(this))
|
||||
.error(this.error.bind(this))
|
||||
.finally(Wait('stop'));
|
||||
},
|
||||
promote: function(group, inventory){
|
||||
Wait('start');
|
||||
this.url = GetBasePath('inventory') + inventory + '/groups/';
|
||||
Rest.setUrl(this.url);
|
||||
return Rest.post({id: group, disassociate: 1})
|
||||
.success(this.success.bind(this))
|
||||
.error(this.error.bind(this))
|
||||
.finally(Wait('stop'));
|
||||
}
|
||||
};
|
||||
}];
|
||||
@ -246,6 +246,7 @@ function($injector, $stateExtender, $log, i18n) {
|
||||
* @returns {array} Array of state definitions [{...}, {...}, ...]
|
||||
*/
|
||||
generateFormListDefinitions: function(form, formStateDefinition, params) {
|
||||
var that = this;
|
||||
function buildRbacUserTeamDirective(){
|
||||
let states = [];
|
||||
|
||||
@ -559,11 +560,23 @@ function($injector, $stateExtender, $log, i18n) {
|
||||
states = _.flatten(states);
|
||||
}
|
||||
if(field && field.addState){
|
||||
states.push(field.addState(field, formStateDefinition, params));
|
||||
let formState = field.addState(field, formStateDefinition, params);
|
||||
states.push(formState);
|
||||
// intent here is to add lookup states for any add-forms
|
||||
if(field.includeForm){
|
||||
let form = field.includeForm ? $injector.get(field.includeForm) : field;
|
||||
states.push(that.generateLookupNodes(form, formState));
|
||||
}
|
||||
states = _.flatten(states);
|
||||
}
|
||||
if(field && field.editState){
|
||||
states.push(field.editState(field, formStateDefinition, params));
|
||||
let formState = field.editState(field, formStateDefinition, params);
|
||||
states.push(formState);
|
||||
// intent here is to add lookup states for any edit-forms
|
||||
if(field.includeForm){
|
||||
let form = field.includeForm ? $injector.get(field.includeForm) : field;
|
||||
states.push(that.generateLookupNodes(form, formState));
|
||||
}
|
||||
states = _.flatten(states);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user