Fixed form-generator and awRequiredWhen directive to not overlap when setting * on required field labels. Fixed job submission to work with new cloud credential implementation.

This commit is contained in:
Chris Houseknecht
2013-11-04 04:03:55 +00:00
committed by Chris Church
parent 0466629718
commit bac22205a8
12 changed files with 280 additions and 84 deletions

View File

@@ -12,7 +12,7 @@
function CredentialsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, CredentialList,
GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller,
ClearScope, ProcessErrors, GetBasePath, SelectionInit)
ClearScope, ProcessErrors, GetBasePath, SelectionInit, GetChoices)
{
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope.
@@ -33,20 +33,45 @@ function CredentialsList ($scope, $rootScope, $location, $log, $routeParams, Res
scope.removePostRefresh();
}
scope.removePostRefresh = scope.$on('PostRefresh', function() {
// After a refresh, populate the organization name on each row
list.fields.kind.searchOptions = scope.credential_kind_options;
// Translate the kind value
for(var i=0; i < scope.credentials.length; i++) {
/*
if (scope.credentials[i].summary_fields.user) {
scope.credentials[i].user_username = scope.credentials[i].summary_fields.user.username;
}
if (scope.credentials[i].summary_fields.team) {
scope.credentials[i].team_name = scope.credentials[i].summary_fields.team.name;
}
*/
for (var j=0; j < scope.credential_kind_options.length; j++) {
if (scope.credential_kind_options[j].value == scope.credentials[i].kind) {
scope.credentials[i].kind = scope.credential_kind_options[j].label
break;
}
}
}
});
SearchInit({ scope: scope, set: 'credentials', list: list, url: defaultUrl });
PaginateInit({ scope: scope, list: list, url: defaultUrl });
scope.search(list.iterator);
if (scope.removeChoicesReady) {
scope.removeChoicesReady();
}
scope.removeChoicesReady = scope.$on('choicesReady', function() {
SearchInit({ scope: scope, set: 'credentials', list: list, url: defaultUrl });
PaginateInit({ scope: scope, list: list, url: defaultUrl });
scope.search(list.iterator);
});
// Load the list of options for Kind
GetChoices({
scope: scope,
url: defaultUrl,
field: 'kind',
variable: 'credential_kind_options',
callback: 'choicesReady'
});
LoadBreadCrumbs();
@@ -84,7 +109,7 @@ function CredentialsList ($scope, $rootScope, $location, $log, $routeParams, Res
CredentialsList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'CredentialList', 'GenerateList',
'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors',
'GetBasePath', 'SelectionInit'];
'GetBasePath', 'SelectionInit', 'GetChoices'];
function CredentialsAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, CredentialForm,
@@ -171,23 +196,39 @@ function CredentialsAdd ($scope, $rootScope, $compile, $location, $log, $routePa
var data = {}
for (var fld in form.fields) {
if (scope[fld] === null) {
data[fld] = "";
}
else {
data[fld] = scope[fld];
if (fld !== 'access_key' && fld !== 'secret_key' && fld !== 'ssh_username' &&
fld !== 'ssh_password') {
if (scope[fld] === null) {
data[fld] = "";
}
else {
data[fld] = scope[fld];
}
}
}
if (!Empty(scope.team)) {
data.team = scope.team;
data.user = "";
}
else {
data.user = scope.user;
data.team = "";
}
data['kind'] = scope['kind'].value;
switch (data['kind']) {
case 'ssh':
data['username'] = scope['ssh_username'];
data['password'] = scope['ssh_password'];
break;
case 'aws':
data['username'] = scope['access_key'];
data['password'] = scope['secret_key'];
break;
}
if (Empty(data.team) && Empty(data.user)) {
Alert('Missing User or Team', 'You must provide either a User or a Team. If this credential will only be accessed by a specific ' +
'user, select a User. To allow a team of users to access this credential, select a Team.', 'alert-danger');
@@ -353,6 +394,29 @@ function CredentialsEdit ($scope, $rootScope, $compile, $location, $log, $routeP
scope['owner'] = 'team';
}
master['owner'] = scope['owner'];
for (var i=0; i < scope.credential_kind_options.length; i++) {
if (scope.credential_kind_options[i].value == data.kind) {
scope.kind = scope.credential_kind_options[i];
break;
}
}
master['kind'] = scope['kind'];
switch (data.kind) {
case 'aws':
scope['access_key'] = data.username;
scope['secret_key'] = data.password;
master['access_key'] = scope['access_key'];
master['secret_key'] = scope['secret_key'];
break;
case 'ssh':
scope['ssh_username'] = data.username;
scope['ssh_password'] = data.password;
master['ssh_username'] = scope['ssh_username'];
master['ssh_password'] = scope['ssh_password'];
break;
}
scope.$emit('credentialLoaded');
})
@@ -373,26 +437,43 @@ function CredentialsEdit ($scope, $rootScope, $compile, $location, $log, $routeP
// Save changes to the parent
scope.formSave = function() {
generator.clearApiErrors();
var data = {}
for (var fld in form.fields) {
if (scope[fld] === null) {
data[fld] = "";
}
else {
data[fld] = scope[fld];
if (fld !== 'access_key' && fld !== 'secret_key' && fld !== 'ssh_username' &&
fld !== 'ssh_password') {
if (scope[fld] === null) {
data[fld] = "";
}
else {
data[fld] = scope[fld];
}
}
}
if (!Empty(scope.team)) {
data.team = scope.team;
data.user = "";
}
else {
data.user = scope.user;
data.team = "";
}
data['kind'] = scope['kind'].value;
switch (data['kind']) {
case 'ssh':
data['username'] = scope['ssh_username'];
data['password'] = scope['ssh_password'];
break;
case 'aws':
data['username'] = scope['access_key'];
data['password'] = scope['secret_key'];
break;
}
if (Empty(data.team) && Empty(data.user)) {
Alert('Missing User or Team', 'You must provide either a User or a Team. If this credential will only be accessed by a specific ' +
'user, select a User. To allow a team of users to access this credential, select a Team.', 'alert-danger');

View File

@@ -82,7 +82,8 @@ JobTemplatesList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$rout
function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm,
GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope,
GetBasePath, InventoryList, CredentialList, ProjectList, LookUpInit, md5Setup, ParseTypeChange)
GetBasePath, InventoryList, CredentialList, ProjectList, LookUpInit,
md5Setup, ParseTypeChange)
{
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope.
@@ -123,23 +124,30 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP
field: 'inventory'
});
LookUpInit({
url: GetBasePath('credentials') + '?cloud=false',
scope: scope,
form: form,
current_item: null,
list: CredentialList,
field: 'credential'
});
// Clone the CredentialList object for use with cloud_credential. Cloning
// and changing properties to avoid collision.
var CloudCredentialList = {};
jQuery.extend(true, CloudCredentialList, CredentialList);
CloudCredentialList.name = 'cloudcredentials',
CloudCredentialList.iterator = 'cloudcredential',
LookUpInit({
url: GetBasePath('credentials') + '?cloud=true',
scope: scope,
form: form,
current_item: null,
list: CredentialList,
list: CloudCredentialList,
field: 'cloud_credential'
});
LookUpInit({
url: GetBasePath('credentials') + '?kind=ssh',
scope: scope,
form: form,
current_item: null,
list: CredentialList,
field: 'credential'
});
// Update playbook select whenever project value changes
var selectPlaybook = function(oldValue, newValue) {
@@ -277,7 +285,8 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP
JobTemplatesAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm',
'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope',
'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', 'md5Setup', 'ParseTypeChange' ];
'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit',
'md5Setup', 'ParseTypeChange' ];
function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm,

View File

@@ -75,15 +75,15 @@ angular.module('CredentialFormDefinition', [])
label: 'Access Key',
type: 'text',
ngShow: "kind.value == 'aws'",
awRequiredWhen: { variable: "aws_required", init: "false" },
awRequiredWhen: { variable: "aws_required", init: false },
autocomplete: false,
apiField: 'username'
},
secret_key: {
label: 'Secrent Key',
label: 'Secret Key',
type: 'password',
ngShow: "kind.value == 'aws'",
awRequiredWhen: { variable: "aws_required", init: "false" },
awRequiredWhen: { variable: "aws_required", init: false },
autocomplete: false,
ask: false,
clear: false,
@@ -97,9 +97,9 @@ angular.module('CredentialFormDefinition', [])
autocomplete: false
},
"password": {
labelBind: 'passwordLabel',
label: 'Password',
type: 'password',
ngShow: "kind.value && kind.value !== 'aws'",
ngShow: "kind.value == 'rax' || kind.value == 'scm'",
awRequiredWhen: {variable: 'rackspace_required', init: false },
ngChange: "clearPWConfirm('password_confirm')",
ask: false,
@@ -108,15 +108,37 @@ angular.module('CredentialFormDefinition', [])
autocomplete: false
},
"password_confirm": {
labelBind: 'passwordConfirmLabel',
label: 'Confirm Password',
type: 'password',
ngShow: "kind.value && kind.value !== 'aws'",
ngShow: "kind.value == 'rax' || kind.value == 'scm'",
addRequired: false,
editRequired: false,
awPassMatch: true,
associated: 'password',
autocomplete: false
},
"ssh_password": {
label: 'SSH Password',
type: 'password',
ngShow: "kind.value == 'ssh'",
ngChange: "clearPWConfirm('password_confirm')",
addRequired: false,
editRequired: false,
ask: true,
clear: true,
associated: 'ssh_password_confirm',
autocomplete: false
},
"ssh_password_confirm": {
label: 'Confirm SSH Password',
type: 'password',
ngShow: "kind.value == 'ssh'",
addRequired: false,
editRequired: false,
awPassMatch: true,
associated: 'ssh_password',
autocomplete: false
},
"ssh_key_data": {
labelBind: 'sshKeyDataLabel',
type: 'textarea',

View File

@@ -87,7 +87,7 @@ angular.module('JobTemplateFormDefinition', [])
type: 'lookup',
sourceModel: 'cloud_credential',
sourceField: 'name',
ngClick: 'lookUpCredential()',
ngClick: 'lookUpCloudcredential()',
addRequired: false,
editRequired: false,
column: 1

View File

@@ -18,27 +18,20 @@ angular.module('CredentialsHelper', ['Utilities'])
// Put things in a default state
scope['usernameLabel'] = 'Username';
scope['passwordLabel'] = 'Password';
scope['passwordConfirmLabel'] = 'Confirm Password';
scope['aws_required'] = false;
scope['rackspace_required'] = false;
scope['sshKeyDataLabel'] = 'SSH Private Key';
form.fields['password'].clear = true;
form.fields['password'].ask = true;
// Apply kind specific settings
switch(scope['kind'].value) {
case 'aws':
scope['aws_required'] = true;
break;
case 'rax':
scope['rackspace_required'] = true;
form.fields['password'].ask = false;
scope['rackspace_required'] = true;
break;
case 'ssh':
case 'ssh':
scope['usernameLabel'] = 'SSH Username';
scope['passwordLabel'] = 'SSH Password';
scope['passwordConfirmLabel'] = 'Confirm SSH Password';
break;
case 'scm':
scope['sshKeyDataLabel'] = 'SCM Private Key';

View File

@@ -8,8 +8,8 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential
'LookUpHelper', 'ProjectFormDefinition', 'JobSubmissionHelper', 'GroupFormDefinition', 'GroupsHelper' ])
.factory('PromptPasswords', ['CredentialForm', 'JobTemplateForm', 'GroupForm', 'ProjectsForm', '$compile', 'Rest', '$location', 'ProcessErrors',
'GetBasePath', 'Alert',
function(CredentialForm, JobTemplateForm, ProjectsForm, GroupForm, $compile, Rest, $location, ProcessErrors, GetBasePath, Alert) {
'GetBasePath', 'Alert', 'Empty',
function(CredentialForm, JobTemplateForm, ProjectsForm, GroupForm, $compile, Rest, $location, ProcessErrors, GetBasePath, Alert, Empty) {
return function(params) {
var scope = params.scope;
@@ -73,7 +73,7 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential
value_supplied = true;
}
});
if (passwords.length == 0 || value_supplied) {
if (Empty(passwords) || passwords.length == 0 || value_supplied) {
Rest.setUrl(start_url);
Rest.post(pswd)
.success( function(data, status, headers, config) {
@@ -97,7 +97,7 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential
}
}
if (passwords.length > 0) {
if (passwords && passwords.length > 0) {
// Prompt for passwords
html += "<form class=\"form-horizontal\" name=\"password_form\" novalidate>\n";
html += (extra_html) ? extra_html : "";
@@ -441,9 +441,9 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential
Rest.get()
.success( function(data, status, headers, config) {
if (data.can_update) {
var extra_html = "<div class=\"inventory-passwd-msg\">Starting inventory update for <em>" + group_name +
"</em>. Please provide the " + group_source + " credentials:</div>\n";
scope.$emit('InventorySubmit', data.passwords_needed_to_update, extra_html);
//var extra_html = "<div class=\"inventory-passwd-msg\">Starting inventory update for <em>" + group_name +
// "</em>. Please provide the " + group_source + " credentials:</div>\n";
scope.$emit('InventorySubmit', data.passwords_needed_to_update);
}
else {
Alert('Permission Denied', 'You do not have access to run the update. Please contact your system administrator.',

View File

@@ -0,0 +1,75 @@
/*********************************************
* Copyright (c) 2013 AnsibleWorks, Inc.
*
* CloudCredentials.js
* List view object for Credential data model.
*
* @dict
*/
angular.module('CloudCredentialsListDefinition', [])
.value(
'CloudCredentialList', {
name: 'cloudcredentials',
iterator: 'cloudcredential',
selectTitle: 'Add Cloud Credentials',
editTitle: 'Cloud Credentials',
selectInstructions: '<p>Select existing credentials by clicking each credential or checking the related checkbox. When finished, click the blue ' +
'<em>Select</em> button, located bottom right.</p> <p>Create a brand new credential by clicking the green <em>Create New</em> button.</p>',
index: true,
hover: true,
fields: {
name: {
key: true,
label: 'Name'
},
description: {
label: 'Description',
excludeModal: false
},
team: {
label: 'Team',
ngBind: 'credential.team_name',
sourceModel: 'team',
sourceField: 'name',
excludeModal: true
},
user: {
label: 'User',
ngBind: 'credential.user_username',
sourceModel: 'user',
sourceField: 'username',
excludeModal: true
}
},
actions: {
add: {
icon: 'icon-plus',
label: 'Create New',
mode: 'all', // One of: edit, select, all
ngClick: 'addCredential()',
"class": 'btn-success btn-xs',
awToolTip: 'Create a new credential'
}
},
fieldActions: {
edit: {
ngClick: "editCredential(\{\{ credential.id \}\})",
icon: 'icon-edit',
label: 'Edit',
"class": 'btn-xs btn-default',
awToolTip: 'View/Edit credential'
},
"delete": {
ngClick: "deleteCredential(\{\{ credential.id \}\},'\{\{ credential.name \}\}')",
icon: 'icon-trash',
label: 'Delete',
"class": 'btn-xs btn-danger',
awToolTip: 'Delete credential'
}
}
});

View File

@@ -26,20 +26,31 @@ angular.module('CredentialsListDefinition', [])
},
description: {
label: 'Description',
excludeModal: true
excludeModal: false
},
kind: {
label: 'Type',
searchType: 'select',
searchOptions: [], // will be set by Options call to credentials resource
excludeModal: true,
nosort: true
}
/*
team: {
label: 'Team',
ngBind: 'credential.team_name',
sourceModel: 'team',
sourceField: 'name'
sourceField: 'name',
excludeModal: true
},
user: {
label: 'User',
ngBind: 'credential.user_username',
sourceModel: 'user',
sourceField: 'username'
sourceField: 'username',
excludeModal: true
}
*/
},
actions: {

View File

@@ -41,7 +41,8 @@ angular.module('ProjectsListDefinition', [])
last_updated: {
label: 'Last Updated',
type: 'date',
excludeModal: true
excludeModal: true,
searchable: false
}
},

View File

@@ -448,8 +448,9 @@ angular.module('Utilities',['RestServices', 'Utilities'])
Rest.options()
.success( function(data, status, headers, config) {
var choices = data.actions.GET[field].choices
// including 'name' property so list can be used by search
for (var i=0; i < choices.length; i++) {
scope[variable].push({ label: choices[i][1], value: choices[i][0] });
scope[variable].push({ label: choices[i][1], value: choices[i][0], name: choices[i][1]});
}
if (callback) {
scope.$emit(callback);
@@ -490,7 +491,8 @@ angular.module('Utilities',['RestServices', 'Utilities'])
/* Empty()
*
* Test if a value is 'empty'. Returns true if val is null | '' | undefined
* Test if a value is 'empty'. Returns true if val is null | '' | undefined.
* Only works on non-Ojbect types.
*
*/
.factory('Empty', [ function() {

View File

@@ -107,26 +107,26 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Hos
function checkIt () {
var viewValue = elm.val();
var txt, label;
var txt, label, p, l, s;
validity = true;
if ( scope[attrs.awRequiredWhen] && (elm.attr('required') == null || elm.attr('required') == undefined) ) {
$(elm).attr('required','required');
if ($(elm).hasClass('lookup')) {
$(elm).parent().parent().parent().find('label').addClass('prepend-asterisk');
$(elm).parent().parent().parent().find('label').first().addClass('prepend-asterisk');
}
else {
$(elm).parent().parent().find('label').addClass('prepend-asterisk');
$(elm).parent().parent().find('label').first().addClass('prepend-asterisk');
}
}
else if (!scope[attrs.awRequiredWhen]) {
elm.removeAttr('required');
if ($(elm).hasClass('lookup')) {
label = $(elm).parent().parent().parent().find('label');
label = $(elm).parent().parent().parent().find('label').first();
label.removeClass('prepend-asterisk');
}
else {
label = $(elm).parent().parent().find('label');
$(elm).parent().parent().find('label').first().removeClass('prepend-asterisk');
}
label.removeClass('prepend-asterisk');
}
if (scope[attrs.awRequiredWhen] && (viewValue == undefined || viewValue == null || viewValue == '')) {
validity = false;

View File

@@ -8,11 +8,11 @@
*
*/
angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies', 'Utilities'])
.factory('GenerateForm', [ '$location', '$cookieStore', '$compile', 'SearchWidget', 'PaginateWidget', 'Attr', 'Icon', 'Column',
'NavigationLink', 'HelpCollapse', 'Button', 'DropDown',
'NavigationLink', 'HelpCollapse', 'Button', 'DropDown', 'Empty',
function($location, $cookieStore, $compile, SearchWidget, PaginateWidget, Attr, Icon, Column, NavigationLink, HelpCollapse, Button,
DropDown) {
DropDown, Empty) {
return {
setForm: function(form) {
@@ -102,18 +102,20 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
// Prepend an asterisk to required field label
$('.form-control[required], input[type="radio"][required]').each(function() {
var label = $(this).parent().parent().find('label');
if ($(this).attr('type') == 'radio') {
label = $(this).parent().parent().parent().find('label').first();
}
if (label) {
var span = label.find('span');
if (span && !span.hasClass('prepend-asterisk')) {
span.addClass('prepend-asterisk');
}
else if (!label.hasClass('prepend-asterisk') && !label.find('.prepend-asterisk')) {
label.addClass('prepend-asterisk');
}
if ( Empty($(this).attr('aw-required-when')) ) {
var label = $(this).parent().parent().find('label').first();
if ($(this).attr('type') == 'radio') {
label = $(this).parent().parent().parent().find('label').first();
}
if (label) {
var span = label.find('span').first();
if (span && !span.hasClass('prepend-asterisk')) {
span.addClass('prepend-asterisk');
}
else if (!label.hasClass('prepend-asterisk') && !label.find('.prepend-asterisk')) {
label.addClass('prepend-asterisk');
}
}
}
});
@@ -463,7 +465,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
html += (field.labelBind) ? "ng-bind=\"" + field.labelBind + "\" " : "";
html += "for=\"" + fld + '">';
html += (field.icon) ? this.icon(field.icon) : "";
html += "<span>" + field.label + '</span></label>' + "\n";
html += "<span class=\"label-text\">" + field.label + '</span></label>' + "\n";
html += (field.awPopOver && field.awPopOverRight) ? this.attr(field, 'awPopOver', fld) : "";
html += "</div>\n";
html += "<div ";
@@ -776,7 +778,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
html += (field.labelBind) ? "ng-bind=\"" + field.labelBind + "\" " : "";
html += "for=\"" + fld + '">';
html += (field.icon) ? this.icon(field.icon) : "";
html += '<span>' + field.label + '</span></label>' + "\n";
html += '<span class=\"label-text\">' + field.label + '</span></label>' + "\n";
html += (field.awPopOver && field.awPopOverRight) ? this.attr(field, 'awPopOver', fld) : "";
html += "</div>\n";
html += "<div ";
@@ -791,7 +793,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
if (field.type == 'radio') {
html += "<label class=\"control-label " + getLabelWidth() + "\" for=\"" + fld + '">';
html += (field.awPopOver) ? this.attr(field, 'awPopOver', fld) : "";
html += '<span>' + field.label + '</span></label>' + "\n";
html += '<span class=\"label-text\">' + field.label + '</span></label>' + "\n";
html += "<div ";
html += "id=\"" + this.form.name + "_" + fld + "_radio_grp\" ";
html += (field.controlNGClass) ? "ng-class=\"" + field.controlNGClass + "\" " : "";