Merge pull request #1622 from mabashian/169-prompt-cleanup

Propagate new launch/relaunch logic across the app
This commit is contained in:
Michael Abashian
2018-03-23 09:31:33 -04:00
committed by GitHub
63 changed files with 1089 additions and 922 deletions

View File

@@ -16,14 +16,11 @@ function ListTemplatesController(
$state, $state,
Alert, Alert,
Dataset, Dataset,
InitiatePlaybookRun,
ProcessErrors, ProcessErrors,
Prompt, Prompt,
PromptService,
resolvedModels, resolvedModels,
strings, strings,
Wait, Wait
Empty
) { ) {
const vm = this || {}; const vm = this || {};
const [jobTemplate, workflowTemplate] = resolvedModels; const [jobTemplate, workflowTemplate] = resolvedModels;
@@ -42,7 +39,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');
@@ -70,21 +67,6 @@ function ListTemplatesController(
} }
}; };
vm.runTemplate = template => {
if (!template) {
Alert(strings.get('error.LAUNCH'), strings.get('alert.MISSING_PARAMETER'));
return;
}
if (isJobTemplate(template)) {
runJobTemplate(template);
} else if (isWorkflowTemplate(template)) {
runWorkflowTemplate(template);
} else {
Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_LAUNCH'));
}
};
vm.scheduleTemplate = template => { vm.scheduleTemplate = template => {
if (!template) { if (!template) {
Alert(strings.get('error.SCHEDULE'), strings.get('alert.MISSING_PARAMETER')); Alert(strings.get('error.SCHEDULE'), strings.get('alert.MISSING_PARAMETER'));
@@ -325,123 +307,6 @@ function ListTemplatesController(
return html; return html;
} }
function runJobTemplate(template) {
const selectedJobTemplate = jobTemplate.create();
const preLaunchPromises = [
selectedJobTemplate.getLaunch(template.id),
selectedJobTemplate.optionsLaunch(template.id),
];
Promise.all(preLaunchPromises)
.then(([launchData, launchOptions]) => {
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,
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;
}
});
}
function runWorkflowTemplate(template) {
InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'workflow_job_template' });
}
$scope.launchJob = () => {
const jobLaunchData = {
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(_.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(createErrorHandler('launch job template', 'POST'));
};
} }
ListTemplatesController.$inject = [ ListTemplatesController.$inject = [
@@ -450,14 +315,11 @@ ListTemplatesController.$inject = [
'$state', '$state',
'Alert', 'Alert',
'Dataset', 'Dataset',
'InitiatePlaybookRun',
'ProcessErrors', 'ProcessErrors',
'Prompt', 'Prompt',
'PromptService',
'resolvedModels', 'resolvedModels',
'TemplatesStrings', 'TemplatesStrings',
'Wait', 'Wait'
'Empty'
]; ];
export default ListTemplatesController; export default ListTemplatesController;

View File

@@ -61,7 +61,7 @@ export default {
Wait('start'); Wait('start');
return qs.search(searchPath, searchParam) return qs.search(searchPath, searchParam)
.finally(() => Wait('stop')) .finally(() => Wait('stop'));
} }
], ],
} }

View File

@@ -98,9 +98,9 @@
</labels-list> </labels-list>
</div> </div>
<div class="at-Row-actions"> <div class="at-Row-actions">
<at-row-action icon="icon-launch" ng-click="vm.runTemplate(template)" <at-launch-template template="template"
ng-show="template.summary_fields.user_capabilities.start"> ng-show="template.summary_fields.user_capabilities.start">
</at-row-action> </at-launch-template>
<at-row-action icon="fa-calendar" ng-click="vm.scheduleTemplate(template)" <at-row-action icon="fa-calendar" ng-click="vm.scheduleTemplate(template)"
ng-show="template.summary_fields.user_capabilities.schedule"> ng-show="template.summary_fields.user_capabilities.schedule">
</at-row-action> </at-row-action>
@@ -121,5 +121,4 @@
query-set="querySet"> query-set="querySet">
</paginate> </paginate>
</at-panel-body> </at-panel-body>
<prompt prompt-data="promptData" on-finish="launchJob()"></launch>
</at-panel> </at-panel>

View File

@@ -1,11 +1,12 @@
@import 'action/_index'; @import 'action/_index';
@import 'input/_index'; @import 'input/_index';
@import 'launchTemplateButton/_index';
@import 'layout/_index'; @import 'layout/_index';
@import 'list/_index'; @import 'list/_index';
@import 'modal/_index'; @import 'modal/_index';
@import 'panel/_index'; @import 'panel/_index';
@import 'popover/_index'; @import 'popover/_index';
@import 'tabs/_index';
@import 'utility/_index';
@import 'truncate/_index';
@import 'relaunchButton/_index'; @import 'relaunchButton/_index';
@import 'tabs/_index';
@import 'truncate/_index';
@import 'utility/_index';

View File

@@ -16,23 +16,24 @@ import inputSlider from '~components/input/slider.directive';
import inputText from '~components/input/text.directive'; import inputText from '~components/input/text.directive';
import inputTextarea from '~components/input/textarea.directive'; import inputTextarea from '~components/input/textarea.directive';
import inputTextareaSecret from '~components/input/textarea-secret.directive'; import inputTextareaSecret from '~components/input/textarea-secret.directive';
import launchTemplate from '~components/launchTemplateButton/launchTemplateButton.component';
import layout from '~components/layout/layout.directive'; import layout from '~components/layout/layout.directive';
import list from '~components/list/list.directive'; import list from '~components/list/list.directive';
import row from '~components/list/row.directive';
import rowItem from '~components/list/row-item.directive';
import rowAction from '~components/list/row-action.directive';
import modal from '~components/modal/modal.directive'; import modal from '~components/modal/modal.directive';
import panel from '~components/panel/panel.directive'; import panel from '~components/panel/panel.directive';
import panelBody from '~components/panel/body.directive'; import panelBody from '~components/panel/body.directive';
import panelHeading from '~components/panel/heading.directive'; import panelHeading from '~components/panel/heading.directive';
import popover from '~components/popover/popover.directive'; import popover from '~components/popover/popover.directive';
import relaunch from '~components/relaunchButton/relaunchButton.component';
import row from '~components/list/row.directive';
import rowItem from '~components/list/row-item.directive';
import rowAction from '~components/list/row-action.directive';
import sideNav from '~components/layout/side-nav.directive'; import sideNav from '~components/layout/side-nav.directive';
import sideNavItem from '~components/layout/side-nav-item.directive'; import sideNavItem from '~components/layout/side-nav-item.directive';
import tab from '~components/tabs/tab.directive'; import tab from '~components/tabs/tab.directive';
import tabGroup from '~components/tabs/group.directive'; import tabGroup from '~components/tabs/group.directive';
import topNavItem from '~components/layout/top-nav-item.directive'; import topNavItem from '~components/layout/top-nav-item.directive';
import truncate from '~components/truncate/truncate.directive'; import truncate from '~components/truncate/truncate.directive';
import relaunch from '~components/relaunchButton/relaunchButton.component';
import BaseInputController from '~components/input/base.controller'; import BaseInputController from '~components/input/base.controller';
import ComponentsStrings from '~components/components.strings'; import ComponentsStrings from '~components/components.strings';
@@ -59,8 +60,10 @@ angular
.directive('atInputText', inputText) .directive('atInputText', inputText)
.directive('atInputTextarea', inputTextarea) .directive('atInputTextarea', inputTextarea)
.directive('atInputTextareaSecret', inputTextareaSecret) .directive('atInputTextareaSecret', inputTextareaSecret)
.component('atLaunchTemplate', launchTemplate)
.directive('atLayout', layout) .directive('atLayout', layout)
.directive('atList', list) .directive('atList', list)
.component('atRelaunch', relaunch)
.directive('atRow', row) .directive('atRow', row)
.directive('atRowItem', rowItem) .directive('atRowItem', rowItem)
.directive('atRowAction', rowAction) .directive('atRowAction', rowAction)
@@ -75,7 +78,6 @@ angular
.directive('atTabGroup', tabGroup) .directive('atTabGroup', tabGroup)
.directive('atTopNavItem', topNavItem) .directive('atTopNavItem', topNavItem)
.directive('atTruncate', truncate) .directive('atTruncate', truncate)
.component('atRelaunch', relaunch)
.service('BaseInputController', BaseInputController) .service('BaseInputController', BaseInputController)
.service('ComponentsStrings', ComponentsStrings); .service('ComponentsStrings', ComponentsStrings);

View File

@@ -0,0 +1,24 @@
.at-LaunchTemplate {
margin-left: 15px;
&--button {
font-size: 16px;
height: 30px;
min-width: 30px;
color: #848992;
background-color: inherit;
border: none;
border-radius: 4px;
}
&--button:hover {
background-color: @at-blue;
color: white;
}
}
.open {
.at-LaunchTemplate--button {
background-color: @at-blue;
color: white;
}
}

View File

@@ -0,0 +1,152 @@
import templateUrl from './launchTemplateButton.partial.html';
const atLaunchTemplate = {
templateUrl,
bindings: {
template: '<'
},
controller: ['JobTemplateModel', 'WorkflowJobTemplateModel', 'PromptService', '$state',
'ProcessErrors', '$scope', 'TemplatesStrings', 'Alert', atLaunchTemplateCtrl],
controllerAs: 'vm'
};
function atLaunchTemplateCtrl (
JobTemplate, WorkflowTemplate, PromptService, $state,
ProcessErrors, $scope, strings, Alert
) {
const vm = this;
const jobTemplate = new JobTemplate();
const workflowTemplate = new WorkflowTemplate();
const createErrorHandler = (path, action) =>
({ data, status }) => {
const hdr = strings.get('error.HEADER');
const msg = strings.get('error.CALL', { path, action, status });
ProcessErrors($scope, data, status, null, { hdr, msg });
};
vm.startLaunchTemplate = () => {
if (vm.template.type === 'job_template') {
const selectedJobTemplate = jobTemplate.create();
const preLaunchPromises = [
selectedJobTemplate.getLaunch(vm.template.id),
selectedJobTemplate.optionsLaunch(vm.template.id),
];
Promise.all(preLaunchPromises)
.then(([launchData, launchOptions]) => {
if (selectedJobTemplate.canLaunchWithoutPrompt()) {
selectedJobTemplate
.postLaunch({ id: vm.template.id })
.then(({ data }) => {
$state.go('jobResult', { id: data.job }, { reload: true });
});
} else {
const promptData = {
launchConf: launchData.data,
launchOptions: launchOptions.data,
template: vm.template.id,
templateType: vm.template.type,
prompts: PromptService.processPromptValues({
launchConf: launchData.data,
launchOptions: launchOptions.data
}),
triggerModalOpen: true,
};
if (launchData.data.survey_enabled) {
selectedJobTemplate.getSurveyQuestions(vm.template.id)
.then(({ data }) => {
const processed = PromptService.processSurveyQuestions({
surveyQuestions: data.spec
});
promptData.surveyQuestions = processed.surveyQuestions;
vm.promptData = promptData;
});
} else {
vm.promptData = promptData;
}
}
});
} else if (vm.template.type === 'workflow_job_template') {
const selectedWorkflowJobTemplate = workflowTemplate.create();
const preLaunchPromises = [
selectedWorkflowJobTemplate.getLaunch(vm.template.id),
selectedWorkflowJobTemplate.optionsLaunch(vm.template.id),
];
Promise.all(preLaunchPromises)
.then(([launchData, launchOptions]) => {
if (selectedWorkflowJobTemplate.canLaunchWithoutPrompt()) {
selectedWorkflowJobTemplate
.postLaunch({ id: vm.template.id })
.then(({ data }) => {
$state.go('workflowResults', { id: data.workflow_job }, { reload: true });
});
} else {
const promptData = {
launchConf: launchData.data,
launchOptions: launchOptions.data,
template: vm.template.id,
templateType: vm.template.type,
prompts: PromptService.processPromptValues({
launchConf: launchData.data,
launchOptions: launchOptions.data
}),
triggerModalOpen: true,
};
if (launchData.data.survey_enabled) {
selectedWorkflowJobTemplate.getSurveyQuestions(vm.template.id)
.then(({ data }) => {
const processed = PromptService.processSurveyQuestions({
surveyQuestions: data.spec
});
promptData.surveyQuestions = processed.surveyQuestions;
vm.promptData = promptData;
});
} else {
vm.promptData = promptData;
}
}
});
} else {
Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_LAUNCH'));
}
};
vm.launchTemplateWithPrompts = () => {
const jobLaunchData = PromptService.bundlePromptDataForLaunch(vm.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) &&
!(
vm.promptData.launchConf.ask_variables_on_launch &&
vm.promptData.launchConf.survey_enabled &&
vm.promptData.surveyQuestions.length > 0
)
) {
delete jobLaunchData.extra_vars;
}
if (vm.promptData.templateType === 'job_template') {
jobTemplate.create().postLaunch({
id: vm.promptData.template,
launchData: jobLaunchData
}).then((launchRes) => {
$state.go('jobResult', { id: launchRes.data.job }, { reload: true });
}).catch(createErrorHandler('launch job template', 'POST'));
} else if (vm.promptData.templateType === 'workflow_job_template') {
workflowTemplate.create().postLaunch({
id: vm.promptData.template,
launchData: jobLaunchData
}).then((launchRes) => {
$state.go('workflowResults', { id: launchRes.data.workflow_job }, { reload: true });
}).catch(createErrorHandler('launch workflow job template', 'POST'));
}
};
}
export default atLaunchTemplate;

View File

@@ -0,0 +1,7 @@
<div class="at-LaunchTemplate">
<button class="at-LaunchTemplate--button"
ng-click="vm.startLaunchTemplate()">
<i class="icon-launch"></i>
</button>
<prompt prompt-data="vm.promptData" on-finish="vm.launchTemplateWithPrompts()"></launch>
</div>

View File

@@ -3,20 +3,118 @@ import templateUrl from './relaunchButton.partial.html';
const atRelaunch = { const atRelaunch = {
templateUrl, templateUrl,
bindings: { bindings: {
state: '<' job: '<'
}, },
controller: ['RelaunchJob', 'InitiatePlaybookRun', 'ComponentsStrings', '$scope', atRelaunchCtrl], controller: ['ProcessErrors', 'AdhocRun', 'ComponentsStrings',
'ProjectModel', 'InventorySourceModel', 'WorkflowJobModel', 'Alert',
'AdHocCommandModel', 'JobModel', 'JobTemplateModel', 'PromptService',
'$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,
$state, $q, $scope
) {
const vm = this; const vm = this;
const scope = $scope.$parent; const jobObj = new Job();
const job = _.get(scope, 'job') || _.get(scope, 'completed_job'); const jobTemplate = new JobTemplate();
const checkRelaunchPlaybook = (option) => {
jobObj.getRelaunch({
id: vm.job.id
}).then((getRelaunchRes) => {
if (
getRelaunchRes.data.passwords_needed_to_start &&
getRelaunchRes.data.passwords_needed_to_start.length > 0
) {
const jobPromises = [
jobObj.request('get', vm.job.id),
jobTemplate.optionsLaunch(vm.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] }));
const verbosity = _.find(
verbosityChoices,
item => item.value === populatedJob.verbosity
);
const jobType = _.find(
jobTypeChoices,
item => item.value === populatedJob.job_type
);
vm.promptData = {
launchConf: {
passwords_needed_to_start:
getRelaunchRes.data.passwords_needed_to_start
},
launchOptions: launchOptions.data,
job: vm.job.id,
relaunchHostType: option ? (option.name).toLowerCase() : null,
prompts: {
credentials: {
value: populatedJob.summary_fields.credentials || []
},
variables: {
value: populatedJob.extra_vars
},
inventory: {
value: populatedJob.summary_fields.inventory || null
},
verbosity: {
value: verbosity,
choices: verbosityChoices
},
jobType: {
value: jobType,
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: vm.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 = vm.job.type !== 'system_job' && vm.job.summary_fields.user_capabilities.start;
vm.showDropdown = job.type === 'job' && job.failed === true; vm.showDropdown = vm.job.type === 'job' && vm.job.failed === true;
vm.createDropdown(); vm.createDropdown();
vm.createTooltips(); vm.createTooltips();
@@ -46,27 +144,97 @@ function atRelaunchCtrl (RelaunchJob, InitiatePlaybookRun, strings, $scope) {
}; };
vm.relaunchJob = () => { vm.relaunchJob = () => {
let typeId; if (vm.job.type === 'inventory_update') {
const inventorySource = new InventorySource();
if (job.type === 'inventory_update') { inventorySource.getUpdate(vm.job.inventory_source)
typeId = job.inventory_source; .then((getUpdateRes) => {
} else if (job.type === 'project_update') { if (getUpdateRes.data.can_update) {
typeId = job.project; inventorySource.postUpdate(vm.job.inventory_source)
} else if (job.type === 'job' || job.type === 'system_job' .then((postUpdateRes) => {
|| job.type === 'ad_hoc_command' || job.type === 'workflow_job') { if (!$state.includes('jobs')) {
typeId = job.id; $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 (vm.job.type === 'project_update') {
const project = new Project();
project.getUpdate(vm.job.project)
.then((getUpdateRes) => {
if (getUpdateRes.data.can_update) {
project.postUpdate(vm.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 (vm.job.type === 'workflow_job') {
const workflowJob = new WorkflowJob();
workflowJob.postRelaunch({
id: vm.job.id
}).then((launchRes) => {
if (!$state.includes('jobs')) {
$state.go('workflowResults', { id: launchRes.data.id }, { reload: true });
}
});
} else if (vm.job.type === 'ad_hoc_command') {
const adHocCommand = new AdHocCommand();
adHocCommand.getRelaunch({
id: vm.job.id
}).then((getRelaunchRes) => {
if (
getRelaunchRes.data.passwords_needed_to_start &&
getRelaunchRes.data.passwords_needed_to_start.length > 0
) {
AdhocRun({ scope: $scope, project_id: vm.job.id, relaunch: true });
} else {
adHocCommand.postRelaunch({
id: vm.job.id
}).then((launchRes) => {
if (!$state.includes('jobs')) {
$state.go('adHocJobStdout', { id: launchRes.data.id }, { reload: true });
}
});
}
});
} else if (vm.job.type === 'job') {
checkRelaunchPlaybook();
} }
RelaunchJob({ scope, id: typeId, type: job.type, name: job.name });
}; };
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>
<prompt prompt-data="vm.promptData" on-finish="vm.relaunchJobWithPassword()"></launch>
</div> </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,8 @@
/* jshint unused: vars */ /* jshint unused: vars */
export default export default
[ 'InitiatePlaybookRun', [ 'templateUrl',
'templateUrl',
'$state', '$state',
'Alert', function JobTemplatesList(templateUrl, $state) {
function JobTemplatesList(InitiatePlaybookRun, templateUrl, $state, Alert) {
return { return {
restrict: 'E', restrict: 'E',
link: link, link: link,
@@ -15,6 +13,7 @@ export default
}; };
function link(scope, element, attr) { function link(scope, element, attr) {
scope.$watch("data", function(data) { scope.$watch("data", function(data) {
if (data) { if (data) {
if (data.length > 0) { if (data.length > 0) {
@@ -30,6 +29,7 @@ export default
// smartStatus?, launchUrl, editUrl, name // smartStatus?, launchUrl, editUrl, name
scope.templates = _.map(list, function(template){ return { scope.templates = _.map(list, function(template){ return {
recent_jobs: template.summary_fields.recent_jobs, recent_jobs: template.summary_fields.recent_jobs,
can_start: template.summary_fields.user_capabilities.start,
name: template.name, name: template.name,
id: template.id, id: template.id,
type: template.type type: template.type
@@ -40,25 +40,6 @@ export default
return (status === "successful"); return (status === "successful");
}; };
scope.launchTemplate = function(template){
if(template) {
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
InitiatePlaybookRun({ scope: scope, id: template.id, job_type: 'job_template' });
}
else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
InitiatePlaybookRun({ scope: scope, id: template.id, job_type: 'workflow_job_template' });
}
else {
// 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.editTemplate = function (template) { scope.editTemplate = 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')) {

View File

@@ -32,9 +32,9 @@
</td> </td>
<td class="List-actionsContainer"> <td class="List-actionsContainer">
<div class="List-actionButtonCell"> <div class="List-actionButtonCell">
<button class="List-actionButton" ng-click="launchTemplate(template)"> <at-launch-template template="template"
<i class="icon-launch"></i> ng-show="template.can_start">
</button> </at-launch-template>
<button class="List-actionButton" ng-click="editTemplate(template)"> <button class="List-actionButton" ng-click="editTemplate(template)">
<i class="fa fa-pencil"></i> <i class="fa fa-pencil"></i>
</button> </button>
@@ -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

@@ -22,7 +22,7 @@
<div class="JobResults-panelHeaderButtonActions"> <div class="JobResults-panelHeaderButtonActions">
<!-- RELAUNCH ACTION --> <!-- RELAUNCH ACTION -->
<at-relaunch state="job"></at-relaunch> <at-relaunch job="job"></at-relaunch>
<!-- CANCEL ACTION --> <!-- CANCEL ACTION -->
<button class="List-actionButton <button class="List-actionButton

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

@@ -10,11 +10,9 @@
* @description This controller's for the jobs page * @description This controller's for the jobs page
*/ */
export default ['$state', '$rootScope', '$scope', '$stateParams', export default ['$state', '$rootScope', '$scope', '$stateParams', 'Find', 'DeleteJob',
'Find', 'DeleteJob', 'RelaunchJob',
'GetBasePath', 'Dataset', 'QuerySet', 'ListDefinition', '$interpolate', 'GetBasePath', 'Dataset', 'QuerySet', 'ListDefinition', '$interpolate',
function($state, $rootScope, $scope, $stateParams, function($state, $rootScope, $scope, $stateParams, Find, DeleteJob,
Find, DeleteJob, RelaunchJob,
GetBasePath, Dataset, qs, ListDefinition, $interpolate) { GetBasePath, Dataset, qs, ListDefinition, $interpolate) {
var list = ListDefinition; var list = ListDefinition;
@@ -95,42 +93,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

@@ -33,11 +33,8 @@ export default ['i18n', function(i18n) {
fieldActions: { fieldActions: {
submit: { submit: {
label: i18n._('Launch'), // uses the at-launch-template directive
mode: 'all', launch: true
ngClick: 'submitJob(job_template.id)',
awToolTip: i18n._('Start a job using this template'),
dataPlacement: 'top'
} }
} }
};}]; };}];

View File

@@ -4,23 +4,12 @@
* All Rights Reserved * All Rights Reserved
*************************************************/ *************************************************/
export function PortalModeJobTemplatesController($scope, PortalJobTemplateList, InitiatePlaybookRun, Dataset) { export function PortalModeJobTemplatesController($scope, PortalJobTemplateList, Dataset) {
var list = PortalJobTemplateList; var list = PortalJobTemplateList;
// search init
init(); $scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
function init() { $scope[list.name] = $scope[`${list.iterator}_dataset`].results;
// search init
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
}
$scope.submitJob = function(id) {
InitiatePlaybookRun({ scope: $scope, id: id, job_type: 'job_template' });
};
} }
PortalModeJobTemplatesController.$inject = ['$scope','PortalJobTemplateList', 'InitiatePlaybookRun', 'job_templatesDataset']; PortalModeJobTemplatesController.$inject = ['$scope','PortalJobTemplateList', 'job_templatesDataset'];

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

@@ -398,7 +398,9 @@ export default ['$compile', 'Attr', 'Icon',
} }
// Plug in Dropdown Component // Plug in Dropdown Component
if (field_action === 'submit' && list.fieldActions[field_action].relaunch === true) { if (field_action === 'submit' && list.fieldActions[field_action].relaunch === true) {
innerTable += `<at-relaunch></at-relaunch>`; innerTable += `<at-relaunch job="${list.iterator}"></at-relaunch>`;
} else if (field_action === 'submit' && list.fieldActions[field_action].launch === true) {
innerTable += `<at-launch-template template="${list.iterator}" ng-if="${list.iterator}.summary_fields.user_capabilities.start"></at-launch-template>`;
} else { } else {
fAction = list.fieldActions[field_action]; fAction = list.fieldActions[field_action];
innerTable += "<button id=\""; innerTable += "<button id=\"";

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 job="job"></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 job="job"></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 job="job"></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
@@ -41,7 +41,6 @@ export default
let defaultUrl = GetBasePath('job_templates'), let defaultUrl = GetBasePath('job_templates'),
generator = GenerateForm, generator = GenerateForm,
form = JobTemplateForm(), form = JobTemplateForm(),
base = $location.path().replace(/^\//, '').split('/')[0],
master = {}, master = {},
id = $stateParams.job_template_id, id = $stateParams.job_template_id,
callback, callback,
@@ -718,50 +717,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', 'WorkflowJobModel',
].forEach((item) => { ].forEach((item) => {
$provide.value(item, {}); $provide.value(item, {});
}); });

View File

@@ -6,9 +6,10 @@ 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', 'WorkflowJobModel']
$provide.value(item, {}); .forEach(function(item) {
}); $provide.value(item, {});
});
$provide.value('$stateExtender', { addState: jasmine.createSpy('addState'), }); $provide.value('$stateExtender', { addState: jasmine.createSpy('addState'), });
$provide.value('moment', moment); $provide.value('moment', moment);
})); }));