1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-02 01:21:21 +03:00

Refactored job submission module, breaking the process of prompting for credentials and passwords into small, isolated units of work. Code can now be shared across each job type and unit tests are feasible.

This commit is contained in:
Chris Houseknecht 2014-03-27 14:21:47 -04:00
parent 62e860afc1
commit 29a8b46e2f
5 changed files with 271 additions and 321 deletions

View File

@ -12,7 +12,7 @@
function JobTemplatesList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobTemplateList, function JobTemplatesList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobTemplateList,
GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors,
GetBasePath, PromptPasswords, JobTemplateForm, CredentialList, LookUpInit, SubmitJob, Wait, Stream) { GetBasePath, JobTemplateForm, CredentialList, LookUpInit, PlaybookRun, Wait, Stream) {
ClearScope(); ClearScope();
@ -96,14 +96,14 @@ function JobTemplatesList($scope, $rootScope, $location, $log, $routeParams, Res
}; };
$scope.submitJob = function (id) { $scope.submitJob = function (id) {
SubmitJob({ scope: $scope, id: id }); PlaybookRun({ scope: $scope, id: id });
}; };
} }
JobTemplatesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'JobTemplateList', JobTemplatesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'JobTemplateList',
'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
'ProcessErrors', 'GetBasePath', 'PromptPasswords', 'JobTemplateForm', 'CredentialList', 'LookUpInit', 'ProcessErrors', 'GetBasePath', 'JobTemplateForm', 'CredentialList', 'LookUpInit',
'SubmitJob', 'Wait', 'Stream' 'PlaybookRun', 'Wait', 'Stream'
]; ];
function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm, function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm,

View File

@ -7,149 +7,110 @@
'use strict'; 'use strict';
angular.module('JobSubmissionHelper', ['RestServices', 'Utilities', 'CredentialFormDefinition', 'CredentialsListDefinition', angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'CredentialFormDefinition', 'CredentialsListDefinition',
'LookUpHelper', 'ProjectFormDefinition', 'JobSubmissionHelper' 'LookUpHelper', 'JobSubmissionHelper' ])
])
.factory('PromptPasswords', ['CredentialForm', 'JobTemplateForm', '$compile', 'Rest', '$location', 'ProcessErrors', .factory('LaunchJob', ['Rest', 'Wait', 'ProcessErrors', function(Rest, Wait, ProcessErrors) {
'GetBasePath', 'Alert', 'Empty', 'Wait', return function(params) {
function (CredentialForm, JobTemplateForm, $compile, Rest, $location, ProcessErrors, GetBasePath, Alert, Empty, Wait) { var scope = params.scope,
return function (params) { passwords = params.passwords || {},
callback = params.callback || 'JobLaunched',
url = params.url;
Wait('start');
Rest.setUrl(url);
Rest.post(passwords)
.success(function () {
scope.$emit(callback);
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Attempt to start job at ' + url + ' failed. POST returned: ' + status });
});
};
}])
var scope = params.scope, .factory('PromptForCredential', ['Wait', 'GetBasePath', 'LookUpInit', 'JobTemplateForm', 'CredentialList',
function(Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList) {
return function(params) {
var scope = params.scope,
callback = params.callback || 'CredentialReady',
selectionMade;
Wait('stop');
scope.credential = '';
selectionMade = function () {
scope.$emit(callback, scope.credential);
};
LookUpInit({
url: GetBasePath('credentials') + '?kind=ssh',
scope: scope,
form: JobTemplateForm,
current_item: null,
list: CredentialList,
field: 'credential',
hdr: 'Credential Required',
instructions: "Launching this job requires a machine credential. Please select your machine credential now or Cancel to quit.",
postAction: selectionMade
});
scope.lookUpCredential();
};
}])
.factory('PromptForPasswords', ['$compile', 'Wait', 'Alert', 'CredentialForm',
function($compile, Wait, Alert, CredentialForm) {
return function(params) {
var parent_scope = params.scope,
passwords = params.passwords, passwords = params.passwords,
start_url = params.start_url, callback = params.callback || 'PasswordsAccepted',
form = params.form, password,
html = '', form = CredentialForm,
field, element, fld, i, current_form, html,
base = $location.path().replace(/^\//, '').split('/')[0], acceptedPasswords = {},
extra_html = params.extra_html; scope = parent_scope.$new();
function navigate(canceled) { Wait('stop');
//Decide where to send the user once the modal dialog closes
if (!canceled) { function promptPassword() {
if (base === 'jobs') { var e, fld, field;
scope.refreshJob();
} else {
$location.path('/jobs');
}
} else {
$location.path('/' + base);
}
}
function cancel() { password = passwords.pop();
// Delete a job
var url = GetBasePath('jobs') + scope.job_id + '/'; // Prompt for password
Rest.setUrl(url);
Rest.destroy()
.success(function () {
if (form.name === 'credential') {
navigate(true);
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
});
}
scope.cancelJob = function () {
// User clicked cancel button
$('#password-modal').modal('hide');
if (form.name === 'credential') {
cancel();
} else {
scope.$emit('UpdateSubmitted', 'canceled');
}
};
scope.startJob = function () {
var pswd = {}, value_supplied = false;
$('#password-modal').modal('hide');
Wait('start');
$('.password-field').each(function () {
pswd[$(this).attr('name')] = $(this).val();
if ($(this).val() !== '' && $(this).val() !== null) {
value_supplied = true;
}
});
if (Empty(passwords) || passwords.length === 0 || value_supplied) {
Rest.setUrl(start_url);
Rest.post(pswd)
.success(function () {
scope.$emit('UpdateSubmitted', 'started');
if (form.name === 'credential') {
navigate(false);
}
})
.error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'POST to ' + start_url + ' failed with status: ' + status });
});
} else {
Wait('stop');
Alert('No Passwords', 'Required password(s) not provided. The request was not submitted.', 'alert-info');
if (form.name === 'credential') {
// No passwords provided, so we can't start the job. Rather than leave the job in a 'new'
// state, let's delete it.
scope.cancelJob();
}
}
};
if (passwords && passwords.length > 0) {
Wait('stop');
// Prompt for passwords
html += "<form class=\"form-horizontal\" name=\"password_form\" novalidate>\n"; html += "<form class=\"form-horizontal\" name=\"password_form\" novalidate>\n";
html += (extra_html) ? extra_html : ""; field = form.fields[password];
for (i = 0; i < passwords.length; i++) { fld = password;
// Add the password field scope[fld] = '';
if (form.name === 'credential') { html += "<div class=\"form-group\">\n";
// this is a job. we could be prompting for inventory and/or SCM passwords html += "<label class=\"col-md-offset-1 col-md-2 col-sm-offset-1 col-sm-2 col-xs-3\" for=\"" + fld + "\">* ";
if (form.fields[passwords[i]]) { html += "</label>\n";
current_form = form; html += "<div class=\"col-md-8 col-sm-8 col-xs-9\">\n";
} html += "<input type=\"password\" ";
else { html += "ng-model=\"" + fld + '" ';
// No match found. Abandon ship! html += 'name="' + fld + '" ';
Alert('Form Not Found', 'Could not locate form for: ' + passwords[i], 'alert-danger'); html += "class=\"password-field form-control\" ";
$location('/#/jobs'); html += "required ";
} html += "/>";
} else { html += "<br />\n";
current_form = form; // Add error messages
} html += "<span class=\"error\" ng-show=\"password_form." + fld + ".$dirty && " +
field = current_form.fields[passwords[i]]; "password_form." + fld + ".$error.required\">A value is required!</span>\n";
fld = passwords[i]; html += "<span class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></span>\n";
scope[fld] = ''; html += "</div>\n";
html += "<div class=\"form-group\">\n"; html += "</div>\n";
html += "<label class=\"control-label col-lg-3 normal-weight\" for=\"" + fld + "\">* ";
html += (field.labelBind) ? scope[field.labelBind] : field.label;
html += "</label>\n";
html += "<div class=\"col-lg-9\">\n";
html += "<input type=\"password\" ";
html += "ng-model=\"" + fld + '" ';
html += 'name="' + fld + '" ';
html += "class=\"password-field form-control\" ";
html += "required ";
html += "/>";
html += "<br />\n";
// Add error messages
html += "<span class=\"error\" ng-show=\"password_form." + fld + ".$dirty && " +
"password_form." + fld + ".$error.required\">A value is required!</span>\n";
html += "<span class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></span>\n";
html += "</div>\n";
html += "</div>\n";
// Add the related confirm field // Add the related confirm field
if (field.associated) {
fld = field.associated; fld = field.associated;
field = current_form.fields[field.associated]; field = form.fields[field.associated];
scope[fld] = ''; scope[fld] = '';
html += "<div class=\"form-group\">\n"; html += "<div class=\"form-group\">\n";
html += "<label class=\"control-label col-lg-3 normal-weight\" for=\"" + fld + "\">* "; html += "<label class=\"col-md-offset-1 col-md-2 col-sm-offset-1 col-sm-2 col-xs-3\" for=\"" + fld + "\">* ";
html += (field.labelBind) ? scope[field.labelBind] : field.label;
html += "</label>\n"; html += "</label>\n";
html += "<div class=\"col-lg-9\">\n"; html += "<div class=\"col-md-8 col-sm-8 col-xs-9\">\n";
html += "<input type=\"password\" "; html += "<input type=\"password\" ";
html += "ng-model=\"" + fld + '" '; html += "ng-model=\"" + fld + '" ';
html += 'name="' + fld + '" '; html += 'name="' + fld + '" ';
@ -161,98 +122,119 @@ angular.module('JobSubmissionHelper', ['RestServices', 'Utilities', 'CredentialF
// Add error messages // Add error messages
html += "<span class=\"error\" ng-show=\"password_form." + fld + ".$dirty && " + html += "<span class=\"error\" ng-show=\"password_form." + fld + ".$dirty && " +
"password_form." + fld + ".$error.required\">A value is required!</span>\n"; "password_form." + fld + ".$error.required\">A value is required!</span>\n";
if (field.awPassMatch) { html += (field.awPassMatch) ? "<span class=\"error\" ng-show=\"password_form." + fld +
html += "<span class=\"error\" ng-show=\"password_form." + fld + ".$error.awpassmatch\">Must match Password value</span>\n" : "";
".$error.awpassmatch\">Must match Password value</span>\n";
}
html += "<span class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></span>\n"; html += "<span class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></span>\n";
html += "</div>\n"; html += "</div>\n";
html += "</div>\n"; html += "</div>\n";
} }
html += "</form>\n"; html += "</form>\n";
element = angular.element(document.getElementById('password-body')); $('#password-body').empty().html(html);
element.html(html); e = angular.element(document.getElementById('password-modal'));
$compile(element.contents())(scope); $compile(e)(scope);
$('#password-modal').modal(); $('#password-modal').modal();
$('#password-modal').on('shown.bs.modal', function () { $('#password-modal').on('shown.bs.modal', function () {
$('#password-body').find('input[type="password"]:first').focus(); $('#password-body').find('input[type="password"]:first').focus();
}); });
} else {
scope.startJob();
} }
scope.passwordAccept = function() {
acceptedPasswords[password] = scope[password];
if (password.length > 0) {
promptPassword();
}
else {
parent_scope.$emit(callback, acceptedPasswords);
}
};
scope.passwordCancel = function() {
Alert('Missing Password', 'Required password(s) not provided. The request will not be submitted.', 'alert-info');
parent_scope.$emit('PasswordsCanceled');
};
}; };
} }])
])
.factory('SubmitJob', ['PromptPasswords', '$compile', 'Rest', '$location', 'GetBasePath', 'CredentialList', // Submit request to run a playbook
'LookUpInit', 'CredentialForm', 'ProcessErrors', 'JobTemplateForm', 'Wait', 'Empty', 'PromptForCredential', .factory('PlaybookRun', ['LaunchJob', 'PromptForPasswords', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Wait', 'Empty', 'PromptForCredential',
function (PromptPasswords, $compile, Rest, $location, GetBasePath, CredentialList, LookUpInit, CredentialForm, function (LaunchJob, PromptForPasswords, Rest, $location, GetBasePath, ProcessErrors, Wait, Empty, PromptForCredential) {
ProcessErrors, JobTemplateForm, Wait, Empty, PromptForCredential) {
return function (params) { return function (params) {
var scope = params.scope, var scope = params.scope,
id = params.id, id = params.id,
template_name = (params.template) ? params.template : null,
base = $location.path().replace(/^\//, '').split('/')[0], base = $location.path().replace(/^\//, '').split('/')[0],
url = GetBasePath(base) + id + '/'; url = GetBasePath(base) + id + '/',
job_template,
new_job_id,
launch_url;
if (scope.removePostTheJob) { if (scope.removePostTheJob) {
scope.removePostTheJob(); scope.removePostTheJob();
} }
scope.removePostTheJob = scope.$on('PostTheJob', function(e, data) { scope.removePostTheJob = scope.$on('PostTheJob', function() {
var dt = new Date().toISOString(), var url = (job_template.related.jobs) ? job_template.related.jobs : job_template.related.job_template + 'jobs/';
url = (data.related.jobs) ? data.related.jobs : data.related.job_template + 'jobs/',
name = (template_name) ? template_name : data.name;
Wait('start'); Wait('start');
Rest.setUrl(url); Rest.setUrl(url);
Rest.post({ Rest.post(job_template).success(function (data) {
name: name + ' ' + dt, // job name required and unique new_job_id = data.id;
description: data.description, launch_url = data.related.start;
job_template: data.id,
inventory: data.inventory,
project: data.project,
playbook: data.playbook,
credential: data.credential,
forks: data.forks,
limit: data.limit,
verbosity: data.verbosity,
extra_vars: data.extra_vars
}).success(function (data) {
scope.job_id = data.id;
if (data.passwords_needed_to_start.length > 0) { if (data.passwords_needed_to_start.length > 0) {
// Passwords needed. Prompt for passwords, then start job. scope.$emit('PromptForPasswords');
PromptPasswords({
scope: scope,
passwords: data.passwords_needed_to_start,
start_url: data.related.start,
form: CredentialForm
});
} else { } else {
// No passwords needed, start the job! scope.$emit('StartPlaybookRun', {});
Rest.setUrl(data.related.start);
Rest.post()
.success(function () {
Wait('stop');
var base = $location.path().replace(/^\//, '').split('/')[0];
if (base === 'jobs') {
scope.refresh();
} else {
$location.path('/jobs');
}
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to start job. POST returned status: ' + status });
});
} }
}).error(function (data, status) { }).error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, null, { hdr: 'Error!', ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to create job. POST returned status: ' + status }); msg: 'Failed to create job. POST returned status: ' + status });
}); });
}); });
if (scope.removePasswordsCanceled) {
scope.removePasswordsCanceled();
}
scope.removePasswordCanceled = scope.$on('PasswordCanceled', function() {
// Delete the job
Wait('start');
Rest.setUrl(GetBasePath('jobs') + new_job_id + '/');
Rest.destroy()
.success(function() {
Wait('stop');
})
.error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
});
});
if (scope.removePlaybookLaunchFinished) {
scope.removePlaybookLaunchFinished();
}
scope.removePlaybookLaunchFinished = scope.$on('PlaybookLaunchFinished', function() {
var base = $location.path().replace(/^\//, '').split('/')[0];
if (base === 'jobs') {
scope.refresh();
} else {
$location.path('/jobs');
}
});
if (scope.removeStartPlaybookRun) {
scope.removeStartPlaybookRun();
}
scope.removeStartJob = scope.$on('StartPlaybookRun', function(e, passwords) {
LaunchJob({
scope: scope,
url: launch_url,
callback: 'PlaybookLaunchFinished',
passwords: passwords
});
});
if (scope.removePromptForPasswords) {
scope.removePromptForPasswords();
}
scope.removePromptForPasswords = scope.$on('PromptForPasswords', function(e, passwords) {
PromptForPasswords({ scope: scope, passwords: passwords, callback: 'StartPlaybookRun' });
});
if (scope.removePromptForCredential) { if (scope.removePromptForCredential) {
scope.removePromptForCredential(); scope.removePromptForCredential();
@ -261,13 +243,25 @@ angular.module('JobSubmissionHelper', ['RestServices', 'Utilities', 'CredentialF
PromptForCredential({ scope: scope, template: data }); PromptForCredential({ scope: scope, template: data });
}); });
if (scope.removeCredentialReady) {
scope.removeCredentialReady();
}
scope.removeCredentialReady = scope.$on('CredentialReady', function(e, credential) {
if (!Empty(credential)) {
job_template.credential = credential;
scope.$emit('PostTheJob');
}
});
// Get the job or job_template record // Get the job or job_template record
Wait('start'); Wait('start');
Rest.setUrl(url); Rest.setUrl(url);
Rest.get() Rest.get()
.success(function (data) { .success(function (data) {
delete data.id;
job_template = data;
if (Empty(data.credential)) { if (Empty(data.credential)) {
scope.$emit('PromptForCredential', data); scope.$emit('PromptForCredential');
} else { } else {
// We have what we need, submit the job // We have what we need, submit the job
scope.$emit('PostTheJob'); scope.$emit('PostTheJob');
@ -281,51 +275,21 @@ angular.module('JobSubmissionHelper', ['RestServices', 'Utilities', 'CredentialF
} }
]) ])
.factory('PromptForCredential', ['GetBasePath', 'LookUpInit', 'JobTemplateForm', 'CredentialList', 'Empty',
function(GetBasePath, LookUpInit, JobTemplateForm, CredentialList, Empty) {
return function(params) {
var scope = params.scope,
template = params.template,
launchJob;
scope.credential = '';
launchJob = function () {
if (!Empty(scope.credential)) {
template.credential = scope.credential;
scope.$emit('PostTheJob', template);
}
};
LookUpInit({
url: GetBasePath('credentials') + '?kind=ssh',
scope: scope,
form: JobTemplateForm,
current_item: null,
list: CredentialList,
field: 'credential',
hdr: 'Credential Required',
instructions: "Launching this job requires a machine credential. Please select your machine credential now or Cancel to quit.",
postAction: launchJob
});
scope.lookUpCredential();
};
}])
// Sumbit SCM Update request // Sumbit SCM Update request
.factory('ProjectUpdate', ['PromptPasswords', '$compile', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', .factory('ProjectUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert',
'ProjectsForm', 'Wait', 'ProjectsForm', 'Wait',
function (PromptPasswords, $compile, Rest, $location, GetBasePath, ProcessErrors, Alert, ProjectsForm, Wait) { function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, ProjectsForm, Wait) {
return function (params) { return function (params) {
var scope = params.scope, var scope = params.scope,
project_id = params.project_id, project_id = params.project_id,
url = GetBasePath('projects') + project_id + '/update/'; url = GetBasePath('projects') + project_id + '/update/',
project;
if (scope.removeUpdateSubmitted) { if (scope.removeUpdateSubmitted) {
scope.removeUpdateSubmitted(); scope.removeUpdateSubmitted();
} }
scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function () { scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function() {
// Refresh the project list after update request submitted // Refresh the project list after update request submitted
Wait('stop'); Wait('stop');
Alert('Update Started', 'The request to start the SCM update process was submitted. ' + Alert('Update Started', 'The request to start the SCM update process was submitted. ' +
@ -333,18 +297,18 @@ function(GetBasePath, LookUpInit, JobTemplateForm, CredentialList, Empty) {
scope.refresh(); scope.refresh();
}); });
if (scope.removeSCMSubmit) { if (scope.removePromptForPasswords) {
scope.removeSCMSubmit(); scope.removePromptForPasswords();
} }
scope.removeSCMSubmit = scope.$on('SCMSubmit', function (e, passwords_needed_to_update, extra_html) { scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() {
// After the call to update, kick off the job. PromptForPasswords({ scope: scope, passwords: project.passwords_needed_to_update, callback: 'StartTheUpdate' });
PromptPasswords({ });
scope: scope,
passwords: passwords_needed_to_update, if (scope.removeStartTheUpdate) {
start_url: url, scope.removeStartTheUpdate();
form: ProjectsForm, }
extra_html: extra_html scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) {
}); LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' });
}); });
// Check to see if we have permission to perform the update and if any passwords are needed // Check to see if we have permission to perform the update and if any passwords are needed
@ -352,49 +316,24 @@ function(GetBasePath, LookUpInit, JobTemplateForm, CredentialList, Empty) {
Rest.setUrl(url); Rest.setUrl(url);
Rest.get() Rest.get()
.success(function (data) { .success(function (data) {
var i, extra_html; project = data;
Wait('stop'); Wait('stop');
if (data.can_update) { if (project.can_update) {
extra_html = ''; if (project.passwords_needed_to_updated) {
for (i = 0; i < scope.projects.length; i++) { scope.$emit('PromptForPasswords');
if (scope.projects[i].id === project_id) {
extra_html += "<div class=\"form-group\">\n";
extra_html += "<label class=\"control-label col-lg-3 normal-weight\" for=\"scm_url\">SCM URL</label>\n";
extra_html += "<div class=\"col-lg-9\">\n";
extra_html += "<input type=\"text\" readonly";
extra_html += ' name=\"scm_url\" ';
extra_html += "class=\"form-control\" ";
extra_html += "value=\"" + scope.projects[i].scm_url + "\" ";
extra_html += "/>";
extra_html += "</div>\n";
extra_html += "</div>\n";
if (scope.projects[i].scm_username) {
extra_html += "<div class=\"form-group\">\n";
extra_html += "<label class=\"control-label col-lg-3 normal-weight\" for=\"scm_username\">SCM Username</label>\n";
extra_html += "<div class=\"col-lg-9\">\n";
extra_html += "<input type=\"text\" readonly";
extra_html += ' name=\"scm_username\" ';
extra_html += "class=\"form-control\" ";
extra_html += "value=\"" + scope.projects[i].scm_username + "\" ";
extra_html += "/>";
extra_html += "</div>\n";
extra_html += "</div>\n";
}
break;
}
} }
extra_html += "</p>"; else {
scope.$emit('SCMSubmit', data.passwords_needed_to_update, extra_html); scope.$emit('StartTheUpdate', {});
} else { }
}
else {
Alert('Permission Denied', 'You do not have access to update this project. Please contact your system administrator.', Alert('Permission Denied', 'You do not have access to update this project. Please contact your system administrator.',
'alert-danger'); 'alert-danger');
} }
}) })
.error(function (data, status) { .error(function (data, status) {
ProcessErrors(scope, data, status, null, { ProcessErrors(scope, data, status, null, { hdr: 'Error!',
hdr: 'Error!', msg: 'Failed to lookup project ' + url + ' GET returned: ' + status });
msg: 'Failed to get project update details: ' + url + ' GET status: ' + status
});
}); });
}; };
} }
@ -402,15 +341,15 @@ function(GetBasePath, LookUpInit, JobTemplateForm, CredentialList, Empty) {
// Submit Inventory Update request // Submit Inventory Update request
.factory('InventoryUpdate', ['PromptPasswords', '$compile', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', .factory('InventoryUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait',
'GroupForm', 'BuildTree', 'Wait', function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, Wait) {
function (PromptPasswords, $compile, Rest, $location, GetBasePath, ProcessErrors, Alert, GroupForm, BuildTree, Wait) {
return function (params) { return function (params) {
var scope = params.scope, var scope = params.scope,
url = params.url, url = params.url,
group_id = params.group_id, group_id = params.group_id,
tree_id = params.tree_id; tree_id = params.tree_id,
inventory_source;
if (scope.removeHostReloadComplete) { if (scope.removeHostReloadComplete) {
scope.removeHostReloadComplete(); scope.removeHostReloadComplete();
@ -427,33 +366,31 @@ function(GetBasePath, LookUpInit, JobTemplateForm, CredentialList, Empty) {
if (scope.removeUpdateSubmitted) { if (scope.removeUpdateSubmitted) {
scope.removeUpdateSubmitted(); scope.removeUpdateSubmitted();
} }
scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function (e, action) { scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function () {
setTimeout(function() { setTimeout(function() {
if (action === 'started') { if (scope.refreshGroups) {
if (scope.refreshGroups) { scope.selected_tree_id = tree_id;
scope.selected_tree_id = tree_id; scope.selected_group_id = group_id;
scope.selected_group_id = group_id; scope.refreshGroups();
scope.refreshGroups(); } else if (scope.refresh) {
} else if (scope.refresh) { scope.refresh();
scope.refresh();
}
scope.$emit('HostReloadComplete');
} }
scope.$emit('HostReloadComplete');
}, 2000); }, 2000);
}); });
if (scope.removeInventorySubmit) { if (scope.removePromptForPasswords) {
scope.removeInventorySubmit(); scope.removePromptForPasswords();
} }
scope.removeInventorySubmit = scope.$on('InventorySubmit', function (e, passwords_needed_to_update, extra_html) { scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() {
// After the call to update, kick off the job. PromptForPasswords({ scope: scope, passwords: inventory_source.passwords_needed_to_update, callback: 'StartTheUpdate' });
PromptPasswords({ });
scope: scope,
passwords: passwords_needed_to_update, if (scope.removeStartTheUpdate) {
start_url: url, scope.removeStartTheUpdate();
form: GroupForm, }
extra_html: extra_html scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) {
}); LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' });
}); });
// Check to see if we have permission to perform the update and if any passwords are needed // Check to see if we have permission to perform the update and if any passwords are needed
@ -461,20 +398,23 @@ function(GetBasePath, LookUpInit, JobTemplateForm, CredentialList, Empty) {
Rest.setUrl(url); Rest.setUrl(url);
Rest.get() Rest.get()
.success(function (data) { .success(function (data) {
inventory_source = data;
if (data.can_update) { if (data.can_update) {
//var extra_html = "<div class=\"inventory-passwd-msg\">Starting inventory update for <em>" + group_name + if (data.passwords_needed_to_update) {
// "</em>. Please provide the " + group_source + " credentials:</div>\n"; scope.$emit('PromptForPasswords');
scope.$emit('InventorySubmit', data.passwords_needed_to_update); }
else {
scope.$emit('StartTheUpdate', {});
}
} else { } else {
Wait('stop'); Wait('stop');
Alert('Permission Denied', 'You do not have access to run the update. Please contact your system administrator.', Alert('Permission Denied', 'You do not have access to run the inventory sync. Please contact your system administrator.',
'alert-danger'); 'alert-danger');
} }
}) })
.error(function (data, status) { .error(function (data, status) {
Wait('stop');
ProcessErrors(scope, data, status, null, { hdr: 'Error!', ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to get inventory_source details. ' + url + 'GET status: ' + status }); msg: 'Failed to get inventory source ' + url + ' GET returned: ' + status });
}); });
}; };
} }

View File

@ -92,6 +92,7 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job
width: x, width: x,
height: y, height: y,
autoOpen: false, autoOpen: false,
closeOnEscape: false,
create: function () { create: function () {
// fix the close button // fix the close button
$('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-titlebar button') $('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-titlebar button')
@ -107,8 +108,11 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job
resizeStop: function () { resizeStop: function () {
// for some reason, after resizing dialog the form and fields (the content) doesn't expand to 100% // for some reason, after resizing dialog the form and fields (the content) doesn't expand to 100%
var dialog = $('.ui-dialog[aria-describedby="status-modal-dialog"]'), var dialog = $('.ui-dialog[aria-describedby="status-modal-dialog"]'),
content = dialog.find('#status-modal-dialog'); titleHeight = dialog.find('.ui-dialog-titlebar').outerHeight(),
buttonHeight = dialog.find('.ui-dialog-buttonpane').outerHeight(),
content = dialog.find('#status-modal-dialog');
content.width(dialog.width() - 28); content.width(dialog.width() - 28);
content.css({ height: (dialog.height() - titleHeight - buttonHeight - 10) });
}, },
close: function () { close: function () {
// Destroy on close // Destroy on close
@ -350,17 +354,16 @@ function(Find, Wait, Rest, InventoryUpdate, ProcessErrors, GetBasePath) {
}) })
.error(function (data, status) { .error(function (data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' +
url + ' POST returned status: ' + status }); url + ' GET returned: ' + status });
}); });
}; };
}]) }])
.factory('RelaunchPlaybook', ['SubmitJob', function(SubmitJob) { .factory('RelaunchPlaybook', ['PlaybookRun', function(PlaybookRun) {
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
id = params.id, id = params.id;
name = params.name; PlaybookRun({ scope: scope, id: id });
SubmitJob({ scope: scope, id: id, template: name });
}; };
}]) }])

View File

@ -5,13 +5,15 @@
* Build a lookup dialog * Build a lookup dialog
* *
* LookUpInit( { * LookUpInit( {
* scope: <form scope>, * scope: <caller's scope>,
* form: <form object>, * form: <form object>,
* current_item: <id of item to select on open>, * current_item: <id of item to select on open>,
* list: <list object>, * list: <list object>,
* field: <name of the form field with which the lookup is associated>, * field: <name of the form field with which the lookup is associated>,
* hdr: <optional. modal dialog header> * hdr: <optional. modal dialog header>
* }) * postAction: optional function to run after selection made,
* callback: optional label to $emit() on parent scope
* })
*/ */
'use strict'; 'use strict';
@ -28,6 +30,7 @@ angular.module('LookUpHelper', ['RestServices', 'Utilities', 'SearchHelper', 'Pa
field = params.field, field = params.field,
instructions = params.instructions, instructions = params.instructions,
postAction = params.postAction, postAction = params.postAction,
callback = params.callback,
defaultUrl, name, watchUrl; defaultUrl, name, watchUrl;
if (params.url) { if (params.url) {
@ -194,10 +197,14 @@ angular.module('LookUpHelper', ['RestServices', 'Utilities', 'SearchHelper', 'Pa
Alert('Missing Selection', 'Oops, you failed to make a selection. Click on a row to make your selection, ' + Alert('Missing Selection', 'Oops, you failed to make a selection. Click on a row to make your selection, ' +
'and then click the Select button. Or, click Cancel to quit.'); 'and then click the Select button. Or, click Cancel to quit.');
} else { } else {
// Selection made
$('#lookup-modal-dialog').dialog('close'); $('#lookup-modal-dialog').dialog('close');
if (postAction) { if (postAction) {
postAction(); postAction();
} }
if (callback) {
parent_scope.$emit(callback);
}
} }
}; };

View File

@ -252,8 +252,8 @@
</div> </div>
<div class="modal-body" id="password-body"></div> <div class="modal-body" id="password-body"></div>
<div class="modal-footer"> <div class="modal-footer">
<a href="" ng-click="cancelJob()" class="btn btn-default" id="password_cancel_btn">Cancel</a> <a href="" ng-click="passwordCancel()" class="btn btn-default" id="password_cancel_btn">Cancel</a>
<a href="" ng-click="startJob()" class="btn btn-primary" id="password_continue_btn" ng-disabled="password_form.$pristine || password_form.$invalid">Continue</a> <a href="" ng-click="passwordAccept()" class="btn btn-primary" id="password_continue_btn" ng-disabled="password_form.$pristine || password_form.$invalid">Continue</a>
</div> </div>
</div><!-- modal-content --> </div><!-- modal-content -->
</div><!-- modal-dialog --> </div><!-- modal-dialog -->