Adding group add and group edit routes back in

This commit is contained in:
jaredevantabor 2017-04-12 11:25:47 -07:00 committed by Jared Tabor
parent c1ad7d69fd
commit b85614082e
15 changed files with 917 additions and 281 deletions

View File

@ -0,0 +1,46 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import GroupAddController from './groups-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: {
'groupForm@inventories': {
templateProvider: function(GenerateForm, GroupForm) {
let form = GroupForm;
return GenerateForm.buildHTML(form, {
mode: 'add',
related: false
});
},
controller: GroupAddController
}
},
resolve: {
'FormDefinition': [params.form, function(definition) {
return definition;
}]
}
};
state = $stateExtender.buildDefinition(stateConfig);
return state;
};
return val;
}
];

View File

@ -0,0 +1,225 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['$state', '$stateParams', '$scope', 'GroupForm',
'ParseTypeChange', 'GenerateForm', 'inventoryData', 'GroupManageService',
'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions',
'rbacUiControlService', 'ToJSON',
function($state, $stateParams, $scope, GroupForm, ParseTypeChange,
GenerateForm, inventoryData, GroupManageService, GetChoices,
GetBasePath, CreateSelect2, GetSourceTypeOptions, rbacUiControlService,
ToJSON) {
let form = GroupForm;
init();
function init() {
// apply form definition's default field values
GenerateForm.applyDefaults(form, $scope);
rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/groups")
.then(function(canAdd) {
$scope.canAdd = canAdd;
});
$scope.parseType = 'yaml';
$scope.envParseType = 'yaml';
ParseTypeChange({
scope: $scope,
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;
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;
}
};
$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
});
}
}
];

View File

@ -0,0 +1,13 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import buildGroupAddState from './build-groups-add-state.factory';
import controller from './groups-add.controller';
export default
angular.module('groupAdd', [])
.factory('buildGroupsAddState', buildGroupAddState)
.controller('GroupAddController', controller);

View File

@ -0,0 +1,247 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default ['$state', '$stateParams', '$scope', 'ParseVariableString', 'rbacUiControlService', 'ToJSON',
'ParseTypeChange', 'GroupManageService', 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', 'groupData', 'inventorySourceData',
function($state, $stateParams, $scope, ParseVariableString, rbacUiControlService, ToJSON,
ParseTypeChange, GroupManageService, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions, groupData, inventorySourceData) {
init();
function init() {
rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/groups")
.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.$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: 'group_variables',
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;
json_data = ToJSON($scope.parseType, $scope.variables, true);
// group fields
var group = {
variables: json_data,
name: $scope.name,
description: $scope.description,
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;
}
};
$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();
});
}
];

View File

@ -0,0 +1,13 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import buildGroupsEditState from './build-groups-edit-state.factory';
import controller from './groups-edit.controller';
export default
angular.module('groupEdit', [])
.factory('buildGroupsEditState', buildGroupsEditState)
.controller('GroupEditController', controller);

View File

@ -0,0 +1,339 @@
/*************************************************
* 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 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 />&emsp; \"somevar\": \"somevalue\",<br />&emsp;\"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 &raquo; us-east-1b</strong></li>" +
"<li>Image ID: <strong>images &raquo; ami-b007ab1e</strong></li>" +
"<li>Instance ID: <strong>instances &raquo; i-ca11ab1e</strong></li>" +
"<li>Instance Type: <strong>types &raquo; type_m1_medium</strong></li>" +
"<li>Key Name: <strong>keys &raquo; key_testing</strong></li>" +
"<li>Region: <strong>regions &raquo; us-east-1</strong></li>" +
"<li>Security Group: <strong>security_groups &raquo; security_group_default</strong></li>" +
"<li>Tags: <strong>tags &raquo; tag_Name &raquo; tag_Name_host1</strong></li>" +
"<li>VPC ID: <strong>vpcs &raquo; vpc-5ca1ab1e</strong></li>" +
"<li>Tag None: <strong>tags &raquo; 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 />&emsp;\"somevar\": \"somevalue\",<br />&emsp;\"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 />&emsp;\"somevar\": \"somevalue\",<br />&emsp;\"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 />&emsp;\"somevar\": \"somevalue\",<br />&emsp;\"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 />&emsp;\"somevar\": \"somevalue\",<br />&emsp;\"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)'
}
}
};

View File

@ -3,10 +3,10 @@
*
* All Rights Reserved
*************************************************/
export default ['InventoryGroupsList', '$stateExtender', 'templateUrl', '$injector',
function(InventoryGroupsList, $stateExtender, templateUrl, $injector){
var val = function(field, formStateDefinition, params) {
import GroupsListController from './groups-list.controller';
export default ['GroupList', '$stateExtender', 'templateUrl', '$injector',
function(GroupList, $stateExtender, templateUrl, $injector){
var val = function(field, formStateDefinition) {
let state,
list = field.include ? $injector.get(field.include) : field,
breadcrumbLabel = (field.iterator.replace('_', ' ') + 's').toUpperCase(),
@ -25,8 +25,8 @@ export default ['InventoryGroupsList', '$stateExtender', 'templateUrl', '$inject
},
views: {
'related': {
templateProvider: function(InventoryGroupsList, generateList, $templateRequest, $stateParams, GetBasePath) {
let list = _.cloneDeep(InventoryGroupsList);
templateProvider: function(GroupList, generateList, $templateRequest, $stateParams, GetBasePath) {
let list = _.cloneDeep(GroupList);
if($stateParams && $stateParams.group) {
list.basePath = GetBasePath('groups') + _.last($stateParams.group) + '/children';
}
@ -44,7 +44,7 @@ export default ['InventoryGroupsList', '$stateExtender', 'templateUrl', '$inject
return html.concat(template);
});
},
// controller: GroupsListController
controller: GroupsListController
}
},
resolve: {
@ -70,23 +70,6 @@ export default ['InventoryGroupsList', '$stateExtender', 'templateUrl', '$inject
}
};
if(params.controllers && params.controllers.related && params.controllers.related[field.name]) {
stateConfig.views.related.controller = params.controllers.related[field.name];
}
else if(field.name === 'permissions') {
stateConfig.views.related.controller = 'PermissionsList';
}
else {
// Generic controller
stateConfig.views.related.controller = ['$scope', 'ListDefinition', 'Dataset',
function($scope, list, Dataset) {
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[`${list.iterator}s`] = $scope[`${list.iterator}_dataset`].results;
}
];
}
state = $stateExtender.buildDefinition(stateConfig);
// appy any default search parameters in form definition
if (field.search) {

View File

@ -1,236 +0,0 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['$scope', '$rootScope', '$state', '$stateParams', 'InventoryGroupsList', 'InventoryUpdate',
'GroupManageService', 'GroupsCancelUpdate', 'ViewUpdateStatus', 'rbacUiControlService', 'GetBasePath',
'GetSyncStatusMsg', 'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData',
function($scope, $rootScope, $state, $stateParams, InventoryGroupsList, InventoryUpdate,
GroupManageService, GroupsCancelUpdate, ViewUpdateStatus, rbacUiControlService, GetBasePath,
GetSyncStatusMsg, GetHostsStatusMsg, Dataset, Find, qs, inventoryData){
let list = InventoryGroupsList;
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(group){
if (group === undefined || group === null) {
group = {};
}
let group_status, 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,
inventory_id: $scope.inventory_id,
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});
}
$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.createGroup = function(){
$state.go('inventoryManage.addGroup');
};
$scope.editGroup = function(id){
$state.go('inventoryManage.editGroup', {group_id: id});
};
$scope.deleteGroup = function(group){
$scope.toDelete = {};
angular.extend($scope.toDelete, group);
if($scope.toDelete.total_groups === 0 && $scope.toDelete.total_hosts === 0) {
// This group doesn't have any child groups or hosts - the user is just trying to delete
// the group
$scope.deleteOption = "delete";
}
$('#group-delete-modal').modal('show');
};
$scope.confirmDelete = function(){
// Bind an even listener for the modal closing. Trying to $state.go() before the modal closes
// will mean that these two things are running async and the modal may not finish closing before
// the state finishes transitioning.
$('#group-delete-modal').off('hidden.bs.modal').on('hidden.bs.modal', function () {
// Remove the event handler so that we don't end up with multiple bindings
$('#group-delete-modal').off('hidden.bs.modal');
// Reload the inventory manage page and show that the group has been removed
$state.go('inventoryManage', null, {reload: true});
});
switch($scope.deleteOption){
case 'promote':
GroupManageService.promote($scope.toDelete.id, $stateParams.inventory_id)
.then(() => {
if (parseInt($state.params.group_id) === $scope.toDelete.id) {
$state.go("inventoryManage", null, {reload: true});
} else {
$state.go($state.current, null, {reload: true});
}
$('#group-delete-modal').modal('hide');
$('body').removeClass('modal-open');
$('.modal-backdrop').remove();
});
break;
default:
GroupManageService.delete($scope.toDelete.id).then(() => {
if (parseInt($state.params.group_id) === $scope.toDelete.id) {
$state.go("inventoryManage", null, {reload: true});
} else {
$state.go($state.current, null, {reload: true});
}
$('#group-delete-modal').modal('hide');
$('body').removeClass('modal-open');
$('.modal-backdrop').remove();
});
}
};
$scope.updateGroup = function(group) {
GroupManageService.getInventorySource({group: group.id}).then(res =>InventoryUpdate({
scope: $scope,
group_id: group.id,
url: res.data.results[0].related.update,
group_name: group.name,
group_source: res.data.results[0].source
}));
};
$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();
});
}];

View File

@ -4,14 +4,14 @@
* All Rights Reserved
*************************************************/
export default
['$scope', '$rootScope', '$state', '$stateParams', 'InventoryGroupsList', 'InventoryUpdate',
['$scope', '$rootScope', '$state', '$stateParams', 'GroupList', 'InventoryUpdate',
'GroupManageService', 'GroupsCancelUpdate', 'ViewUpdateStatus', 'rbacUiControlService', 'GetBasePath',
'GetSyncStatusMsg', 'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData',
function($scope, $rootScope, $state, $stateParams, InventoryGroupsList, InventoryUpdate,
function($scope, $rootScope, $state, $stateParams, GroupList, InventoryUpdate,
GroupManageService, GroupsCancelUpdate, ViewUpdateStatus, rbacUiControlService, GetBasePath,
GetSyncStatusMsg, GetHostsStatusMsg, Dataset, Find, qs, inventoryData){
let list = InventoryGroupsList;
let list = GroupList;
init();
@ -88,10 +88,10 @@
}, {reload: true});
};
$scope.createGroup = function(){
$state.go('inventoryManage.addGroup');
$state.go('inventories.edit.groups.add');
};
$scope.editGroup = function(id){
$state.go('inventoryManage.editGroup', {group_id: id});
$state.go('inventories.edit.groups.edit', {group_id: id});
};
$scope.deleteGroup = function(group){
$scope.toDelete = {};

View File

@ -4,12 +4,10 @@
* All Rights Reserved
*************************************************/
import buildGroupListState from './build-groups-list-state.factory';
import buildGroupsListState from './build-groups-list-state.factory';
import controller from './groups-list.controller';
import InventoryGroupsList from './inventory-groups.list';
export default
angular.module('groupList', [])
.factory('buildGroupListState', buildGroupListState)
.value('InventoryGroupsList', InventoryGroupsList)
angular.module('groupsList', [])
.factory('buildGroupsListState', buildGroupsListState)
.controller('GroupsListController', controller);

View File

@ -5,6 +5,10 @@
*************************************************/
import groupList from './list/main';
import groupAdd from './add/main';
import groupEdit from './edit/main';
import groupFormDefinition from './groups.form';
import groupListDefinition from './groups.list';
import service from './groups.service';
import GetHostsStatusMsg from './factories/get-hosts-status-msg.factory';
import GetSourceTypeOptions from './factories/get-source-type-options.factory';
@ -14,8 +18,12 @@ import ViewUpdateStatus from './factories/view-update-status.factory';
export default
angular.module('group', [
groupList.name
groupList.name,
groupAdd.name,
groupEdit.name
])
.value('GroupForm', groupFormDefinition)
.value('GroupList', groupListDefinition)
.factory('GetHostsStatusMsg', GetHostsStatusMsg)
.factory('GetSourceTypeOptions', GetSourceTypeOptions)
.factory('GetSyncStatusMsg', GetSyncStatusMsg)

View File

@ -1,4 +1,5 @@
<div class="tab-pane" id="inventories-panel">
<div ui-view="groupForm"></div>
<div ui-view="form"></div>
<div ng-cloak id="htmlTemplate" class="Panel">
<div class="row Form-tabRow">

View File

@ -10,8 +10,8 @@
* @description This form is for adding/editing an inventory
*/
export default ['i18n', 'buildGroupListState',
function(i18n,buildGroupListState) {
export default ['i18n', 'buildGroupsListState', 'buildGroupsAddState', 'buildGroupsEditState',
function(i18n, buildGroupsListState, buildGroupsAddState, buildGroupsEditState) {
return {
addTitle: i18n._('NEW INVENTORY'),
@ -134,10 +134,12 @@ function(i18n,buildGroupListState) {
},
groups: {
name: 'groups',
include: "InventoryGroupsList",
include: "GroupList",
title: i18n._('Groups'),
iterator: 'group',
stateGeneratorFunction: buildGroupListState
listState: buildGroupsListState,
addState: buildGroupsAddState,
editState: buildGroupsEditState
},
hosts: {
name: 'hosts',

View File

@ -246,7 +246,6 @@ function($injector, $stateExtender, $log, i18n) {
* @returns {array} Array of state definitions [{...}, {...}, ...]
*/
generateFormListDefinitions: function(form, formStateDefinition, params) {
function buildRbacUserTeamDirective(){
let states = [];
@ -508,10 +507,6 @@ function($injector, $stateExtender, $log, i18n) {
]
}
});
// // 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;
}
@ -559,7 +554,9 @@ function($injector, $stateExtender, $log, i18n) {
function buildListNodes(field) {
let states = [];
if(field.iterator === 'group'){
states.push(field.stateGeneratorFunction(field, formStateDefinition, params));
states.push(field.listState(field, formStateDefinition));
states.push(field.addState(field, formStateDefinition, params));
states.push(field.editState(field, formStateDefinition, params));
states = _.flatten(states);
}
else if(field.iterator === 'notification'){