diff --git a/awx/ui/static/js/controllers/Groups.js b/awx/ui/static/js/controllers/Groups.js
index ef3e64a4c7..d01b884a9b 100644
--- a/awx/ui/static/js/controllers/Groups.js
+++ b/awx/ui/static/js/controllers/Groups.js
@@ -18,6 +18,7 @@ function InventoryGroups ($scope, $rootScope, $compile, $location, $log, $routeP
var generator = GenerateForm;
var form = InventoryGroupsForm;
var defaultUrl=GetBasePath('inventory');
+
$('#tree-view').empty();
var scope = generator.inject(form, { mode: 'edit', related: true, buildTree: true });
var base = $location.path().replace(/^\//,'').split('/')[0];
@@ -127,10 +128,13 @@ function InventoryGroups ($scope, $rootScope, $compile, $location, $log, $routeP
var type = node.attr('type');
var url;
+ scope['nodeSelectValue'] = n;
scope['selectedNode'] = node;
scope['selectedNodeName'] = node.attr('name');
scope['grpBtnDisable'] = false;
-
+ scope['flashMessage'] = null;
+ scope['groupUpdateHide'] = true;
+
$('#tree-view').jstree('open_node',node);
if (type == 'group') {
@@ -145,11 +149,13 @@ function InventoryGroups ($scope, $rootScope, $compile, $location, $log, $routeP
// Load the form
GroupsEdit({ "inventory_id": id, group_id: scope.group_id });
- //scope.groupName = n.data;
- //scope.groupTitle = '
. Seems to be a bug in TB3 RC1
$(this).remove();
});
- }, 1000);
+ // Set the focust to the first form field
+ $('input:first').focus();
+ });
+
+ // Disable all the group related buttons
+ scope.grpBtnDisable = false;
+
+ }
+
+ scope.closeForm = function() {
+ // Slide in the group properties form
+ $('#tree-form').hide('slide',{ direction: 'right' }, 500, function() { $('#tree-form').empty(); });
+ scope.grpBtnDisable = false;
}
scope.editInventory = function() {
diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js
index a356afb37e..5a530f3a9a 100644
--- a/awx/ui/static/js/controllers/Inventories.js
+++ b/awx/ui/static/js/controllers/Inventories.js
@@ -238,54 +238,18 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
}
};
- // Related set: Add button
- scope.add = function(set) {
- $rootScope.flashMessage = null;
- $location.path('/' + base + '/' + $routeParams.id + '/groups/' + scope.group_id + '/' + set + '/add');
- };
-
- // Related set: Edit button
- scope.edit = function(set, id, name) {
- $rootScope.flashMessage = null;
- $location.path('/' + base + '/' + $routeParams.id + '/' + set + '/' + id);
- };
-
if (scope.removeInventorySaved) {
scope.removeInventorySaved();
}
scope.removeInventorySaved = scope.$on('inventorySaved', function() {
$location.path('/inventories');
});
+
scope.formSave = function() {
generator.clearApiErrors();
SaveInventory({ scope: scope });
}
- // Related set: Delete button
- scope['delete'] = function(set, itm_id, name, title) {
- $rootScope.flashMessage = null;
-
- var action = function() {
- var url = defaultUrl + id + '/' + set + '/';
- Rest.setUrl(url);
- Rest.post({ id: itm_id, disassociate: 1 })
- .success( function(data, status, headers, config) {
- $('#prompt-modal').modal('hide');
- scope.search(form.related[set].iterator);
- })
- .error( function(data, status, headers, config) {
- $('#prompt-modal').modal('hide');
- ProcessErrors(scope, data, status, null,
- { hdr: 'Error!', msg: 'Call to ' + url + ' failed. POST returned status: ' + status });
- });
- };
-
- Prompt({ hdr: 'Delete',
- body: 'Are you sure you want to remove ' + name + ' from ' + scope.name + ' ' + title + '?',
- action: action
- });
-
- };
}
InventoriesEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'InventoryForm',
diff --git a/awx/ui/static/js/forms/Groups.js b/awx/ui/static/js/forms/Groups.js
index ccf928e9fa..ccd5142616 100644
--- a/awx/ui/static/js/forms/Groups.js
+++ b/awx/ui/static/js/forms/Groups.js
@@ -11,21 +11,15 @@ angular.module('GroupFormDefinition', [])
'GroupForm', {
addTitle: 'Create Group', //Legend in add mode
- editTitle: '{{ name }}', //Legend in edit mode
+ editTitle: 'Group Properties: {{ name }}', //Legend in edit mode
+ showTitle: true,
+ cancelButton: false,
name: 'group', //Form name attribute
well: false, //Wrap the form with TB well
- //formLabelSize: 'col-lg-3',
- //formFieldSize: 'col-lg-9',
+ formLabelSize: 'col-lg-3',
+ formFieldSize: 'col-lg-9',
fields: {
- /*has_active_failures: {
- label: 'Status',
- control: '
' +
- ' Contains hosts with failed jobs
',
- type: 'custom',
- ngShow: 'has_active_failures',
- readonly: true
- },*/
name: {
label: 'Name',
type: 'text',
@@ -44,11 +38,11 @@ angular.module('GroupFormDefinition', [])
addRequired: false,
editRequird: false,
rows: 10,
- "class": 'modal-input-xlarge',
- "default": "---",
+ 'default': '---',
dataTitle: 'Group Variables',
dataPlacement: 'left',
- awPopOver: "
Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.
" +
+ 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.
" +
"JSON:
\n" +
"
{ \"somevar\": \"somevalue\", \"password\": \"magic\" } \n" +
"YAML:
\n" +
@@ -56,10 +50,164 @@ angular.module('GroupFormDefinition', [])
'
View JSON examples at www.json.org
' +
'
View YAML examples at ansibleworks.com
',
dataContainer: 'body'
- }
+ },
+ source: {
+ label: 'Source',
+ excludeModal: true,
+ type: 'select',
+ ngOptions: 'source.label for source in source_type_options',
+ ngChange: 'sourceChange()',
+ addRequired: false,
+ editRequired: false,
+ 'default': { label: 'Manual', value: null }
+ },
+ source_path: {
+ label: 'Script Path',
+ excludeModal: true,
+ ngShow: "source.value == 'file'",
+ type: 'text',
+ awRequiredWhen: {variable: "sourcePathRequired", init: "false" }
+ },
+ source_env: {
+ label: 'Script Environment Variables',
+ ngShow: "source.value == 'file'",
+ type: 'textarea',
+ addRequired: false,
+ editRequird: false,
+ excludeModal: true,
+ rows: 10,
+ 'default': '---',
+ parseTypeName: 'envParseType',
+ dataTitle: 'Script Environment Variables',
+ dataPlacement: 'left',
+ awPopOver: "
Define environment variables here that will be referenced by the inventory script at runtime. " +
+ "Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.
" +
+ "JSON:
\n" +
+ "
{ \"somevar\": \"somevalue\", \"password\": \"magic\" } \n" +
+ "YAML:
\n" +
+ "
--- somevar: somevalue password: magic \n" +
+ '
View JSON examples at www.json.org
' +
+ '
View YAML examples at ansibleworks.com
',
+ dataContainer: 'body',
+ awPopOverRight: true
+ },
+ source_username: {
+ labelBind: 'sourceUsernameLabel',
+ excludeModal: true,
+ type: 'text',
+ ngShow: "source.value == 'rackspace' || source.value == 'ec2'",
+ awRequiredWhen: {variable: "sourceUsernameRequired", init: "false" }
+ },
+ source_password: {
+ labelBind: 'sourcePasswordLabel',
+ excludeModal: true,
+ type: 'password',
+ ngShow: "source.value == 'rackspace' || source.value == 'ec2'",
+ editRequired: false,
+ addRequired: false,
+ ngChange: "clearPWConfirm('source_password_confirm')",
+ ask: true,
+ clear: true,
+ associated: 'source_password_confirm',
+ autocomplete: false
+ },
+ source_password_confirm: {
+ labelBind: 'sourcePasswordConfirmLabel',
+ type: 'password',
+ ngShow: "source.value == 'rackspace' || source.value == 'ec2'",
+ addRequired: false,
+ editRequired: false,
+ awPassMatch: true,
+ associated: 'source_password',
+ autocomplete: false
+ },
+ source_regions: {
+ label: 'Regions',
+ excludeModal: true,
+ type: 'text',
+ ngShow: "source.value == 'rackspace' || source.value == 'ec2'",
+ addRequired: false,
+ editRequired: false
+ },
+ source_tags: {
+ label: 'Tags',
+ excludeModal: true,
+ type: 'text',
+ ngShow: "source.value == 'rackspace' || source.value == 'ec2'",
+ addRequired: false,
+ editRequired: false
+ },
+ checkbox_group: {
+ label: 'Update Options',
+ type: 'checkbox_group',
+ ngShow: "source.value !== '' && source.value !== null",
+
+ fields: [
+ {
+ name: 'overwite_hosts',
+ label: 'Overwrite Hosts',
+ type: 'checkbox',
+ ngShow: "source.value !== '' && source.value !== null",
+ addRequired: false,
+ editRequired: false,
+ awPopOver: '
Replace AWX inventory hosts with cloud inventory hosts.
',
+ dataTitle: 'Overwrite Hosts',
+ dataContainer: 'body',
+ dataPlacement: 'left',
+ labelClass: 'checkbox-options',
+ inline: false
+ },
+ {
+ name: 'overwite_vars',
+ label: 'Overwrite Variables',
+ type: 'checkbox',
+ ngShow: "source.value !== '' && source.value !== null",
+ addRequired: false,
+ editRequired: false,
+ awPopOver: '
',
+ dataTitle: 'Overwrite Variables',
+ dataContainer: 'body',
+ dataPlacement: 'left',
+ labelClass: 'checkbox-options',
+ inline: false
+ },
+ {
+ name: 'keep_vars',
+ label: 'Keep Variables',
+ type: 'checkbox',
+ ngShow: "source.value !== '' && source.value !== null",
+ addRequired: false,
+ editRequired: false,
+ awPopOver: '
',
+ dataTitle: 'Keep Variables',
+ dataContainer: 'body',
+ dataPlacement: 'left',
+ labelClass: 'checkbox-options',
+ inline: false
+ },
+ {
+ name: 'update_on_launch',
+ label: 'Update on Launch',
+ type: 'checkbox',
+ ngShow: "source.value !== '' && source.value !== null",
+ addRequired: false,
+ editRequired: false,
+ awPopOver: '
Each time a job runs using this inventory, refresh the inventory from the selected source
',
+ dataTitle: 'Update on Launch',
+ dataContainer: 'body',
+ dataPlacement: 'left',
+ labelClass: 'checkbox-options',
+ inline: false
+ }
+ ]
+ }
},
buttons: { //for now always generates
tags
+
+ labelClass: 'col-lg-3',
+ controlClass: 'col-lg-5',
+
save: {
label: 'Save',
icon: 'icon-ok',
diff --git a/awx/ui/static/js/forms/Inventories.js b/awx/ui/static/js/forms/Inventories.js
index e948744b5a..61408a21e1 100644
--- a/awx/ui/static/js/forms/Inventories.js
+++ b/awx/ui/static/js/forms/Inventories.js
@@ -76,8 +76,8 @@ angular.module('InventoryFormDefinition', [])
'View JSON examples at www.json.org
' +
'View YAML examples at ansibleworks.com
',
dataTitle: 'Inventory Variables',
- dataPlacement: 'bottom',
- dataContainer: '#inventory'
+ dataPlacement: 'left',
+ dataContainer: 'body'
}
},
diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js
index dfc015e97b..d84f69bdb9 100644
--- a/awx/ui/static/js/helpers/Groups.js
+++ b/awx/ui/static/js/helpers/Groups.js
@@ -12,6 +12,17 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
'InventoryHelper', 'SelectionHelper'
])
+ .factory('getSourceTypeOptions', [ function() {
+ return function() {
+ return [
+ { label: 'Manual', value: null },
+ { label: 'Amazon EC2', value: 'ec2' },
+ { label: 'Rackspace', value: 'rackspace' },
+ { label: 'Local script', value: 'file' }
+ ];
+ }
+ }])
+
.factory('GroupsList', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupList', 'GenerateList',
'Prompt', 'SearchInit', 'PaginateInit', 'ProcessErrors', 'GetBasePath', 'GroupsAdd', 'RefreshTree', 'SelectionInit',
function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupList, GenerateList, Prompt, SearchInit, PaginateInit,
@@ -113,9 +124,9 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
.factory('GroupsAdd', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupForm', 'GenerateForm',
- 'Prompt', 'ProcessErrors', 'GetBasePath', 'RefreshTree', 'ParseTypeChange',
+ 'Prompt', 'ProcessErrors', 'GetBasePath', 'RefreshTree', 'ParseTypeChange', 'GroupsEdit',
function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors,
- GetBasePath, RefreshTree, ParseTypeChange) {
+ GetBasePath, RefreshTree, ParseTypeChange, GroupsEdit) {
return function(params) {
var inventory_id = params.inventory_id;
@@ -132,9 +143,14 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
scope.formModalHeader = 'Create New Group';
scope.formModalCancelShow = true;
scope.parseType = 'yaml';
+ scope.source = { label: 'Manual', value: null };
ParseTypeChange(scope);
$('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success');
+ $('#form-modal').off('hide.bs.modal').on('hide.bs.modal', function() {
+ //GroupsEdit({ "inventory_id": scope['inventory_id'], group_id: scope['group_id'] });
+ scope.$emit('NodeSelect', scope['nodeSelectValue']);
+ });
generator.reset();
var master={};
@@ -218,9 +234,9 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
.factory('GroupsEdit', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupForm', 'GenerateForm',
- 'Prompt', 'ProcessErrors', 'GetBasePath', 'RefreshGroupName', 'ParseTypeChange',
+ 'Prompt', 'ProcessErrors', 'GetBasePath', 'RefreshGroupName', 'ParseTypeChange', 'getSourceTypeOptions',
function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors,
- GetBasePath, RefreshGroupName, ParseTypeChange) {
+ GetBasePath, RefreshGroupName, ParseTypeChange, getSourceTypeOptions) {
return function(params) {
var group_id = params.group_id;
@@ -229,27 +245,30 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
var form = GroupForm;
var defaultUrl = GetBasePath('groups') + group_id + '/';
- $('#tree-form').hide('slide',{ direction: 'right' }, 500);
- $('#tree-form').empty();
+ $('#tree-form').hide().empty();
+
var scope = generator.inject(form, { mode: 'edit', modal: false, related: false, id: 'tree-form', breadCrumbs: false });
- //$('#tree-form').show('slide',{ direction: 'up' }, 500);
- /*
- var scope = generator.inject(form, { mode: 'edit', modal: true, related: false});
- */
generator.reset();
var master = {};
var relatedSets = {};
- //scope.formModalActionLabel = 'Save';
- //scope.formModalHeader = 'Edit Group';
- //scope.formModalCancelShow = true;
+ scope.source_type_options = getSourceTypeOptions();
scope.parseType = 'yaml';
+ scope[form.fields['source_env'].parseTypeName] = 'yaml';
+ scope.sourcePasswordRequired = false;
+ scope.sourceUsernameRequired = false;
+ scope.sourceUsernameLabel = 'Username';
+ scope.sourcePasswordLabel = 'Password';
+ scope.sourcePasswordConfirmLabel = 'Confirm Password';
+ scope.sourcePathRequired = false;
+
ParseTypeChange(scope);
+ ParseTypeChange(scope, 'source_env', form.fields['source_env'].parseTypeName);
//$('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success');
- // After the group record is loaded, retrieve any group variables
+ // After the group record is loaded, retrieve related data
if (scope.groupLoadedRemove) {
scope.groupLoadedRemove();
}
@@ -258,6 +277,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
scope.search(relatedSets[set].iterator);
}
if (scope.variable_url) {
+ // get group variables
Rest.setUrl(scope.variable_url);
Rest.get()
.success( function(data, status, headers, config) {
@@ -278,6 +298,65 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
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;
+ if (data['source'] == '') {
+ data['source'] = null;
+ }
+ 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 == null) {
+ scope['groupUpdateHide'] = true;
+ }
+ else {
+ scope['groupUpdateHide'] = false;
+ }
+ master['source'] = scope['source'];
+ }
+ else if (fld == 'source_env') {
+ // Parse source_env, converting to YAML.
+ if ($.isEmptyObject(data.source_env) || data.source_env == "\{\}" ||
+ data.source_env == "null" || data.source_env == "") {
+ scope.source_env = "---";
+ }
+ else {
+ var json_obj = JSON.parse(data.extra_vars);
+ scope.source_env = jsyaml.safeDump(json_obj);
+ }
+ master.source_env = scope.variables;
+ }
+ else if (data[fld]) {
+ scope[fld] = data[fld];
+ master[fld] = scope[fld];
+ }
+ }
+ })
+ .error( function(data, status, headers, config) {
+ scope.source = null;
+ ProcessErrors(scope, data, status, form,
+ { hdr: 'Error!', msg: 'Failed to retrieve inventory source. GET status: ' + status });
+ });
+ }
});
// Retrieve detail record and prepopulate the form
@@ -297,6 +376,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
}
}
scope.variable_url = data.related.variable_data;
+ scope.source_url = data.related.inventory_source;
scope.$emit('groupLoaded');
})
.error( function(data, status, headers, config) {
@@ -307,9 +387,73 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
if (!scope.$$phase) {
scope.$digest();
}
-
+
+ if (scope.removeFormSaveSuccess) {
+ scope.removeFormSaveSuccess();
+ }
+ scope.removeFormSaveSuccess = scope.$on('formSaveSuccess', function(e, group_id) {
+
+ var parseError = false;
+ var saveError = false;
+
+ if (scope.source.value !== null && scope.source.value !== '') {
+ var data = { group: group_id,
+ source: scope['source'].value,
+ source_path: scope['source_path'],
+ source_username: scope['source_username'],
+ source_password: scope['source_password'],
+ source_regions: scope['source_regions'],
+ source_tags: scope['source_tags'],
+ overwrite_hosts: scope['overwite_hosts'],
+ overwrite_vars: scope['overwite_vars'],
+ keep_vars: scope['keep_vars'],
+ update_on_launch: scope['update_on_launch']
+ };
+
+ if (scope['source'].value == 'file') {
+ try {
+ // Make sure we have valid variable data
+ if (scope.envParseType == 'json') {
+ var json_data = JSON.parse(scope.source_env); //make sure JSON parses
+ }
+ else {
+ var json_data = jsyaml.load(scope.source_env); //parse yaml
+ }
+
+ // Make sure our JSON is actually an object
+ if (typeof json_data !== 'object') {
+ throw "failed to return an object!";
+ }
+ data.source_env = JSON.stringify(json_data, undefined, '\t');
+ }
+ catch(err) {
+ parseError = true;
+ Alert("Error", "Error parsing extra variables. Parser returned: " + err);
+ }
+ }
+
+ if (!parseError) {
+ Rest.setUrl(scope.source_url)
+ Rest.put(data)
+ .error( function(data, status, headers, config) {
+ saveError = true;
+ ProcessErrors(scope, data, status, form,
+ { hdr: 'Error!', msg: 'Failed to update group inventory source. PUT status: ' + status });
+ });
+ }
+ }
+
+ if (!saveError && !parseError) {
+ // Reset the form, adjust buttons and let user know changese saved
+ scope[form.name + '_form'].$setPristine();
+ scope['groupUpdateHide'] = (scope['source'].value !== null && scope['source'].value !== '') ? false : true;
+ Alert("Changes Saved", "Your changes to inventory group " + scope['name'] + " were successfully saved.", 'alert-info');
+ }
+
+ });
+
// Save changes to the parent
- scope.formModalAction = function() {
+ scope.formSave = function() {
try {
var refreshHosts = false;
@@ -332,39 +476,19 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
}
data['inventory'] = inventory_id;
- // Update hosts with new group name/description
- if (master['description'] != data['description'] ||
- master['name'] != data['name']) {
- scope.groupTitle = '' + data['name'] + ' ';
- scope.groupTitle += '' + data['description'] + '
';
- }
-
Rest.setUrl(defaultUrl);
Rest.put(data)
.success( function(data, status, headers, config) {
if (scope.variables) {
//update group variables
- Rest.setUrl(GetBasePath('groups') + data.id + '/variable_data/');
+ Rest.setUrl(scope.variable_url);
Rest.put(json_data)
- .success( function(data, status, headers, config) {
- $('#form-modal').modal('hide');
- RefreshGroupName($('li[group_id="' + group_id + '"]'), scope['name'])
- if (refreshHosts) {
- scope.$emit('hostsReload');
- }
- })
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
- { hdr: 'Error!', msg: 'Failed to update group varaibles. PUT returned status: ' + status });
+ { hdr: 'Error!', msg: 'Failed to update group varaibles. PUT status: ' + status });
});
}
- else {
- $('#form-modal').modal('hide');
- RefreshGroupName($('li[group_id="' + group_id + '"]'), scope['name']);
- if (refreshHosts) {
- scope.$emit('hostsReload');
- }
- }
+ scope.$emit('formSaveSuccess', data.id);
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
@@ -376,14 +500,79 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', '
}
};
+ scope.sourceChange = function() {
+ if (scope['source'].value == 'ec2' || scope['source'].value == 'rackspace') {
+ scope.sourcePasswordRequired = true;
+ scope.sourceUsernameRequired = true;
+ if (scope['source'].value == 'ec2') {
+ scope.sourceUsernameLabel = 'Access Key ID';
+ scope.sourcePasswordLabel = 'Secret Access Key';
+ scope.sourcePasswordConfirmLabel = 'Confirm Secret Access Key';
+ }
+ else {
+ scope.sourceUsernameLabel = 'Username';
+ scope.sourcePasswordLabel = 'Password';
+ scope.sourcePasswordConfirmLabel = 'Confirm Password';
+ }
+ }
+ else {
+ scope.sourcePasswordRequired = false;
+ scope.sourceUsernameRequired = false;
+ // reset fields
+ scope.source_password = '';
+ scope.source_password_confirm = '';
+ scope.source_username = '';
+ scope[form.name + '_form']['source_username'].$setValidity('required',true);
+ }
+
+ if (scope['source'].value == 'file') {
+ scope.sourcePathRequired = true;
+ }
+ else {
+ scope.sourcePathRequired = false;
+ // reset fields
+ scope.source_path = '';
+ scope[form.name + '_form']['source_path'].$setValidity('required',true);
+ }
+ }
+
+ // Password change
+ scope.clearPWConfirm = function(fld) {
+ // If password value changes, make sure password_confirm must be re-entered
+ scope[fld] = '';
+ scope[form.name + '_form'][fld].$setValidity('awpassmatch', false);
+ }
+
+ // Respond to 'Ask at runtime?' checkbox
+ scope.ask = function(fld, associated) {
+ if (scope[fld + '_ask']) {
+ scope[fld] = 'ASK';
+ scope[associated] = '';
+ scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
+ }
+ else {
+ scope[fld] = '';
+ scope[associated] = '';
+ scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
+ }
+ }
+
+ // Click clear button
+ scope.clear = function(fld, associated) {
+ scope[fld] = '';
+ scope[associated] = '';
+ scope[form.name + '_form'][associated].$setValidity('awpassmatch', true);
+ scope[form.name + '_form'].$setDirty();
+ }
+
// Cancel
scope.formReset = function() {
- generator.reset();
- for (var fld in master) {
- scope[fld] = master[fld];
- }
- scope.parseType = 'yaml';
- }
+ generator.reset();
+ for (var fld in master) {
+ scope[fld] = master[fld];
+ }
+ scope.parseType = 'yaml';
+ }
}
}])
diff --git a/awx/ui/static/js/helpers/Parse.js b/awx/ui/static/js/helpers/Parse.js
index ca8e5d924a..bdc997464d 100644
--- a/awx/ui/static/js/helpers/Parse.js
+++ b/awx/ui/static/js/helpers/Parse.js
@@ -20,10 +20,10 @@ angular.module('ParseHelper', [])
scope.blockParseTypeWatch = false;
scope.blockVariableDataWatch = false;
- if (scope.removeParseTypeWatch) {
- scope.removeParseTypeWatch();
+ if (scope['remove' + fld + 'Watch']) {
+ scope['remove' + fld + 'Watch']();
}
- scope.removeParseTypeWatch = scope.$watch(pfld, function(newVal, oldVal) {
+ scope['remove' + fld + 'Watch'] = scope.$watch(pfld, function(newVal, oldVal) {
if (newVal !== oldVal) {
if (newVal == 'json') {
if ( scope[fld] && !/^---$/.test(scope[fld])) {
diff --git a/awx/ui/static/js/lists/Inventories.js b/awx/ui/static/js/lists/Inventories.js
index aaf4ca01c8..0f704c19ad 100644
--- a/awx/ui/static/js/lists/Inventories.js
+++ b/awx/ui/static/js/lists/Inventories.js
@@ -28,8 +28,7 @@ angular.module('InventoriesListDefinition', [])
badgeTipPlacement: 'bottom'
},
description: {
- label: 'Description',
- link: true
+ label: 'Description'
},
organization: {
label: 'Organization',
diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less
index 895335738e..a01307505f 100644
--- a/awx/ui/static/less/ansible-ui.less
+++ b/awx/ui/static/less/ansible-ui.less
@@ -318,6 +318,24 @@ a:hover {
max-width: 100px;
}
+.form-title {
+ display: inline-block;
+ width: 100%;
+ vertical-align: middle;
+ font-weight: bold;
+ padding-left: 15px;
+ margin-bottom: 10px;
+}
+
+.form-cancel {
+ float: right;
+ margin-right: 10px;
+}
+
+.form-title-hr {
+ margin-bottom: 20px;
+}
+
.form-horizontal .buttons {
margin-top: 25px;
}
@@ -533,14 +551,10 @@ select.field-mini-height {
}
.checkbox-options {
- padding-left: 40px;
+ font-weight: normal;
+ padding-right: 20px;
}
-.checkbox-group .checkbox-options:first-child {
- padding-left: 35px;
-}
-
-
/* Display list actions next to search widget */
/*
.list-actions {
@@ -739,11 +753,15 @@ select.field-mini-height {
}
.tree-view-container {
- padding: 0;
+ min-height: 650px;
+ padding: 0 0 10px 0;
+
+ .col-lg-4 {
+ padding-right: 10px;
+ }
}
.tree-form-container {
- min-height: 800px;
padding-left: 0;
padding-right: 25px;
}
@@ -753,32 +771,67 @@ select.field-mini-height {
border-radius: 6px;
background-color: #e3e3e3;
padding-top: 10px;
+ margin-top: 10px;
+ min-height: 100px;
}
-
+
+ /*
+ #tree-form:before {
+ content: "";
+ border-color: transparent transparent #e3e3e3 transparent;
+ border-style: solid;
+ border-width: 15px;
+ width: 0;
+ height: 0;
+ position: relative;
+ top: -34px;
+ left: 28px;
+ }
+ */
+
#tree-form {
display: none;
- margin-top: 15px;
+ padding: 15px 10px 0 10px;
+ margin-top: 10px;
border: 1px solid #e3e3e3;
background-color: #e3e3e3;
border-radius: 6px;
- padding-top: 15px;
+
+ .form-title {
+ color: #999;
+ padding-left: 10px;
+ }
+
+ hr {
+ background-color: #ccc;
+ height: 1px;
+ margin-top: 5px;
+ margin-bottom: 30px;
+ }
}
.tree-controls {
- padding: 10px;
- border-bottom: 1px solid #e3e3e3;
+ .btn-container{
+ padding-left: 0;
+ }
+
+ .btn-container-inner {
+ padding-top: 10px;
+ }
.title {
- display: inline-block;
- width: 263px;
- padding-top: 7px;
+ padding-top: 15px;
+ padding-left: 13px;
color: @grey;
- font-size: 14px;
font-weight: bold;
- padding-right: 5px;
}
}
+ .tree-control-divider {
+ width: 98%;
+ margin: 10px auto;
+ }
+
/* Inventory-> Hosts */
.hosts-well {
@@ -901,10 +954,6 @@ select.field-mini-height {
margin-left: 3px;
}
-#tree-view {
- min-height: 100px;
-}
-
.slider {
display: inline-block;
width: 100px;
@@ -1046,26 +1095,18 @@ tr td button i {
@media (min-width: 1200px) {
- .delete-btn {
- /* Used on job page to make cancel and delete buttons have an equal width */
- width: 60px;
- }
-
- #tree-form:before {
- content:" ";
- border-color: transparent transparent #e3e3e3 transparent;
- border-style: solid;
- border-width: 15px;
- width: 0;
- height: 0;
- position: relative;
- top: -46px;
- left: 28px;
+ .delete-btn {
+ /* Used on job page to make cancel and delete buttons have an equal width */
+ width: 60px;
}
#tree-view {
margin-left: 10px;
- margin-top: 16px;
+ margin-top: 10px;
+ }
+
+ .label-text {
+ text-align: right;
}
}
@@ -1081,8 +1122,17 @@ tr td button i {
.tree-form-container {
padding-left: 15px;
padding-right: 15px;
+
}
+ .tree-view-container .col-lg-4 {
+ padding-right: 15px;
+ }
+
+ .tree-controls .btn-container {
+ padding-left: 15px;
+ }
+
#tree-view {
margin-left: 0;
margin-top: 10px;
@@ -1091,6 +1141,10 @@ tr td button i {
#tree-form {
margin-top: 10px;
}
+
+ .label-text {
+ text-align: left;
+ }
}
@@ -1131,4 +1185,16 @@ tr td button i {
padding-right: 15px;
}
+ .tree-view-container .col-lg-4 {
+ padding-right: 15px;
+ }
+
+ .tree-controls .btn-container {
+ padding-left: 15px;
+ }
+
+ .label-text {
+ text-align: left;
+ }
+
}
diff --git a/awx/ui/static/lib/ansible/form-generator.js b/awx/ui/static/lib/ansible/form-generator.js
index 9bbd2f28ff..8d71c078df 100644
--- a/awx/ui/static/lib/ansible/form-generator.js
+++ b/awx/ui/static/lib/ansible/form-generator.js
@@ -374,7 +374,8 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
function buildCheckbox(form, field, fld, idx) {
var html='';
- html += "";
html += " \n";
- html += (field.awPopOver) ? this.attr(field, 'awPopOver', fld) : "";
+ html += (field.awPopOver && !field.awPopOverRight) ? this.attr(field, 'awPopOver', fld) : "";
html += "';
html += (field.icon) ? this.icon(field.icon) : "";
html += field.label + ' ' + "\n";
+ html += (field.awPopOver && field.awPopOverRight) ? this.attr(field, 'awPopOver', fld) : "";
html += " \n";
html += "\n";
- html += (field.awPopOver) ? this.attr(field, 'awPopOver', fld) : "";
+ html += (field.awPopOver && !field.awPopOverRight) ? this.attr(field, 'awPopOver', fld) : "";
html += "';
html += field.label + ' ' + "\n";
+ html += (field.awPopOver && field.awPopOverRight) ? this.attr(field, 'awPopOver', fld) : "";
html += "
\n";
html += "" +
field.label + "\n";
- html += "
\n";
for (var i=0; i < field.fields.length; i++) {
@@ -770,7 +775,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
//lookup type fields
if (field.type == 'lookup' && (field.excludeMode == undefined || field.excludeMode != options.mode)) {
- html += "
\n";
}
+ // Add a title and optionally a close button (used on Inventory->Groups)
+ if ( (!options.modal) && this.form.showTitle ) {
+ html += "
";
+ html += (options.mode == 'edit') ? this.form.editTitle : this.form.addTitle;
+ if (this.form.cancelButton) {
+ html += "" +
+ "× \n";
+ }
+ html += "
\n";
+ html += "
\n";
+ }
+
html += "
\n";
- }
- html += "
\n";
- html += "
" + field.group + " \n";
- group = field.group;
+ if (!(options.modal && field.excludeModal)) {
+ if (field.group && field.group != group) {
+ if (group !== '') {
+ html += "\n";
+ }
+ html += "
\n";
+ html += "
" + field.group + " \n";
+ group = field.group;
+ }
+ if (field.section && field.section != section) {
+ if (section !== '') {
+ html += "\n";
+ }
+ else {
+ html += "
\n";
+ html += "
\n";
+ }
+ var sectionShow = (this.form[field.section + 'Show']) ? " ng-show=\"" + this.form[field.section + 'Show'] + "\"" : "";
+ html += "
" + field.section + " \n";
+ html += "
\n";
+ section = field.section;
+ }
+ html += this.buildField(fld, field, options, this.form);
}
-
- if (field.section && field.section != section) {
- if (section !== '') {
- html += "
\n";
- }
- else {
- html += "
\n";
- html += "
\n";
- }
- var sectionShow = (this.form[field.section + 'Show']) ? " ng-show=\"" + this.form[field.section + 'Show'] + "\"" : "";
- html += "
" + field.section + " \n";
- html += "
\n";
- section = field.section;
- }
-
- html += this.buildField(fld, field, options, this.form);
}
if (section !== '') {
html += "
\n
\n";
@@ -1038,44 +1054,53 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
//buttons
if (!this.modal) {
if (this.has('buttons')) {
+
if (this.form.twoColumns) {
- html += "
\n";
- html += "
\n";
- html += "
\n";
+ html += "
\n";
+ html += "
\n";
+ html += "
\n";
}
+
html += "