Propagate launch/relaunch logic across the app. Removed some old launch related factories.

This commit is contained in:
mabashian
2018-03-20 15:53:12 -04:00
parent 8056ac5393
commit 2e858790db
53 changed files with 1123 additions and 808 deletions

View File

@@ -16,14 +16,12 @@ function ListTemplatesController(
$state, $state,
Alert, Alert,
Dataset, Dataset,
InitiatePlaybookRun,
ProcessErrors, ProcessErrors,
Prompt, Prompt,
PromptService, PromptService,
resolvedModels, resolvedModels,
strings, strings,
Wait, Wait
Empty
) { ) {
const vm = this || {}; const vm = this || {};
const [jobTemplate, workflowTemplate] = resolvedModels; const [jobTemplate, workflowTemplate] = resolvedModels;
@@ -42,7 +40,7 @@ function ListTemplatesController(
position: 'right', position: 'right',
arrowHeight: 15 arrowHeight: 15
} }
} };
$scope.canAddJobTemplate = jobTemplate.options('actions.POST'); $scope.canAddJobTemplate = jobTemplate.options('actions.POST');
$scope.canAddWorkflowJobTemplate = workflowTemplate.options('actions.POST'); $scope.canAddWorkflowJobTemplate = workflowTemplate.options('actions.POST');
@@ -347,6 +345,7 @@ function ListTemplatesController(
launchConf: launchData.data, launchConf: launchData.data,
launchOptions: launchOptions.data, launchOptions: launchOptions.data,
template: template.id, template: template.id,
templateType: template.type,
prompts: PromptService.processPromptValues({ prompts: PromptService.processPromptValues({
launchConf: launchData.data, launchConf: launchData.data,
launchOptions: launchOptions.data launchOptions: launchOptions.data
@@ -368,79 +367,75 @@ function ListTemplatesController(
} }
function runWorkflowTemplate(template) { function runWorkflowTemplate(template) {
InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'workflow_job_template' }); const selectedWorkflowJobTemplate = workflowTemplate.create();
const preLaunchPromises = [
selectedWorkflowJobTemplate.getLaunch(template.id),
selectedWorkflowJobTemplate.optionsLaunch(template.id),
];
Promise.all(preLaunchPromises)
.then(([launchData, launchOptions]) => {
if (selectedWorkflowJobTemplate.canLaunchWithoutPrompt()) {
return selectedWorkflowJobTemplate
.postLaunch({ id: template.id })
.then(({ data }) => {
$state.go('workflowResults', { id: data.workflow_job }, { reload: true });
});
}
const promptData = {
launchConf: launchData.data,
launchOptions: launchOptions.data,
template: template.id,
templateType: template.type,
prompts: PromptService.processPromptValues({
launchConf: launchData.data,
launchOptions: launchOptions.data
}),
triggerModalOpen: true,
};
if (launchData.data.survey_enabled) {
selectedWorkflowJobTemplate.getSurveyQuestions(template.id)
.then(({ data }) => {
const processed = PromptService.processSurveyQuestions({ surveyQuestions: data.spec });
promptData.surveyQuestions = processed.surveyQuestions;
$scope.promptData = promptData;
});
} else {
$scope.promptData = promptData;
}
});
} }
$scope.launchJob = () => { $scope.launchJob = () => {
const jobLaunchData = { const jobLaunchData = PromptService.bundlePromptDataForLaunch($scope.promptData);
extra_vars: $scope.promptData.extraVars
};
if ($scope.promptData.launchConf.ask_tags_on_launch){
jobLaunchData.job_tags = $scope.promptData.prompts.tags.value.map(a => a.value).join();
}
if ($scope.promptData.launchConf.ask_skip_tags_on_launch){
jobLaunchData.skip_tags = $scope.promptData.prompts.skipTags.value.map(a => a.value).join();
}
if ($scope.promptData.launchConf.ask_limit_on_launch && _.has($scope, 'promptData.prompts.limit.value')){
jobLaunchData.limit = $scope.promptData.prompts.limit.value;
}
if ($scope.promptData.launchConf.ask_job_type_on_launch && _.has($scope, 'promptData.prompts.jobType.value.value')) {
jobLaunchData.job_type = $scope.promptData.prompts.jobType.value.value;
}
if ($scope.promptData.launchConf.ask_verbosity_on_launch && _.has($scope, 'promptData.prompts.verbosity.value.value')) {
jobLaunchData.verbosity = $scope.promptData.prompts.verbosity.value.value;
}
if ($scope.promptData.launchConf.ask_inventory_on_launch && !Empty($scope.promptData.prompts.inventory.value.id)){
jobLaunchData.inventory_id = $scope.promptData.prompts.inventory.value.id;
}
if ($scope.promptData.launchConf.ask_credential_on_launch){
jobLaunchData.credentials = [];
$scope.promptData.prompts.credentials.value.forEach((credential) => {
jobLaunchData.credentials.push(credential.id);
});
}
if ($scope.promptData.launchConf.ask_diff_mode_on_launch && _.has($scope, 'promptData.prompts.diffMode.value')) {
jobLaunchData.diff_mode = $scope.promptData.prompts.diffMode.value;
}
if ($scope.promptData.prompts.credentials.passwords) {
_.forOwn($scope.promptData.prompts.credentials.passwords, (val, key) => {
if (!jobLaunchData.credential_passwords) {
jobLaunchData.credential_passwords = {};
}
if (key === "ssh_key_unlock") {
jobLaunchData.credential_passwords.ssh_key_unlock = val.value;
} else if (key !== "vault") {
jobLaunchData.credential_passwords[`${key}_password`] = val.value;
} else {
_.each(val, (vaultCred) => {
jobLaunchData.credential_passwords[vaultCred.vault_id ? `${key}_password.${vaultCred.vault_id}` : `${key}_password`] = vaultCred.value;
});
}
});
}
// If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything. // If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything.
if(_.isEmpty(jobLaunchData.extra_vars) && !($scope.promptData.launchConf.ask_variables_on_launch && $scope.promptData.launchConf.survey_enabled && $scope.promptData.surveyQuestions.length > 0)){ if(_.isEmpty(jobLaunchData.extra_vars) && !($scope.promptData.launchConf.ask_variables_on_launch && $scope.promptData.launchConf.survey_enabled && $scope.promptData.surveyQuestions.length > 0)){
delete jobLaunchData.extra_vars; delete jobLaunchData.extra_vars;
} }
jobTemplate.create().postLaunch({ if($scope.promptData.templateType === 'job_template') {
id: $scope.promptData.template, jobTemplate.create().postLaunch({
launchData: jobLaunchData id: $scope.promptData.template,
}) launchData: jobLaunchData
.then((launchRes) => { })
$state.go('jobResult', { id: launchRes.data.job }, { reload: true }); .then((launchRes) => {
}) $state.go('jobResult', { id: launchRes.data.job }, { reload: true });
.catch(createErrorHandler('launch job template', 'POST')); })
.catch(createErrorHandler('launch job template', 'POST'));
} else if($scope.promptData.templateType === 'workflow_job_template') {
workflowTemplate.create().postLaunch({
id: $scope.promptData.template,
launchData: jobLaunchData
})
.then((launchRes) => {
$state.go('workflowResults', { id: launchRes.data.workflow_job }, { reload: true });
})
.catch(createErrorHandler('launch workflow job template', 'POST'));
}
}; };
} }
@@ -450,14 +445,12 @@ ListTemplatesController.$inject = [
'$state', '$state',
'Alert', 'Alert',
'Dataset', 'Dataset',
'InitiatePlaybookRun',
'ProcessErrors', 'ProcessErrors',
'Prompt', 'Prompt',
'PromptService', 'PromptService',
'resolvedModels', 'resolvedModels',
'TemplatesStrings', 'TemplatesStrings',
'Wait', 'Wait'
'Empty'
]; ];
export default ListTemplatesController; export default ListTemplatesController;

View File

@@ -5,17 +5,108 @@ const atRelaunch = {
bindings: { bindings: {
state: '<' state: '<'
}, },
controller: ['RelaunchJob', 'InitiatePlaybookRun', 'ComponentsStrings', '$scope', atRelaunchCtrl], controller: ['ProcessErrors', 'AdhocRun', 'ComponentsStrings',
'ProjectModel', 'InventorySourceModel', 'WorkflowJobModel', 'Alert',
'AdHocCommandModel', 'JobModel', 'JobTemplateModel', 'PromptService',
'GetBasePath', '$state', '$q', '$scope', atRelaunchCtrl
],
controllerAs: 'vm' controllerAs: 'vm'
}; };
function atRelaunchCtrl (RelaunchJob, InitiatePlaybookRun, strings, $scope) { function atRelaunchCtrl (
ProcessErrors, AdhocRun, strings,
Project, InventorySource, WorkflowJob, Alert,
AdHocCommand, Job, JobTemplate, PromptService,
GetBasePath, $state, $q, $scope
) {
const vm = this; const vm = this;
const scope = $scope.$parent; const scope = $scope.$parent;
const job = _.get(scope, 'job') || _.get(scope, 'completed_job'); const job = _.get(scope, 'job') || _.get(scope, 'completed_job');
const jobObj = new Job();
const jobTemplate = new JobTemplate();
const checkRelaunchPlaybook = (option) => {
jobObj.getRelaunch({
id: job.id
}).then((getRelaunchRes) => {
if (
getRelaunchRes.data.passwords_needed_to_start &&
getRelaunchRes.data.passwords_needed_to_start.length > 0
) {
const jobPromises = [
jobObj.request('get', job.id),
jobTemplate.optionsLaunch(job.unified_job_template)
];
$q.all(jobPromises)
.then(([jobRes, launchOptions]) => {
const populatedJob = jobRes.data;
const jobTypeChoices = _.get(
launchOptions,
'data.actions.POST.job_type.choices',
[]
).map(c => ({ label: c[1], value: c[0] }));
const verbosityChoices = _.get(
launchOptions,
'data.actions.POST.verbosity.choices',
[]
).map(c => ({ label: c[1], value: c[0] }));
vm.promptData = {
launchConf: {
passwords_needed_to_start: getRelaunchRes.data.passwords_needed_to_start
},
launchOptions: launchOptions.data,
job: job.id,
relaunchHostType: option ? (option.name).toLowerCase() : null,
prompts: {
credentials: {
value: populatedJob.summary_fields.credentials ? populatedJob.summary_fields.credentials : []
},
variables: {
value: populatedJob.extra_vars
},
inventory: {
value: populatedJob.summary_fields.inventory ? populatedJob.summary_fields.inventory : null
},
verbosity: {
value: _.find(verbosityChoices, item => item.value === populatedJob.verbosity),
choices: verbosityChoices
},
jobType: {
value: _.find(jobTypeChoices, item => item.value === populatedJob.job_type),
choices: jobTypeChoices
},
limit: {
value: populatedJob.limit
},
tags: {
value: populatedJob.job_tags
},
skipTags: {
value: populatedJob.skip_tags
},
diffMode: {
value: populatedJob.diff_mode
}
},
triggerModalOpen: true
};
});
} else {
jobObj.postRelaunch({
id: job.id
}).then((launchRes) => {
if (!$state.includes('jobs')) {
$state.go('jobResult', { id: launchRes.data.id }, { reload: true });
}
});
}
});
};
vm.$onInit = () => { vm.$onInit = () => {
vm.showRelaunch = !(job.type === 'system_job') && job.summary_fields.user_capabilities.start; vm.showRelaunch = job.type !== 'system_job' && job.summary_fields.user_capabilities.start;
vm.showDropdown = job.type === 'job' && job.failed === true; vm.showDropdown = job.type === 'job' && job.failed === true;
vm.createDropdown(); vm.createDropdown();
@@ -46,27 +137,97 @@ function atRelaunchCtrl (RelaunchJob, InitiatePlaybookRun, strings, $scope) {
}; };
vm.relaunchJob = () => { vm.relaunchJob = () => {
let typeId;
if (job.type === 'inventory_update') { if (job.type === 'inventory_update') {
typeId = job.inventory_source; const inventorySource = new InventorySource();
} else if (job.type === 'project_update') {
typeId = job.project;
} else if (job.type === 'job' || job.type === 'system_job'
|| job.type === 'ad_hoc_command' || job.type === 'workflow_job') {
typeId = job.id;
}
RelaunchJob({ scope, id: typeId, type: job.type, name: job.name }); inventorySource.getUpdate(job.inventory_source)
.then((getUpdateRes) => {
if (getUpdateRes.data.can_update) {
inventorySource.postUpdate(job.inventory_source)
.then((postUpdateRes) => {
if (!$state.includes('jobs')) {
$state.go('inventorySyncStdout', { id: postUpdateRes.data.id }, { reload: true });
}
});
} else {
Alert(
'Permission Denied', 'You do not have permission to sync this inventory source. Please contact your system administrator.',
'alert-danger'
);
}
});
} else if (job.type === 'project_update') {
const project = new Project();
project.getUpdate(job.project)
.then((getUpdateRes) => {
if (getUpdateRes.data.can_update) {
project.postUpdate(job.project)
.then((postUpdateRes) => {
if (!$state.includes('jobs')) {
$state.go('scmUpdateStdout', { id: postUpdateRes.data.id }, { reload: true });
}
});
} else {
Alert(
'Permission Denied', 'You do not have access to update this project. Please contact your system administrator.',
'alert-danger'
);
}
});
} else if (job.type === 'workflow_job') {
const workflowJob = new WorkflowJob();
workflowJob.postRelaunch({
id: job.id
}).then((launchRes) => {
if (!$state.includes('jobs')) {
$state.go('workflowResults', { id: launchRes.data.id }, { reload: true });
}
});
} else if (job.type === 'ad_hoc_command') {
const adHocCommand = new AdHocCommand();
adHocCommand.getRelaunch({
id: job.id
}).then((getRelaunchRes) => {
if (
getRelaunchRes.data.passwords_needed_to_start &&
getRelaunchRes.data.passwords_needed_to_start.length > 0
) {
AdhocRun({ scope, project_id: job.id, relaunch: true });
} else {
adHocCommand.postRelaunch({
id: job.id
}).then((launchRes) => {
if (!$state.includes('jobs')) {
$state.go('adHocJobStdout', { id: launchRes.data.id }, { reload: true });
}
});
}
});
} else if (job.type === 'job') {
checkRelaunchPlaybook();
}
}; };
vm.relaunchOn = (option) => { vm.relaunchOn = (option) => {
InitiatePlaybookRun({ checkRelaunchPlaybook(option);
scope, };
id: job.id,
relaunch: true, vm.relaunchJobWithPassword = () => {
job_type: job.type, jobObj.postRelaunch({
host_type: (option.name).toLowerCase() id: vm.promptData.job,
relaunchData: PromptService.bundlePromptDataForRelaunch(vm.promptData)
}).then((launchRes) => {
if (!$state.includes('jobs')) {
$state.go('jobResult', { id: launchRes.data.job }, { reload: true });
}
}).catch(({ data, status }) => {
ProcessErrors($scope, data, status, null, {
hdr: 'Error!',
msg: `Error relaunching job. POST returned status: ${status}`
});
}); });
}; };
} }

View File

@@ -30,4 +30,5 @@
ng-if="!vm.showDropdown"> ng-if="!vm.showDropdown">
<i class="{{ vm.icon }}"></i> <i class="{{ vm.icon }}"></i>
</button> </button>
</div> <prompt prompt-data="vm.promptData" on-finish="vm.relaunchJobWithPassword()"></launch>
</div>

View File

@@ -0,0 +1,44 @@
let Base;
let $http;
function getRelaunch (params) {
const req = {
method: 'GET',
url: `${this.path}${params.id}/relaunch/`
};
return $http(req);
}
function postRelaunch (params) {
const req = {
method: 'POST',
url: `${this.path}${params.id}/relaunch/`
};
return $http(req);
}
function AdHocCommandModel (method, resource, config) {
Base.call(this, 'ad_hoc_commands');
this.Constructor = AdHocCommandModel;
this.postRelaunch = postRelaunch.bind(this);
this.getRelaunch = getRelaunch.bind(this);
return this.create(method, resource, config);
}
function AdHocCommandModelLoader (BaseModel, _$http_) {
Base = BaseModel;
$http = _$http_;
return AdHocCommandModel;
}
AdHocCommandModelLoader.$inject = [
'BaseModel',
'$http'
];
export default AdHocCommandModelLoader;

View File

@@ -1,5 +1,6 @@
let Base; let Base;
let WorkflowJobTemplateNode; let WorkflowJobTemplateNode;
let $http;
function setDependentResources (id) { function setDependentResources (id) {
this.dependentResources = [ this.dependentResources = [
@@ -12,28 +13,51 @@ function setDependentResources (id) {
]; ];
} }
function getUpdate (id) {
const req = {
method: 'GET',
url: `${this.path}${id}/update/`
};
return $http(req);
}
function postUpdate (id) {
const req = {
method: 'POST',
url: `${this.path}${id}/update/`
};
return $http(req);
}
function InventorySourceModel (method, resource, config) { function InventorySourceModel (method, resource, config) {
Base.call(this, 'inventory_sources'); Base.call(this, 'inventory_sources');
this.Constructor = InventorySourceModel; this.Constructor = InventorySourceModel;
this.setDependentResources = setDependentResources.bind(this); this.setDependentResources = setDependentResources.bind(this);
this.getUpdate = getUpdate.bind(this);
this.postUpdate = postUpdate.bind(this);
return this.create(method, resource, config); return this.create(method, resource, config);
} }
function InventorySourceModelLoader ( function InventorySourceModelLoader (
BaseModel, BaseModel,
WorkflowJobTemplateNodeModel WorkflowJobTemplateNodeModel,
_$http_
) { ) {
Base = BaseModel; Base = BaseModel;
WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel; WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel;
$http = _$http_;
return InventorySourceModel; return InventorySourceModel;
} }
InventorySourceModelLoader.$inject = [ InventorySourceModelLoader.$inject = [
'BaseModel', 'BaseModel',
'WorkflowJobTemplateNodeModel' 'WorkflowJobTemplateNodeModel',
'$http'
]; ];
export default InventorySourceModelLoader; export default InventorySourceModelLoader;

View File

@@ -1,21 +1,48 @@
let Base; let Base;
let $http;
function getRelaunch (params) {
const req = {
method: 'GET',
url: `${this.path}${params.id}/relaunch/`
};
return $http(req);
}
function postRelaunch (params) {
const req = {
method: 'POST',
url: `${this.path}${params.id}/relaunch/`
};
if (params.relaunchData) {
req.data = params.relaunchData;
}
return $http(req);
}
function JobModel (method, resource, config) { function JobModel (method, resource, config) {
Base.call(this, 'jobs'); Base.call(this, 'jobs');
this.Constructor = JobModel; this.Constructor = JobModel;
this.postRelaunch = postRelaunch.bind(this);
this.getRelaunch = getRelaunch.bind(this);
return this.create(method, resource, config); return this.create(method, resource, config);
} }
function JobModelLoader (BaseModel) { function JobModelLoader (BaseModel, _$http_) {
Base = BaseModel; Base = BaseModel;
$http = _$http_;
return JobModel; return JobModel;
} }
JobModelLoader.$inject = [ JobModelLoader.$inject = [
'BaseModel' 'BaseModel',
'$http'
]; ];
export default JobModelLoader; export default JobModelLoader;

View File

@@ -2,6 +2,7 @@ let Base;
let JobTemplate; let JobTemplate;
let WorkflowJobTemplateNode; let WorkflowJobTemplateNode;
let InventorySource; let InventorySource;
let $http;
function setDependentResources (id) { function setDependentResources (id) {
this.dependentResources = [ this.dependentResources = [
@@ -26,11 +27,31 @@ function setDependentResources (id) {
]; ];
} }
function getUpdate (id) {
const req = {
method: 'GET',
url: `${this.path}${id}/update/`
};
return $http(req);
}
function postUpdate (id) {
const req = {
method: 'POST',
url: `${this.path}${id}/update/`
};
return $http(req);
}
function ProjectModel (method, resource, config) { function ProjectModel (method, resource, config) {
Base.call(this, 'projects'); Base.call(this, 'projects');
this.Constructor = ProjectModel; this.Constructor = ProjectModel;
this.setDependentResources = setDependentResources.bind(this); this.setDependentResources = setDependentResources.bind(this);
this.getUpdate = getUpdate.bind(this);
this.postUpdate = postUpdate.bind(this);
return this.create(method, resource, config); return this.create(method, resource, config);
} }
@@ -40,11 +61,13 @@ function ProjectModelLoader (
JobTemplateModel, JobTemplateModel,
WorkflowJobTemplateNodeModel, WorkflowJobTemplateNodeModel,
InventorySourceModel, InventorySourceModel,
_$http_
) { ) {
Base = BaseModel; Base = BaseModel;
JobTemplate = JobTemplateModel; JobTemplate = JobTemplateModel;
WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel; WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel;
InventorySource = InventorySourceModel; InventorySource = InventorySourceModel;
$http = _$http_;
return ProjectModel; return ProjectModel;
} }
@@ -53,7 +76,8 @@ ProjectModelLoader.$inject = [
'BaseModel', 'BaseModel',
'JobTemplateModel', 'JobTemplateModel',
'WorkflowJobTemplateNodeModel', 'WorkflowJobTemplateNodeModel',
'InventorySourceModel' 'InventorySourceModel',
'$http'
]; ];
export default ProjectModelLoader; export default ProjectModelLoader;

View File

@@ -1,21 +1,34 @@
let Base; let Base;
let $http;
function postRelaunch (params) {
const req = {
method: 'POST',
url: `${this.path}${params.id}/relaunch/`
};
return $http(req);
}
function WorkflowJobModel (method, resource, config) { function WorkflowJobModel (method, resource, config) {
Base.call(this, 'workflow_jobs'); Base.call(this, 'workflow_jobs');
this.Constructor = WorkflowJobModel; this.Constructor = WorkflowJobModel;
this.postRelaunch = postRelaunch.bind(this);
return this.create(method, resource, config); return this.create(method, resource, config);
} }
function WorkflowJobModelLoader (BaseModel) { function WorkflowJobModelLoader (BaseModel, _$http_) {
Base = BaseModel; Base = BaseModel;
$http = _$http_;
return WorkflowJobModel; return WorkflowJobModel;
} }
WorkflowJobModelLoader.$inject = [ WorkflowJobModelLoader.$inject = [
'BaseModel' 'BaseModel',
'$http'
]; ];
export default WorkflowJobModelLoader; export default WorkflowJobModelLoader;

View File

@@ -1,21 +1,85 @@
let Base; let Base;
let $http;
function optionsLaunch (id) {
const req = {
method: 'OPTIONS',
url: `${this.path}${id}/launch/`
};
return $http(req);
}
function getLaunch (id) {
const req = {
method: 'GET',
url: `${this.path}${id}/launch/`
};
return $http(req)
.then(res => {
this.model.launch.GET = res.data;
return res;
});
}
function postLaunch (params) {
const req = {
method: 'POST',
url: `${this.path}${params.id}/launch/`
};
if (params.launchData) {
req.data = params.launchData;
}
return $http(req);
}
function getSurveyQuestions (id) {
const req = {
method: 'GET',
url: `${this.path}${id}/survey_spec/`
};
return $http(req);
}
function canLaunchWithoutPrompt () {
const launchData = this.model.launch.GET;
return (
launchData.can_start_without_user_input &&
!launchData.survey_enabled
);
}
function WorkflowJobTemplateModel (method, resource, config) { function WorkflowJobTemplateModel (method, resource, config) {
Base.call(this, 'workflow_job_templates'); Base.call(this, 'workflow_job_templates');
this.Constructor = WorkflowJobTemplateModel; this.Constructor = WorkflowJobTemplateModel;
this.optionsLaunch = optionsLaunch.bind(this);
this.getLaunch = getLaunch.bind(this);
this.postLaunch = postLaunch.bind(this);
this.getSurveyQuestions = getSurveyQuestions.bind(this);
this.canLaunchWithoutPrompt = canLaunchWithoutPrompt.bind(this);
this.model.launch = {};
return this.create(method, resource, config); return this.create(method, resource, config);
} }
function WorkflowJobTemplateModelLoader (BaseModel) { function WorkflowJobTemplateModelLoader (BaseModel, _$http_) {
Base = BaseModel; Base = BaseModel;
$http = _$http_;
return WorkflowJobTemplateModel; return WorkflowJobTemplateModel;
} }
WorkflowJobTemplateModelLoader.$inject = [ WorkflowJobTemplateModelLoader.$inject = [
'BaseModel' 'BaseModel',
'$http'
]; ];
export default WorkflowJobTemplateModelLoader; export default WorkflowJobTemplateModelLoader;

View File

@@ -1,6 +1,7 @@
import atLibServices from '~services'; import atLibServices from '~services';
import Application from '~models/Application'; import Application from '~models/Application';
import AdHocCommand from '~models/AdHocCommand';
import Base from '~models/Base'; import Base from '~models/Base';
import Config from '~models/Config'; import Config from '~models/Config';
import Credential from '~models/Credential'; import Credential from '~models/Credential';
@@ -30,6 +31,7 @@ angular
atLibServices atLibServices
]) ])
.service('ApplicationModel', Application) .service('ApplicationModel', Application)
.service('AdHocCommandModel', AdHocCommand)
.service('BaseModel', Base) .service('BaseModel', Base)
.service('ConfigModel', Config) .service('ConfigModel', Config)
.service('CredentialModel', Credential) .service('CredentialModel', Credential)

View File

@@ -1,10 +1,13 @@
/* jshint unused: vars */ /* jshint unused: vars */
export default export default
[ 'InitiatePlaybookRun', [ 'templateUrl',
'templateUrl',
'$state', '$state',
'Alert', 'Alert',
function JobTemplatesList(InitiatePlaybookRun, templateUrl, $state, Alert) { 'JobTemplateModel',
'WorkflowJobTemplateModel',
'PromptService',
'ProcessErrors',
function JobTemplatesList(templateUrl, $state, Alert, JobTemplate, WorkflowJobTemplate, PromptService, ProcessErrors) {
return { return {
restrict: 'E', restrict: 'E',
link: link, link: link,
@@ -15,6 +18,9 @@ export default
}; };
function link(scope, element, attr) { function link(scope, element, attr) {
const jobTemplate = new JobTemplate();
const workflowTemplate = new WorkflowJobTemplate();
scope.$watch("data", function(data) { scope.$watch("data", function(data) {
if (data) { if (data) {
if (data.length > 0) { if (data.length > 0) {
@@ -42,21 +48,131 @@ export default
scope.launchTemplate = function(template){ scope.launchTemplate = function(template){
if(template) { if(template) {
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) { if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
InitiatePlaybookRun({ scope: scope, id: template.id, job_type: 'job_template' }); const selectedJobTemplate = jobTemplate.create();
} const preLaunchPromises = [
else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) { selectedJobTemplate.getLaunch(template.id),
InitiatePlaybookRun({ scope: scope, id: template.id, job_type: 'workflow_job_template' }); selectedJobTemplate.optionsLaunch(template.id),
} ];
else {
// Something went wrong - Let the user know that we're unable to launch because we don't know Promise.all(preLaunchPromises)
// what type of job template this is .then(([launchData, launchOptions]) => {
Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while launching.'); if (selectedJobTemplate.canLaunchWithoutPrompt()) {
} return selectedJobTemplate
.postLaunch({ id: template.id })
.then(({ data }) => {
$state.go('jobResult', { id: data.job }, { reload: true });
});
}
const promptData = {
launchConf: launchData.data,
launchOptions: launchOptions.data,
template: template.id,
templateType: template.type,
prompts: PromptService.processPromptValues({
launchConf: launchData.data,
launchOptions: launchOptions.data
}),
triggerModalOpen: true,
};
if (launchData.data.survey_enabled) {
selectedJobTemplate.getSurveyQuestions(template.id)
.then(({ data }) => {
const processed = PromptService.processSurveyQuestions({ surveyQuestions: data.spec });
promptData.surveyQuestions = processed.surveyQuestions;
scope.promptData = promptData;
});
} else {
scope.promptData = promptData;
}
});
}
else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
const selectedWorkflowJobTemplate = workflowTemplate.create();
const preLaunchPromises = [
selectedWorkflowJobTemplate.getLaunch(template.id),
selectedWorkflowJobTemplate.optionsLaunch(template.id),
];
Promise.all(preLaunchPromises)
.then(([launchData, launchOptions]) => {
if (selectedWorkflowJobTemplate.canLaunchWithoutPrompt()) {
return selectedWorkflowJobTemplate
.postLaunch({ id: template.id })
.then(({ data }) => {
$state.go('workflowResults', { id: data.workflow_job }, { reload: true });
});
}
const promptData = {
launchConf: launchData.data,
launchOptions: launchOptions.data,
template: template.id,
templateType: template.type,
prompts: PromptService.processPromptValues({
launchConf: launchData.data,
launchOptions: launchOptions.data
}),
triggerModalOpen: true,
};
if (launchData.data.survey_enabled) {
selectedWorkflowJobTemplate.getSurveyQuestions(template.id)
.then(({ data }) => {
const processed = PromptService.processSurveyQuestions({ surveyQuestions: data.spec });
promptData.surveyQuestions = processed.surveyQuestions;
scope.promptData = promptData;
});
} else {
scope.promptData = promptData;
}
});
} }
else { else {
Alert('Error: Unable to launch template', 'Template parameter is missing'); // Something went wrong - Let the user know that we're unable to launch because we don't know
// what type of job template this is
Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while launching.');
} }
}
else {
Alert('Error: Unable to launch template', 'Template parameter is missing');
}
};
scope.launchJob = () => {
const jobLaunchData = PromptService.bundlePromptDataForLaunch(scope.promptData);
// If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything.
if(_.isEmpty(jobLaunchData.extra_vars) && !(scope.promptData.launchConf.ask_variables_on_launch && scope.promptData.launchConf.survey_enabled && scope.promptData.surveyQuestions.length > 0)){
delete jobLaunchData.extra_vars;
}
if(scope.promptData.templateType === 'job_template') {
jobTemplate.create().postLaunch({
id: scope.promptData.template,
launchData: jobLaunchData
})
.then((launchRes) => {
$state.go('jobResult', { id: launchRes.data.job }, { reload: true });
})
.catch(({data, status}) => {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to launch job template: ' + status });
});
} else if(scope.promptData.templateType === 'workflow_job_template') {
workflowTemplate.create().postLaunch({
id: scope.promptData.template,
launchData: jobLaunchData
})
.then((launchRes) => {
$state.go('workflowResults', { id: launchRes.data.workflow_job }, { reload: true });
})
.catch(({data, status}) => {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to launch workflow job template: ' + status });
});
}
}; };
scope.editTemplate = function (template) { scope.editTemplate = function (template) {

View File

@@ -56,3 +56,4 @@
<translate>You can create a job template <a href="#/templates/add_job_template">here</a>.</translate></p> <translate>You can create a job template <a href="#/templates/add_job_template">here</a>.</translate></p>
</div> </div>
</div> </div>
<prompt prompt-data="promptData" on-finish="launchJob()"></launch>

View File

@@ -230,25 +230,25 @@ function adhocController($q, $scope, $stateParams,
$scope.removeStartAdhocRun(); $scope.removeStartAdhocRun();
} }
$scope.removeStartAdhocRun = $scope.$on('StartAdhocRun', function() { $scope.removeStartAdhocRun = $scope.$on('StartAdhocRun', function() {
var password; var password;
for (password in $scope.passwords) { for (password in $scope.passwords) {
data[$scope.passwords[password]] = $scope[ data[$scope.passwords[password]] = $scope[
$scope.passwords[password] $scope.passwords[password]
]; ];
} }
// Launch the adhoc job // Launch the adhoc job
Rest.setUrl(GetBasePath('inventory') + id + '/ad_hoc_commands/'); Rest.setUrl(GetBasePath('inventory') + id + '/ad_hoc_commands/');
Rest.post(data) Rest.post(data)
.then(({data}) => { .then(({data}) => {
Wait('stop'); Wait('stop');
$state.go('adHocJobStdout', {id: data.id}); $state.go('adHocJobStdout', {id: data.id});
}) })
.catch(({data, status}) => { .catch(({data, status}) => {
ProcessErrors($scope, data, status, adhocForm, { ProcessErrors($scope, data, status, adhocForm, {
hdr: 'Error!', hdr: 'Error!',
msg: 'Failed to launch adhoc command. POST ' + msg: 'Failed to launch adhoc command. POST ' +
'returned status: ' + status }); 'returned status: ' + status });
}); });
}); });
if ($scope.removeCreateLaunchDialog) { if ($scope.removeCreateLaunchDialog) {

View File

@@ -71,12 +71,8 @@ export default ['i18n', function(i18n) {
columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4', columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4',
submit: { submit: {
icon: 'icon-rocket',
mode: 'all',
ngClick: 'relaunchJob($event, completed_job.id)',
awToolTip: i18n._('Relaunch using the same parameters'),
dataPlacement: 'top',
ngShow: "!completed_job.type == 'system_job' || completed_job.summary_fields.user_capabilities.start", ngShow: "!completed_job.type == 'system_job' || completed_job.summary_fields.user_capabilities.start",
// uses the at-relaunch directive
relaunch: true relaunch: true
}, },
"delete": { "delete": {

View File

@@ -237,10 +237,6 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy
jobResultsService.cancelJob($scope.job); jobResultsService.cancelJob($scope.job);
}; };
$scope.relaunchJob = function() {
jobResultsService.relaunchJob($scope);
};
$scope.lessLabels = false; $scope.lessLabels = false;
$scope.toggleLessLabels = function() { $scope.toggleLessLabels = function() {
if (!$scope.lessLabels) { if (!$scope.lessLabels) {

View File

@@ -47,7 +47,7 @@ export default {
return [preScope, eventOn]; return [preScope, eventOn];
}], }],
// the GET for the particular job // the GET for the particular job
jobData: ['Rest', 'GetBasePath', '$stateParams', '$q', '$state', 'Alert', 'jobResultsService', function(Rest, GetBasePath, $stateParams, $q, $state, Alert, jobResultsService) { jobData: ['jobResultsService', '$stateParams', function(jobResultsService, $stateParams) {
return jobResultsService.getJobData($stateParams.id); return jobResultsService.getJobData($stateParams.id);
}], }],
Dataset: ['QuerySet', '$stateParams', 'jobData', Dataset: ['QuerySet', '$stateParams', 'jobData',

View File

@@ -5,8 +5,8 @@
*************************************************/ *************************************************/
export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'InitiatePlaybookRun', 'GetBasePath', 'Alert', '$rootScope', 'i18n', export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'GetBasePath', 'Alert', '$rootScope', 'i18n',
function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybookRun, GetBasePath, Alert, $rootScope, i18n) { function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, GetBasePath, Alert, $rootScope, i18n) {
var val = { var val = {
// the playbook_on_stats event returns the count data in a weird format. // the playbook_on_stats event returns the count data in a weird format.
// format to what we need! // format to what we need!
@@ -162,10 +162,6 @@ function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybo
actionText: i18n._('PROCEED') actionText: i18n._('PROCEED')
}); });
}, },
relaunchJob: function(scope) {
InitiatePlaybookRun({ scope: scope, id: scope.job.id,
relaunch: true });
},
getJobData: function(id){ getJobData: function(id){
var val = $q.defer(); var val = $q.defer();

View File

@@ -24,13 +24,6 @@ export default
} }
}); });
if (scope.removePromptForPasswords) {
scope.removePromptForPasswords();
}
scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() {
PromptForPasswords({ scope: scope, passwords: inventory_source.passwords_needed_to_update, callback: 'StartTheUpdate' });
});
if (scope.removeStartTheUpdate) { if (scope.removeStartTheUpdate) {
scope.removeStartTheUpdate(); scope.removeStartTheUpdate();
} }
@@ -49,13 +42,7 @@ export default
else { else {
inventory_source = data; inventory_source = data;
if (data.can_update) { if (data.can_update) {
if (data.passwords_needed_to_update) { scope.$emit('StartTheUpdate', {});
Wait('stop');
scope.$emit('PromptForPasswords');
}
else {
scope.$emit('StartTheUpdate', {});
}
} else { } else {
Wait('stop'); Wait('stop');
Alert('Error Launching Sync', 'Unable to execute the inventory sync. Please contact your system administrator.', Alert('Error Launching Sync', 'Unable to execute the inventory sync. Please contact your system administrator.',

View File

@@ -25,13 +25,6 @@ export default
} }
}); });
if (scope.removePromptForPasswords) {
scope.removePromptForPasswords();
}
scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() {
PromptForPasswords({ scope: scope, passwords: project.passwords_needed_to_update, callback: 'StartTheUpdate' });
});
if (scope.removeStartTheUpdate) { if (scope.removeStartTheUpdate) {
scope.removeStartTheUpdate(); scope.removeStartTheUpdate();
} }
@@ -46,13 +39,7 @@ export default
.then(({data}) => { .then(({data}) => {
project = data; project = data;
if (project.can_update) { if (project.can_update) {
if (project.passwords_needed_to_updated) { scope.$emit('StartTheUpdate', {});
Wait('stop');
scope.$emit('PromptForPasswords');
}
else {
scope.$emit('StartTheUpdate', {});
}
} }
else { else {
Alert('Permission Denied', 'You do not have access to update this project. Please contact your system administrator.', Alert('Permission Denied', 'You do not have access to update this project. Please contact your system administrator.',

View File

@@ -92,12 +92,8 @@ export default ['i18n', function(i18n) {
dataPlacement: "top" dataPlacement: "top"
}, },
submit: { submit: {
icon: 'icon-rocket',
mode: 'all',
ngClick: 'relaunchJob($event, job.id)',
awToolTip: i18n._('Relaunch using the same parameters'),
dataPlacement: 'top',
ngShow: "!(job.type == 'system_job') && job.summary_fields.user_capabilities.start", ngShow: "!(job.type == 'system_job') && job.summary_fields.user_capabilities.start",
// uses the at-relaunch directive
relaunch: true, relaunch: true,
}, },
cancel: { cancel: {

View File

@@ -1,31 +0,0 @@
export default
function JobStatusToolTip() {
return function(status) {
var toolTip;
switch (status) {
case 'successful':
case 'success':
toolTip = 'There were no failed tasks.';
break;
case 'failed':
toolTip = 'Some tasks encountered errors.';
break;
case 'canceled':
toolTip = 'Stopped by user request.';
break;
case 'new':
toolTip = 'In queue, waiting on task manager.';
break;
case 'waiting':
toolTip = 'SCM Update or Inventory Update is executing.';
break;
case 'pending':
toolTip = 'Not in queue, waiting on task manager.';
break;
case 'running':
toolTip = 'Playbook tasks executing.';
break;
}
return toolTip;
};
}

View File

@@ -1,49 +0,0 @@
export default
function JobsListUpdate() {
return function(params) {
var scope = params.scope,
parent_scope = params.parent_scope,
list = params.list;
scope[list.name].forEach(function(item, item_idx) {
var fld, field,
itm = scope[list.name][item_idx];
//if (item.type === 'inventory_update') {
// itm.name = itm.name.replace(/^.*?:/,'').replace(/^: /,'');
//}
// Set the item type label
if (list.fields.type) {
parent_scope.type_choices.forEach(function(choice) {
if (choice.value === item.type) {
itm.type_label = choice.label;
}
});
}
// Set the job status label
parent_scope.status_choices.forEach(function(status) {
if (status.value === item.status) {
itm.status_label = status.label;
}
});
if (list.name === 'completed_jobs' || list.name === 'running_jobs') {
itm.status_tip = itm.status_label + '. Click for details.';
}
else if (list.name === 'queued_jobs') {
itm.status_tip = 'Pending';
}
// Copy summary_field values
for (field in list.fields) {
fld = list.fields[field];
if (fld.sourceModel) {
if (itm.summary_fields[fld.sourceModel]) {
itm[field] = itm.summary_fields[fld.sourceModel][fld.sourceField];
}
}
}
});
};
}

View File

@@ -1,11 +0,0 @@
export default
function RelaunchAdhoc(AdhocRun) {
return function(params) {
var scope = params.scope,
id = params.id;
AdhocRun({ scope: scope, project_id: id, relaunch: true });
};
}
RelaunchAdhoc.$inject =
[ 'AdhocRun' ];

View File

@@ -1,26 +0,0 @@
export default
function RelaunchInventory(Wait, Rest, InventoryUpdate, ProcessErrors, GetBasePath) {
return function(params) {
var scope = params.scope,
id = params.id,
url = GetBasePath('inventory_sources') + id + '/';
Wait('start');
Rest.setUrl(url);
Rest.get()
.then(({data}) => {
InventoryUpdate({
scope: scope,
url: data.related.update
});
})
.catch(({data, status}) => {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' +
url + ' GET returned: ' + status });
});
};
}
RelaunchInventory.$inject =
[ 'Wait', 'Rest',
'InventoryUpdate', 'ProcessErrors', 'GetBasePath'
];

View File

@@ -1,28 +0,0 @@
export default
function RelaunchJob(RelaunchInventory, RelaunchPlaybook, RelaunchSCM, RelaunchAdhoc) {
return function(params) {
var scope = params.scope,
id = params.id,
type = params.type,
name = params.name;
if (type === 'inventory_update') {
RelaunchInventory({ scope: scope, id: id});
}
else if (type === 'ad_hoc_command') {
RelaunchAdhoc({ scope: scope, id: id, name: name });
}
else if (type === 'job' || type === 'system_job' || type === 'workflow_job') {
RelaunchPlaybook({ scope: scope, id: id, name: name, job_type: type });
}
else if (type === 'project_update') {
RelaunchSCM({ scope: scope, id: id });
}
};
}
RelaunchJob.$inject =
[ 'RelaunchInventory',
'RelaunchPlaybook',
'RelaunchSCM',
'RelaunchAdhoc'
];

View File

@@ -1,12 +0,0 @@
export default
function RelaunchPlaybook(InitiatePlaybookRun) {
return function(params) {
var scope = params.scope,
id = params.id,
job_type = params.job_type;
InitiatePlaybookRun({ scope: scope, id: id, relaunch: true, job_type: job_type });
};
}
RelaunchPlaybook.$inject =
[ 'InitiatePlaybookRun' ];

View File

@@ -1,11 +0,0 @@
export default
function RelaunchSCM(ProjectUpdate) {
return function(params) {
var scope = params.scope,
id = params.id;
ProjectUpdate({ scope: scope, project_id: id });
};
}
RelaunchSCM.$inject =
[ 'ProjectUpdate' ];

View File

@@ -11,11 +11,15 @@
*/ */
export default ['$state', '$rootScope', '$scope', '$stateParams', export default ['$state', '$rootScope', '$scope', '$stateParams',
'Find', 'DeleteJob', 'RelaunchJob', 'Find', 'DeleteJob',
'GetBasePath', 'Dataset', 'QuerySet', 'ListDefinition', '$interpolate', 'GetBasePath', 'Dataset', 'QuerySet', 'ListDefinition', '$interpolate',
'WorkflowJobModel', 'ProjectModel', 'Alert', 'InventorySourceModel',
'AdHocCommandModel', 'JobModel',
function($state, $rootScope, $scope, $stateParams, function($state, $rootScope, $scope, $stateParams,
Find, DeleteJob, RelaunchJob, Find, DeleteJob,
GetBasePath, Dataset, qs, ListDefinition, $interpolate) { GetBasePath, Dataset, qs, ListDefinition, $interpolate,
WorkflowJob, Project, Alert, InventorySource,
AdHocCommand, Job) {
var list = ListDefinition; var list = ListDefinition;
@@ -95,42 +99,6 @@
DeleteJob({ scope: $scope, id: id }); DeleteJob({ scope: $scope, id: id });
}; };
$scope.relaunchJob = function(event, id) {
let job, typeId, jobs;
try {
$(event.target).tooltip('hide');
} catch (e) {
//ignore
}
if ($scope.completed_jobs) {
jobs = $scope.completed_jobs;
}
else if ($scope.running_jobs) {
jobs = $scope.running_jobs;
}
else if ($scope.queued_jobs) {
jobs = $scope.queued_jobs;
}
else if ($scope.all_jobs) {
jobs = $scope.all_jobs;
}
else if ($scope.jobs) {
jobs = $scope.jobs;
}
job = Find({list: jobs, key: 'id', val: id });
if (job.type === 'inventory_update') {
typeId = job.inventory_source;
} else if (job.type === 'project_update') {
typeId = job.project;
} else if (job.type === 'job' || job.type === "system_job" || job.type === 'ad_hoc_command' || job.type === 'workflow_job') {
typeId = job.id;
}
RelaunchJob({ scope: $scope, id: typeId, type: job.type, name: job.name });
};
$scope.viewjobResults = function(job) { $scope.viewjobResults = function(job) {
var goTojobResults = function(state) { var goTojobResults = function(state) {

View File

@@ -7,13 +7,6 @@
import jobsList from './jobs-list.controller'; import jobsList from './jobs-list.controller';
import jobsRoute from './jobs.route'; import jobsRoute from './jobs.route';
import DeleteJob from './factories/delete-job.factory'; import DeleteJob from './factories/delete-job.factory';
import JobStatusToolTip from './factories/job-status-tool-tip.factory';
import JobsListUpdate from './factories/jobs-list-update.factory';
import RelaunchAdhoc from './factories/relaunch-adhoc.factory';
import RelaunchInventory from './factories/relaunch-inventory.factory';
import RelaunchJob from './factories/relaunch-job.factory';
import RelaunchPlaybook from './factories/relaunch-playbook.factory';
import RelaunchSCM from './factories/relaunch-scm.factory';
import AllJobsList from './all-jobs.list'; import AllJobsList from './all-jobs.list';
export default export default
@@ -23,11 +16,4 @@ export default
}]) }])
.controller('JobsList', jobsList) .controller('JobsList', jobsList)
.factory('DeleteJob', DeleteJob) .factory('DeleteJob', DeleteJob)
.factory('JobStatusToolTip', JobStatusToolTip)
.factory('JobsListUpdate', JobsListUpdate)
.factory('RelaunchAdhoc', RelaunchAdhoc)
.factory('RelaunchInventory', RelaunchInventory)
.factory('RelaunchJob', RelaunchJob)
.factory('RelaunchPlaybook', RelaunchPlaybook)
.factory('RelaunchSCM', RelaunchSCM)
.factory('AllJobsList', AllJobsList); .factory('AllJobsList', AllJobsList);

View File

@@ -6,11 +6,11 @@
export default ['$scope', '$rootScope', export default ['$scope', '$rootScope',
'$stateParams', 'Rest', 'ProcessErrors', '$stateParams', 'Rest', 'ProcessErrors',
'GetBasePath', 'InitiatePlaybookRun', 'Wait', 'TemplateCopyService', 'GetBasePath', 'InitiatePlaybookRun', 'Wait',
'$state', 'OrgJobTemplateList', 'OrgJobTemplateDataset', 'QuerySet', '$state', 'OrgJobTemplateList', 'OrgJobTemplateDataset', 'QuerySet',
function($scope, $rootScope, function($scope, $rootScope,
$stateParams, Rest, ProcessErrors, $stateParams, Rest, ProcessErrors,
GetBasePath, InitiatePlaybookRun, Wait, TemplateCopyService, GetBasePath, InitiatePlaybookRun, Wait,
$state, OrgJobTemplateList, Dataset, qs) { $state, OrgJobTemplateList, Dataset, qs) {
var list = OrgJobTemplateList, var list = OrgJobTemplateList,
@@ -81,24 +81,24 @@ export default ['$scope', '$rootScope',
$state.go('jobTemplateSchedules', { id: id }); $state.go('jobTemplateSchedules', { id: id });
}; };
$scope.copyTemplate = function(id) { // $scope.copyTemplate = function(id) {
Wait('start'); // Wait('start');
TemplateCopyService.get(id) // TemplateCopyService.get(id)
.then((data) => { // .then((data) => {
TemplateCopyService.set(data.results) // TemplateCopyService.set(data.results)
.then((results) => { // .then((results) => {
Wait('stop'); // Wait('stop');
if(results.type && results.type === 'job_template') { // if(results.type && results.type === 'job_template') {
$state.go('templates.editJobTemplate', {job_template_id: results.id}, {reload: true}); // $state.go('templates.editJobTemplate', {job_template_id: results.id}, {reload: true});
} // }
}); // });
}) // })
.catch(({data, status}) => { // .catch(({data, status}) => {
ProcessErrors($rootScope, data, status, null, {hdr: 'Error!', // ProcessErrors($rootScope, data, status, null, {hdr: 'Error!',
msg: 'Call failed. Return status: '+ status}); // msg: 'Call failed. Return status: '+ status});
}); // });
//
}; // };
} }
]; ];

View File

@@ -4,23 +4,79 @@
* All Rights Reserved * All Rights Reserved
*************************************************/ *************************************************/
export function PortalModeJobTemplatesController($scope, PortalJobTemplateList, InitiatePlaybookRun, Dataset) { export function PortalModeJobTemplatesController($scope, PortalJobTemplateList, Dataset, $state, PromptService, JobTemplate, ProcessErrors) {
var list = PortalJobTemplateList; var list = PortalJobTemplateList;
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
init(); const jobTemplate = new JobTemplate();
function init() {
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
}
$scope.submitJob = function(id) { $scope.submitJob = function(id) {
InitiatePlaybookRun({ scope: $scope, id: id, job_type: 'job_template' }); const selectedJobTemplate = jobTemplate.create();
const preLaunchPromises = [
selectedJobTemplate.getLaunch(id),
selectedJobTemplate.optionsLaunch(id),
];
Promise.all(preLaunchPromises)
.then(([launchData, launchOptions]) => {
if (selectedJobTemplate.canLaunchWithoutPrompt()) {
return selectedJobTemplate
.postLaunch({ id: id })
.then(({ data }) => {
$state.go('jobResult', { id: data.job }, { reload: true });
});
}
const promptData = {
launchConf: launchData.data,
launchOptions: launchOptions.data,
template: id,
templateType: 'job_template',
prompts: PromptService.processPromptValues({
launchConf: launchData.data,
launchOptions: launchOptions.data
}),
triggerModalOpen: true,
};
if (launchData.data.survey_enabled) {
selectedJobTemplate.getSurveyQuestions(id)
.then(({ data }) => {
const processed = PromptService.processSurveyQuestions({ surveyQuestions: data.spec });
promptData.surveyQuestions = processed.surveyQuestions;
$scope.promptData = promptData;
});
} else {
$scope.promptData = promptData;
}
});
};
$scope.launchJob = () => {
const jobLaunchData = PromptService.bundlePromptDataForLaunch($scope.promptData);
// If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything.
if(_.isEmpty(jobLaunchData.extra_vars) && !($scope.promptData.launchConf.ask_variables_on_launch && $scope.promptData.launchConf.survey_enabled && $scope.promptData.surveyQuestions.length > 0)){
delete jobLaunchData.extra_vars;
}
jobTemplate.create().postLaunch({
id: $scope.promptData.template,
launchData: jobLaunchData
})
.then((launchRes) => {
$state.go('jobResult', { id: launchRes.data.job }, { reload: true });
})
.catch(({data, status}) => {
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to launch job template: ' + status });
});
}; };
} }
PortalModeJobTemplatesController.$inject = ['$scope','PortalJobTemplateList', 'InitiatePlaybookRun', 'job_templatesDataset']; PortalModeJobTemplatesController.$inject = ['$scope','PortalJobTemplateList', 'job_templatesDataset', '$state', 'PromptService', 'JobTemplateModel', 'ProcessErrors'];

View File

@@ -50,7 +50,7 @@ export default {
list: PortalJobTemplateList, list: PortalJobTemplateList,
mode: 'edit' mode: 'edit'
}); });
return html; return html + '<prompt prompt-data="promptData" on-finish="launchJob()"></launch>';
}, },
controller: PortalModeJobTemplatesController controller: PortalModeJobTemplatesController
} }

View File

@@ -1,5 +1,5 @@
export default export default
function SchedulePost(Rest, ProcessErrors, RRuleToAPI, Wait, $q, Schedule) { function SchedulePost(Rest, ProcessErrors, RRuleToAPI, Wait, $q, Schedule, PromptService) {
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
url = params.url, url = params.url,
@@ -36,57 +36,10 @@ export default
} }
if(promptData) { if(promptData) {
if(promptData.launchConf.survey_enabled){ scheduleData = PromptService.bundlePromptDataForSaving({
for (var i=0; i < promptData.surveyQuestions.length; i++){ promptData: promptData,
var fld = promptData.surveyQuestions[i].variable; dataToSave: scheduleData
// grab all survey questions that have answers });
if(promptData.surveyQuestions[i].required || (promptData.surveyQuestions[i].required === false && promptData.surveyQuestions[i].model.toString()!=="")) {
if(!scheduleData.extra_data) {
scheduleData.extra_data = {};
}
scheduleData.extra_data[fld] = promptData.surveyQuestions[i].model;
}
if(promptData.surveyQuestions[i].required === false && _.isEmpty(promptData.surveyQuestions[i].model)) {
switch (promptData.surveyQuestions[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 (promptData.surveyQuestions[i].min === 0) {
scheduleData.extra_data[fld] = "";
}
break;
}
}
}
}
if(_.has(promptData, 'prompts.jobType.value.value') && _.get(promptData, 'launchConf.ask_job_type_on_launch')) {
scheduleData.job_type = promptData.launchConf.defaults.job_type && promptData.launchConf.defaults.job_type === promptData.prompts.jobType.value.value ? null : promptData.prompts.jobType.value.value;
}
if(_.has(promptData, 'prompts.tags.value') && _.get(promptData, 'launchConf.ask_tags_on_launch')){
const templateDefaultJobTags = promptData.launchConf.defaults.job_tags.split(',');
scheduleData.job_tags = (_.isEqual(templateDefaultJobTags.sort(), promptData.prompts.tags.value.map(a => a.value).sort())) ? null : promptData.prompts.tags.value.map(a => a.value).join();
}
if(_.has(promptData, 'prompts.skipTags.value') && _.get(promptData, 'launchConf.ask_skip_tags_on_launch')){
const templateDefaultSkipTags = promptData.launchConf.defaults.skip_tags.split(',');
scheduleData.skip_tags = (_.isEqual(templateDefaultSkipTags.sort(), promptData.prompts.skipTags.value.map(a => a.value).sort())) ? null : promptData.prompts.skipTags.value.map(a => a.value).join();
}
if(_.has(promptData, 'prompts.limit.value') && _.get(promptData, 'launchConf.ask_limit_on_launch')){
scheduleData.limit = promptData.launchConf.defaults.limit && promptData.launchConf.defaults.limit === promptData.prompts.limit.value ? null : promptData.prompts.limit.value;
}
if(_.has(promptData, 'prompts.verbosity.value.value') && _.get(promptData, 'launchConf.ask_verbosity_on_launch')){
scheduleData.verbosity = promptData.launchConf.defaults.verbosity && promptData.launchConf.defaults.verbosity === promptData.prompts.verbosity.value.value ? null : promptData.prompts.verbosity.value.value;
}
if(_.has(promptData, 'prompts.inventory.value') && _.get(promptData, 'launchConf.ask_inventory_on_launch')){
scheduleData.inventory = promptData.launchConf.defaults.inventory && promptData.launchConf.defaults.inventory.id === promptData.prompts.inventory.value.id ? null : promptData.prompts.inventory.value.id;
}
if(_.has(promptData, 'prompts.diffMode.value') && _.get(promptData, 'launchConf.ask_diff_mode_on_launch')){
scheduleData.diff_mode = promptData.launchConf.defaults.diff_mode && promptData.launchConf.defaults.diff_mode === promptData.prompts.diffMode.value ? null : promptData.prompts.diffMode.value;
}
} }
Rest.setUrl(url); Rest.setUrl(url);
@@ -212,5 +165,6 @@ SchedulePost.$inject =
'RRuleToAPI', 'RRuleToAPI',
'Wait', 'Wait',
'$q', '$q',
'ScheduleModel' 'ScheduleModel',
'PromptService'
]; ];

View File

@@ -8,7 +8,9 @@
RESULTS RESULTS
</div> </div>
<div class="StandardOut-actions"> <div class="StandardOut-actions">
<button id="relaunch-job-button" class="List-actionButton jobResult-launchButton" data-placement="top" mode="all" ng-click="relaunchJob()" aw-tool-tip="Relaunch using the same parameters" data-original-title="" title=""><i class="icon-launch"></i> </button> <div>
<at-relaunch></at-relaunch>
</div>
<button id="cancel-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="Cancel" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button> <button id="cancel-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="Cancel" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button>
<button id="delete-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="Delete" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button> <button id="delete-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="Delete" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
</div> </div>

View File

@@ -23,5 +23,14 @@ export default {
"ad_hoc_command_events": [] "ad_hoc_command_events": []
} }
} }
},
resolve: {
jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) {
Rest.setUrl(GetBasePath('base') + 'ad_hoc_commands/' + $stateParams.id + '/');
return Rest.get()
.then(({data}) => {
return data;
});
}]
} }
}; };

View File

@@ -8,7 +8,7 @@
RESULTS RESULTS
</div> </div>
<div class="StandardOut-actions"> <div class="StandardOut-actions">
<button id="relaunch-job-button" class="List-actionButton jobResult-launchButton" data-placement="top" mode="all" ng-click="relaunchJob()" aw-tool-tip="Relaunch using the same parameters" data-original-title="" title=""><i class="icon-launch"></i> </button> <at-relaunch></at-relaunch>
<button id="cancel-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="Cancel" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button> <button id="cancel-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="Cancel" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button>
<button id="delete-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="Delete" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button> <button id="delete-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="Delete" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
</div> </div>

View File

@@ -25,5 +25,14 @@ export default {
} }
}, },
jobType: 'inventory_updates' jobType: 'inventory_updates'
},
resolve: {
jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) {
Rest.setUrl(GetBasePath('base') + 'inventory_updates/' + $stateParams.id + '/');
return Rest.get()
.then(({data}) => {
return data;
});
}]
} }
}; };

View File

@@ -23,5 +23,14 @@ export default {
"system_job_events": [], "system_job_events": [],
} }
} }
},
resolve: {
jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) {
Rest.setUrl(GetBasePath('base') + 'system_jobs/' + $stateParams.id + '/');
return Rest.get()
.then(({data}) => {
return data;
});
}]
} }
}; };

View File

@@ -8,7 +8,7 @@
RESULTS RESULTS
</div> </div>
<div class="StandardOut-actions"> <div class="StandardOut-actions">
<button id="relaunch-job-button" class="List-actionButton jobResult-launchButton" data-placement="top" mode="all" ng-click="relaunchJob()" aw-tool-tip="{{'Relaunch using the same parameters'|translate}}" data-original-title="" title=""><i class="icon-launch"></i> </button> <at-relaunch></at-relaunch>
<button id="cancel-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="{{'Cancel'|translate}}" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button> <button id="cancel-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-show="job.status === 'waiting' || job.status === 'running' || job.status === 'pending'" aw-tool-tip="{{'Cancel'|translate}}" data-original-title="" title=""><i class="fa fa-minus-circle"></i> </button>
<button id="delete-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="{{'Delete'|translate}}" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button> <button id="delete-job-button" class="List-actionButton List-actionButton--delete jobResult-launchButton" data-placement="top" ng-click="deleteJob()" ng-hide="job.status === 'waiting' || job.status === 'running' || job.status === 'pending' " aw-tool-tip="{{'Delete'|translate}}" data-original-title="" title=""><i class="fa fa-trash-o"></i> </button>
</div> </div>

View File

@@ -25,5 +25,14 @@ export default {
"project_update_events": [], "project_update_events": [],
} }
}, },
},
resolve: {
jobData: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams) {
Rest.setUrl(GetBasePath('base') + 'project_updates/' + $stateParams.id + '/');
return Rest.get()
.then(({data}) => {
return data;
});
}]
} }
}; };

View File

@@ -131,6 +131,10 @@ standard-out-log {
font-size: 20px; font-size: 20px;
} }
.StandardOut-actions {
display: flex;
}
.StandardOut-actionButton { .StandardOut-actionButton {
font-size: 16px; font-size: 16px;
height: 30px; height: 30px;

View File

@@ -12,8 +12,9 @@
export function JobStdoutController ($rootScope, $scope, $state, $stateParams, export function JobStdoutController ($rootScope, $scope, $state, $stateParams,
GetBasePath, Rest, ProcessErrors, Empty, GetChoices, LookUpName, GetBasePath, Rest, ProcessErrors, Empty, GetChoices, LookUpName,
ParseTypeChange, ParseVariableString, RelaunchJob, DeleteJob, Wait, i18n, ParseTypeChange, ParseVariableString, DeleteJob, Wait, i18n,
fieldChoices, fieldLabels) { fieldChoices, fieldLabels, Project, Alert, InventorySource,
jobData) {
var job_id = $stateParams.id, var job_id = $stateParams.id,
jobType = $state.current.data.jobType; jobType = $state.current.data.jobType;
@@ -34,7 +35,12 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams,
if (data.status === 'failed' || data.status === 'canceled' || data.status === 'error' || data.status === 'successful') { if (data.status === 'failed' || data.status === 'canceled' || data.status === 'error' || data.status === 'successful') {
// Go out and refresh the job details // Go out and refresh the job details
getjobResults();
Rest.setUrl(GetBasePath('base') + jobType + '/' + job_id + '/');
Rest.get()
.then(({data}) => {
updateJobObj(data);
});
} }
}); });
@@ -72,149 +78,142 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams,
// Set the parse type so that CodeMirror knows how to display extra params YAML/JSON // Set the parse type so that CodeMirror knows how to display extra params YAML/JSON
$scope.parseType = 'yaml'; $scope.parseType = 'yaml';
function getjobResults() { function updateJobObj(updatedJobData) {
// Go out and get the job details based on the job type. jobType gets defined // Go out and get the job details based on the job type. jobType gets defined
// in the data block of the route declaration for each of the different types // in the data block of the route declaration for each of the different types
// of stdout jobs. // of stdout jobs.
Rest.setUrl(GetBasePath('base') + jobType + '/' + job_id + '/');
Rest.get()
.then(({data}) => {
$scope.job = data;
$scope.job_template_name = data.name;
$scope.created_by = data.summary_fields.created_by;
$scope.project_name = (data.summary_fields.project) ? data.summary_fields.project.name : '';
$scope.inventory_name = (data.summary_fields.inventory) ? data.summary_fields.inventory.name : '';
$scope.job_template_url = '/#/templates/' + data.unified_job_template;
if($scope.inventory_name && data.inventory && data.summary_fields.inventory && data.summary_fields.inventory.kind) {
if(data.summary_fields.inventory.kind === '') {
$scope.inventory_url = '/#/inventories/inventory' + data.inventory;
}
else if(data.summary_fields.inventory.kind === 'smart') {
$scope.inventory_url = '/#/inventories/smart_inventory' + data.inventory;
}
}
else {
$scope.inventory_url = '';
}
$scope.project_url = ($scope.project_name && data.project) ? '/#/projects/' + data.project : '';
$scope.credential_name = (data.summary_fields.credential) ? data.summary_fields.credential.name : '';
$scope.credential_url = (data.credential) ? '/#/credentials/' + data.credential : '';
$scope.cloud_credential_url = (data.cloud_credential) ? '/#/credentials/' + data.cloud_credential : '';
if(data.summary_fields && data.summary_fields.source_workflow_job &&
data.summary_fields.source_workflow_job.id){
$scope.workflow_result_link = `/#/workflows/${data.summary_fields.source_workflow_job.id}`;
}
$scope.playbook = data.playbook;
$scope.credential = data.credential;
$scope.cloud_credential = data.cloud_credential;
$scope.forks = data.forks;
$scope.limit = data.limit;
$scope.verbosity = data.verbosity;
$scope.job_tags = data.job_tags;
$scope.job.module_name = data.module_name;
if (data.extra_vars) {
$scope.variables = ParseVariableString(data.extra_vars);
}
$scope.$on('getInventorySource', function(e, d) { $scope.job = updatedJobData;
$scope.inv_manage_group_link = '/#/inventories/inventory/' + d.inventory + '/inventory_sources/edit/' + d.id; $scope.job_template_name = updatedJobData.name;
$scope.created_by = updatedJobData.summary_fields.created_by;
$scope.project_name = (updatedJobData.summary_fields.project) ? updatedJobData.summary_fields.project.name : '';
$scope.inventory_name = (updatedJobData.summary_fields.inventory) ? updatedJobData.summary_fields.inventory.name : '';
$scope.job_template_url = '/#/templates/' + updatedJobData.unified_job_template;
if($scope.inventory_name && updatedJobData.inventory && updatedJobData.summary_fields.inventory && updatedJobData.summary_fields.inventory.kind) {
if(updatedJobData.summary_fields.inventory.kind === '') {
$scope.inventory_url = '/#/inventories/inventory' + updatedJobData.inventory;
}
else if(updatedJobData.summary_fields.inventory.kind === 'smart') {
$scope.inventory_url = '/#/inventories/smart_inventory' + updatedJobData.inventory;
}
}
else {
$scope.inventory_url = '';
}
$scope.project_url = ($scope.project_name && updatedJobData.project) ? '/#/projects/' + updatedJobData.project : '';
$scope.credential_name = (updatedJobData.summary_fields.credential) ? updatedJobData.summary_fields.credential.name : '';
$scope.credential_url = (updatedJobData.credential) ? '/#/credentials/' + updatedJobData.credential : '';
$scope.cloud_credential_url = (updatedJobData.cloud_credential) ? '/#/credentials/' + updatedJobData.cloud_credential : '';
if(updatedJobData.summary_fields && updatedJobData.summary_fields.source_workflow_job &&
updatedJobData.summary_fields.source_workflow_job.id){
$scope.workflow_result_link = `/#/workflows/${updatedJobData.summary_fields.source_workflow_job.id}`;
}
$scope.playbook = updatedJobData.playbook;
$scope.credential = updatedJobData.credential;
$scope.cloud_credential = updatedJobData.cloud_credential;
$scope.forks = updatedJobData.forks;
$scope.limit = updatedJobData.limit;
$scope.verbosity = updatedJobData.verbosity;
$scope.job_tags = updatedJobData.job_tags;
$scope.job.module_name = updatedJobData.module_name;
if (updatedJobData.extra_vars) {
$scope.variables = ParseVariableString(updatedJobData.extra_vars);
}
$scope.$on('getInventorySource', function(e, d) {
$scope.inv_manage_group_link = '/#/inventories/inventory/' + d.inventory + '/inventory_sources/edit/' + d.id;
});
// If we have a source then we have to go get the source choices from the server
if (!Empty(updatedJobData.source)) {
if ($scope.removeChoicesReady) {
$scope.removeChoicesReady();
}
$scope.removeChoicesReady = $scope.$on('ChoicesReady', function() {
$scope.source_choices.every(function(e) {
if (e.value === updatedJobData.source) {
$scope.source = e.label;
return false;
}
return true;
}); });
// If we have a source then we have to go get the source choices from the server
if (!Empty(data.source)) {
if ($scope.removeChoicesReady) {
$scope.removeChoicesReady();
}
$scope.removeChoicesReady = $scope.$on('ChoicesReady', function() {
$scope.source_choices.every(function(e) {
if (e.value === data.source) {
$scope.source = e.label;
return false;
}
return true;
});
});
// GetChoices can be found in the helper: Utilities.js
// It attaches the source choices to $scope.source_choices.
// Then, when the callback is fired, $scope.source is bound
// to the corresponding label.
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source',
variable: 'source_choices',
choice_name: 'choices',
callback: 'ChoicesReady'
});
}
// LookUpName can be found in the lookup-name.factory
// It attaches the name that it gets (based on the url)
// to the $scope variable defined by the attribute scope_var.
if (!Empty(data.credential)) {
LookUpName({
scope: $scope,
scope_var: 'credential',
url: GetBasePath('credentials') + data.credential + '/',
ignore_403: true
});
}
if (!Empty(data.inventory)) {
LookUpName({
scope: $scope,
scope_var: 'inventory',
url: GetBasePath('inventory') + data.inventory + '/'
});
}
if (!Empty(data.project)) {
LookUpName({
scope: $scope,
scope_var: 'project',
url: GetBasePath('projects') + data.project + '/'
});
}
if (!Empty(data.cloud_credential)) {
LookUpName({
scope: $scope,
scope_var: 'cloud_credential',
url: GetBasePath('credentials') + data.cloud_credential + '/',
ignore_403: true
});
}
if (!Empty(data.inventory_source)) {
LookUpName({
scope: $scope,
scope_var: 'inventory_source',
url: GetBasePath('inventory_sources') + data.inventory_source + '/',
callback: 'getInventorySource'
});
}
if (data.extra_vars) {
ParseTypeChange({
scope: $scope,
field_id: 'pre-formatted-variables',
readOnly: true
});
}
// If the job isn't running we want to clear out the interval that goes out and checks for stdout updates.
// This interval is defined in the standard out log directive controller.
if (data.status === 'successful' || data.status === 'failed' || data.status === 'error' || data.status === 'canceled') {
if ($rootScope.jobStdOutInterval) {
window.clearInterval($rootScope.jobStdOutInterval);
}
}
})
.catch(({data, status}) => {
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve job: ' + job_id + '. GET returned: ' + status });
}); });
// GetChoices can be found in the helper: Utilities.js
// It attaches the source choices to $scope.source_choices.
// Then, when the callback is fired, $scope.source is bound
// to the corresponding label.
GetChoices({
scope: $scope,
url: GetBasePath('inventory_sources'),
field: 'source',
variable: 'source_choices',
choice_name: 'choices',
callback: 'ChoicesReady'
});
}
// LookUpName can be found in the lookup-name.factory
// It attaches the name that it gets (based on the url)
// to the $scope variable defined by the attribute scope_var.
if (!Empty(updatedJobData.credential)) {
LookUpName({
scope: $scope,
scope_var: 'credential',
url: GetBasePath('credentials') + updatedJobData.credential + '/',
ignore_403: true
});
}
if (!Empty(updatedJobData.inventory)) {
LookUpName({
scope: $scope,
scope_var: 'inventory',
url: GetBasePath('inventory') + updatedJobData.inventory + '/'
});
}
if (!Empty(updatedJobData.project)) {
LookUpName({
scope: $scope,
scope_var: 'project',
url: GetBasePath('projects') + updatedJobData.project + '/'
});
}
if (!Empty(updatedJobData.cloud_credential)) {
LookUpName({
scope: $scope,
scope_var: 'cloud_credential',
url: GetBasePath('credentials') + updatedJobData.cloud_credential + '/',
ignore_403: true
});
}
if (!Empty(updatedJobData.inventory_source)) {
LookUpName({
scope: $scope,
scope_var: 'inventory_source',
url: GetBasePath('inventory_sources') + updatedJobData.inventory_source + '/',
callback: 'getInventorySource'
});
}
if (updatedJobData.extra_vars) {
ParseTypeChange({
scope: $scope,
field_id: 'pre-formatted-variables',
readOnly: true
});
}
// If the job isn't running we want to clear out the interval that goes out and checks for stdout updates.
// This interval is defined in the standard out log directive controller.
if (updatedJobData.status === 'successful' || updatedJobData.status === 'failed' || updatedJobData.status === 'error' || updatedJobData.status === 'canceled') {
if ($rootScope.jobStdOutInterval) {
window.clearInterval($rootScope.jobStdOutInterval);
}
}
} }
@@ -255,26 +254,13 @@ export function JobStdoutController ($rootScope, $scope, $state, $stateParams,
}); });
}; };
$scope.relaunchJob = function() { updateJobObj(jobData);
var typeId, job = $scope.job;
if (job.type === 'inventory_update') {
typeId = job.inventory_source;
}
else if (job.type === 'project_update') {
typeId = job.project;
}
else if (job.type === 'job' || job.type === "system_job" || job.type === 'ad_hoc_command') {
typeId = job.id;
}
RelaunchJob({ scope: $scope, id: typeId, type: job.type, name: job.name });
};
getjobResults();
} }
JobStdoutController.$inject = [ '$rootScope', '$scope', '$state', JobStdoutController.$inject = [ '$rootScope', '$scope', '$state',
'$stateParams', 'GetBasePath', 'Rest', 'ProcessErrors', '$stateParams', 'GetBasePath', 'Rest', 'ProcessErrors',
'Empty', 'GetChoices', 'LookUpName', 'ParseTypeChange', 'Empty', 'GetChoices', 'LookUpName', 'ParseTypeChange',
'ParseVariableString', 'RelaunchJob', 'DeleteJob', 'Wait', 'i18n', 'ParseVariableString', 'DeleteJob', 'Wait', 'i18n',
'fieldChoices', 'fieldLabels']; 'fieldChoices', 'fieldLabels', 'ProjectModel', 'Alert', 'InventorySourceModel',
'jobData'];

View File

@@ -72,12 +72,9 @@ export default ['i18n', function(i18n) {
columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4', columnClass: 'col-lg-2 col-md-2 col-sm-3 col-xs-4',
submit: { submit: {
icon: 'icon-rocket', ngShow: "!completed_job.type == 'system_job' || completed_job.summary_fields.user_capabilities.start",
mode: 'all', // uses the at-relaunch directive
ngClick: 'relaunchJob($event, completed_job.id)', relaunch: true
awToolTip: i18n._('Relaunch using the same parameters'),
dataPlacement: 'top',
ngShow: "!completed_job.type == 'system_job' || completed_job.summary_fields.user_capabilities.start"
}, },
"delete": { "delete": {
mode: 'all', mode: 'all',

View File

@@ -16,7 +16,7 @@ export default
'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', 'md5Setup', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', 'md5Setup',
'ParseTypeChange', 'Wait', 'selectedLabels', 'i18n', 'ParseTypeChange', 'Wait', 'selectedLabels', 'i18n',
'Empty', 'Prompt', 'ToJSON', 'GetChoices', 'CallbackHelpInit', 'Empty', 'Prompt', 'ToJSON', 'GetChoices', 'CallbackHelpInit',
'InitiatePlaybookRun' , 'initSurvey', '$state', 'CreateSelect2', 'initSurvey', '$state', 'CreateSelect2',
'ToggleNotification','$q', 'InstanceGroupsService', 'InstanceGroupsData', 'ToggleNotification','$q', 'InstanceGroupsService', 'InstanceGroupsData',
'MultiCredentialService', 'availableLabels', 'projectGetPermissionDenied', 'MultiCredentialService', 'availableLabels', 'projectGetPermissionDenied',
'inventoryGetPermissionDenied', 'jobTemplateData', 'ParseVariableString', 'ConfigData', 'inventoryGetPermissionDenied', 'jobTemplateData', 'ParseVariableString', 'ConfigData',
@@ -26,7 +26,7 @@ export default
ProcessErrors, GetBasePath, md5Setup, ProcessErrors, GetBasePath, md5Setup,
ParseTypeChange, Wait, selectedLabels, i18n, ParseTypeChange, Wait, selectedLabels, i18n,
Empty, Prompt, ToJSON, GetChoices, CallbackHelpInit, Empty, Prompt, ToJSON, GetChoices, CallbackHelpInit,
InitiatePlaybookRun, SurveyControllerInit, $state, CreateSelect2, SurveyControllerInit, $state, CreateSelect2,
ToggleNotification, $q, InstanceGroupsService, InstanceGroupsData, ToggleNotification, $q, InstanceGroupsService, InstanceGroupsData,
MultiCredentialService, availableLabels, projectGetPermissionDenied, MultiCredentialService, availableLabels, projectGetPermissionDenied,
inventoryGetPermissionDenied, jobTemplateData, ParseVariableString, ConfigData inventoryGetPermissionDenied, jobTemplateData, ParseVariableString, ConfigData
@@ -718,50 +718,5 @@ export default
$scope.formCancel = function () { $scope.formCancel = function () {
$state.go('templates'); $state.go('templates');
}; };
// Related set: Add button
$scope.add = function (set) {
$rootScope.flashMessage = null;
$location.path('/' + base + '/' + $stateParams.job_template_id + '/' + set);
};
// Related set: Edit button
$scope.edit = function (set, id) {
$rootScope.flashMessage = null;
$location.path('/' + set + '/' + id);
};
// Launch a job using the selected template
$scope.launch = function() {
if ($scope.removePromptForSurvey) {
$scope.removePromptForSurvey();
}
$scope.removePromptForSurvey = $scope.$on('PromptForSurvey', function() {
var action = function () {
// $scope.$emit("GatherFormFields");
Wait('start');
$('#prompt-modal').modal('hide');
$scope.addSurvey();
};
Prompt({
hdr: 'Incomplete Survey',
body: '<div class="Prompt-bodyQuery">Do you want to create a survey before proceeding?</div>',
action: action
});
});
if($scope.survey_enabled === true && $scope.survey_exists!==true){
$scope.$emit("PromptForSurvey");
}
else {
InitiatePlaybookRun({
scope: $scope,
id: id,
job_type: 'job_template'
});
}
};
} }
]; ];

View File

@@ -65,37 +65,30 @@ export default [ 'Rest', 'GetBasePath', 'ProcessErrors', 'CredentialTypeModel',
} }
})); }));
vm.promptData.prompts.credentials.passwordsNeededToStart = vm.promptData.launchConf.passwords_needed_to_start;
vm.promptData.prompts.credentials.passwords = {}; vm.promptData.prompts.credentials.passwords = {};
vm.promptData.prompts.credentials.value.forEach((credential) => { vm.promptData.launchConf.passwords_needed_to_start.forEach((passwordNeeded) => {
if (credential.passwords_needed && credential.passwords_needed.length > 0) { if(passwordNeeded === "ssh_password") {
credential.passwords_needed.forEach(passwordNeeded => { vm.promptData.prompts.credentials.passwords.ssh = {};
let credPassObj = { }
id: credential.id, if(passwordNeeded === "become_password") {
name: credential.name vm.promptData.prompts.credentials.passwords.become = {};
}; }
if(passwordNeeded === "ssh_key_unlock") {
vm.promptData.prompts.credentials.passwords.ssh_key_unlock = {};
}
if(passwordNeeded.startsWith("vault_password")) {
let vault_id;
if(passwordNeeded.includes('.')) {
vault_id = passwordNeeded.split(/\.(.+)/)[1];
}
if(passwordNeeded === "ssh_password") { if(!vm.promptData.prompts.credentials.passwords.vault) {
vm.promptData.prompts.credentials.passwords.ssh = credPassObj; vm.promptData.prompts.credentials.passwords.vault = [];
} }
if(passwordNeeded === "become_password") {
vm.promptData.prompts.credentials.passwords.become = credPassObj;
}
if(passwordNeeded === "ssh_key_unlock") {
vm.promptData.prompts.credentials.passwords.ssh_key_unlock = credPassObj;
}
if(passwordNeeded.startsWith("vault_password")) {
if(passwordNeeded.includes('.')) {
credPassObj.vault_id = passwordNeeded.split(/\.(.+)/)[1];
}
if(!vm.promptData.prompts.credentials.passwords.vault) { vm.promptData.prompts.credentials.passwords.vault.push({
vm.promptData.prompts.credentials.passwords.vault = []; vault_id: vault_id
}
vm.promptData.prompts.credentials.passwords.vault.push(credPassObj);
}
}); });
} }
}); });

View File

@@ -1,7 +1,7 @@
function PromptService (Empty, $filter) { function PromptService (Empty, $filter) {
this.processPromptValues = (params) => { this.processPromptValues = (params) => {
let prompts = { const prompts = {
credentials: {}, credentials: {},
inventory: {}, inventory: {},
variables: {}, variables: {},
@@ -16,10 +16,23 @@ function PromptService (Empty, $filter) {
prompts.credentials.value = _.has(params, 'launchConf.defaults.credentials') ? _.cloneDeep(params.launchConf.defaults.credentials) : []; prompts.credentials.value = _.has(params, 'launchConf.defaults.credentials') ? _.cloneDeep(params.launchConf.defaults.credentials) : [];
prompts.inventory.value = _.has(params, 'currentValues.summary_fields.inventory') ? params.currentValues.summary_fields.inventory : (_.has(params, 'launchConf.defaults.inventory') ? params.launchConf.defaults.inventory : null); prompts.inventory.value = _.has(params, 'currentValues.summary_fields.inventory') ? params.currentValues.summary_fields.inventory : (_.has(params, 'launchConf.defaults.inventory') ? params.launchConf.defaults.inventory : null);
let skipTags = _.has(params, 'currentValues.skip_tags') && params.currentValues.skip_tags ? params.currentValues.skip_tags : (_.has(params, 'launchConf.defaults.skip_tags') ? params.launchConf.defaults.skip_tags : ""); const skipTags = _.has(params, 'currentValues.skip_tags') && params.currentValues.skip_tags ? params.currentValues.skip_tags : (_.has(params, 'launchConf.defaults.skip_tags') ? params.launchConf.defaults.skip_tags : "");
let jobTags = _.has(params, 'currentValues.job_tags') && params.currentValues.job_tags ? params.currentValues.job_tags : (_.has(params, 'launchConf.defaults.job_tags') ? params.launchConf.defaults.job_tags : ""); const jobTags = _.has(params, 'currentValues.job_tags') && params.currentValues.job_tags ? params.currentValues.job_tags : (_.has(params, 'launchConf.defaults.job_tags') ? params.launchConf.defaults.job_tags : "");
prompts.variables.value = _.has(params, 'launchConf.defaults.extra_vars') && params.launchConf.defaults.extra_vars !== "" ? params.launchConf.defaults.extra_vars : "---"; let extraVars = '';
const hasCurrentExtraVars = _.get(params, 'currentValues.extra_data'),
hasDefaultExtraVars = _.get(params, 'launchConf.defaults.extra_vars');
if(hasCurrentExtraVars && hasDefaultExtraVars) {
extraVars = _.merge(jsyaml.safeLoad(params.launchConf.defaults.extra_vars), params.currentValues.extra_data);
} else if(hasCurrentExtraVars) {
extraVars = params.currentValues.extra_data;
} else if(hasDefaultExtraVars) {
extraVars = jsyaml.safeLoad(params.launchConf.defaults.extra_vars);
}
prompts.variables.value = extraVars && extraVars !== '' ? '---\n' + jsyaml.safeDump(extraVars) : '---\n';
prompts.verbosity.choices = _.get(params, 'launchOptions.actions.POST.verbosity.choices', []).map(c => ({label: c[1], value: c[0]})); prompts.verbosity.choices = _.get(params, 'launchOptions.actions.POST.verbosity.choices', []).map(c => ({label: c[1], value: c[0]}));
prompts.verbosity.value = _.has(params, 'currentValues.verbosity') && params.currentValues.verbosity ? _.find(prompts.verbosity.choices, item => item.value === params.currentValues.verbosity) : _.find(prompts.verbosity.choices, item => item.value === params.launchConf.defaults.verbosity); prompts.verbosity.value = _.has(params, 'currentValues.verbosity') && params.currentValues.verbosity ? _.find(prompts.verbosity.choices, item => item.value === params.currentValues.verbosity) : _.find(prompts.verbosity.choices, item => item.value === params.launchConf.defaults.verbosity);
prompts.jobType.choices = _.get(params, 'launchOptions.actions.POST.job_type.choices', []).map(c => ({label: c[1], value: c[0]})); prompts.jobType.choices = _.get(params, 'launchOptions.actions.POST.job_type.choices', []).map(c => ({label: c[1], value: c[0]}));
@@ -27,7 +40,7 @@ function PromptService (Empty, $filter) {
prompts.limit.value = _.has(params, 'currentValues.limit') && params.currentValues.limit ? params.currentValues.limit : (_.has(params, 'launchConf.defaults.limit') ? params.launchConf.defaults.limit : ""); prompts.limit.value = _.has(params, 'currentValues.limit') && params.currentValues.limit ? params.currentValues.limit : (_.has(params, 'launchConf.defaults.limit') ? params.launchConf.defaults.limit : "");
prompts.tags.options = prompts.tags.value = (jobTags && jobTags !== "") ? jobTags.split(',').map((i) => ({name: i, label: i, value: i})) : []; prompts.tags.options = prompts.tags.value = (jobTags && jobTags !== "") ? jobTags.split(',').map((i) => ({name: i, label: i, value: i})) : [];
prompts.skipTags.options = prompts.skipTags.value = (skipTags && skipTags !== "") ? skipTags.split(',').map((i) => ({name: i, label: i, value: i})) : []; prompts.skipTags.options = prompts.skipTags.value = (skipTags && skipTags !== "") ? skipTags.split(',').map((i) => ({name: i, label: i, value: i})) : [];
prompts.diffMode.value = _.has(params, 'currentValues.diff_mode') && typeof params.currentValues.diff_mode === 'boolean' ? params.currentValues.diff_mode : (_.has(params, 'launchConf.defaults.diff_mode') ? params.launchConf.defaults.diff_mode : false); prompts.diffMode.value = _.has(params, 'currentValues.diff_mode') && typeof params.currentValues.diff_mode === 'boolean' ? params.currentValues.diff_mode : (_.has(params, 'launchConf.defaults.diff_mode') ? params.launchConf.defaults.diff_mode : null);
return prompts; return prompts;
}; };
@@ -117,6 +130,144 @@ function PromptService (Empty, $filter) {
missingSurveyValue: missingSurveyValue missingSurveyValue: missingSurveyValue
}; };
}; };
this.bundlePromptDataForLaunch = (promptData) => {
const launchData = {
extra_vars: promptData.extraVars
};
if (promptData.launchConf.ask_tags_on_launch){
launchData.job_tags = promptData.prompts.tags.value.map(a => a.value).join();
}
if (promptData.launchConf.ask_skip_tags_on_launch){
launchData.skip_tags = promptData.prompts.skipTags.value.map(a => a.value).join();
}
if (promptData.launchConf.ask_limit_on_launch && _.has(promptData, 'prompts.limit.value')){
launchData.limit = promptData.prompts.limit.value;
}
if (promptData.launchConf.ask_job_type_on_launch && _.has(promptData, 'prompts.jobType.value.value')) {
launchData.job_type = promptData.prompts.jobType.value.value;
}
if (promptData.launchConf.ask_verbosity_on_launch && _.has(promptData, 'prompts.verbosity.value.value')) {
launchData.verbosity = promptData.prompts.verbosity.value.value;
}
if (promptData.launchConf.ask_inventory_on_launch && !Empty(promptData.prompts.inventory.value.id)){
launchData.inventory_id = promptData.prompts.inventory.value.id;
}
if (promptData.launchConf.ask_credential_on_launch){
launchData.credentials = [];
promptData.prompts.credentials.value.forEach((credential) => {
launchData.credentials.push(credential.id);
});
}
if (promptData.launchConf.ask_diff_mode_on_launch && _.has(promptData, 'prompts.diffMode.value')) {
launchData.diff_mode = promptData.prompts.diffMode.value;
}
if (promptData.prompts.credentials.passwords) {
_.forOwn(promptData.prompts.credentials.passwords, (val, key) => {
if (!launchData.credential_passwords) {
launchData.credential_passwords = {};
}
if (key === "ssh_key_unlock") {
launchData.credential_passwords.ssh_key_unlock = val.value;
} else if (key !== "vault") {
launchData.credential_passwords[`${key}_password`] = val.value;
} else {
_.each(val, (vaultCred) => {
launchData.credential_passwords[vaultCred.vault_id ? `${key}_password.${vaultCred.vault_id}` : `${key}_password`] = vaultCred.value;
});
}
});
}
return launchData;
};
this.bundlePromptDataForRelaunch = (promptData) => {
const launchData = {};
if(promptData.relaunchHostType) {
launchData.hosts = promptData.relaunchHostType;
}
if (promptData.prompts.credentials.passwords) {
_.forOwn(promptData.prompts.credentials.passwords, (val, key) => {
if (!launchData.credential_passwords) {
launchData.credential_passwords = {};
}
if (key === "ssh_key_unlock") {
launchData.credential_passwords.ssh_key_unlock = val.value;
} else if (key !== "vault") {
launchData.credential_passwords[`${key}_password`] = val.value;
} else {
_.each(val, (vaultCred) => {
launchData.credential_passwords[vaultCred.vault_id ? `${key}_password.${vaultCred.vault_id}` : `${key}_password`] = vaultCred.value;
});
}
});
}
return launchData;
};
this.bundlePromptDataForSaving = (params) => {
const promptDataToSave = params.dataToSave ? params.dataToSave : {};
if(params.promptData.launchConf.survey_enabled){
for (var i=0; i < params.promptData.surveyQuestions.length; i++){
var fld = params.promptData.surveyQuestions[i].variable;
// grab all survey questions that have answers
if(params.promptData.surveyQuestions[i].required || (params.promptData.surveyQuestions[i].required === false && params.promptData.surveyQuestions[i].model.toString()!=="")) {
if(!promptDataToSave.extra_data) {
promptDataToSave.extra_data = {};
}
promptDataToSave.extra_data[fld] = params.promptData.surveyQuestions[i].model;
}
if(params.promptData.surveyQuestions[i].required === false && _.isEmpty(params.promptData.surveyQuestions[i].model)) {
switch (params.promptData.surveyQuestions[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 (params.promptData.surveyQuestions[i].min === 0) {
promptDataToSave.extra_data[fld] = "";
}
break;
}
}
}
}
if(_.has(params, 'promptData.prompts.jobType.value.value') && _.get(params, 'promptData.launchConf.ask_job_type_on_launch')) {
promptDataToSave.job_type = params.promptData.launchConf.defaults.job_type && params.promptData.launchConf.defaults.job_type === params.promptData.prompts.jobType.value.value ? null : params.promptData.prompts.jobType.value.value;
}
if(_.has(params, 'promptData.prompts.tags.value') && _.get(params, 'promptData.launchConf.ask_tags_on_launch')){
const templateDefaultJobTags = params.promptData.launchConf.defaults.job_tags.split(',');
promptDataToSave.job_tags = (_.isEqual(templateDefaultJobTags.sort(), params.promptData.prompts.tags.value.map(a => a.value).sort())) ? null : params.promptData.prompts.tags.value.map(a => a.value).join();
}
if(_.has(params, 'promptData.prompts.skipTags.value') && _.get(params, 'promptData.launchConf.ask_skip_tags_on_launch')){
const templateDefaultSkipTags = params.promptData.launchConf.defaults.skip_tags.split(',');
promptDataToSave.skip_tags = (_.isEqual(templateDefaultSkipTags.sort(), params.promptData.prompts.skipTags.value.map(a => a.value).sort())) ? null : params.promptData.prompts.skipTags.value.map(a => a.value).join();
}
if(_.has(params, 'promptData.prompts.limit.value') && _.get(params, 'promptData.launchConf.ask_limit_on_launch')){
promptDataToSave.limit = params.promptData.launchConf.defaults.limit && params.promptData.launchConf.defaults.limit === params.promptData.prompts.limit.value ? null : params.promptData.prompts.limit.value;
}
if(_.has(params, 'promptData.prompts.verbosity.value.value') && _.get(params, 'promptData.launchConf.ask_verbosity_on_launch')){
promptDataToSave.verbosity = params.promptData.launchConf.defaults.verbosity && params.promptData.launchConf.defaults.verbosity === params.promptData.prompts.verbosity.value.value ? null : params.promptData.prompts.verbosity.value.value;
}
if(_.has(params, 'promptData.prompts.inventory.value') && _.get(params, 'promptData.launchConf.ask_inventory_on_launch')){
promptDataToSave.inventory = params.promptData.launchConf.defaults.inventory && params.promptData.launchConf.defaults.inventory.id === params.promptData.prompts.inventory.value.id ? null : params.promptData.prompts.inventory.value.id;
}
if(_.has(params, 'promptData.prompts.diffMode.value') && _.get(params, 'promptData.launchConf.ask_diff_mode_on_launch')){
promptDataToSave.diff_mode = params.promptData.launchConf.defaults.diff_mode && params.promptData.launchConf.defaults.diff_mode === params.promptData.prompts.diffMode.value ? null : params.promptData.prompts.diffMode.value;
}
return promptDataToSave;
};
} }
PromptService.$inject = ['Empty', '$filter']; PromptService.$inject = ['Empty', '$filter'];

View File

@@ -72,7 +72,7 @@ export default
}); });
} }
scope.promptExtraVars = $.isEmptyObject(scope.promptData.extraVars) ? '---' : jsyaml.safeDump(scope.promptData.extraVars); scope.promptExtraVars = $.isEmptyObject(scope.promptData.extraVars) ? '---' : '---\n' + jsyaml.safeDump(scope.promptData.extraVars);
ParseTypeChange({ ParseTypeChange({
scope: scope, scope: scope,

View File

@@ -1,5 +1,5 @@
<div> <div>
<div class="Prompt-previewRow--flex"> <div class="Prompt-previewRow--flex" ng-if="promptData.prompts.jobType.value">
<div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.JOB_TYPE') }}</div> <div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.JOB_TYPE') }}</div>
<div class="Prompt-previewRowValue"> <div class="Prompt-previewRowValue">
<span ng-if="promptData.prompts.jobType.value.value === 'run'">{{:: vm.strings.get('prompt.PLAYBOOK_RUN') }}</span> <span ng-if="promptData.prompts.jobType.value.value === 'run'">{{:: vm.strings.get('prompt.PLAYBOOK_RUN') }}</span>
@@ -24,7 +24,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="Prompt-previewRow--flex"> <div class="Prompt-previewRow--flex" ng-if="promptData.prompts.inventory.value.id">
<div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.INVENTORY') }}</div> <div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.INVENTORY') }}</div>
<div class="Prompt-previewRowValue" ng-bind="promptData.prompts.inventory.value.name"></div> <div class="Prompt-previewRowValue" ng-bind="promptData.prompts.inventory.value.name"></div>
</div> </div>
@@ -32,7 +32,7 @@
<div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.LIMIT') }}</div> <div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.LIMIT') }}</div>
<div class="Prompt-previewRowValue" ng-bind="promptData.prompts.limit.value"></div> <div class="Prompt-previewRowValue" ng-bind="promptData.prompts.limit.value"></div>
</div> </div>
<div class="Prompt-previewRow--flex"> <div class="Prompt-previewRow--flex" ng-if="promptData.prompts.inventory.value.label">
<div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.VERBOSITY') }}</div> <div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.VERBOSITY') }}</div>
<div class="Prompt-previewRowValue" ng-bind="promptData.prompts.verbosity.value.label"></div> <div class="Prompt-previewRowValue" ng-bind="promptData.prompts.verbosity.value.label"></div>
</div> </div>
@@ -68,7 +68,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="Prompt-previewRow--flex"> <div class="Prompt-previewRow--flex" ng-if="promptData.prompts.diffMode.value !== null">
<div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.SHOW_CHANGES') }}</div> <div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.SHOW_CHANGES') }}</div>
<div class="Prompt-previewRowValue"> <div class="Prompt-previewRowValue">
<span ng-if="promptData.prompts.diffMode.value">{{:: vm.strings.get('ON') }}</span> <span ng-if="promptData.prompts.diffMode.value">{{:: vm.strings.get('ON') }}</span>

View File

@@ -77,61 +77,36 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
unified_job_template: params.node.unifiedJobTemplate.id unified_job_template: params.node.unifiedJobTemplate.id
}; };
if(_.has(params, 'node.promptData.extraVars')) {
if(_.get(params, 'node.promptData.launchConf.defaults.extra_vars')) {
if(!sendableNodeData.extra_data) {
sendableNodeData.extra_data = {};
}
const defaultVars = jsyaml.safeLoad(params.node.promptData.launchConf.defaults.extra_vars);
// Only include extra vars that differ from the template default vars
_.forOwn(params.node.promptData.extraVars, (value, key) => {
if(!defaultVars[key] || defaultVars[key] !== value) {
sendableNodeData.extra_data[key] = value;
}
});
if(sendableNodeData.extra_data === {}) {
delete sendableNodeData.extra_data;
}
} else {
sendableNodeData.extra_data = params.node.promptData.extraVars;
}
}
// Check to see if the user has provided any prompt values that are different // Check to see if the user has provided any prompt values that are different
// from the defaults in the job template // from the defaults in the job template
if(params.node.unifiedJobTemplate.type === "job_template" && params.node.promptData) { if(params.node.unifiedJobTemplate.type === "job_template" && params.node.promptData) {
if(params.node.promptData.launchConf.survey_enabled){ sendableNodeData = PromptService.bundlePromptDataForSaving({
for (var i=0; i < params.node.promptData.surveyQuestions.length; i++){ promptData: params.node.promptData,
var fld = params.node.promptData.surveyQuestions[i].variable; dataToSave: sendableNodeData
// grab all survey questions that have answers });
if(params.node.promptData.surveyQuestions[i].required || (params.node.promptData.surveyQuestions[i].required === false && params.node.promptData.surveyQuestions[i].model.toString()!=="")) {
if(!sendableNodeData.extra_data) {
sendableNodeData.extra_data = {};
}
sendableNodeData.extra_data[fld] = params.node.promptData.surveyQuestions[i].model;
}
if(params.node.promptData.surveyQuestions[i].required === false && _.isEmpty(params.node.promptData.surveyQuestions[i].model)) {
switch (params.node.promptData.surveyQuestions[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 (params.node.promptData.surveyQuestions[i].min === 0) {
sendableNodeData.extra_data[fld] = "";
}
break;
}
}
}
}
if(_.has(params, 'node.promptData.prompts.jobType.value.value') && _.get(params, 'node.promptData.launchConf.ask_job_type_on_launch')) {
sendableNodeData.job_type = params.node.promptData.prompts.jobType.templateDefault === params.node.promptData.prompts.jobType.value.value ? null : params.node.promptData.prompts.jobType.value.value;
}
if(_.has(params, 'node.promptData.prompts.tags.value') && _.get(params, 'node.promptData.launchConf.ask_tags_on_launch')){
let templateDefaultJobTags = params.node.promptData.prompts.tags.templateDefault.split(',');
sendableNodeData.job_tags = (_.isEqual(templateDefaultJobTags.sort(), params.node.promptData.prompts.tags.value.map(a => a.value).sort())) ? null : params.node.promptData.prompts.tags.value.map(a => a.value).join();
}
if(_.has(params, 'node.promptData.prompts.skipTags.value') && _.get(params, 'node.promptData.launchConf.ask_skip_tags_on_launch')){
let templateDefaultSkipTags = params.node.promptData.prompts.skipTags.templateDefault.split(',');
sendableNodeData.skip_tags = (_.isEqual(templateDefaultSkipTags.sort(), params.node.promptData.prompts.skipTags.value.map(a => a.value).sort())) ? null : params.node.promptData.prompts.skipTags.value.map(a => a.value).join();
}
if(_.has(params, 'node.promptData.prompts.limit.value') && _.get(params, 'node.promptData.launchConf.ask_limit_on_launch')){
sendableNodeData.limit = params.node.promptData.prompts.limit.templateDefault === params.node.promptData.prompts.limit.value ? null : params.node.promptData.prompts.limit.value;
}
if(_.has(params, 'node.promptData.prompts.verbosity.value.value') && _.get(params, 'node.promptData.launchConf.ask_verbosity_on_launch')){
sendableNodeData.verbosity = params.node.promptData.prompts.verbosity.templateDefault === params.node.promptData.prompts.verbosity.value.value ? null : params.node.promptData.prompts.verbosity.value.value;
}
if(_.has(params, 'node.promptData.prompts.inventory.value') && _.get(params, 'node.promptData.launchConf.ask_inventory_on_launch')){
sendableNodeData.inventory = _.has(params, 'node.promptData.prompts.inventory.templateDefault.id') && params.node.promptData.prompts.inventory.templateDefault.id === params.node.promptData.prompts.inventory.value.id ? null : params.node.promptData.prompts.inventory.value.id;
}
if(_.has(params, 'node.promptData.prompts.diffMode.value') && _.get(params, 'node.promptData.launchConf.ask_diff_mode_on_launch')){
sendableNodeData.diff_mode = params.node.promptData.prompts.diffMode.templateDefault === params.node.promptData.prompts.diffMode.value ? null : params.node.promptData.prompts.diffMode.value;
}
} }
return sendableNodeData; return sendableNodeData;
@@ -1028,7 +1003,7 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService',
}), }),
}; };
$scope.$watch('promptData.surveyQuestions', () => { surveyQuestionWatcher = $scope.$watch('promptData.surveyQuestions', () => {
let missingSurveyValue = false; let missingSurveyValue = false;
_.each($scope.promptData.surveyQuestions, (question) => { _.each($scope.promptData.surveyQuestions, (question) => {
if(question.required && (Empty(question.model) || question.model === [])) { if(question.required && (Empty(question.model) || question.model === [])) {

View File

@@ -5,7 +5,7 @@
*************************************************/ *************************************************/
export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'InitiatePlaybookRun', '$interval', 'moment', function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybookRun, $interval, moment) { export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'WorkflowJobModel', '$interval', 'moment', function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, WorkflowJob, $interval, moment) {
var val = { var val = {
getCounts: function(workflowNodes){ getCounts: function(workflowNodes){
var nodeArr = []; var nodeArr = [];
@@ -107,8 +107,13 @@ export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErr
}); });
}, },
relaunchJob: function(scope) { relaunchJob: function(scope) {
InitiatePlaybookRun({ scope: scope, id: scope.workflow.id, const workflowJob = new WorkflowJob();
relaunch: true, job_type: 'workflow_job' });
workflowJob.postRelaunch({
id: scope.workflow.id
}).then((launchRes) => {
$state.go('workflowResults', { id: launchRes.data.id }, { reload: true });
});
}, },
createOneSecondTimer: function(startTime, fn) { createOneSecondTimer: function(startTime, fn) {
return $interval(function(){ return $interval(function(){

View File

@@ -393,19 +393,6 @@ describe('Controller: jobResultsController', () => {
}); });
}); });
describe('$scope.relaunchJob', () => {
beforeEach(() => {
bootstrapTest();
});
it('should relaunch the job', () => {
let scope = $scope;
$scope.relaunchJob();
expect(jobResultsService.relaunchJob)
.toHaveBeenCalledWith(scope);
});
});
describe('count stuff', () => { describe('count stuff', () => {
beforeEach(() => { beforeEach(() => {
count = { count = {

View File

@@ -18,7 +18,7 @@ describe('Controller: workflowResults', () => {
beforeEach(angular.mock.module('workflowResults', ($provide) => { beforeEach(angular.mock.module('workflowResults', ($provide) => {
['PromptDialog', 'Prompt', 'Wait', 'Rest', '$state', 'ProcessErrors', ['PromptDialog', 'Prompt', 'Wait', 'Rest', '$state', 'ProcessErrors',
'InitiatePlaybookRun', 'jobLabels', 'workflowNodes', 'count', 'jobLabels', 'workflowNodes', 'count',
].forEach((item) => { ].forEach((item) => {
$provide.value(item, {}); $provide.value(item, {});
}); });

View File

@@ -6,7 +6,7 @@ describe('workflowResultsService', () => {
let $interval; let $interval;
beforeEach(angular.mock.module('workflowResults', ($provide) => { beforeEach(angular.mock.module('workflowResults', ($provide) => {
['PromptDialog', 'Prompt', 'Wait', 'Rest', 'ProcessErrors', 'InitiatePlaybookRun', '$state'].forEach(function(item) { ['PromptDialog', 'Prompt', 'Wait', 'Rest', 'ProcessErrors', '$state'].forEach(function(item) {
$provide.value(item, {}); $provide.value(item, {});
}); });
$provide.value('$stateExtender', { addState: jasmine.createSpy('addState'), }); $provide.value('$stateExtender', { addState: jasmine.createSpy('addState'), });