mirror of
https://github.com/ansible/awx.git
synced 2026-01-15 11:50:42 -03:30
Job launch ui overhaul
This commit is contained in:
parent
9ba98d9cac
commit
d1e6a3323d
@ -439,11 +439,12 @@ input[type='radio']:checked:before {
|
||||
background-color: @default-bg;
|
||||
color: @default-interface-txt;
|
||||
border-color: @default-border;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.Form-saveButton{
|
||||
background-color: @submit-button-bg;
|
||||
margin-right: 20px;
|
||||
margin-left: 20px;
|
||||
color: @submit-button-text;
|
||||
text-transform: uppercase;
|
||||
transition: background-color 0.2s;
|
||||
@ -469,6 +470,7 @@ input[type='radio']:checked:before {
|
||||
transition: background-color 0.2s;
|
||||
padding-left:15px;
|
||||
padding-right: 15px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.Form-cancelButton:hover{
|
||||
@ -493,6 +495,7 @@ input[type='radio']:checked:before {
|
||||
.Form-formGroup--singleColumn {
|
||||
width: 100% !important;
|
||||
padding-right: 0px;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.Form-subCheckbox {
|
||||
|
||||
@ -161,6 +161,8 @@ function adhocController($q, $scope, $rootScope, $location, $stateParams,
|
||||
privateFn.initializeFields(urls.machineCredentialUrl, urls.adhocUrl);
|
||||
};
|
||||
|
||||
var urls = privateFn.setAvailableUrls();
|
||||
|
||||
privateFn.initializeForm(id, urls, hostPattern);
|
||||
|
||||
$scope.formCancel = function(){
|
||||
|
||||
@ -33,6 +33,7 @@ import organizations from './organizations/main';
|
||||
import permissions from './permissions/main';
|
||||
import managementJobs from './management-jobs/main';
|
||||
import jobDetail from './job-detail/main';
|
||||
import jobSubmission from './job-submission/main';
|
||||
import notifications from './notifications/main';
|
||||
import access from './access/main';
|
||||
|
||||
@ -103,6 +104,7 @@ var tower = angular.module('Tower', [
|
||||
activityStream.name,
|
||||
footer.name,
|
||||
jobDetail.name,
|
||||
jobSubmission.name,
|
||||
notifications.name,
|
||||
standardOut.name,
|
||||
access.name,
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
/* jshint unused: vars */
|
||||
export default
|
||||
[ "PlaybookRun",
|
||||
[ 'InitiatePlaybookRun',
|
||||
'templateUrl',
|
||||
'$location',
|
||||
function JobTemplatesList(PlaybookRun, templateUrl, $location) {
|
||||
function JobTemplatesList(InitiatePlaybookRun, templateUrl, $location) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: link,
|
||||
@ -43,7 +43,7 @@ export default
|
||||
};
|
||||
|
||||
scope.launchJobTemplate = function(jobTemplateId){
|
||||
PlaybookRun({ scope: scope, id: jobTemplateId });
|
||||
InitiatePlaybookRun({ scope: scope, id: jobTemplateId });
|
||||
};
|
||||
|
||||
scope.editJobTemplate = function (jobTemplateId) {
|
||||
|
||||
@ -55,12 +55,9 @@ export default
|
||||
// Submit request to run an adhoc comamand
|
||||
.factory('AdhocRun', ['$location','$stateParams', 'LaunchJob',
|
||||
'PromptForPasswords', 'Rest', 'GetBasePath', 'Alert', 'ProcessErrors',
|
||||
'Wait', 'Empty', 'PromptForCredential', 'PromptForVars',
|
||||
'PromptForSurvey' , 'CreateLaunchDialog',
|
||||
'Wait', 'Empty', 'CreateLaunchDialog',
|
||||
function ($location, $stateParams, LaunchJob, PromptForPasswords,
|
||||
Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty,
|
||||
PromptForCredential, PromptForVars, PromptForSurvey,
|
||||
CreateLaunchDialog) {
|
||||
Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty, CreateLaunchDialog) {
|
||||
return function (params) {
|
||||
var id = params.project_id,
|
||||
scope = params.scope.$new(),
|
||||
|
||||
@ -1,306 +1,17 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2015 Ansible, Inc.
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name helpers.function:JobSubmission
|
||||
* @description
|
||||
* The JobSubmission.js file handles launching a job via a playbook run. There is a workflow that is involved in gathering all the
|
||||
* variables needed to launch a job, including credentials, passwords, extra variables, and survey data. Depending on what information
|
||||
* is needed to launch the job, a modal is built that prompts the user for any required information. This modal is built by creating
|
||||
* an html string with all the fields necessary to launch the job. This html string then gets compiled and opened in a dialog modal.
|
||||
*
|
||||
* #Workflow when user hits launch button
|
||||
*
|
||||
* A 'get' call is made to the API's 'job_templates/:job_template_id/launch' endpoint for that job template. The response from the API will specify
|
||||
*
|
||||
*```
|
||||
* "credential_needed_to_start": true,
|
||||
* "can_start_without_user_input": false,
|
||||
* "ask_variables_on_launch": false,
|
||||
* "passwords_needed_to_start": [],
|
||||
* "variables_needed_to_start": [],
|
||||
* "survey_enabled": false
|
||||
*```
|
||||
* #Step 1a - Check if there is a credential included in the job template: PromptForCredential
|
||||
*
|
||||
* The first step is to check if a credential was specified in the job template, by looking at the value of `credential_needed_to_start` .
|
||||
* If this boolean is true, then that means that the user did NOT specify a credential in the job template and we must prompt them to select a credential.
|
||||
* This emits a call to `PromptForCredential` which will do a lookup on the credentials endpoint and show a modal window with the list
|
||||
* of credentials for the user to choose from.
|
||||
*
|
||||
* #Step 1b - Check if the credential requires a password: CheckPasswords
|
||||
*
|
||||
* The second part of this process is to check if the credential the user picks requires a prompt for a password. A call is made (in the `CheckPasswords` factory)
|
||||
* to the chosen credential
|
||||
* and checks if ``password: ASK`` , ``become_password:ASK`` , or ``vault_password: ASK``. If any of these are ASK, then we begin building the html string for
|
||||
* each required password (see step 2). If none of these require a password, then we contine on to prompting for vars (see step 3)
|
||||
*
|
||||
* #Step 2 - Build password html string: PromptForPasswords
|
||||
*
|
||||
* We may detect from the inital 'get' call that we may need to prompt the user for passwords. The ``passwords_needed_to_start`` array from the 'get' call
|
||||
* will explictly tell us which passwords we must prompt for. Alternatively, we may have found that in steps 1a and 1b that
|
||||
* we have must prompt for passwords. Either way, we arrive in `PromptForPasswords` factory which builds the html string depending on how the particular credential is setup.
|
||||
*
|
||||
* #Step 3 - extra vars text editor: PromptForVars
|
||||
*
|
||||
* We may arrive at step three if the credential selected does not require a password, or if the password html string is already done being built.
|
||||
* if ``ask_variables_on_launch`` was true in the inital 'get' call, then we build the extra_vars text editor in the `PromptForVars` factory.
|
||||
* This factory makes a REST call to the job template and finds if any 'extra_vars' were specified in the job template. It takes any specified
|
||||
* extra vars and includes them in the extra_vars text editor that is built in the same factory. This code is added to the html string and passed along
|
||||
* to the next step.
|
||||
*
|
||||
* #Step 4 - Survey Taker: PromptForSurvey
|
||||
*
|
||||
* The last step in building the job submission modal is building the survey taker. If ``survey_enabled`` is true from the initial 'get' call,
|
||||
* we make a REST call to the survey endpoint for the specified job and gather the survey data. The `PromptForSurvey` factory takes the survey
|
||||
* data and adds to the html string any various survey question.
|
||||
*
|
||||
* #Step 5 - build the modal: CreateLaunchDialog
|
||||
*
|
||||
* At this point, we need to compile our giant html string onto the modal and open the job submission modal. This happens in the `CreateLaunch`
|
||||
* factory. In this factory the 'Launch' button for the job is tied to the validity of the form, which handles the validation of these fields.
|
||||
*
|
||||
* #Step 6 - Launch the job: LaunchJob
|
||||
*
|
||||
* This is maybe the most crucial step. We have setup everything we need in order to gather information from the user and now we want to be sure
|
||||
* we handle it correctly. And there are many scenarios to take into account. The first scenario we check for is is ``survey_enabled=true`` and
|
||||
* ``prompt_for_vars=false``, in which case we want to make sure to include the extra_vars from the job template in the data being
|
||||
* sent to the API (it is important to note that anything specified in the extra vars on job submission will override vars specified in the job template.
|
||||
* Likewise, any variables specified in the extra vars that are duplicated by the survey vars, will get overridden by the survey vars).
|
||||
* If the previous scenario is NOT the case, then we continue to gather the modal's answers regularly: gather the passwords, then the extra_vars, then
|
||||
* any survey results. Also note that we must gather any required survey answers, as well as any optional survey answers that happened to be provided
|
||||
* by the user. We also include the credential that was chosen if the user was prompted to select a credential.
|
||||
* At this point we have all the info we need and we are almost ready to perform a POST to the '/launch' endpoint. We must lastly check
|
||||
* if the user was not prompted for anything and therefore we don't want to pass any extra_vars to the POST. Once this is done we
|
||||
* make the REST POST call and provide all the data to hte API. The response from the API will be the job ID, which is used to redirect the user
|
||||
* to the job detail page for that job run.
|
||||
*
|
||||
* @Usage
|
||||
* This is usage information.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export default
|
||||
angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'CredentialFormDefinition', 'CredentialsListDefinition',
|
||||
'LookUpHelper', 'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog', 'FormGenerator', 'JobVarsPromptFormDefinition'])
|
||||
|
||||
.factory('LaunchJob', ['Rest', 'Wait', 'ProcessErrors', 'ToJSON', 'Empty', 'GetBasePath',
|
||||
function(Rest, Wait, ProcessErrors, ToJSON, Empty, GetBasePath) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
callback = params.callback || 'JobLaunched',
|
||||
job_launch_data = {},
|
||||
url = params.url,
|
||||
vars_url = GetBasePath('job_templates')+scope.job_template_id + '/',
|
||||
// fld,
|
||||
extra_vars;
|
||||
|
||||
//found it easier to assume that there will be extra vars, and then check for a blank object at the end
|
||||
job_launch_data.extra_vars = {};
|
||||
|
||||
//gather the extra vars from the job template if survey is enabled and prompt for vars is false
|
||||
if (scope.removeGetExtraVars) {
|
||||
scope.removeGetExtraVars();
|
||||
}
|
||||
scope.removeGetExtraVars = scope.$on('GetExtraVars', function() {
|
||||
|
||||
Rest.setUrl(vars_url);
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
if(!Empty(data.extra_vars)){
|
||||
data.extra_vars = ToJSON('yaml', data.extra_vars, false);
|
||||
$.each(data.extra_vars, function(key,value){
|
||||
job_launch_data.extra_vars[key] = value;
|
||||
});
|
||||
}
|
||||
scope.$emit('BuildData');
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, { hdr: 'Error!',
|
||||
msg: 'Failed to retrieve job template extra variables.' });
|
||||
});
|
||||
});
|
||||
|
||||
//build the data object to be sent to the job launch endpoint. Any variables gathered from the survey and the extra variables text editor are inserted into the extra_vars dict of the job_launch_data
|
||||
if (scope.removeBuildData) {
|
||||
scope.removeBuildData();
|
||||
}
|
||||
scope.removeBuildData = scope.$on('BuildData', function() {
|
||||
if(!Empty(scope.passwords_needed_to_start) && scope.passwords_needed_to_start.length>0){
|
||||
scope.passwords.forEach(function(password) {
|
||||
job_launch_data[password] = scope[password];
|
||||
scope.passwords_needed_to_start.push(password+'_confirm'); // i'm pushing these values into this array for use during the survey taker parsing
|
||||
});
|
||||
}
|
||||
if(scope.prompt_for_vars===true){
|
||||
extra_vars = ToJSON(scope.parseType, scope.extra_vars, false);
|
||||
if(!Empty(extra_vars)){
|
||||
$.each(extra_vars, function(key,value){
|
||||
job_launch_data.extra_vars[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(scope.survey_enabled===true){
|
||||
for (var i=0; i < scope.survey_questions.length; i++){
|
||||
var fld = scope.survey_questions[i].variable;
|
||||
// grab all survey questions that have answers
|
||||
if(scope.survey_questions[i].required || (scope.survey_questions[i].required === false && scope[fld].toString()!=="")) {
|
||||
job_launch_data.extra_vars[fld] = scope[fld];
|
||||
}
|
||||
|
||||
|
||||
if(scope.survey_questions[i].required === false && _.isEmpty(scope[fld])) {
|
||||
switch (scope.survey_questions[i].type) {
|
||||
// for optional text and text-areas, submit a blank string if min length is 0
|
||||
// -- this is confusing, for an explanation see:
|
||||
// http://docs.ansible.com/ansible-tower/latest/html/userguide/job_templates.html#optional-survey-questions
|
||||
//
|
||||
case "text":
|
||||
case "textarea":
|
||||
if (scope.survey_questions[i].min === 0) {
|
||||
job_launch_data.extra_vars[fld] = "";
|
||||
}
|
||||
break;
|
||||
|
||||
// for optional select lists, if they are left blank make sure we submit
|
||||
// a value that the API will consider "empty"
|
||||
//
|
||||
case "multiplechoice":
|
||||
job_launch_data.extra_vars[fld] = "";
|
||||
break;
|
||||
case "multiselect":
|
||||
job_launch_data.extra_vars[fld] = [];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// include the credential used if the user was prompted to choose a cred
|
||||
if(!Empty(scope.credential)){
|
||||
job_launch_data.credential_id = scope.credential;
|
||||
}
|
||||
|
||||
// If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything.
|
||||
if(jQuery.isEmptyObject(job_launch_data.extra_vars)===true && scope.prompt_for_vars===false){
|
||||
delete job_launch_data.extra_vars;
|
||||
}
|
||||
|
||||
Rest.setUrl(url);
|
||||
Rest.post(job_launch_data)
|
||||
.success(function(data) {
|
||||
Wait('stop');
|
||||
if(!$('#password-modal').is(':hidden')){
|
||||
$('#password-modal').dialog('close');
|
||||
}
|
||||
scope.$emit(callback, data);
|
||||
})
|
||||
.error(function(data, status) {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed updating job ' + scope.job_template_id + ' with variables. POST returned: ' + status });
|
||||
});
|
||||
});
|
||||
|
||||
// if the user has a survey and does not have 'prompt for vars' selected, then we want to
|
||||
// include the extra vars from the job template in the job launch. so first check for these conditions
|
||||
// and then overlay any survey vars over those.
|
||||
if(scope.prompt_for_vars===false && scope.survey_enabled===true){
|
||||
scope.$emit('GetExtraVars');
|
||||
}
|
||||
else {
|
||||
scope.$emit('BuildData');
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('PromptForCredential', ['$location', 'Wait', 'GetBasePath', 'LookUpInit', 'JobTemplateForm', 'CredentialList', 'Rest', 'Prompt', 'ProcessErrors',
|
||||
'CheckPasswords',
|
||||
function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList, Rest, Prompt, ProcessErrors, CheckPasswords) {
|
||||
return function(params) {
|
||||
|
||||
var scope = params.scope,
|
||||
selectionMade;
|
||||
|
||||
Wait('stop');
|
||||
scope.credential = '';
|
||||
|
||||
if (scope.removeShowLookupDialog) {
|
||||
scope.removeShowLookupDialog();
|
||||
}
|
||||
scope.removeShowLookupDialog = scope.$on('ShowLookupDialog', function() {
|
||||
selectionMade = function () {
|
||||
// scope.$emit(callback, scope.credential);
|
||||
CheckPasswords({
|
||||
scope: scope,
|
||||
credential: scope.credential,
|
||||
callback: 'ContinueCred'
|
||||
});
|
||||
};
|
||||
|
||||
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,
|
||||
input_type: 'radio'
|
||||
});
|
||||
scope.lookUpCredential();
|
||||
});
|
||||
|
||||
if (scope.removeAlertNoCredentials) {
|
||||
scope.removeAlertNoCredentials();
|
||||
}
|
||||
scope.removeAlertNoCredentials = scope.$on('AlertNoCredentials', function() {
|
||||
var action = function () {
|
||||
$('#prompt-modal').modal('hide');
|
||||
$location.url('/credentials/add');
|
||||
};
|
||||
|
||||
Prompt({
|
||||
hdr: 'Machine Credential Required',
|
||||
body: "<div class=\"Prompt-bodyQuery\">There are no machine credentials defined in Tower. Launching this job requires a machine credential. " +
|
||||
"Create one now?",
|
||||
action: action
|
||||
});
|
||||
});
|
||||
|
||||
Rest.setUrl(GetBasePath('credentials') + '?kind=ssh');
|
||||
Rest.get()
|
||||
.success(function(data) {
|
||||
if (data.results.length > 0) {
|
||||
scope.$emit('ShowLookupDialog');
|
||||
}
|
||||
else {
|
||||
scope.$emit('AlertNoCredentials');
|
||||
}
|
||||
})
|
||||
.error(function(data,status) {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Checking for machine credentials failed. GET returned: ' + status });
|
||||
});
|
||||
};
|
||||
}])
|
||||
|
||||
|
||||
|
||||
.factory('CreateLaunchDialog', ['$compile', 'Rest', 'GetBasePath', 'TextareaResize', 'CreateDialog', 'GenerateForm',
|
||||
'JobVarsPromptForm', 'Wait', 'ParseTypeChange',
|
||||
function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm,
|
||||
JobVarsPromptForm, Wait, ParseTypeChange) {
|
||||
.factory('CreateLaunchDialog', ['$compile', 'CreateDialog', 'Wait', 'ParseTypeChange',
|
||||
function($compile, CreateDialog, Wait, ParseTypeChange) {
|
||||
return function(params) {
|
||||
var buttons,
|
||||
scope = params.scope,
|
||||
@ -363,43 +74,25 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm,
|
||||
$('#password-accept-button').attr('ng-disabled', 'job_launch_form.$invalid' );
|
||||
e = angular.element(document.getElementById('password-accept-button'));
|
||||
$compile(e)(scope);
|
||||
// if(scope.prompt_for_vars===true){
|
||||
// setTimeout(function() {
|
||||
// TextareaResize({
|
||||
// scope: scope,
|
||||
// textareaId: 'job_variables',
|
||||
// modalId: 'password-modal',
|
||||
// formId: 'job_launch_form',
|
||||
// parse: true
|
||||
// });
|
||||
// }, 300);
|
||||
// }
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
}])
|
||||
|
||||
|
||||
|
||||
|
||||
.factory('PromptForPasswords', ['$compile', 'Wait', 'Alert', 'CredentialForm',
|
||||
function($compile, Wait, Alert, CredentialForm) {
|
||||
.factory('PromptForPasswords', ['CredentialForm',
|
||||
function(CredentialForm) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
callback = params.callback || 'PasswordsAccepted',
|
||||
url = params.url,
|
||||
form = CredentialForm,
|
||||
// acceptedPasswords = {},
|
||||
fld, field,
|
||||
html=params.html || "";
|
||||
|
||||
scope.passwords = params.passwords;
|
||||
// Wait('stop');
|
||||
|
||||
|
||||
html += "<div class=\"alert alert-info\">Launching this job requires the passwords listed below. Enter and confirm each password before continuing.</div>\n";
|
||||
// html += "<form name=\"password_form\" novalidate>\n";
|
||||
|
||||
scope.passwords.forEach(function(password) {
|
||||
// Prompt for password
|
||||
field = form.fields[password];
|
||||
@ -409,7 +102,6 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm,
|
||||
html += "<label for=\"" + fld + "\">" + field.label + "</label>\n";
|
||||
html += "<input type=\"password\" ";
|
||||
html += "ng-model=\"" + fld + '" ';
|
||||
// html += "ng-keydown=\"keydown($event)\" ";
|
||||
html += 'name="' + fld + '" ';
|
||||
html += "class=\"password-field form-control input-sm\" ";
|
||||
html += (field.associated) ? "ng-change=\"clearPWConfirm('" + field.associated + "')\" " : "";
|
||||
@ -430,7 +122,6 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm,
|
||||
html += "<label for=\"" + fld + "\"> " + field.label + "</label>\n";
|
||||
html += "<input type=\"password\" ";
|
||||
html += "ng-model=\"" + fld + '" ';
|
||||
// html += "ng-keydown=\"keydown($event)\" ";
|
||||
html += 'name="' + fld + '" ';
|
||||
html += "class=\"form-control input-sm\" ";
|
||||
html += "ng-change=\"checkStatus()\" ";
|
||||
@ -468,246 +159,8 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm,
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('PromptForVars', ['$compile', 'Rest', 'GetBasePath', 'TextareaResize', 'CreateLaunchDialog', 'GenerateForm', 'JobVarsPromptForm', 'Wait',
|
||||
'ParseVariableString', 'ToJSON', 'ProcessErrors', '$stateParams' ,
|
||||
function($compile, Rest, GetBasePath, TextareaResize,CreateLaunchDialog, GenerateForm, JobVarsPromptForm, Wait,
|
||||
ParseVariableString, ToJSON, ProcessErrors, $stateParams) {
|
||||
return function(params) {
|
||||
var
|
||||
// parent_scope = params.scope,
|
||||
scope = params.scope,
|
||||
callback = params.callback,
|
||||
// job = params.job,
|
||||
url = params.url,
|
||||
vars_url = GetBasePath('job_templates')+scope.job_template_id + '/',
|
||||
html = params.html || "";
|
||||
|
||||
|
||||
function buildHtml(extra_vars){
|
||||
|
||||
html += GenerateForm.buildHTML(JobVarsPromptForm, { mode: 'edit', modal: true, scope: scope });
|
||||
html = html.replace("</form>", "");
|
||||
scope.helpContainer = "<div style=\"display:inline-block; font-size: 12px; margin-top: 6px;\" class=\"help-container pull-right\">\n" +
|
||||
"<a href=\"\" id=\"awp-promote\" href=\"\" aw-pop-over=\"{{ helpText }}\" aw-tool-tip=\"Click for help\" aw-pop-over-watch=\"helpText\" " +
|
||||
"aw-tip-placement=\"top\" data-placement=\"bottom\" data-container=\"body\" data-title=\"Help\" class=\"help-link\"><i class=\"fa fa-question-circle\">" +
|
||||
"</i> click for help</a></div>\n";
|
||||
|
||||
scope.helpText = "<p>After defining any extra variables, click Continue to start the job. Otherwise, click cancel to abort.</p>" +
|
||||
"<p>Extra variables are passed as command line variables to the playbook run. It is equivalent to the -e or --extra-vars " +
|
||||
"command line parameter for ansible-playbook. Provide key/value pairs using either YAML or JSON.</p>" +
|
||||
"JSON:<br />\n" +
|
||||
"<blockquote>{<br /> \"somevar\": \"somevalue\",<br /> \"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||
"YAML:<br />\n" +
|
||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n";
|
||||
|
||||
scope.extra_vars = ParseVariableString(extra_vars);
|
||||
scope.parseType = 'yaml';
|
||||
scope.$emit(callback, html, url);
|
||||
}
|
||||
|
||||
Rest.setUrl(vars_url);
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
buildHtml(data.extra_vars);
|
||||
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, { hdr: 'Error!',
|
||||
msg: 'Failed to retrieve organization: ' + $stateParams.id + '. GET status: ' + status });
|
||||
});
|
||||
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('PromptForSurvey', ['$filter', '$compile', 'Wait', 'Alert', 'CredentialForm', 'CreateLaunchDialog', 'GetBasePath', 'Rest' , 'Empty',
|
||||
'GenerateForm', 'ProcessErrors', '$stateParams' ,
|
||||
function($filter, $compile, Wait, Alert, CredentialForm, CreateLaunchDialog, GetBasePath, Rest, Empty,
|
||||
GenerateForm, ProcessErrors, $stateParams) {
|
||||
return function(params) {
|
||||
var html = params.html || "",
|
||||
id= params.id,
|
||||
url = params.url,
|
||||
callback=params.callback,
|
||||
scope = params.scope,
|
||||
i,
|
||||
requiredAsterisk,
|
||||
requiredClasses,
|
||||
defaultValue,
|
||||
choices,
|
||||
element,
|
||||
minlength, maxlength,
|
||||
min, max,
|
||||
survey_url = GetBasePath('job_templates') + id + '/survey_spec/' ;
|
||||
|
||||
//for toggling the input on password inputs
|
||||
scope.toggleInput = function(id) {
|
||||
var buttonId = id + "_show_input_button",
|
||||
inputId = id,
|
||||
buttonInnerHTML = $(buttonId).html();
|
||||
if (buttonInnerHTML.indexOf("Show") > -1) {
|
||||
$(buttonId).html("Hide");
|
||||
$(inputId).attr("type", "text");
|
||||
} else {
|
||||
$(buttonId).html("Show");
|
||||
$(inputId).attr("type", "password");
|
||||
}
|
||||
};
|
||||
|
||||
function buildHtml(question, index){
|
||||
question.index = index;
|
||||
question.question_name = $filter('sanitize')(question.question_name);
|
||||
question.question_description = (question.question_description) ? $filter('sanitize')(question.question_description) : undefined;
|
||||
|
||||
|
||||
requiredAsterisk = (question.required===true) ? "prepend-asterisk" : "";
|
||||
requiredClasses = (question.required===true) ? "ng-pristine ng-invalid-required ng-invalid" : "";
|
||||
|
||||
html+='<div id="taker_'+question.index+'" class="form-group">';
|
||||
html += '<label for="'+question.variable+'"><span class="label-text '+requiredAsterisk+'"> '+question.question_name+'</span></label>';
|
||||
|
||||
// html += '<label for="'+question.variable+'"> '+ question.question_name+'</label>\n';
|
||||
|
||||
if(!Empty(question.question_description)){
|
||||
html += '<div class="survey_taker_description"><i>'+question.question_description+'</i></div>\n';
|
||||
}
|
||||
|
||||
// if(question.default && question.default.indexOf('<') !== -1){
|
||||
// question.default = (question.default) ? question.default.replace(/</g, "<") : undefined;
|
||||
// }
|
||||
// if (question.default && question.default.indexOf('>') !== -1){
|
||||
// question.default = (question.default) ? question.default.replace(/>/g, ">") : undefined;
|
||||
// }
|
||||
scope[question.variable] = question.default;
|
||||
|
||||
if(question.type === 'text' ){
|
||||
minlength = (!Empty(question.min)) ? Number(question.min) : "";
|
||||
maxlength =(!Empty(question.max)) ? Number(question.max) : "" ;
|
||||
html+='<input type="text" id="'+question.variable+'" ng-model="'+question.variable+'" '+
|
||||
'name=" '+question.variable+' " ' +
|
||||
'ng-minlength="'+minlength+'" ng-maxlength="'+maxlength+'" '+
|
||||
'class="form-control" ng-required='+question.required+'>'+
|
||||
'<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$dirty && ' +
|
||||
'job_launch_form.'+question.variable+'.$error.required\">Please enter an answer.</div>'+
|
||||
'<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$error.minlength || ' +
|
||||
'job_launch_form.'+question.variable+'.$error.maxlength\">Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.</div>'+
|
||||
'<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>';
|
||||
}
|
||||
|
||||
if(question.type === "textarea"){
|
||||
scope[question.variable] = (question.default_textarea) ? question.default_textarea : (question.default) ? question.default : "";
|
||||
minlength = (!Empty(question.min)) ? Number(question.min) : "";
|
||||
maxlength =(!Empty(question.max)) ? Number(question.max) : "" ;
|
||||
html+='<textarea id="'+question.variable+'" name="'+question.variable+'" ng-model="'+question.variable+'" '+
|
||||
'ng-minlength="'+minlength+'" ng-maxlength="'+maxlength+'" '+
|
||||
'class="form-control final" ng-required="'+question.required+'" rows="3"></textarea>'+
|
||||
'<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$dirty && ' +
|
||||
'job_launch_form.'+question.variable+'.$error.required\">Please enter an answer.</div>'+
|
||||
'<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$error.minlength || ' +
|
||||
'job_launch_form.'+question.variable+'.$error.maxlength\">Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.</div>'+
|
||||
'<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>';
|
||||
}
|
||||
if(question.type === 'password' ){
|
||||
minlength = (!Empty(question.min)) ? Number(question.min) : "";
|
||||
maxlength =(!Empty(question.max)) ? Number(question.max) : "" ;
|
||||
html+= '<div class="input-group">'+
|
||||
'<span class="input-group-btn">'+
|
||||
'<button class="btn btn-default show_input_button" id="'+question.variable +'_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="toggleInput("#'+question.variable+'")" data-original-title="" title="">Show</button>'+
|
||||
'</span>'+
|
||||
'<input id="'+question.variable+'" type="password" ng-model="'+question.variable+'" name="'+question.variable+'" '+
|
||||
'ng-required="'+question.required+'"'+
|
||||
'ng-minlength="'+minlength+'" ng-maxlength="'+maxlength+'" '+
|
||||
'class="form-control ng-pristine ng-valid-api-error ng-invalid" autocomplete="false">'+
|
||||
'</div>'+
|
||||
'<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$dirty && ' +
|
||||
'job_launch_form.'+question.variable+'.$error.required\">Please enter an answer.</div>'+
|
||||
'<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$error.minlength || ' +
|
||||
'job_launch_form.'+question.variable+'.$error.maxlength\">Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.</div>'+
|
||||
'<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>';
|
||||
}
|
||||
if(question.type === 'multiplechoice'){
|
||||
choices = question.choices.split(/\n/);
|
||||
element = (question.type==="multiselect") ? "checkbox" : 'radio';
|
||||
|
||||
if (question.default) {
|
||||
scope[question.variable] = question.default;
|
||||
} else {
|
||||
scope[question.variable] = '';
|
||||
}
|
||||
|
||||
html+='<div class="survey_taker_input" > ';
|
||||
html += '<survey-question type="' + question.type + '" index="' + question.index + '" survey-questions="survey_questions" ng-model="' + question.variable + '" ng-required="' + question.required + '"></survey-question>';
|
||||
// html+= '<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$dirty && ' +
|
||||
// 'job_launch_form.'+question.variable+'.$error.required\">Please select an answer.</div>'+
|
||||
// '<div class=\"error api-error\" ng-bind=\"" + fld + "_api_error\"></div>';
|
||||
html+= '</div>'; //end survey_taker_input
|
||||
}
|
||||
|
||||
if(question.type === "multiselect"){
|
||||
//seperate the choices out into an array
|
||||
choices = question.choices.split(/\n/);
|
||||
//ensure that the default answers are in an array
|
||||
if (question.default) {
|
||||
scope[question.variable] = question.default.split(/\n/);
|
||||
} else {
|
||||
scope[question.variable] = '';
|
||||
}
|
||||
//create a new object to be used by the surveyCheckboxes directive
|
||||
html += '<survey-question type="' + question.type + '" index="' + question.index + '" survey-questions="survey_questions" ng-model="' + question.variable + '" ng-required="' + question.required + '"></survey-question>';
|
||||
// html += '<survey-checkboxes name="'+question.variable+'" ng-model=" '+question.variable + '_object " ng-required="'+question.required+'">'+
|
||||
// '</survey-checkboxes>{{job_launch_form.'+question.variable+'_object.$error.checkbox}}'+
|
||||
// '<div class="error survey_error" ng-show="job_launch_form.'+question.variable+'.$error.checkbox">Please select at least one answer.</div>';
|
||||
}
|
||||
|
||||
if(question.type === 'integer'){
|
||||
min = (!Empty(question.min)) ? Number(question.min) : "";
|
||||
max = (!Empty(question.max)) ? Number(question.max) : "" ;
|
||||
html+='<input type="number" id="'+question.variable+'" ng-model="'+question.variable+'" class="form-control" name="'+question.variable+'" ng-required="'+question.required+'" integer aw-min="'+min+'" aw-max="'+max+'" />'+
|
||||
'<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$dirty && job_launch_form.'+question.variable+'.$error.required">Please enter an answer.</div>'+
|
||||
'<div class="error survey_error" ng-show="job_launch_form.'+question.variable+'.$error.number || job_launch_form.'+question.variable+'.$error.integer" >Please enter an answer that is a valid integer.</div>'+
|
||||
'<div class="error survey_error" ng-show="job_launch_form.'+question.variable+'.$error.awMin || job_launch_form.'+question.variable+'.$error.awMax">Please enter an answer between {{'+min+'}} and {{'+max+'}}.</div>';
|
||||
|
||||
}
|
||||
|
||||
if(question.type === "float"){
|
||||
min = (!Empty(question.min)) ? question.min : "";
|
||||
max = (!Empty(question.max)) ? question.max : "" ;
|
||||
defaultValue = (!Empty(question.default)) ? question.default : (!Empty(question.default_float)) ? question.default_float : "" ;
|
||||
html+='<input type="number" id="'+question.variable+'" ng-model="'+question.variable+'" class=" form-control" name="'+question.variable+'" ng-required="'+question.required+'" smart-float aw-min="'+min+'" aw-max="'+max+'"/>'+
|
||||
'<div class="error survey_error" ng-show="job_launch_form.'+ question.variable + '.$dirty && job_launch_form.'+question.variable+'.$error.required">Please enter an answer.</div>'+
|
||||
'<div class="error survey_error" ng-show="job_launch_form.'+question.variable+'.$error.number || job_launch_form.'+question.variable+'.$error.float">Please enter an answer that is a decimal number.</div>'+
|
||||
'<div class="error survey_error" ng-show="job_launch_form.'+question.variable+'.$error.awMin || job_launch_form.'+question.variable+'.$error.awMax">Please enter a decimal number between {{'+min+'}} and {{'+max+'}}.</div>';
|
||||
}
|
||||
html+='</div>';
|
||||
if(question.index === scope.survey_questions.length-1){
|
||||
scope.$emit(callback, html, url);
|
||||
}
|
||||
}
|
||||
|
||||
Rest.setUrl(survey_url);
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
if(!Empty(data)){
|
||||
scope.survey_name = data.name;
|
||||
scope.survey_description = data.description;
|
||||
scope.survey_questions = data.spec;
|
||||
|
||||
for(i=0; i<scope.survey_questions.length; i++){
|
||||
buildHtml(scope.survey_questions[i], i);
|
||||
}
|
||||
}
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, { hdr: 'Error!',
|
||||
msg: 'Failed to retrieve organization: ' + $stateParams.id + '. GET status: ' + status });
|
||||
});
|
||||
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('CheckPasswords', ['$compile', 'Rest', 'GetBasePath', 'TextareaResize', 'CreateLaunchDialog', 'GenerateForm', 'JobVarsPromptForm', 'Wait',
|
||||
'ParseVariableString', 'ToJSON', 'ProcessErrors', '$stateParams', 'Empty',
|
||||
function($compile, Rest, GetBasePath, TextareaResize,CreateLaunchDialog, GenerateForm, JobVarsPromptForm, Wait,
|
||||
ParseVariableString, ToJSON, ProcessErrors, $stateParams, Empty) {
|
||||
.factory('CheckPasswords', ['Rest', 'GetBasePath', 'ProcessErrors', 'Empty',
|
||||
function(Rest, GetBasePath, ProcessErrors, Empty) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
callback = params.callback,
|
||||
@ -743,224 +196,9 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm,
|
||||
};
|
||||
}])
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name helpers.function:JobSubmission#PlaybookRun
|
||||
* @methodOf helpers.function:JobSubmission
|
||||
* @description The playbook Run function is run when the user clicks the launch button
|
||||
*
|
||||
*/
|
||||
// Submit request to run a playbook
|
||||
.factory('PlaybookRun', ['$location', '$state', '$stateParams', 'LaunchJob', 'PromptForPasswords', 'Rest', 'GetBasePath', 'Alert', 'ProcessErrors', 'Wait', 'Empty',
|
||||
'PromptForCredential', 'PromptForVars', 'PromptForSurvey' , 'CreateLaunchDialog',
|
||||
function ($location, $state, $stateParams, LaunchJob, PromptForPasswords, Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty,
|
||||
PromptForCredential, PromptForVars, PromptForSurvey, CreateLaunchDialog) {
|
||||
return function (params) {
|
||||
var //parent_scope = params.scope,
|
||||
scope = params.scope.$new(),
|
||||
id = params.id,
|
||||
system_job = params.system_job || false,
|
||||
base = $location.path().replace(/^\//, '').split('/')[0],
|
||||
url,
|
||||
extra_vars,
|
||||
new_job_id,
|
||||
launch_url,
|
||||
html;
|
||||
scope.job_template_id = id;
|
||||
if (base === 'job_templates' || base === 'portal' || base === 'inventories' || base === 'home') {
|
||||
url = GetBasePath('job_templates') + id + '/launch/';
|
||||
}
|
||||
else {
|
||||
url = GetBasePath('jobs') + id + '/relaunch/';
|
||||
}
|
||||
|
||||
if(!Empty(system_job) && system_job === 'launch'){
|
||||
url = GetBasePath('system_job_templates') + id + '/launch/';
|
||||
}
|
||||
|
||||
if (scope.removeCancelJob) {
|
||||
scope.removeCancelJob();
|
||||
}
|
||||
scope.removeCancelJob = scope.$on('CancelJob', 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(e, data) {
|
||||
var job = data.job || data.system_job;
|
||||
if((scope.portalMode===false || scope.$parent.portalMode===false ) && Empty(data.system_job) ||
|
||||
(base === 'home')){
|
||||
// use $state.go with reload: true option to re-instantiate sockets in
|
||||
$state.go('jobDetail', {id: job}, {reload: true});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (scope.removeStartPlaybookRun) {
|
||||
scope.removeStartPlaybookRun();
|
||||
}
|
||||
scope.removeStartPlaybookRun = scope.$on('StartPlaybookRun', function() {
|
||||
LaunchJob({
|
||||
scope: scope,
|
||||
url: url,
|
||||
callback: 'PlaybookLaunchFinished'
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
if (scope.removePromptForPasswords) {
|
||||
scope.removePromptForPasswords();
|
||||
}
|
||||
scope.removePromptForPasswords = scope.$on('PromptForPasswords', function(e, passwords_needed_to_start,html, url) {
|
||||
PromptForPasswords({ scope: scope,
|
||||
passwords: passwords_needed_to_start,
|
||||
callback: 'PromptForVars',
|
||||
html: html,
|
||||
url: url
|
||||
});
|
||||
});
|
||||
|
||||
if (scope.removePromptForCredential) {
|
||||
scope.removePromptForCredential();
|
||||
}
|
||||
scope.removePromptForCredential = scope.$on('PromptForCredential', function(e, data) {
|
||||
PromptForCredential({ scope: scope, template: data });
|
||||
});
|
||||
|
||||
if (scope.removePromptForVars) {
|
||||
scope.removePromptForVars();
|
||||
}
|
||||
scope.removePromptForVars = scope.$on('PromptForVars', function(e, html, url) {
|
||||
// passwords = pwds;
|
||||
|
||||
if (scope.prompt_for_vars) {
|
||||
// call prompt with callback of StartPlaybookRun, passwords
|
||||
PromptForVars({
|
||||
scope: scope,
|
||||
job: {id:scope.job_template_id},
|
||||
variables: extra_vars,
|
||||
callback: 'PromptForSurvey',
|
||||
html: html,
|
||||
url: url
|
||||
});
|
||||
}
|
||||
else {
|
||||
scope.$emit('PromptForSurvey', html, url);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (scope.removePromptForSurvey) {
|
||||
scope.removePromptForSurvey();
|
||||
}
|
||||
scope.removePromptForSurvey = scope.$on('PromptForSurvey', function(e, html, url) {
|
||||
|
||||
if (scope.survey_enabled) {
|
||||
// call prompt with callback of StartPlaybookRun, passwords
|
||||
PromptForSurvey({
|
||||
scope: scope,
|
||||
id: scope.job_template_id,
|
||||
variables: extra_vars,
|
||||
callback: 'CreateModal',
|
||||
url: url,
|
||||
html: html
|
||||
});
|
||||
}
|
||||
else {
|
||||
scope.$emit('CreateModal', html, url);
|
||||
// CreateLaunchDialog({scope: scope, html: html, url: url});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (scope.removeCreateModal) {
|
||||
scope.removeCreateModal();
|
||||
}
|
||||
scope.removeCreateModal = scope.$on('CreateModal', function(e, html, url) {
|
||||
CreateLaunchDialog({
|
||||
scope: scope,
|
||||
html: html,
|
||||
url: url,
|
||||
callback: 'StartPlaybookRun'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
if (scope.removeContinueCred) {
|
||||
scope.removeContinueCred();
|
||||
}
|
||||
scope.removeContinueCred = scope.$on('ContinueCred', function(e, passwords) {
|
||||
if(passwords.length>0){
|
||||
scope.passwords_needed_to_start = passwords;
|
||||
scope.$emit('PromptForPasswords', passwords, html, url);
|
||||
}
|
||||
else if (scope.ask_variables_on_launch){
|
||||
scope.$emit('PromptForVars', html, url);
|
||||
}
|
||||
else if (!Empty(scope.survey_enabled) && scope.survey_enabled===true) {
|
||||
scope.$emit('PromptForSurvey', html, url);
|
||||
}
|
||||
else {
|
||||
scope.$emit('StartPlaybookRun', url);
|
||||
}
|
||||
});
|
||||
|
||||
// Get the job or job_template record
|
||||
Wait('start');
|
||||
Rest.setUrl(url);
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
new_job_id = data.id;
|
||||
launch_url = url;//data.related.start;
|
||||
scope.passwords_needed_to_start = data.passwords_needed_to_start;
|
||||
scope.prompt_for_vars = data.ask_variables_on_launch;
|
||||
scope.survey_enabled = data.survey_enabled;
|
||||
scope.ask_variables_on_launch = data.ask_variables_on_launch;
|
||||
scope.variables_needed_to_start = data.variables_needed_to_start;
|
||||
html = '<form class="ng-valid ng-valid-required" name="job_launch_form" id="job_launch_form" autocomplete="off" nonvalidate>';
|
||||
|
||||
if(data.credential_needed_to_start === true){
|
||||
scope.$emit('PromptForCredential');
|
||||
}
|
||||
else if (!Empty(data.passwords_needed_to_start) && data.passwords_needed_to_start.length > 0) {
|
||||
scope.$emit('PromptForPasswords', data.passwords_needed_to_start, html, url);
|
||||
}
|
||||
else if (data.ask_variables_on_launch) {
|
||||
scope.$emit('PromptForVars', html, url);
|
||||
}
|
||||
else if (!Empty(data.survey_enabled) && data.survey_enabled===true) {
|
||||
scope.$emit('PromptForSurvey', html, url);
|
||||
}
|
||||
else {
|
||||
scope.$emit('StartPlaybookRun', url);
|
||||
}
|
||||
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to get job template details. GET returned status: ' + status });
|
||||
});
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
// Submit SCM Update request
|
||||
.factory('ProjectUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert',
|
||||
'ProjectsForm', 'Wait',
|
||||
function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, ProjectsForm, Wait) {
|
||||
.factory('ProjectUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait',
|
||||
function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, Wait) {
|
||||
return function (params) {
|
||||
var scope = params.scope,
|
||||
project_id = params.project_id,
|
||||
@ -1028,10 +266,9 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm,
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
// Submit Inventory Update request
|
||||
.factory('InventoryUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait',
|
||||
function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, Wait) {
|
||||
.factory('InventoryUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait',
|
||||
function (PromptForPasswords, LaunchJob, Rest, GetBasePath, ProcessErrors, Alert, Wait) {
|
||||
return function (params) {
|
||||
|
||||
var scope = params.scope,
|
||||
|
||||
@ -561,11 +561,11 @@ export default
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('RelaunchPlaybook', ['PlaybookRun', function(PlaybookRun) {
|
||||
.factory('RelaunchPlaybook', ['InitiatePlaybookRun', function(InitiatePlaybookRun) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
id = params.id;
|
||||
PlaybookRun({ scope: scope, id: id });
|
||||
InitiatePlaybookRun({ scope: scope, id: id });
|
||||
};
|
||||
}])
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ function InventoriesEdit($scope, $rootScope, $compile, $location,
|
||||
ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit,
|
||||
PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON,
|
||||
ParseVariableString, RelatedSearchInit, RelatedPaginateInit,
|
||||
Prompt, PlaybookRun, CreateDialog, deleteJobTemplate, $state) {
|
||||
Prompt, InitiatePlaybookRun, CreateDialog, deleteJobTemplate, $state) {
|
||||
|
||||
ClearScope();
|
||||
|
||||
@ -153,7 +153,7 @@ function InventoriesEdit($scope, $rootScope, $compile, $location,
|
||||
};
|
||||
|
||||
$scope.launchScanJob = function(){
|
||||
PlaybookRun({ scope: $scope, id: this.scan_job_template.id });
|
||||
InitiatePlaybookRun({ scope: $scope, id: this.scan_job_template.id });
|
||||
};
|
||||
|
||||
$scope.scheduleScanJob = function(){
|
||||
@ -327,6 +327,6 @@ export default ['$scope', '$rootScope', '$compile', '$location',
|
||||
'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit',
|
||||
'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString',
|
||||
'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt',
|
||||
'PlaybookRun', 'CreateDialog', 'deleteJobTemplate', '$state',
|
||||
'InitiatePlaybookRun', 'CreateDialog', 'deleteJobTemplate', '$state',
|
||||
InventoriesEdit,
|
||||
];
|
||||
|
||||
@ -14,7 +14,7 @@ export default
|
||||
[ '$location', '$rootScope', '$filter', '$scope', '$compile',
|
||||
'$stateParams', '$log', 'ClearScope', 'GetBasePath', 'Wait',
|
||||
'ProcessErrors', 'SelectPlay', 'SelectTask', 'GetElapsed',
|
||||
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun',
|
||||
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'InitiatePlaybookRun',
|
||||
'LoadPlays', 'LoadTasks', 'HostsEdit',
|
||||
'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels',
|
||||
'EditSchedule', 'ParseTypeChange', 'JobDetailService',
|
||||
@ -24,7 +24,7 @@ export default
|
||||
SelectPlay, SelectTask, GetElapsed,
|
||||
JobIsFinished,
|
||||
SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob,
|
||||
PlaybookRun, LoadPlays, LoadTasks,
|
||||
InitiatePlaybookRun, LoadPlays, LoadTasks,
|
||||
HostsEdit, ParseVariableString, GetChoices, fieldChoices,
|
||||
fieldLabels, EditSchedule, ParseTypeChange, JobDetailService
|
||||
) {
|
||||
@ -879,7 +879,7 @@ export default
|
||||
};
|
||||
|
||||
scope.relaunchJob = function() {
|
||||
PlaybookRun({
|
||||
InitiatePlaybookRun({
|
||||
scope: scope,
|
||||
id: scope.job.id
|
||||
});
|
||||
|
||||
@ -0,0 +1,77 @@
|
||||
export default
|
||||
function GetSurveyQuestions($filter, GetBasePath, Rest, Empty, ProcessErrors, $stateParams) {
|
||||
|
||||
// This factory goes out and gets a job template's survey questions and attaches
|
||||
// them to scope so that they can be ng-repeated in the job submission template
|
||||
|
||||
return function(params) {
|
||||
var id= params.id,
|
||||
scope = params.scope,
|
||||
i,
|
||||
survey_url = GetBasePath('job_templates') + id + '/survey_spec/';
|
||||
|
||||
Rest.setUrl(survey_url);
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
if(!Empty(data)){
|
||||
scope.survey_name = data.name;
|
||||
scope.survey_description = data.description;
|
||||
scope.survey_questions = data.spec;
|
||||
|
||||
for(i=0; i<scope.survey_questions.length; i++){
|
||||
var question = scope.survey_questions[i];
|
||||
question.index = i;
|
||||
question.question_name = $filter('sanitize')(question.question_name);
|
||||
question.question_description = (question.question_description) ? $filter('sanitize')(question.question_description) : undefined;
|
||||
|
||||
if(question.type === "textarea" && !Empty(question.default_textarea)) {
|
||||
question.model = angular.copy(question.default_textarea);
|
||||
}
|
||||
else if(question.type === "multiselect") {
|
||||
question.model = question.default.split(/\n/);
|
||||
question.choices = question.choices.split(/\n/);
|
||||
}
|
||||
else if(question.type === "multiplechoice") {
|
||||
question.model = question.default ? angular.copy(question.default) : "";
|
||||
question.choices = question.choices.split(/\n/);
|
||||
}
|
||||
else if(question.type === "float"){
|
||||
question.model = (!Empty(question.default)) ? angular.copy(question.default) : (!Empty(question.default_float)) ? angular.copy(question.default_float) : "";
|
||||
}
|
||||
else {
|
||||
question.model = question.default ? angular.copy(question.default) : "";
|
||||
}
|
||||
|
||||
if(question.type === "text" || question.type === "textarea" || question.type === "password") {
|
||||
question.minlength = (!Empty(question.min)) ? Number(question.min) : "";
|
||||
question.maxlength = (!Empty(question.max)) ? Number(question.max) : "" ;
|
||||
}
|
||||
else if(question.type === "integer") {
|
||||
question.minValue = (!Empty(question.min)) ? Number(question.min) : "";
|
||||
question.maxValue = (!Empty(question.max)) ? Number(question.max) : "" ;
|
||||
}
|
||||
else if(question.type === "float") {
|
||||
question.minValue = (!Empty(question.min)) ? question.min : "";
|
||||
question.maxValue = (!Empty(question.max)) ? question.max : "" ;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, { hdr: 'Error!',
|
||||
msg: 'Failed to retrieve organization: ' + $stateParams.id + '. GET status: ' + status });
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
GetSurveyQuestions.$inject =
|
||||
[ '$filter',
|
||||
'GetBasePath',
|
||||
'Rest' ,
|
||||
'Empty',
|
||||
'ProcessErrors',
|
||||
'$stateParams'
|
||||
];
|
||||
@ -0,0 +1,25 @@
|
||||
export default
|
||||
function InitiatePlaybookRun($location, GetBasePath, Empty, $compile) {
|
||||
|
||||
// This factory drops the submit-job directive into the dom which
|
||||
// either launches the job (when no user input is needed) or shows
|
||||
// the user a job sumbission modal with varying steps based on what
|
||||
// is being prompted/what passwords are needed.
|
||||
|
||||
return function (params) {
|
||||
var scope = params.scope.$new(),
|
||||
id = params.id,
|
||||
system_job = params.system_job || false;
|
||||
scope.job_template_id = id;
|
||||
|
||||
var el = $compile( "<submit-job data-submit-job-id=" + id + " data-submit-job-system=" + system_job + "></submit-job>" )( scope );
|
||||
$('#content-container').remove('submit-job').append( el );
|
||||
};
|
||||
}
|
||||
|
||||
InitiatePlaybookRun.$inject =
|
||||
[ '$location',
|
||||
'GetBasePath',
|
||||
'Empty',
|
||||
'$compile'
|
||||
];
|
||||
@ -0,0 +1,164 @@
|
||||
export default
|
||||
function LaunchJob(Rest, Wait, ProcessErrors, ToJSON, Empty, GetBasePath, $state, $location) {
|
||||
|
||||
// This factory gathers up all the job launch data and POST's it.
|
||||
|
||||
// TODO: outline how these things are gathered
|
||||
|
||||
return function (params) {
|
||||
var scope = params.scope,
|
||||
job_launch_data = {},
|
||||
url = params.url,
|
||||
vars_url = GetBasePath('job_templates')+scope.job_template_id + '/',
|
||||
base = $location.path().replace(/^\//, '').split('/')[0],
|
||||
extra_vars;
|
||||
|
||||
//found it easier to assume that there will be extra vars, and then check for a blank object at the end
|
||||
job_launch_data.extra_vars = {};
|
||||
|
||||
//build the data object to be sent to the job launch endpoint. Any variables gathered from the survey and the extra variables text editor are inserted into the extra_vars dict of the job_launch_data
|
||||
var buildData = function() {
|
||||
if(scope.ssh_password_required) {
|
||||
job_launch_data.ssh_password = scope.passwords.ssh_password;
|
||||
}
|
||||
if(scope.ssh_key_unlock_required) {
|
||||
job_launch_data.ssh_key_unlock = scope.passwords.ssh_key_unlock;
|
||||
}
|
||||
if(scope.become_password_required) {
|
||||
job_launch_data.become_password = scope.passwords.become_password;
|
||||
}
|
||||
if(scope.vault_password_required) {
|
||||
job_launch_data.vault_password = scope.passwords.vault_password;
|
||||
}
|
||||
|
||||
if(scope.ask_variables_on_launch){
|
||||
extra_vars = ToJSON(scope.parseType, scope.variables, false);
|
||||
if(!Empty(extra_vars)){
|
||||
$.each(extra_vars, function(key,value){
|
||||
job_launch_data.extra_vars[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(scope.ask_tags_on_launch && scope.other_prompt_data && scope.other_prompt_data.job_tags){
|
||||
job_launch_data.job_tags = scope.other_prompt_data.job_tags;
|
||||
}
|
||||
|
||||
if(scope.ask_limit_on_launch && scope.other_prompt_data && scope.other_prompt_data.limit){
|
||||
job_launch_data.limit = scope.other_prompt_data.limit;
|
||||
}
|
||||
|
||||
if(scope.ask_job_type_on_launch && scope.other_prompt_data && scope.other_prompt_data.job_type) {
|
||||
job_launch_data.job_type = scope.other_prompt_data.job_type;
|
||||
}
|
||||
|
||||
if(scope.survey_enabled===true){
|
||||
for (var i=0; i < scope.survey_questions.length; i++){
|
||||
var fld = scope.survey_questions[i].variable;
|
||||
// grab all survey questions that have answers
|
||||
if(scope.survey_questions[i].required || (scope.survey_questions[i].required === false && scope.survey_questions[i].model.toString()!=="")) {
|
||||
job_launch_data.extra_vars[fld] = scope.survey_questions[i].model;
|
||||
}
|
||||
|
||||
if(scope.survey_questions[i].required === false && _.isEmpty(scope.survey_questions[i].model)) {
|
||||
switch (scope.survey_questions[i].type) {
|
||||
// for optional text and text-areas, submit a blank string if min length is 0
|
||||
// -- this is confusing, for an explanation see:
|
||||
// http://docs.ansible.com/ansible-tower/latest/html/userguide/job_templates.html#optional-survey-questions
|
||||
//
|
||||
case "text":
|
||||
case "textarea":
|
||||
if (scope.survey_questions[i].min === 0) {
|
||||
job_launch_data.extra_vars[fld] = "";
|
||||
}
|
||||
break;
|
||||
|
||||
// for optional select lists, if they are left blank make sure we submit
|
||||
// a value that the API will consider "empty"
|
||||
//
|
||||
case "multiplechoice":
|
||||
job_launch_data.extra_vars[fld] = "";
|
||||
break;
|
||||
case "multiselect":
|
||||
job_launch_data.extra_vars[fld] = [];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// include the inventory used if the user was prompted to choose a cred
|
||||
if(scope.ask_inventory_on_launch && !Empty(scope.selected_inventory)){
|
||||
job_launch_data.inventory_id = scope.selected_inventory.id;
|
||||
}
|
||||
|
||||
// include the credential used if the user was prompted to choose a cred
|
||||
if(scope.ask_credential_on_launch && !Empty(scope.selected_credential)){
|
||||
job_launch_data.credential_id = scope.selected_credential.id;
|
||||
}
|
||||
|
||||
// If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything.
|
||||
if(jQuery.isEmptyObject(job_launch_data.extra_vars)===true && scope.prompt_for_vars===false){
|
||||
delete job_launch_data.extra_vars;
|
||||
}
|
||||
|
||||
Rest.setUrl(url);
|
||||
Rest.post(job_launch_data)
|
||||
.success(function(data) {
|
||||
Wait('stop');
|
||||
var job = data.job || data.system_job;
|
||||
if((scope.portalMode===false || scope.$parent.portalMode===false ) && Empty(data.system_job) || (base === 'home')){
|
||||
// use $state.go with reload: true option to re-instantiate sockets in
|
||||
$state.go('jobDetail', {id: job}, {reload: true});
|
||||
}
|
||||
scope.clearDialog();
|
||||
})
|
||||
.error(function(data, status) {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed updating job ' + scope.job_template_id + ' with variables. POST returned: ' + status });
|
||||
});
|
||||
};
|
||||
|
||||
//gather the extra vars from the job template if survey is enabled and prompt for vars is false
|
||||
var getExtraVars = function() {
|
||||
Rest.setUrl(vars_url);
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
if(!Empty(data.extra_vars)){
|
||||
data.extra_vars = ToJSON('yaml', data.extra_vars, false);
|
||||
$.each(data.extra_vars, function(key,value){
|
||||
job_launch_data.extra_vars[key] = value;
|
||||
});
|
||||
}
|
||||
buildData();
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors(scope, data, status, { hdr: 'Error!',
|
||||
msg: 'Failed to retrieve job template extra variables.' });
|
||||
});
|
||||
};
|
||||
|
||||
// if the user has a survey and does not have 'prompt for vars' selected, then we want to
|
||||
// include the extra vars from the job template in the job launch. so first check for these conditions
|
||||
// and then overlay any survey vars over those.
|
||||
if(scope.prompt_for_vars===false && scope.survey_enabled===true){
|
||||
getExtraVars();
|
||||
}
|
||||
else {
|
||||
buildData();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
LaunchJob.$inject =
|
||||
[ 'Rest',
|
||||
'Wait',
|
||||
'ProcessErrors',
|
||||
'ToJSON',
|
||||
'Empty',
|
||||
'GetBasePath',
|
||||
'$state',
|
||||
'$location'
|
||||
];
|
||||
220
awx/ui/client/src/job-submission/job-submission.block.less
Normal file
220
awx/ui/client/src/job-submission/job-submission.block.less
Normal file
@ -0,0 +1,220 @@
|
||||
@import '../shared/branding/colors.less';
|
||||
@import '../shared/branding/colors.default.less';
|
||||
|
||||
.JobSubmission {
|
||||
padding: 20px!important;
|
||||
display: none;
|
||||
height: auto!important;
|
||||
min-height: 400px!important;
|
||||
}
|
||||
.JobSubmission-container {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
height: auto;
|
||||
min-height: 360px;
|
||||
}
|
||||
.JobSubmission-dialog {
|
||||
padding: 0px;
|
||||
margin-bottom: 20px;
|
||||
.ui-dialog-buttonpane, .ui-dialog-titlebar {
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
.JobSubmission-header {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
}
|
||||
.JobSubmission-title {
|
||||
align-items: center;
|
||||
flex: 1 0 auto;
|
||||
display: flex;
|
||||
}
|
||||
.JobSubmission-titleText {
|
||||
color: @list-title-txt;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.JobSubmission-titleLockup {
|
||||
margin-left: 4px;
|
||||
margin-right: 6px;
|
||||
display: inline-block;
|
||||
margin-top: 0px;
|
||||
padding-bottom: 2px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.JobSubmission-titleLockup:before {
|
||||
content: "\007C";
|
||||
color: @default-icon-hov;
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
}
|
||||
.JobSubmission-close {
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
}
|
||||
.JobSubmission-exit{
|
||||
cursor:pointer;
|
||||
padding:0px;
|
||||
border: none;
|
||||
height:20px;
|
||||
font-size: 20px;
|
||||
background-color:@default-bg;
|
||||
color:@d7grey;
|
||||
transition: color 0.2s;
|
||||
line-height:1;
|
||||
}
|
||||
.JobSubmission-exit:hover{
|
||||
color:@default-icon;
|
||||
}
|
||||
.JobSubmission-stepsContainer {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
margin-top: 25px;
|
||||
}
|
||||
.JobSubmission-steps {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
min-height: 30px;
|
||||
}
|
||||
.JobSubmission-step {
|
||||
color: @default-interface-txt;
|
||||
background-color: @default-bg;
|
||||
font-size: 12px;
|
||||
border: 1px solid @default-border;
|
||||
height: 30px;
|
||||
border-radius: 5px;
|
||||
margin-right: 20px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
transition: background-color 0.2s;
|
||||
text-transform: uppercase;
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.JobSubmission-step:hover {
|
||||
color: @btn-txt;
|
||||
background-color: @btn-bg-hov;
|
||||
cursor: pointer;
|
||||
}
|
||||
.JobSubmission-step--active {
|
||||
color: @default-bg!important;
|
||||
background-color: @d7grey!important;
|
||||
border-color: @d7grey!important;
|
||||
cursor: default!important;
|
||||
}
|
||||
.JobSubmission-step--disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed!important;
|
||||
}
|
||||
.JobSubmission-formContainer {
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
.JobSubmission-form {
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
max-width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
.JobSubmission-footerContainer {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.JobSubmission-footerPreview {
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
.JobSubmission-footerButtons {
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
}
|
||||
.JobSubmission-previewItem {
|
||||
min-width: 150px;
|
||||
font-weight: normal;
|
||||
font-size: small;
|
||||
}
|
||||
.JobSubmission-previewItemTitle {
|
||||
color: @default-interface-txt;
|
||||
}
|
||||
.JobSubmission-previewItemNone {
|
||||
color: @default-icon;
|
||||
}
|
||||
.JobSubmission-actionButton {
|
||||
background-color: @submit-button-bg;
|
||||
color: @submit-button-text;
|
||||
height: 30px;
|
||||
padding-left:15px;
|
||||
padding-right: 15px;
|
||||
width: 85px;
|
||||
}
|
||||
.JobSubmission-actionButton:hover,
|
||||
.JobSubmission-actionButton:focus {
|
||||
color: @submit-button-text;
|
||||
background-color: @submit-button-bg-hov;
|
||||
}
|
||||
.JobSubmission-defaultButton{
|
||||
background-color: @default-bg;
|
||||
color: @btn-txt;
|
||||
text-transform: uppercase;
|
||||
border-radius: 5px;
|
||||
border: 1px solid @btn-bord;
|
||||
padding-left:15px;
|
||||
padding-right: 15px;
|
||||
height: 30px;
|
||||
min-width: 85px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.JobSubmission-defaultButton:hover{
|
||||
background-color: @btn-bg-hov;
|
||||
color: @btn-txt;
|
||||
}
|
||||
.JobSubmission-revertButton {
|
||||
background-color: @default-link;
|
||||
color: @default-bg;
|
||||
text-transform: uppercase;
|
||||
padding-left:15px;
|
||||
padding-right: 15px;
|
||||
font-size: 9px;
|
||||
}
|
||||
.JobSubmission-revertButton:hover{
|
||||
background-color: @default-link-hov;
|
||||
color: @default-bg;
|
||||
}
|
||||
.JobSubmission-selectedItem {
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.JobSubmission-selectedItemInfo {
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
.JobSubmission-selectedItemRevert {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.JobSubmission-selectedItemLabel {
|
||||
color: @default-interface-txt;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.JobSubmission-selectedItemNone {
|
||||
color: @default-icon;
|
||||
}
|
||||
.JobSubmission-selectedItemContainer {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.JobSubmission-instructions {
|
||||
color: @default-interface-txt;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.JobSubmission-passwordButton {
|
||||
padding: 7px 15px!important;
|
||||
}
|
||||
518
awx/ui/client/src/job-submission/job-submission.controller.js
Normal file
518
awx/ui/client/src/job-submission/job-submission.controller.js
Normal file
@ -0,0 +1,518 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name controllers.function:JobSubmission
|
||||
* @description This controller's for the Job Submission Modal
|
||||
* The job-submission directive is intended to handle job launch/relaunch from a playbook. There are 4 potential steps involved in launching a job:
|
||||
*
|
||||
* Select an Inventory
|
||||
* Select a Credential
|
||||
* Extra prompts (extra vars, limit, job type, job tags)
|
||||
* Fill in survey
|
||||
*
|
||||
* #Workflow when user hits launch button
|
||||
*
|
||||
* A 'get' call is made to the API's 'job_templates/:job_template_id/launch' endpoint for that job template. The response from the API will specify
|
||||
*
|
||||
*```
|
||||
* "credential_needed_to_start": true,
|
||||
* "can_start_without_user_input": false,
|
||||
* "ask_variables_on_launch": false,
|
||||
* "passwords_needed_to_start": [],
|
||||
* "variables_needed_to_start": [],
|
||||
* "survey_enabled": false
|
||||
*```
|
||||
* #Step 1 - Hit the launch/relaunch endpoint
|
||||
*
|
||||
* The launch/relaunch endpoint(s) let us know what the default values are for a particular job template. It also lets us know what the creator of
|
||||
* the job template selected as "promptable" fields.
|
||||
*
|
||||
* #Step 2 - Gather inv/credential lists and job template survey questions
|
||||
*
|
||||
* If the job template allows for inventory or credential prompting then we need to go out and get the available inventories and credentials for the
|
||||
* launching user. We also go out and get the survey from its endpoint if a survey has been created and is enabled for this job template (getsurveyquestions.factory).
|
||||
*
|
||||
* #Step 3 - Fill out the job launch form
|
||||
*
|
||||
* No server calls needed as a user fills out the form. Note that if no user input is required (no prompts, no passwords) then we skip ahead to the next
|
||||
* step.
|
||||
*
|
||||
* #Step 4 - Launch the job: LaunchJob
|
||||
*
|
||||
* This is maybe the most crucial step. We have setup everything we need in order to gather information from the user and now we want to be sure
|
||||
* we handle it correctly. And there are many scenarios to take into account. The first scenario we check for is is ``survey_enabled=true`` and
|
||||
* ``prompt_for_vars=false``, in which case we want to make sure to include the extra_vars from the job template in the data being
|
||||
* sent to the API (it is important to note that anything specified in the extra vars on job submission will override vars specified in the job template.
|
||||
* Likewise, any variables specified in the extra vars that are duplicated by the survey vars, will get overridden by the survey vars).
|
||||
* If the previous scenario is NOT the case, then we continue to gather the modal's answers regularly: gather the passwords, then the extra_vars, then
|
||||
* any survey results. Also note that we must gather any required survey answers, as well as any optional survey answers that happened to be provided
|
||||
* by the user. We also include the credential that was chosen if the user was prompted to select a credential.
|
||||
* At this point we have all the info we need and we are almost ready to perform a POST to the '/launch' endpoint. We must lastly check
|
||||
* if the user was not prompted for anything and therefore we don't want to pass any extra_vars to the POST. Once this is done we
|
||||
* make the REST POST call and provide all the data to hte API. The response from the API will be the job ID, which is used to redirect the user
|
||||
* to the job detail page for that job run.
|
||||
*
|
||||
* @Usage
|
||||
* This is usage information.
|
||||
*/
|
||||
|
||||
export default
|
||||
[ '$scope', '$location', 'GetBasePath', 'Empty', 'Wait', 'Rest', 'ProcessErrors',
|
||||
'LaunchJob', '$state', 'generateList', 'InventoryList', 'SearchInit', 'PaginateInit', 'CredentialList', 'ParseTypeChange', 'GetSurveyQuestions',
|
||||
function($scope, $location, GetBasePath, Empty, Wait, Rest, ProcessErrors,
|
||||
LaunchJob, $state, GenerateList, InventoryList, SearchInit, PaginateInit, CredentialList, ParseTypeChange, GetSurveyQuestions) {
|
||||
|
||||
var launch_url;
|
||||
|
||||
var clearRequiredPasswords = function() {
|
||||
$scope.ssh_password_required = false;
|
||||
$scope.ssh_key_unlock_required = false;
|
||||
$scope.become_password_required = false;
|
||||
$scope.vault_password_required = false;
|
||||
|
||||
$scope.ssh_password = "";
|
||||
$scope.ssh_key_unlock = "";
|
||||
$scope.become_password = "";
|
||||
$scope.vault_password = "";
|
||||
};
|
||||
|
||||
var updateRequiredPasswords = function() {
|
||||
if($scope.selected_credential) {
|
||||
if($scope.selected_credential.id === $scope.defaults.credential.id) {
|
||||
clearRequiredPasswords();
|
||||
for(var i=0; i<$scope.passwords_needed_to_start.length; i++) {
|
||||
var password = $scope.passwords_needed_to_start[i];
|
||||
switch(password) {
|
||||
case "ssh_password":
|
||||
$scope.ssh_password_required = true;
|
||||
break;
|
||||
case "ssh_key_unlock":
|
||||
$scope.ssh_key_unlock_required = true;
|
||||
break;
|
||||
case "become_password":
|
||||
$scope.become_password_required = true;
|
||||
break;
|
||||
case "vault_password":
|
||||
$scope.vault_password_required = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if($scope.selected_credential.kind === "ssh"){
|
||||
$scope.ssh_password_required = ($scope.selected_credential.password === "ASK") ? true : false;
|
||||
$scope.ssh_key_unlock_required = ($scope.selected_credential.ssh_key_unlock === "ASK") ? true : false;
|
||||
$scope.become_password_required = ($scope.selected_credential.become_password === "ASK") ? true : false;
|
||||
$scope.vault_password_required = ($scope.selected_credential.vault_password === "ASK") ? true : false;
|
||||
}
|
||||
else {
|
||||
clearRequiredPasswords();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var launchJob = function() {
|
||||
LaunchJob({
|
||||
scope: $scope,
|
||||
url: launch_url
|
||||
});
|
||||
};
|
||||
|
||||
// This gets things started - goes out and hits the launch endpoint (based on launch/relaunch) and
|
||||
// prepares the form fields, defauts, etc.
|
||||
$scope.init = function() {
|
||||
|
||||
$scope.forms = {};
|
||||
$scope.passwords = {};
|
||||
|
||||
var base = $location.path().replace(/^\//, '').split('/')[0],
|
||||
isRelaunch = !(base === 'job_templates' || base === 'portal' || base === 'inventories' || base === 'home');
|
||||
|
||||
if (!isRelaunch) {
|
||||
launch_url = GetBasePath('job_templates') + $scope.submitJobId + '/launch/';
|
||||
}
|
||||
else {
|
||||
launch_url = GetBasePath('jobs') + $scope.submitJobId + '/relaunch/';
|
||||
}
|
||||
|
||||
// Get the job or job_template record
|
||||
Wait('start');
|
||||
Rest.setUrl(launch_url);
|
||||
Rest.get()
|
||||
.success(function (data) {
|
||||
|
||||
// Put all the data that we get back about the launch onto scope
|
||||
angular.extend($scope, data);
|
||||
|
||||
// General catch-all for "other prompts" - used in this link function and to hide the Other Prompts tab when
|
||||
// it should be hidden
|
||||
$scope.has_other_prompts = (data.ask_job_type_on_launch || data.ask_limit_on_launch || data.ask_tags_on_launch || data.ask_variables_on_launch) ? true : false;
|
||||
$scope.password_needed = data.passwords_needed_to_start && data.passwords_needed_to_start.length > 0;
|
||||
$scope.has_default_inventory = data.defaults && data.defaults.inventory && data.defaults.inventory.id;
|
||||
$scope.has_default_credential = data.defaults && data.defaults.credential && data.defaults.credential.id;
|
||||
|
||||
$scope.other_prompt_data = {};
|
||||
|
||||
if($scope.ask_job_type_on_launch) {
|
||||
$scope.other_prompt_data.job_type = (data.defaults && data.defaults.job_type) ? data.defaults.job_type : "";
|
||||
}
|
||||
|
||||
if($scope.ask_limit_on_launch) {
|
||||
$scope.other_prompt_data.limit = (data.defaults && data.defaults.limit) ? data.defaults.limit : "";
|
||||
}
|
||||
|
||||
if($scope.ask_tags_on_launch) {
|
||||
$scope.other_prompt_data.job_tags = (data.defaults && data.defaults.job_tags) ? data.defaults.job_tags : "";
|
||||
}
|
||||
|
||||
if($scope.ask_variables_on_launch) {
|
||||
$scope.variables = (data.defaults && data.defaults.extra_vars) ? data.defaults.extra_vars : "---";
|
||||
$scope.parseType = 'yaml';
|
||||
}
|
||||
|
||||
if($scope.has_default_inventory) {
|
||||
$scope.selected_inventory = angular.copy($scope.defaults.inventory);
|
||||
}
|
||||
|
||||
if($scope.has_default_credential) {
|
||||
$scope.selected_credential = angular.copy($scope.defaults.credential);
|
||||
updateRequiredPasswords();
|
||||
}
|
||||
|
||||
if($scope.can_start_without_user_input && !$scope.ask_inventory_on_launch && !$scope.ask_credential_on_launch && !$scope.has_other_prompts) {
|
||||
// The job can be launched without any user input
|
||||
launchJob();
|
||||
Wait('stop');
|
||||
}
|
||||
else {
|
||||
|
||||
var initiateModal = function() {
|
||||
// Figure out which step the user needs to start on
|
||||
if($scope.ask_inventory_on_launch) {
|
||||
$scope.setStep("inventory");
|
||||
}
|
||||
else if($scope.ask_credential_on_launch || $scope.password_needed) {
|
||||
$scope.setStep("credential");
|
||||
}
|
||||
else if($scope.has_other_prompts) {
|
||||
$scope.setStep("otherprompts");
|
||||
}
|
||||
else if($scope.survey_enabled) {
|
||||
$scope.setStep("survey");
|
||||
}
|
||||
|
||||
$scope.openLaunchModal();
|
||||
};
|
||||
|
||||
if(isRelaunch) {
|
||||
// Go out and get some of the job details like inv, cred, name
|
||||
Rest.setUrl(GetBasePath('jobs') + $scope.submitJobId);
|
||||
Rest.get()
|
||||
.success(function (jobDetailData) {
|
||||
$scope.job_template_data = {
|
||||
name: jobDetailData.name
|
||||
};
|
||||
$scope.defaults = {};
|
||||
if(jobDetailData.summary_fields.inventory) {
|
||||
$scope.defaults.inventory = angular.copy(jobDetailData.summary_fields.inventory);
|
||||
$scope.selected_inventory = angular.copy(jobDetailData.summary_fields.inventory);
|
||||
}
|
||||
if(jobDetailData.summary_fields.credential) {
|
||||
$scope.defaults.credential = angular.copy(jobDetailData.summary_fields.credential);
|
||||
$scope.selected_credential = angular.copy(jobDetailData.summary_fields.credential);
|
||||
updateRequiredPasswords();
|
||||
}
|
||||
initiateModal();
|
||||
})
|
||||
.error(function(data, status) {
|
||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to get job details. GET returned status: ' + status });
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Move forward with the modal
|
||||
initiateModal();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
.error(function (data, status) {
|
||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Failed to get job template details. GET returned status: ' + status });
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setStep = function(step) {
|
||||
$scope.step = step;
|
||||
|
||||
if(step === "credential") {
|
||||
$scope.credentialTabEnabled = true;
|
||||
}
|
||||
else if(step === "otherprompts") {
|
||||
$scope.otherPromptsTabEnabled = true;
|
||||
if(!$scope.extra_vars_code_mirror_loaded) {
|
||||
ParseTypeChange({
|
||||
scope: $scope,
|
||||
field_id: 'job_launch_variables'
|
||||
});
|
||||
|
||||
$scope.extra_vars_code_mirror_loaded = true;
|
||||
}
|
||||
}
|
||||
else if(step === "survey") {
|
||||
$scope.surveyTabEnabled = true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$scope.getListsAndSurvey = function() {
|
||||
if($scope.ask_inventory_on_launch) {
|
||||
var inventory_url = GetBasePath('inventory');
|
||||
|
||||
GenerateList.inject(InventoryList, {
|
||||
mode: 'lookup',
|
||||
id: 'job-submission-inventory-lookup',
|
||||
scope: $scope,
|
||||
input_type: 'radio'
|
||||
});
|
||||
|
||||
SearchInit({
|
||||
scope: $scope,
|
||||
set: InventoryList.name,
|
||||
list: InventoryList,
|
||||
url: inventory_url
|
||||
});
|
||||
|
||||
PaginateInit({
|
||||
scope: $scope,
|
||||
list: InventoryList,
|
||||
url: inventory_url,
|
||||
mode: 'lookup'
|
||||
});
|
||||
|
||||
$scope.search(InventoryList.iterator);
|
||||
|
||||
$scope.$watchCollection('inventories', function () {
|
||||
if($scope.selected_inventory) {
|
||||
// Loop across the inventories and see if one of them should be "checked"
|
||||
$scope.inventories.forEach(function(row, i) {
|
||||
if (row.id === $scope.selected_inventory.id) {
|
||||
$scope.inventories[i].checked = 1;
|
||||
}
|
||||
else {
|
||||
$scope.inventories[i].checked = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if($scope.ask_credential_on_launch) {
|
||||
var credential_url = GetBasePath('credentials') + '?kind=ssh';
|
||||
|
||||
GenerateList.inject(CredentialList, {
|
||||
mode: 'lookup',
|
||||
id: 'job-submission-credential-lookup',
|
||||
scope: $scope,
|
||||
input_type: 'radio'
|
||||
});
|
||||
|
||||
SearchInit({
|
||||
scope: $scope,
|
||||
set: CredentialList.name,
|
||||
list: CredentialList,
|
||||
url: credential_url
|
||||
});
|
||||
|
||||
PaginateInit({
|
||||
scope: $scope,
|
||||
list: CredentialList,
|
||||
url: credential_url,
|
||||
mode: 'lookup'
|
||||
});
|
||||
|
||||
$scope.search(CredentialList.iterator);
|
||||
|
||||
$scope.$watchCollection('credentials', function () {
|
||||
if($scope.selected_credential) {
|
||||
// Loop across the inventories and see if one of them should be "checked"
|
||||
$scope.credentials.forEach(function(row, i) {
|
||||
if (row.id === $scope.selected_credential.id) {
|
||||
$scope.credentials[i].checked = 1;
|
||||
}
|
||||
else {
|
||||
$scope.credentials[i].checked = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if($scope.survey_enabled) {
|
||||
GetSurveyQuestions({
|
||||
scope: $scope,
|
||||
id: $scope.submitJobId
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
$scope.revertToDefaultInventory = function() {
|
||||
if($scope.has_default_inventory) {
|
||||
$scope.selected_inventory = angular.copy($scope.defaults.inventory);
|
||||
|
||||
// Loop across inventories and set update the "checked" attribute for each row
|
||||
$scope.inventories.forEach(function(row, i) {
|
||||
if (row.id === $scope.selected_inventory.id) {
|
||||
$scope.inventories[i].checked = 1;
|
||||
} else {
|
||||
$scope.inventories[i].checked = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.revertToDefaultCredential = function() {
|
||||
if($scope.has_default_credential) {
|
||||
$scope.selected_credential = angular.copy($scope.defaults.credential);
|
||||
updateRequiredPasswords();
|
||||
|
||||
// Loop across credentials and set update the "checked" attribute for each row
|
||||
$scope.credentials.forEach(function(row, i) {
|
||||
if (row.id === $scope.selected_credential.id) {
|
||||
$scope.credentials[i].checked = 1;
|
||||
} else {
|
||||
$scope.credentials[i].checked = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggle_inventory = function(id) {
|
||||
$scope.inventories.forEach(function(row, i) {
|
||||
if (row.id === id) {
|
||||
$scope.selected_inventory = angular.copy(row);
|
||||
$scope.inventories[i].checked = 1;
|
||||
} else {
|
||||
$scope.inventories[i].checked = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggle_credential = function(id) {
|
||||
$scope.credentials.forEach(function(row, i) {
|
||||
if (row.id === id) {
|
||||
$scope.selected_credential = angular.copy(row);
|
||||
updateRequiredPasswords();
|
||||
$scope.credentials[i].checked = 1;
|
||||
} else {
|
||||
$scope.credentials[i].checked = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.getActionButtonText = function() {
|
||||
if($scope.step === "inventory") {
|
||||
return ($scope.ask_credential_on_launch || $scope.password_needed || $scope.has_other_prompts || $scope.survey_enabled) ? "NEXT" : "LAUNCH";
|
||||
}
|
||||
else if($scope.step === "credential") {
|
||||
return ($scope.has_other_prompts || $scope.survey_enabled) ? "NEXT" : "LAUNCH";
|
||||
}
|
||||
else if($scope.step === "otherprompts") {
|
||||
return ($scope.survey_enabled) ? "NEXT" : "LAUNCH";
|
||||
}
|
||||
else if($scope.step === "survey") {
|
||||
return "LAUNCH";
|
||||
}
|
||||
};
|
||||
|
||||
$scope.actionButtonDisabled = function() {
|
||||
if($scope.step === "inventory") {
|
||||
if($scope.selected_inventory) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
$scope.credentialTabEnabled = false;
|
||||
$scope.otherPromptsTabEnabled = false;
|
||||
$scope.surveyTabEnabled = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if($scope.step === "credential") {
|
||||
if($scope.selected_credential && $scope.forms.credentialpasswords.$valid) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
$scope.otherPromptsTabEnabled = false;
|
||||
$scope.surveyTabEnabled = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if($scope.step === "otherprompts") {
|
||||
if($scope.forms.otherprompts.$valid) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
$scope.surveyTabEnabled = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if($scope.step === "survey") {
|
||||
if($scope.forms.survey.$valid) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$scope.takeAction = function() {
|
||||
if($scope.step === "inventory") {
|
||||
// Check to see if there's another step after this one
|
||||
if($scope.ask_credential_on_launch || $scope.password_needed) {
|
||||
$scope.setStep("credential");
|
||||
}
|
||||
else if($scope.has_other_prompts) {
|
||||
$scope.setStep("otherprompts");
|
||||
}
|
||||
else if($scope.survey_enabled) {
|
||||
$scope.setStep("survey");
|
||||
}
|
||||
else {
|
||||
launchJob();
|
||||
}
|
||||
}
|
||||
else if($scope.step === "credential") {
|
||||
if($scope.has_other_prompts) {
|
||||
$scope.setStep("otherprompts");
|
||||
}
|
||||
else if($scope.survey_enabled) {
|
||||
$scope.setStep("survey");
|
||||
}
|
||||
else {
|
||||
launchJob();
|
||||
}
|
||||
}
|
||||
else if($scope.step === "otherprompts") {
|
||||
if($scope.survey_enabled) {
|
||||
$scope.setStep("survey");
|
||||
}
|
||||
else {
|
||||
launchJob();
|
||||
}
|
||||
}
|
||||
else {
|
||||
launchJob();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
];
|
||||
85
awx/ui/client/src/job-submission/job-submission.directive.js
Normal file
85
awx/ui/client/src/job-submission/job-submission.directive.js
Normal file
@ -0,0 +1,85 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import jobSubmissionController from './job-submission.controller';
|
||||
|
||||
export default [ 'templateUrl', 'CreateDialog', 'Wait', 'CreateSelect2',
|
||||
function(templateUrl, CreateDialog, Wait, CreateSelect2) {
|
||||
return {
|
||||
scope: {
|
||||
submitJobId: '=',
|
||||
submitJobSystem: '='
|
||||
},
|
||||
templateUrl: templateUrl('job-submission/job-submission'),
|
||||
controller: jobSubmissionController,
|
||||
restrict: 'E',
|
||||
link: function(scope) {
|
||||
|
||||
scope.openLaunchModal = function() {
|
||||
if (scope.removeLaunchJobModalReady) {
|
||||
scope.removeLaunchJobModalReady();
|
||||
}
|
||||
scope.removeLaunchJobModalReady = scope.$on('LaunchJobModalReady', function() {
|
||||
// Go get the list/survey data that we need from the server
|
||||
scope.getListsAndSurvey();
|
||||
|
||||
$('#job-launch-modal').dialog('open');
|
||||
|
||||
// select2-ify the job type dropdown
|
||||
CreateSelect2({
|
||||
element: '#job_launch_job_type',
|
||||
multiple: false
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
CreateDialog({
|
||||
id: 'job-launch-modal',
|
||||
scope: scope,
|
||||
width: 800,
|
||||
minWidth: 400,
|
||||
draggable: false,
|
||||
dialogClass: 'JobSubmission-dialog',
|
||||
onOpen: function() {
|
||||
Wait('stop');
|
||||
},
|
||||
callback: 'LaunchJobModalReady'
|
||||
});
|
||||
};
|
||||
|
||||
scope.clearDialog = function() {
|
||||
// Destroy the dialog
|
||||
if($("#job-launch-modal").hasClass('ui-dialog-content')) {
|
||||
$('#job-launch-modal').dialog('destroy');
|
||||
}
|
||||
// Remove the directive from the page
|
||||
$('#content-container').find('submit-job').remove();
|
||||
|
||||
// Clear out the scope (we'll create a new scope the next time
|
||||
// job launch is called)
|
||||
scope.$destroy();
|
||||
};
|
||||
|
||||
// This function is used to hide/show the contents of a password
|
||||
// within a form
|
||||
scope.togglePassword = function(id) {
|
||||
var buttonId = id + "_show_input_button",
|
||||
inputId = id,
|
||||
buttonInnerHTML = $(buttonId).html();
|
||||
if (buttonInnerHTML.indexOf("Show") > -1) {
|
||||
$(buttonId).html("Hide");
|
||||
$(inputId).attr("type", "text");
|
||||
} else {
|
||||
$(buttonId).html("Show");
|
||||
$(inputId).attr("type", "password");
|
||||
}
|
||||
};
|
||||
|
||||
scope.init();
|
||||
|
||||
}
|
||||
};
|
||||
}];
|
||||
229
awx/ui/client/src/job-submission/job-submission.partial.html
Normal file
229
awx/ui/client/src/job-submission/job-submission.partial.html
Normal file
@ -0,0 +1,229 @@
|
||||
<div id="job-launch-modal" class="JobSubmission">
|
||||
<div class="JobSubmission-container">
|
||||
<div class="JobSubmission-header">
|
||||
<div class="JobSubmission-title">
|
||||
<div class="JobSubmission-titleText">LAUNCH JOB<div class="JobSubmission-titleLockup"></div>{{job_template_data.name}}</div>
|
||||
</div>
|
||||
<div class="JobSubmission-close">
|
||||
<button class="JobSubmission-exit" ng-click="clearDialog()"><i class="fa fa-times-circle"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="JobSubmission-stepsContainer">
|
||||
<div class="JobSubmission-steps">
|
||||
<div class="JobSubmission-step" ng-class="{'JobSubmission-step--active': step==='inventory'}" ng-if="ask_inventory_on_launch" ng-click="setStep('inventory')">Inventory</div>
|
||||
<div class="JobSubmission-step" ng-class="{'JobSubmission-step--active': step==='credential', 'JobSubmission-step--disabled': !credentialTabEnabled}" ng-if="ask_credential_on_launch || password_needed" ng-click="!credentialTabEnabled || setStep('credential')">Credential</div>
|
||||
<div class="JobSubmission-step" ng-class="{'JobSubmission-step--active': step==='otherprompts', 'JobSubmission-step--disabled': !otherPromptsTabEnabled}" ng-if="has_other_prompts" ng-click="!otherPromptsTabEnabled || setStep('otherprompts')">Other Prompts</div>
|
||||
<div class="JobSubmission-step" ng-class="{'JobSubmission-step--active': step==='survey', 'JobSubmission-step--disabled': !surveyTabEnabled}" ng-if="survey_enabled" ng-click="!surveyTabEnabled || setStep('survey')">Survey</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="JobSubmission-formContainer">
|
||||
<div ng-if="ask_inventory_on_launch" ng-show="step === 'inventory'" class="JobSubmission-form">
|
||||
<div class="JobSubmission-selectedItemContainer">
|
||||
<div class="JobSubmission-selectedItem">
|
||||
<div class="JobSubmission-selectedItemInfo">
|
||||
<span class="JobSubmission-selectedItemLabel">SELECTED INVENTORY:</span>
|
||||
<span ng-show="selected_inventory" ng-bind="selected_inventory.name"></span>
|
||||
<span class="JobSubmission-selectedItemNone" ng-show="!selected_inventory">None selected</span>
|
||||
</div>
|
||||
<div class="JobSubmission-selectedItemRevert" ng-if="ask_inventory_on_launch && has_default_inventory">
|
||||
<button class="btn btn-xs JobSubmission-revertButton" ng-disabled="selected_inventory.id === defaults.inventory.id" ng-click="revertToDefaultInventory()">REVERT TO DEFAULT</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="job-submission-inventory-lookup"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="ask_credential_on_launch || password_needed" ng-show="step === 'credential'" class="JobSubmission-form">
|
||||
<div class="JobSubmission-selectedItemContainer">
|
||||
<div class="JobSubmission-selectedItem">
|
||||
<div class="JobSubmission-selectedItemInfo">
|
||||
<span class="JobSubmission-selectedItemLabel">SELECTED CREDENTIAL:</span>
|
||||
<span ng-show="selected_inventory" ng-bind="selected_credential.name"></span>
|
||||
<span class="JobSubmission-selectedItemNone" ng-show="!selected_credential">None selected</span>
|
||||
</div>
|
||||
<div class="JobSubmission-selectedItemRevert" ng-if="ask_credential_on_launch && has_default_credential">
|
||||
<button class="btn btn-xs JobSubmission-revertButton" ng-disabled="selected_credential.id === defaults.credential.id" ng-click="revertToDefaultCredential()">REVERT TO DEFAULT</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="job-submission-credential-lookup"></div>
|
||||
<div ng-show="ssh_password_required || ssh_key_unlock_required || become_password_required || vault_password_required">
|
||||
<div class="JobSubmission-instructions">Launching this job requires the passwords listed below. Enter and confirm each password before continuing.</div>
|
||||
<form name="forms.credentialpasswords" autocomplete="off" novalidate>
|
||||
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="ssh_password_required">
|
||||
<label for="ssh_password">
|
||||
<span class="Form-inputLabel prepend-asterisk"> Password</span>
|
||||
</label>
|
||||
<div>
|
||||
<input type="password" ng-model="passwords.ssh_password" ng-keydown="keydown($event)" name="ssh_password" class="password-field form-control input-sm" required>
|
||||
<div class="error" ng-show="forms.credentialpasswords.ssh_password.$dirty && forms.credentialpasswords.ssh_password.$error.required">Please enter a password.</div>
|
||||
<div class="error api-error" ng-bind="ssh_password_api_error"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="ssh_key_unlock_required">
|
||||
<label for="ssh_key_unlock">
|
||||
<span class="Form-inputLabel prepend-asterisk"> Private Key Passphrase</span>
|
||||
</label>
|
||||
<div>
|
||||
<input type="password" ng-model="passwords.ssh_key_unlock" ng-keydown="keydown($event)" name="ssh_key_unlock" class="password-field form-control input-sm" required>
|
||||
<div class="error" ng-show="forms.credentialpasswords.ssh_key_unlock.$dirty && forms.credentialpasswords.ssh_key_unlock.$error.required">Please enter a password.</div>
|
||||
<div class="error api-error" ng-bind="ssh_key_unlock_api_error"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="become_password_required">
|
||||
<label for="become_password">
|
||||
<span class="Form-inputLabel prepend-asterisk"> Privilege Escalation Password</span>
|
||||
</label>
|
||||
<div>
|
||||
<input type="password" ng-model="passwords.become_password" ng-keydown="keydown($event)" name="become_password" class="password-field form-control input-sm" required>
|
||||
<div class="error" ng-show="forms.credentialpasswords.become_password.$dirty && forms.credentialpasswords.become_password.$error.required">Please enter a password.</div>
|
||||
<div class="error api-error" ng-bind="become_password_api_error"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="vault_password_required">
|
||||
<label for="vault_password">
|
||||
<span class="Form-inputLabel prepend-asterisk"> Vault Password</span>
|
||||
</label>
|
||||
<div>
|
||||
<input type="password" ng-model="passwords.vault_password" ng-keydown="keydown($event)" name="vault_password" class="password-field form-control input-sm" required>
|
||||
<div class="error" ng-show="forms.credentialpasswords.vault_password.$dirty && forms.credentialpasswords.vault_password.$error.required">Please enter a password.</div>
|
||||
<div class="error api-error" ng-bind="vault_password_api_error"></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="has_other_prompts" ng-show="step === 'otherprompts'" class="JobSubmission-form">
|
||||
<form name="forms.otherprompts" autocomplete="off" novalidate>
|
||||
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="ask_variables_on_launch">
|
||||
<label for="variables">
|
||||
<span class="Form-inputLabel">Extra Variables</span>
|
||||
<a id="awp-variables" href="" aw-pop-over="<p>Pass extra command line variables to the playbook. This is the -e or --extra-vars command line parameter for ansible-playbook. Provide key/value pairs using either YAML or JSON.</p>JSON:<br /><blockquote>{<br />"somevar": "somevalue",<br />"password": "magic"<br /> }</blockquote>YAML:<br /><blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>" data-placement="right" data-container="body" over-title="Extra Variables" class="help-link" data-original-title="" title="" tabindex="-1">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
</a>
|
||||
<div class="parse-selection" id="job_launch_variables_parse_type">
|
||||
<input type="radio" ng-model="parseType" value="yaml" ng-change="parseTypeChange()">
|
||||
<span class="parse-label">YAML</span>
|
||||
<input type="radio" ng-model="parseType" value="json" ng-change="parseTypeChange()">
|
||||
<span class="parse-label">JSON</span>
|
||||
</div>
|
||||
</label>
|
||||
<div>
|
||||
<textarea rows="6" ng-model="other_prompt_data.extra_vars" name="variables" class="form-control Form-textArea Form-textAreaLabel" id="job_launch_variables" aw-watch=""></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="ask_job_type_on_launch">
|
||||
<label for="job_type">
|
||||
<span class="Form-inputLabel prepend-asterisk"> Job Type</span>
|
||||
</label>
|
||||
<div>
|
||||
<select ng-model="other_prompt_data.job_type" name="job_type" class="form-control Form-dropDown" id="job_launch_job_type" tabindex="-1" aria-hidden="true" required>
|
||||
<option value="" class="" selected="selected">Choose a job type</option>
|
||||
<option label="Run" value="run" selected="selected">Run</option>
|
||||
<option label="Check" value="check">Check</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="ask_limit_on_launch">
|
||||
<label for="limit">
|
||||
<span class="Form-inputLabel">Limit</span>
|
||||
</label>
|
||||
<div>
|
||||
<input type="text" ng-model="other_prompt_data.limit" name="limit" class="form-control Form-textInput">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="ask_tags_on_launch">
|
||||
<label for="tags">
|
||||
<span class="Form-inputLabel">Job Tags</span>
|
||||
</label>
|
||||
<div>
|
||||
<textarea rows="1" ng-model="other_prompt_data.job_tags" name="tags" class="form-control Form-textArea Form-textInput"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div ng-if="survey_enabled" ng-show="step === 'survey'" class="JobSubmission-form">
|
||||
<form name="forms.survey" autocomplete="off" novalidate>
|
||||
<div ng-repeat="question in survey_questions" id="taker_'+question.index+'" class="form-group Form-formGroup Form-formGroup--singleColumn">
|
||||
<label ng-attr-for="{{question.variable}}">
|
||||
<span class="label-text Form-inputLabel" ng-class="{'prepend-asterisk': question.required===true}"> {{question.question_name}}</span>
|
||||
</label>
|
||||
|
||||
<div class="survey_taker_description" ng-if="question.question_description">
|
||||
<i ng-bind="question.question_description"></i>
|
||||
</div>
|
||||
<div ng-if="question.type === 'text'">
|
||||
<input type="text" id="survey_question_{{$index}}" ng-model="question.model" name="survey_question_{{$index}}" ng-minlength="question.minlength" ng-maxlength="question.maxlength" class="form-control" ng-required="question.required">
|
||||
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$dirty && forms.survey.survey_question_{{$index}}.$error.required">Please enter an answer.</div>
|
||||
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.minlength || forms.survey.survey_question_{{$index}}.$error.maxlength">Please enter an answer between {{question.minlength}} to {{question.maxlength}} characters long.</div>
|
||||
</div>
|
||||
<div ng-if="question.type === 'textarea'">
|
||||
<textarea id="survey_question_{{$index}}" name="survey_question_{{$index}}" ng-model="question.model" ng-minlength="question.minlength" ng-maxlength="question.maxlength" class="form-control final" ng-required="question.required" rows="3"></textarea>
|
||||
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$dirty && forms.survey.survey_question_{{$index}}.$error.required">Please enter an answer.</div>
|
||||
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.minlength || forms.survey.survey_question_{{$index}}.$error.maxlength">Please enter an answer between {{question.minlength}} to {{question.maxlength}} characters long.</div>
|
||||
</div>
|
||||
<div ng-if="question.type === 'password'">
|
||||
<div class="input-group">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default show_input_button JobSubmission-passwordButton" id="survey_question_{{$index}}_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="togglePassword('#survey_question_' + $index)" data-original-title="" title="">Show</button>
|
||||
</span>
|
||||
<input id="survey_question_{{$index}}" type="password" ng-model="question.model" name="survey_question_{{$index}}" ng-required="question.required" ng-minlength="question.minlength" ng-maxlength="question.maxlength" class="form-control ng-pristine ng-valid-api-error ng-invalid" autocomplete="false">
|
||||
</div>
|
||||
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$dirty && forms.survey.survey_question_{{$index}}.$error.required">Please enter an answer.</div>
|
||||
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.minlength || forms.survey.survey_question_{{$index}}.$error.maxlength">Please enter an answer between {{question.minlength}} to {{question.maxlength}} characters long.</div>
|
||||
</div>
|
||||
<div ng-if="question.type === 'integer'">
|
||||
<input type="number" id="survey_question_{{$index}}" ng-model="question.model" class="form-control" name="survey_question_{{$index}}" ng-required="question.required" integer aw-min="question.minValue" aw-max="question.maxValue"/>
|
||||
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$dirty && forms.survey.survey_question_{{$index}}.$error.required">Please enter an answer.</div>
|
||||
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.number || forms.survey.survey_question_{{$index}}.$error.integer" >Please enter an answer that is a valid integer.</div>
|
||||
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.awMin || forms.survey.survey_question_{{$index}}.$error.awMax">Please enter an answer between {{question.minValue}} and {{question.maxValue}}.</div>
|
||||
</div>
|
||||
<div ng-if="question.type === 'float'">
|
||||
<input type="number" id="survey_question_{{$index}}" ng-model="question.model" class="form-control" name="survey_question_{{$index}}" ng-required="question.required" smart-float aw-min="question.minValue" aw-max="question.maxValue"/>
|
||||
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$dirty && forms.survey.survey_question_{{$index}}.$error.required">Please enter an answer.</div>
|
||||
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.number || forms.survey.survey_question_{{$index}}.$error.float">Please enter an answer that is a decimal number.</div>
|
||||
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.awMin || forms.survey.survey_question_{{$index}}.$error.awMax">Please enter an answer between {{question.minValue}} and {{question.maxValue}}.</div>
|
||||
</div>
|
||||
<div ng-if="question.type === 'multiplechoice'">
|
||||
<div class="survey_taker_input">
|
||||
<multiple-choice
|
||||
multi-select="false"
|
||||
question="question"
|
||||
choices="question.choices"
|
||||
ng-required="question.required"
|
||||
ng-model="question.model">
|
||||
</multiple-choice>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="question.type === 'multiselect'">
|
||||
<multiple-choice
|
||||
multi-select="true"
|
||||
question="question"
|
||||
choices="question.choices"
|
||||
ng-required="question.required"
|
||||
ng-model="question.model">
|
||||
</multiple-choice>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="JobSubmission-footerContainer">
|
||||
<div class="JobSubmission-footerPreview">
|
||||
<div class="JobSubmission-previewItem">
|
||||
<div class="JobSubmission-previewItemTitle">INVENTORY</div>
|
||||
<div ng-show="selected_inventory" ng-bind="selected_inventory.name"></div>
|
||||
<div class="JobSubmission-previewItemNone" ng-show="!selected_inventory">None selected</div>
|
||||
</div>
|
||||
<div class="JobSubmission-previewItem">
|
||||
<div class="JobSubmission-previewItemTitle">CREDENTIAL</div>
|
||||
<div ng-show="selected_credential" ng-bind="selected_credential.name"></div>
|
||||
<div class="JobSubmission-previewItemNone" ng-show="!selected_credential">None selected</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="JobSubmission-footerButtons">
|
||||
<button id="job-submission-close-button" class="btn btn-sm JobSubmission-defaultButton" ng-click="clearDialog()">CANCEL</button>
|
||||
<button id="job-submission-action-button" class="btn btn-sm JobSubmission-actionButton" ng-click="takeAction()" ng-disabled="actionButtonDisabled()">{{getActionButtonText()}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
17
awx/ui/client/src/job-submission/main.js
Normal file
17
awx/ui/client/src/job-submission/main.js
Normal file
@ -0,0 +1,17 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import InitiatePlaybookRun from './job-submission-factories/initiateplaybookrun.factory';
|
||||
import LaunchJob from './job-submission-factories/launchjob.factory';
|
||||
import GetSurveyQuestions from './job-submission-factories/getsurveyquestions.factory';
|
||||
import submitJob from './job-submission.directive';
|
||||
|
||||
export default
|
||||
angular.module('jobSubmission', [])
|
||||
.factory('InitiatePlaybookRun', InitiatePlaybookRun)
|
||||
.factory('LaunchJob', LaunchJob)
|
||||
.factory('GetSurveyQuestions', GetSurveyQuestions)
|
||||
.directive('submitJob', submitJob);
|
||||
@ -20,7 +20,7 @@ export default
|
||||
'Empty', 'Prompt', 'ParseVariableString', 'ToJSON',
|
||||
'SchedulesControllerInit', 'JobsControllerInit', 'JobsListUpdate',
|
||||
'GetChoices', 'SchedulesListInit', 'SchedulesList', 'CallbackHelpInit',
|
||||
'PlaybookRun' , 'initSurvey', '$state', 'CreateSelect2',
|
||||
'InitiatePlaybookRun' , 'initSurvey', '$state', 'CreateSelect2',
|
||||
'ToggleNotification', 'NotificationsListInit', '$q',
|
||||
function(
|
||||
$filter, $scope, $rootScope, $compile,
|
||||
@ -30,7 +30,7 @@ export default
|
||||
GetBasePath, md5Setup, ParseTypeChange, JobStatusToolTip, FormatDate, Wait,
|
||||
Empty, Prompt, ParseVariableString, ToJSON, SchedulesControllerInit,
|
||||
JobsControllerInit, JobsListUpdate, GetChoices, SchedulesListInit,
|
||||
SchedulesList, CallbackHelpInit, PlaybookRun, SurveyControllerInit, $state,
|
||||
SchedulesList, CallbackHelpInit, InitiatePlaybookRun, SurveyControllerInit, $state,
|
||||
CreateSelect2, ToggleNotification, NotificationsListInit, $q
|
||||
) {
|
||||
|
||||
@ -653,7 +653,7 @@ export default
|
||||
}
|
||||
else {
|
||||
|
||||
PlaybookRun({
|
||||
InitiatePlaybookRun({
|
||||
scope: $scope,
|
||||
id: id
|
||||
});
|
||||
|
||||
@ -9,14 +9,14 @@ export default
|
||||
'$stateParams', 'Rest', 'Alert', 'JobTemplateList', 'generateList',
|
||||
'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
|
||||
'ProcessErrors', 'GetBasePath', 'JobTemplateForm', 'CredentialList',
|
||||
'LookUpInit', 'PlaybookRun', 'Wait', '$compile',
|
||||
'LookUpInit', 'InitiatePlaybookRun', 'Wait', '$compile',
|
||||
'$state',
|
||||
|
||||
function(
|
||||
$scope, $rootScope, $location, $log,
|
||||
$stateParams, Rest, Alert, JobTemplateList, GenerateList, Prompt,
|
||||
SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors,
|
||||
GetBasePath, JobTemplateForm, CredentialList, LookUpInit, PlaybookRun,
|
||||
GetBasePath, JobTemplateForm, CredentialList, LookUpInit, InitiatePlaybookRun,
|
||||
Wait, $compile, $state
|
||||
) {
|
||||
|
||||
@ -95,7 +95,7 @@ export default
|
||||
};
|
||||
|
||||
$scope.submitJob = function (id) {
|
||||
PlaybookRun({ scope: $scope, id: id });
|
||||
InitiatePlaybookRun({ scope: $scope, id: id });
|
||||
};
|
||||
|
||||
$scope.scheduleJob = function (id) {
|
||||
|
||||
@ -304,17 +304,17 @@ export default
|
||||
}
|
||||
},
|
||||
buttons: {
|
||||
submit_question: {
|
||||
ngClick: 'submitQuestion($event)',
|
||||
ngDisabled: true,
|
||||
'class': 'btn btn-sm Form-saveButton',
|
||||
label: '{{editQuestionIndex === null ? "ADD" : "UPDATE"}}'
|
||||
},
|
||||
question_cancel : {
|
||||
label: 'Cancel',
|
||||
'class' : 'btn btn-default Form-cancelButton',
|
||||
ngClick: 'generateAddQuestionForm()',
|
||||
ngDisabled: 'survey_question_form.$pristine'
|
||||
},
|
||||
submit_question: {
|
||||
ngClick: 'submitQuestion($event)',
|
||||
ngDisabled: true,
|
||||
'class': 'btn btn-sm Form-saveButton',
|
||||
label: '{{editQuestionIndex === null ? "ADD" : "UPDATE"}}'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -102,7 +102,6 @@
|
||||
text-transform: uppercase;
|
||||
padding-left:15px;
|
||||
padding-right: 15px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.SurveyMaker-deleteButton:hover {
|
||||
background-color: @default-err-hov;
|
||||
|
||||
@ -26,7 +26,7 @@ export default
|
||||
key: true,
|
||||
label: 'Name',
|
||||
columnClass: 'col-md-3 col-sm-9 col-xs-9',
|
||||
modalColumnClass: 'col-md-8'
|
||||
modalColumnClass: 'col-md-11'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
|
||||
@ -46,7 +46,7 @@ export default
|
||||
key: true,
|
||||
label: 'Name',
|
||||
columnClass: 'col-md-5 col-sm-5 col-xs-8 List-staticColumnAdjacent',
|
||||
modalColumnClass: 'col-md-8',
|
||||
modalColumnClass: 'col-md-11',
|
||||
linkTo: '/#/inventories/{{inventory.id}}/manage'
|
||||
},
|
||||
organization: {
|
||||
|
||||
@ -14,8 +14,8 @@
|
||||
<div class="Prompt-bodyTarget">{{survey_questions[questionToBeDeleted].question_name}}</div>
|
||||
</div>
|
||||
<div class="Modal-footer">
|
||||
<button ng-click="deleteMode === 'survey' ? deleteSurvey() : (deleteMode === 'question' ? deleteQuestion(questionToBeDeleted) : '')" class="btn Modal-footerButton ng-binding Modal-errorButton">DELETE</a>
|
||||
<button ng-click="hideDeleteOverlay()" class="btn Modal-defaultButton Modal-footerButton">CANCEL</a>
|
||||
<button ng-click="deleteMode === 'survey' ? deleteSurvey() : (deleteMode === 'question' ? deleteQuestion(questionToBeDeleted) : '')" class="btn Modal-footerButton ng-binding Modal-errorButton">DELETE</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -81,9 +81,9 @@
|
||||
</div>
|
||||
<div class="SurveyMaker-panelFooter">
|
||||
<div class="Form-buttons">
|
||||
<button id="survey-save-button" class="btn btn-sm Form-saveButton" ng-click="saveSurvey()" ng-disabled="survey_questions.length < 1 || !can_edit || editQuestionIndex !== null">SAVE</button>
|
||||
<button id="survey-close-button" class="btn btn-sm Form-buttonDefault" ng-click="closeSurvey('survey-modal-dialog')">CANCEL</button>
|
||||
<button id="survey-delete-button" class="btn btn-sm SurveyMaker-deleteButton" ng-show="survey_exists" ng-click="showDeleteOverlay('survey')">DELETE SURVEY</button>
|
||||
<button id="survey-close-button" class="btn btn-sm Form-buttonDefault" ng-click="closeSurvey('survey-modal-dialog')">CANCEL</button>
|
||||
<button id="survey-save-button" class="btn btn-sm Form-saveButton" ng-click="saveSurvey()" ng-disabled="survey_questions.length < 1 || !can_edit || editQuestionIndex !== null">SAVE</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export function PortalModeJobTemplatesController($scope, $rootScope, GetBasePath, GenerateList, PortalJobTemplateList, SearchInit, PaginateInit, PlaybookRun){
|
||||
export function PortalModeJobTemplatesController($scope, $rootScope, GetBasePath, GenerateList, PortalJobTemplateList, SearchInit, PaginateInit, InitiatePlaybookRun){
|
||||
|
||||
|
||||
var list = PortalJobTemplateList,
|
||||
@ -13,7 +13,7 @@ export function PortalModeJobTemplatesController($scope, $rootScope, GetBasePat
|
||||
pageSize = 12;
|
||||
|
||||
$scope.submitJob = function (id) {
|
||||
PlaybookRun({ scope: $scope, id: id });
|
||||
InitiatePlaybookRun({ scope: $scope, id: id });
|
||||
};
|
||||
|
||||
var init = function(){
|
||||
@ -42,5 +42,5 @@ export function PortalModeJobTemplatesController($scope, $rootScope, GetBasePat
|
||||
init();
|
||||
}
|
||||
|
||||
PortalModeJobTemplatesController.$inject = ['$scope', '$rootScope', 'GetBasePath', 'generateList', 'PortalJobTemplateList', 'SearchInit', 'PaginateInit', 'PlaybookRun'
|
||||
PortalModeJobTemplatesController.$inject = ['$scope', '$rootScope', 'GetBasePath', 'generateList', 'PortalJobTemplateList', 'SearchInit', 'PaginateInit', 'InitiatePlaybookRun'
|
||||
];
|
||||
|
||||
@ -454,14 +454,14 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate
|
||||
// Change layout if a lookup list, place radio buttons before labels
|
||||
if (options.mode === 'lookup') {
|
||||
if(options.input_type==="radio"){ //added by JT so that lookup forms can be either radio inputs or check box inputs
|
||||
innerTable += "<td class=\"List-tableCell\"><input type=\"radio\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" +
|
||||
innerTable += "<td class=\"List-tableCell\"><input type=\"radio\" ng-model=\"" + list.iterator + ".checked\" name=\"check_" + list.iterator + "_{{" +
|
||||
list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ".id, true)\" ng-value=\"1\" " +
|
||||
"ng-false-value=\"0\" id=\"check_{{" + list.iterator + ".id}}\" /></td>";
|
||||
"ng-false-value=\"0\" id=\"check_" + list.iterator + "_{{" + list.iterator + ".id}}\" /></td>";
|
||||
}
|
||||
else { // its assumed that options.input_type = checkbox
|
||||
innerTable += "<td class=\"List-tableCell\"><input type=\"checkbox\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" +
|
||||
innerTable += "<td class=\"List-tableCell\"><input type=\"checkbox\" ng-model=\"" + list.iterator + ".checked\" name=\"check_" + list.iterator + "_{{" +
|
||||
list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ".id, true)\" ng-true-value=\"1\" " +
|
||||
"ng-false-value=\"0\" id=\"check_{{" + list.iterator + ".id}}\" /></td>";
|
||||
"ng-false-value=\"0\" id=\"check_" + list.iterator + "_{{" + list.iterator + ".id}}\" /></td>";
|
||||
}
|
||||
}
|
||||
|
||||
@ -619,7 +619,7 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate
|
||||
html += buildSelectAll().prop('outerHTML');
|
||||
}
|
||||
else if (options.mode === 'lookup') {
|
||||
html += "<th class=\"List-tableHeader col-lg-1 col-md-1 col-sm-2 col-xs-2\"></th>";
|
||||
html += "<th class=\"List-tableHeader List-staticColumn--smallStatus col-lg-1 col-md-1 col-sm-2 col-xs-2\"></th>";
|
||||
}
|
||||
for (fld in list.fields) {
|
||||
if ((list.fields[fld].searchOnly === undefined || list.fields[fld].searchOnly === false) &&
|
||||
|
||||
@ -218,8 +218,6 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="overlay"></div>
|
||||
<div class="spinny"><i class="fa fa-cog fa-spin fa-2x"></i> <p>working...</p></div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user