From f26956d2cc05f4279794279e8499661f26ec66a6 Mon Sep 17 00:00:00 2001
From: Chris Houseknecht
Date: Mon, 30 Dec 2013 09:45:30 +0000
Subject: [PATCH] Group add and edit now mostly work. The Add Group dialog now
includes tabs and supports source. The tree is refrshed on load. Moved
retrieval of update status info to InventoryTree.
---
awx/ui/static/js/controllers/Inventories.js | 14 +-
awx/ui/static/js/forms/Groups.js | 12 +-
awx/ui/static/js/helpers/Groups.js | 545 +++++++++++++-------
awx/ui/static/less/ansible-ui.less | 2 +-
awx/ui/static/lib/ansible/InventoryTree.js | 33 +-
awx/ui/static/lib/ansible/Utilities.js | 4 +
awx/ui/static/lib/ansible/form-generator.js | 2 +-
awx/ui/templates/ui/index.html | 1 -
8 files changed, 399 insertions(+), 214 deletions(-)
diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js
index 4e01f20ce1..77cdc152f7 100644
--- a/awx/ui/static/js/controllers/Inventories.js
+++ b/awx/ui/static/js/controllers/Inventories.js
@@ -327,21 +327,15 @@ function InventoriesEdit ($scope, $location, $routeParams, GenerateList, ClearSc
$scope.inventory_id = $routeParams.inventory_id;
- $scope.$on('searchTreeReady', function(e, inventory_name, groups) {
+ if ($scope.removeSearchTreeReady) {
+ $scope.removeSearchTreeReady();
+ }
+ $scope.removeSearchTreeReady = $scope.$on('searchTreeReady', function(e, inventory_name, groups) {
// After the tree data loads, generate the groups list
generator.inject(list, { mode: 'edit', id: 'groups-container', breadCrumbs: false, searchSize: 'col-lg-5' });
$scope.groups = groups;
$scope.inventory_name = inventory_name;
-
- for (var i=0; i < $scope.groups.length; i++) {
- var stat = UpdateStatusMsg({ status: $scope.groups[i].status });
- $scope.groups[i].status_badge_class = stat['class'];
- $scope.groups[i].status_badge_tooltip = stat['tooltip'];
- $scope.groups[i].status = stat['status'];
- }
-
InjectHosts({ scope: $scope, inventory_id: $scope.inventory_id });
-
Wait('stop');
});
diff --git a/awx/ui/static/js/forms/Groups.js b/awx/ui/static/js/forms/Groups.js
index d737a7b811..ef543d6fd4 100644
--- a/awx/ui/static/js/forms/Groups.js
+++ b/awx/ui/static/js/forms/Groups.js
@@ -47,7 +47,7 @@ angular.module('GroupFormDefinition', [])
rows: 10,
'default': '---',
dataTitle: 'Group Variables',
- dataPlacement: 'left',
+ dataPlacement: 'right',
awPopOver: "Variables defined here apply to all child groups and hosts.
" +
"Enter variables using either JSON or YAML syntax. Use the " +
"radio button to toggle between the two.
" +
@@ -96,7 +96,7 @@ angular.module('GroupFormDefinition', [])
editRequired: false,
awMultiselect: 'source_region_choices',
dataTitle: 'Source Regions',
- dataPlacement: 'left',
+ dataPlacement: 'right',
awPopOver: "Click on the regions field to see a list of regions for your cloud provider. You can select multiple regions, " +
"or choose All to include all regions. AWX will only be updated with Hosts associated with the selected regions." +
"
",
@@ -113,7 +113,7 @@ angular.module('GroupFormDefinition', [])
'default': '---',
parseTypeName: 'envParseType',
dataTitle: 'Source Variables',
- dataPlacement: 'left',
+ dataPlacement: 'right',
awPopOver: "Override variables found in ec2.ini and used by the inventory update script. For a detailed description of these variables " +
"" +
"view ec2.ini in the Ansible github repo.
" +
@@ -161,7 +161,7 @@ angular.module('GroupFormDefinition', [])
'remain untouched by the inventory update process.
',
dataTitle: 'Overwrite',
dataContainer: 'body',
- dataPlacement: 'left',
+ dataPlacement: 'right',
labelClass: 'checkbox-options'
},
{
@@ -176,7 +176,7 @@ angular.module('GroupFormDefinition', [])
'those found on the external source.',
dataTitle: 'Overwrite Variables',
dataContainer: 'body',
- dataPlacement: 'left',
+ dataPlacement: 'right',
labelClass: 'checkbox-options'
},
{
@@ -190,7 +190,7 @@ angular.module('GroupFormDefinition', [])
'executing job tasks.',
dataTitle: 'Update on Launch',
dataContainer: 'body',
- dataPlacement: 'left',
+ dataPlacement: 'right',
labelClass: 'checkbox-options'
}
]
diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js
index d268d7ff40..9321d8c0f1 100644
--- a/awx/ui/static/js/helpers/Groups.js
+++ b/awx/ui/static/js/helpers/Groups.js
@@ -12,7 +12,8 @@
angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'GroupListDefinition',
'SearchHelper', 'PaginateHelper', 'ListGenerator', 'AuthService', 'GroupsHelper',
'InventoryHelper', 'SelectionHelper', 'JobSubmissionHelper', 'RefreshHelper',
- 'PromptDialog', 'InventorySummaryHelpDefinition', 'CredentialsListDefinition'
+ 'PromptDialog', 'InventorySummaryHelpDefinition', 'CredentialsListDefinition',
+ 'InventoryTree'
])
.factory('GetSourceTypeOptions', [ 'Rest', 'ProcessErrors', 'GetBasePath', function(Rest, ProcessErrors, GetBasePath) {
@@ -586,13 +587,16 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
}])
.factory('GroupsAdd', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupForm', 'GenerateForm',
- 'Prompt', 'ProcessErrors', 'GetBasePath', 'ParseTypeChange', 'GroupsEdit', 'ClickNode', 'Wait',
+ 'Prompt', 'ProcessErrors', 'GetBasePath', 'ParseTypeChange', 'GroupsEdit', 'ClickNode', 'Wait', 'GetChoices',
+ 'GetSourceTypeOptions', 'CredentialList', 'LookUpInit', 'BuildTree',
function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors,
- GetBasePath, ParseTypeChange, GroupsEdit, ClickNode, Wait) {
+ GetBasePath, ParseTypeChange, GroupsEdit, ClickNode, Wait, GetChoices, GetSourceTypeOptions, CredentialList,
+ LookUpInit, BuildTree) {
return function(params) {
var inventory_id = params.inventory_id;
var group_id = (params.group_id !== undefined) ? params.group_id : null;
+ var inventory_scope = params.scope;
// Inject dynamic view
var defaultUrl = (group_id !== null) ? GetBasePath('groups') + group_id + '/children/' :
@@ -610,84 +614,192 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
$('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success');
$('#form-modal').on('hidden.bs.modal', function () {
- if (!groupCreated) {
- ClickNode({ selector: '#' + scope['selectedNode'].attr('id') });
- }
+ //if (!groupCreated) {
+ // ClickNode({ selector: '#' + scope['selectedNode'].attr('id') });
+ //}
});
generator.reset();
var master={};
- if (!scope.$$phase) {
- scope.$digest();
+ if (scope.removeSearchTreeReady) {
+ scope.removeSearchTreeReady();
}
+ scope.removeSearchTreeReady = scope.$on('searchTreeReady', function(e) {
+ Wait('stop');
+ $('#form-modal').modal('hide');
+ });
+
+ if (scope.removeSaveComplete) {
+ scope.removeSaveComplete();
+ }
+ scope.removeSaveComplete = scope.$on('SaveComplete', function(e, error) {
+ if (!error) {
+ scope.formModalActionDisabled = false;
+ scope.showGroupHelp = false; //get rid of the Hint
+ BuildTree({ scope: inventory_scope, inventory_id: inventory_id });
+ }
+ });
+
+ if (scope.removeFormSaveSuccess) {
+ scope.removeFormSaveSuccess();
+ }
+ scope.removeFormSaveSuccess = scope.$on('formSaveSuccess', function(e, group_id, url) {
+
+ // Source data gets stored separately from the group. Validate and store Source
+ // related fields, then call SaveComplete to wrap things up.
+
+ var parseError = false;
+ var saveError = false;
+
+ // Update the selector tree with new group name, descr
+ //SetNodeName({ scope: scope['selectedNode'], group_id: group_id,
+ // name: scope.name, description: scope.description });
+
+ if (scope.source.value !== null && scope.source.value !== '') {
+ var data = {
+ group: group_id,
+ source: scope['source'].value,
+ source_path: scope['source_path'],
+ credential: scope['credential'],
+ overwrite: scope['overwrite'],
+ overwrite_vars: scope['overwrite_vars'],
+ update_on_launch: scope['update_on_launch']
+ //update_interval: scope['update_interval'].value
+ };
+
+ // Create a string out of selected list of regions
+ var regions = $('#s2id_group_source_regions').select2("data");
+ var r = [];
+ for (var i=0; i < regions.length; i++) {
+ r.push(regions[i].id);
+ }
+ data['source_regions'] = r.join();
+
+ if (scope['source'].value == 'ec2') {
+ // for ec2, validate variable data
+ try {
+ if (scope.envParseType == 'json') {
+ var json_data = JSON.parse(scope.source_vars); //make sure JSON parses
+ }
+ else {
+ var json_data = jsyaml.load(scope.source_vars); //parse yaml
+ }
+
+ // Make sure our JSON is actually an object
+ if (typeof json_data !== 'object') {
+ throw "failed to return an object!";
+ }
+
+ // Send JSON as a string
+ if ($.isEmptyObject(json_data)) {
+ data.source_vars = "";
+ }
+ else {
+ data.source_vars = JSON.stringify(json_data, undefined, '\t');
+ }
+ }
+ catch(err) {
+ parseError = true;
+ scope.$emit('SaveComplete', true);
+ Alert("Error", "Error parsing extra variables. Parser returned: " + err);
+ }
+ }
+
+ if (!parseError) {
+ Rest.setUrl(url)
+ Rest.put(data)
+ .success( function(data, status, headers, config) {
+ scope.$emit('SaveComplete', false);
+ })
+ .error( function(data, status, headers, config) {
+ scope.$emit('SaveComplete', true);
+ ProcessErrors(scope, data, status, form,
+ { hdr: 'Error!', msg: 'Failed to update group inventory source. PUT status: ' + status });
+ });
+ }
+ }
+ else {
+ // No source value
+ scope.$emit('SaveComplete', false);
+ }
+ });
+
// Save
scope.formModalAction = function() {
- Wait('start');
- try {
- scope.formModalActionDisabled = true;
+ Wait('start');
+ try {
+ scope.formModalActionDisabled = true;
- // Make sure we have valid variable data
- if (scope.parseType == 'json') {
- var json_data = JSON.parse(scope.variables); //make sure JSON parses
- }
- else {
- var json_data = jsyaml.load(scope.variables); //parse yaml
- }
+ // Make sure we have valid variable data
+ if (scope.parseType == 'json') {
+ var json_data = JSON.parse(scope.variables); //make sure JSON parses
+ }
+ else {
+ var json_data = jsyaml.load(scope.variables); //parse yaml
+ }
- // Make sure our JSON is actually an object
- if (typeof json_data !== 'object') {
- throw "failed to return an object!";
- }
-
- var data = {
- name: scope['name'],
- description: scope['description']
- };
+ // Make sure our JSON is actually an object
+ if (typeof json_data !== 'object') {
+ throw "failed to return an object!";
+ }
- if (inventory_id) {
- data['inventory'] = inventory_id;
- }
+ var data = {
+ name: scope['name'],
+ description: scope['description']
+ };
- if ($.isEmptyObject(json_data)) {
- data['variables'] = "";
- }
- else {
- data['variables'] = JSON.stringify(json_data, undefined, '\t');
- }
+ if (inventory_id) {
+ data['inventory'] = inventory_id;
+ }
- Rest.setUrl(defaultUrl);
- Rest.post(data)
- .success( function(data, status, headers, config) {
- Wait('stop');
- groupCreated = true;
- scope.formModalActionDisabled = false;
- scope.showGroupHelp = false; //get rid of the Hint
- $('#form-modal').modal('hide');
- /*BuildTree({
- scope: scope,
- inventory_id: scope['inventory_id'],
- emit_on_select: 'NodeSelect',
- target_id: 'search-tree-container',
- refresh: true,
- moveable: true,
- group_id: data.id
- });*/
- })
- .error( function(data, status, headers, config) {
- Wait('stop');
- scope.formModalActionDisabled = false;
- ProcessErrors(scope, data, status, form,
- { hdr: 'Error!', msg: 'Failed to add new group. POST returned status: ' + status });
- });
- }
- catch(err) {
- Wait('stop');
- scope.formModalActionDisabled = false;
- Alert("Error", "Error parsing group variables. Parser returned: " + err);
- }
- }
+ if ($.isEmptyObject(json_data)) {
+ data['variables'] = "";
+ }
+ else {
+ data['variables'] = JSON.stringify(json_data, undefined, '\t');
+ }
+
+ Rest.setUrl(defaultUrl);
+ Rest.post(data)
+ .success( function(data, status, headers, config) {
+ scope.$emit('formSaveSuccess', data.id, GetBasePath('inventory_sources') + data.id + '/');
+ })
+ .error( function(data, status, headers, config) {
+ Wait('stop');
+ scope.formModalActionDisabled = false;
+ ProcessErrors(scope, data, status, form,
+ { hdr: 'Error!', msg: 'Failed to add new group. POST returned status: ' + status });
+ });
+ }
+ catch(err) {
+ Wait('stop');
+ scope.formModalActionDisabled = false;
+ Alert("Error", "Error parsing group variables. Parser returned: " + err);
+ }
+ }
+
+ scope.sourceChange = function() {
+ if (scope['source'].value == 'rax') {
+ scope['source_region_choices'] = scope['rax_regions'];
+ //$('#s2id_group_source_regions').select2('data', []);
+ $('#s2id_group_source_regions').select2('data', [{ id: 'all', text: 'All' }]);
+ }
+ else if (scope['source'].value == 'ec2') {
+ scope['source_region_choices'] = scope['ec2_regions'];
+ //$('#s2id_group_source_regions').select2('data', []);
+ $('#s2id_group_source_regions').select2('data', [{ id: 'all', text: 'All' }]);
+ }
+ LookUpInit({
+ url: GetBasePath('credentials') +
+ '?cloud=true&kind=' + [ (scope.source.value == 'rax') ? 'rax' : 'aws' ],
+ scope: scope,
+ form: form,
+ list: CredentialList,
+ field: 'credential'
+ });
+ }
// Cancel
scope.formReset = function() {
@@ -695,15 +807,50 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
generator.reset();
};
+ var choicesReady = 0;
+
+ if (scope.removeChoicesReady) {
+ scope.removeChoicesReady();
+ }
+ scope.removeChoicesReady = scope.$on('choicesReadyGroup', function() {
+ choicesReady++;
+ if (choicesReady == 2) {
+ Wait('stop');
+ }
+ });
+
+ // Load options for source regions
+ 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'
+ });
+
+ GetSourceTypeOptions({ scope: scope, variable: 'source_type_options' });
+
+ Wait('start');
+
}
}])
.factory('GroupsEdit', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupForm', 'GenerateForm',
'Prompt', 'ProcessErrors', 'GetBasePath', 'SetNodeName', 'ParseTypeChange', 'GetSourceTypeOptions', 'InventoryUpdate',
- 'GetUpdateIntervalOptions', 'ClickNode', 'LookUpInit', 'CredentialList', 'Empty', 'Wait', 'GetChoices',
+ 'GetUpdateIntervalOptions', 'ClickNode', 'LookUpInit', 'CredentialList', 'Empty', 'Wait', 'GetChoices', 'UpdateGroup',
function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors,
GetBasePath, SetNodeName, ParseTypeChange, GetSourceTypeOptions, InventoryUpdate, GetUpdateIntervalOptions, ClickNode,
- LookUpInit, CredentialList, Empty, Wait, GetChoices) {
+ LookUpInit, CredentialList, Empty, Wait, GetChoices, UpdateGroup) {
return function(params) {
var group_id = params.group_id;
@@ -711,7 +858,9 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
var generator = GenerateForm;
var form = GroupForm;
var defaultUrl = GetBasePath('groups') + group_id + '/';
+ var groups_scope = params.scope;
+ //var scope =
var scope = generator.inject(form, { mode: 'edit', modal: true, related: false });
generator.reset();
@@ -741,140 +890,142 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
}
if (scope.variable_url) {
- // get group variables
- Rest.setUrl(scope.variable_url);
- Rest.get()
+ // get group variables
+ Rest.setUrl(scope.variable_url);
+ Rest.get()
.success( function(data, status, headers, config) {
if ($.isEmptyObject(data)) {
- scope.variables = "---";
+ scope.variables = "---";
}
else {
- scope.variables = jsyaml.safeDump(data);
+ scope.variables = jsyaml.safeDump(data);
}
+ Wait('stop');
})
.error( function(data, status, headers, config) {
scope.variables = null;
+ Wait('stop');
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to retrieve group variables. GET returned status: ' + status });
});
}
else {
- scope.variables = "---";
+ scope.variables = "---";
}
master.variables = scope.variables;
if (scope.source_url) {
- // get source data
- Rest.setUrl(scope.source_url);
- Rest.get()
- .success( function(data, status, headers, config) {
- for (var fld in form.fields) {
- if (fld == 'checkbox_group') {
- for (var i = 0; i < form.fields[fld].fields.length; i++) {
- var flag = form.fields[fld].fields[i];
- if (data[flag.name] !== undefined) {
- scope[flag.name] = data[flag.name];
- master[flag.name] = scope[flag.name];
- }
- }
- }
- if (fld == 'source') {
- var found = false;
- for (var i=0; i < scope.source_type_options.length; i++) {
- if (scope.source_type_options[i].value == data['source']) {
- scope['source'] = scope.source_type_options[i];
- found = true;
- }
- }
- if (!found || scope['source'].value == "") {
- scope['groupUpdateHide'] = true;
- }
- else {
- scope['groupUpdateHide'] = false;
- }
- master['source'] = scope['source'];
- }
- else if (fld == 'update_interval') {
- if (data[fld] == '' || data[fld] == null || data[fld] == undefined) {
- data[fld] = 0;
- }
- for (var i=0; i < scope.update_interval_options.length; i++) {
- if (scope.update_interval_options[i].value ==
- data[fld]) {
- scope[fld] = scope.update_interval_options[i];
- }
- }
- }
- else if (fld == 'source_vars') {
- // Parse source_vars, converting to YAML.
- if ($.isEmptyObject(data.source_vars) || data.source_vars == "\{\}" ||
- data.source_vars == "null" || data.source_vars == "") {
- scope.source_vars = "---";
- }
- else {
- var json_obj = JSON.parse(data.extra_vars);
- scope.source_vars = jsyaml.safeDump(json_obj);
- }
- master.source_vars = scope.variables;
- }
- else if (data[fld]) {
- scope[fld] = data[fld];
- master[fld] = scope[fld];
- }
+ // get source data
+ Rest.setUrl(scope.source_url);
+ Rest.get()
+ .success( function(data, status, headers, config) {
+ for (var fld in form.fields) {
+ if (fld == 'checkbox_group') {
+ for (var i = 0; i < form.fields[fld].fields.length; i++) {
+ var flag = form.fields[fld].fields[i];
+ if (data[flag.name] !== undefined) {
+ scope[flag.name] = data[flag.name];
+ master[flag.name] = scope[flag.name];
+ }
+ }
+ }
+ if (fld == 'source') {
+ var found = false;
+ for (var i=0; i < scope.source_type_options.length; i++) {
+ if (scope.source_type_options[i].value == data['source']) {
+ scope['source'] = scope.source_type_options[i];
+ found = true;
+ }
+ }
+ if (!found || scope['source'].value == "") {
+ scope['groupUpdateHide'] = true;
+ }
+ else {
+ scope['groupUpdateHide'] = false;
+ }
+ master['source'] = scope['source'];
+ }
+ else if (fld == 'update_interval') {
+ if (data[fld] == '' || data[fld] == null || data[fld] == undefined) {
+ data[fld] = 0;
+ }
+ for (var i=0; i < scope.update_interval_options.length; i++) {
+ if (scope.update_interval_options[i].value ==
+ data[fld]) {
+ scope[fld] = scope.update_interval_options[i];
+ }
+ }
+ }
+ else if (fld == 'source_vars') {
+ // Parse source_vars, converting to YAML.
+ if ($.isEmptyObject(data.source_vars) || data.source_vars == "\{\}" ||
+ data.source_vars == "null" || data.source_vars == "") {
+ scope.source_vars = "---";
+ }
+ else {
+ var json_obj = JSON.parse(data.extra_vars);
+ scope.source_vars = jsyaml.safeDump(json_obj);
+ }
+ master.source_vars = scope.variables;
+ }
+ else if (data[fld]) {
+ scope[fld] = data[fld];
+ master[fld] = scope[fld];
+ }
- if (form.fields[fld].sourceModel && data.summary_fields &&
- data.summary_fields[form.fields[fld].sourceModel]) {
- scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
- data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
- master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
- data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
- }
- }
+ if (form.fields[fld].sourceModel && data.summary_fields &&
+ data.summary_fields[form.fields[fld].sourceModel]) {
+ scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
+ data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
+ master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
+ data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
+ }
+ }
- LookUpInit({
- url: GetBasePath('credentials') +
- '?cloud=true&kind=' + [(scope.source.value == 'rax') ? 'rax' : 'aws'],
- scope: scope,
- form: form,
- list: CredentialList,
- field: 'credential'
- });
+ LookUpInit({
+ url: GetBasePath('credentials') +
+ '?cloud=true&kind=' + (scope.source.value == 'rax') ? 'rax' : 'aws',
+ scope: scope,
+ form: form,
+ list: CredentialList,
+ field: 'credential'
+ });
- scope.sourceChange(); //set defaults that rely on source value
+ scope.sourceChange(); //set defaults that rely on source value
- if (data['source_regions']) {
- if (data['source'] == 'ec2' || data['source'] == 'rax') {
- var set = (data['source'] == 'ec2') ? scope['ec2_regions'] : scope['rax_regions'];
- var opts = [];
- var list = data['source_regions'].split(',');
- for (var i=0; i < list.length; i++) {
- for (var j=0; j < set.length; j++) {
- if (list[i] == set[j].value) {
- opts.push({ id: set[j].value, text: set[j].label });
- }
- }
- }
- master['source_regions'] = opts;
- $('#s2id_group_source_regions').select2('data', opts);
- }
- }
- else {
- // If empty, default to all
- master['source_regions'] = [{ id: 'all', text: 'All' }];
- }
-
- scope['group_update_url'] = data.related['update'];
-
- })
- .error( function(data, status, headers, config) {
- scope.source = "";
- ProcessErrors(scope, data, status, form,
- { hdr: 'Error!', msg: 'Failed to retrieve inventory source. GET status: ' + status });
- });
+ if (data['source_regions']) {
+ if (data['source'] == 'ec2' || data['source'] == 'rax') {
+ var set = (data['source'] == 'ec2') ? scope['ec2_regions'] : scope['rax_regions'];
+ var opts = [];
+ var list = data['source_regions'].split(',');
+ for (var i=0; i < list.length; i++) {
+ for (var j=0; j < set.length; j++) {
+ if (list[i] == set[j].value) {
+ opts.push({ id: set[j].value, text: set[j].label });
+ }
+ }
+ }
+ master['source_regions'] = opts;
+ $('#s2id_group_source_regions').select2('data', opts);
+ }
+ }
+ else {
+ // If empty, default to all
+ master['source_regions'] = [{ id: 'all', text: 'All' }];
+ }
+ scope['group_update_url'] = data.related['update'];
+ Wait('stop');
+ })
+ .error( function(data, status, headers, config) {
+ scope.source = "";
+ Wait('stop');
+ ProcessErrors(scope, data, status, form,
+ { hdr: 'Error!', msg: 'Failed to retrieve inventory source. GET status: ' + status });
+ });
}
});
-
+
if (scope.removeChoicesComplete) {
scope.removeChoicesComplete();
}
@@ -885,14 +1036,14 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
.success( function(data, status, headers, config) {
for (var fld in form.fields) {
if (data[fld]) {
- scope[fld] = data[fld];
- master[fld] = scope[fld];
+ scope[fld] = data[fld];
+ master[fld] = scope[fld];
}
}
var related = data.related;
for (var set in form.related) {
if (related[set]) {
- relatedSets[set] = { url: related[set], iterator: form.related[set].iterator };
+ relatedSets[set] = { url: related[set], iterator: form.related[set].iterator };
}
}
scope.variable_url = data.related.variable_data;
@@ -935,20 +1086,30 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
choice_name: 'ec2_region_choices',
callback: 'choicesReadyGroup'
});
+
+ Wait('start');
- if (!scope.$$phase) {
- scope.$digest();
- }
+ //if (!scope.$$phase) {
+ // scope.$digest();
+ //}
if (scope.removeSaveComplete) {
scope.removeSaveComplete();
}
scope.removeSaveComplete = scope.$on('SaveComplete', function(e, error) {
if (!error) {
- //scope['flashMessage'] = 'Your changes to ' + scope['name'] + ' were saved.';
- //ClickNode({ selector: '#inventory-root-node' });
+ UpdateGroup({
+ scope: groups_scope,
+ group_id: group_id,
+ properties: {
+ name: scope.name,
+ description: scope.description,
+ has_inventory_sources: (scope.source) ? true : false
+ }
+ });
scope.formModalActionDisabled = false;
scope.showGroupHelp = false; //get rid of the Hint
+ Wait('stop');
$('#form-modal').modal('hide');
}
});
@@ -981,7 +1142,11 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
// Create a string out of selected list of regions
var regions = $('#s2id_group_source_regions').select2("data");
- data['source_regions'] = regions.join();
+ var r = [];
+ for (var i=0; i < regions.length; i++) {
+ r.push(regions[i].id);
+ }
+ data['source_regions'] = r.join();
if (scope['source'].value == 'ec2') {
// for ec2, validate variable data
@@ -1055,12 +1220,12 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
for (var fld in form.fields) {
data[fld] = scope[fld];
}
+
data['inventory'] = inventory_id;
Rest.setUrl(defaultUrl);
Rest.put(data)
.success( function(data, status, headers, config) {
- Wait('stop');
if (scope.variables) {
//update group variables
Rest.setUrl(scope.variable_url);
@@ -1106,7 +1271,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
}
LookUpInit({
url: GetBasePath('credentials') +
- '?cloud=true&kind=' + [(scope.source.value == 'rax') ? 'rax' : 'aws'],
+ '?cloud=true&kind=' + (scope.source.value == 'rax') ? 'rax' : 'aws',
scope: scope,
form: form,
list: CredentialList,
diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less
index 011055e154..87af45f22f 100644
--- a/awx/ui/static/less/ansible-ui.less
+++ b/awx/ui/static/less/ansible-ui.less
@@ -882,7 +882,7 @@ input[type="checkbox"].checkbox-no-label {
}
.field-badge {
- font-size: 14px;
+ font-size: 12px;
}
.license-warning,
diff --git a/awx/ui/static/lib/ansible/InventoryTree.js b/awx/ui/static/lib/ansible/InventoryTree.js
index 44b4c2dea0..f63a2f6316 100644
--- a/awx/ui/static/lib/ansible/InventoryTree.js
+++ b/awx/ui/static/lib/ansible/InventoryTree.js
@@ -9,7 +9,7 @@
*
*/
-angular.module('InventoryTree', ['Utilities', 'RestServices'])
+angular.module('InventoryTree', ['Utilities', 'RestServices', 'GroupsHelper'])
.factory('SortNodes', [ function() {
return function(data) {
@@ -171,8 +171,8 @@ angular.module('InventoryTree', ['Utilities', 'RestServices'])
}
}])
- .factory('BuildTree', ['Rest', 'GetBasePath', 'ProcessErrors', 'SortNodes', 'Wait',
- function(Rest, GetBasePath, ProcessErrors, SortNodes, Wait) {
+ .factory('BuildTree', ['Rest', 'GetBasePath', 'ProcessErrors', 'SortNodes', 'Wait', 'UpdateStatusMsg',
+ function(Rest, GetBasePath, ProcessErrors, SortNodes, Wait, UpdateStatusMsg) {
return function(params) {
var inventory_id = params.inventory_id;
@@ -185,6 +185,7 @@ angular.module('InventoryTree', ['Utilities', 'RestServices'])
var sorted = SortNodes(tree_data);
for (var i=0; i < sorted.length; i++) {
var currentId= id;
+ var stat = UpdateStatusMsg({ status: sorted[i].status });
var group = {
name: sorted[i].name,
has_active_failures: sorted[i].has_active_failures,
@@ -194,12 +195,15 @@ angular.module('InventoryTree', ['Utilities', 'RestServices'])
groups_with_active_failures: sorted[i].groups_with_active_failures,
parent: parent,
has_children: (sorted[i].children.length > 0) ? true : false,
+ has_inventory_sources: sorted[i].has_inventory_sources,
id: id,
group_id: sorted[i].id,
event_level: level,
ngicon: (sorted[i].children.length > 0) ? 'icon-collapse-alt' : null,
related: { children: (sorted[i].children.length > 0) ? sorted[i].related.children : '' },
- status: sorted[i].summary_fields.inventory_source.status
+ status: sorted[i].summary_fields.inventory_source.status,
+ status_badge_class: stat['class'],
+ status_badge_tooltip: stat['tooltip']
}
groups.push(group);
id++;
@@ -244,9 +248,28 @@ angular.module('InventoryTree', ['Utilities', 'RestServices'])
}
loadTreeData();
-
}
}])
+
+ // Update a group with a set of properties
+ .factory('UpdateGroup', [ function() {
+ return function(params) {
+
+ var scope = params.scope;
+ var group_id = params.group_id;
+ var properties = params.properties; // object of key:value pairs to update
+
+ for (var i=0; i < scope.groups.length; i++) {
+ if (scope.groups[i].group_id == group_id) {
+ var grp = scope.groups[i];
+ for (var p in properties) {
+ scope.groups[i][p] = properties[p];
+ }
+ }
+ }
+ }
+ }])
+
// Set node name and description after an update to Group properties.
.factory('SetNodeName', [ function() {
diff --git a/awx/ui/static/lib/ansible/Utilities.js b/awx/ui/static/lib/ansible/Utilities.js
index 6ff891d659..555f0a0857 100644
--- a/awx/ui/static/lib/ansible/Utilities.js
+++ b/awx/ui/static/lib/ansible/Utilities.js
@@ -129,6 +129,10 @@ angular.module('Utilities',['RestServices', 'Utilities'])
else if (form) {
var fieldErrors = false;
for (var field in form.fields ) {
+ if (data[field] && form.fields[field].tab) {
+ // If the form is part of a tab group, activate the tab
+ $('#' + form.name + "_tabs a[href=\"#" + form.fields[field].tab + '"]').tab('show');
+ }
if (form.fields[field].realName) {
if (data[form.fields[field].realName]) {
scope[field + '_api_error'] = data[form.fields[field]][0];
diff --git a/awx/ui/static/lib/ansible/form-generator.js b/awx/ui/static/lib/ansible/form-generator.js
index 7f6806ff20..8c5566261e 100644
--- a/awx/ui/static/lib/ansible/form-generator.js
+++ b/awx/ui/static/lib/ansible/form-generator.js
@@ -1240,7 +1240,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
html += "\n";
}
else if (this.form.tabs) {
- html += "\n";
+ html += "\n";
for (var i=0; i < this.form.tabs.length; i++) {
var tab = this.form.tabs[i];
html += "-
-