Merge pull request #1096 from mabashian/169-v1

UI support for prompting on job template schedules
This commit is contained in:
Jake McDermott
2018-02-09 11:34:25 -05:00
committed by GitHub
55 changed files with 2813 additions and 607 deletions

View File

@@ -42,4 +42,3 @@
</at-panel> </at-panel>
<div ng-if="$state.current.name.includes('permissions.add')" ui-view="modal"></div> <div ng-if="$state.current.name.includes('permissions.add')" ui-view="modal"></div>

View File

@@ -1,4 +1,4 @@
function ListTemplatesController (model, JobTemplate, WorkflowJobTemplate, strings, $state, $scope, rbacUiControlService, Dataset, $filter, Alert, InitiatePlaybookRun, Prompt, Wait, ProcessErrors, TemplateCopyService) { function ListTemplatesController (model, JobTemplate, WorkflowJobTemplate, strings, $state, $scope, rbacUiControlService, Dataset, $filter, Alert, InitiatePlaybookRun, Prompt, Wait, ProcessErrors, TemplateCopyService, $q, Empty, i18n, PromptService) {
const vm = this || {}, const vm = this || {},
unifiedJobTemplate = model, unifiedJobTemplate = model,
jobTemplate = new JobTemplate(), jobTemplate = new JobTemplate(),
@@ -85,28 +85,157 @@ function ListTemplatesController (model, JobTemplate, WorkflowJobTemplate, strin
// TODO: edit indicator doesn't update when you enter edit route after initial load right now // TODO: edit indicator doesn't update when you enter edit route after initial load right now
vm.activeId = parseInt($state.params.job_template_id || $state.params.workflow_template_id); vm.activeId = parseInt($state.params.job_template_id || $state.params.workflow_template_id);
// TODO: update to new way of launching job after mike opens his pr
vm.submitJob = function(template) { vm.submitJob = function(template) {
if(template) { if(template) {
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) { if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'job_template' }); let jobTemplate = new JobTemplate();
$q.all([jobTemplate.optionsLaunch(template.id), jobTemplate.getLaunch(template.id)])
.then((responses) => {
if(jobTemplate.canLaunchWithoutPrompt()) {
jobTemplate.postLaunch({id: template.id})
.then((launchRes) => {
$state.go('jobResult', { id: launchRes.data.job }, { reload: true });
});
} else {
if(responses[1].data.survey_enabled) {
// go out and get the survey questions
jobTemplate.getSurveyQuestions(template.id)
.then((surveyQuestionRes) => {
let processed = PromptService.processSurveyQuestions({
surveyQuestions: surveyQuestionRes.data.spec
});
$scope.promptData = {
launchConf: responses[1].data,
launchOptions: responses[0].data,
surveyQuestions: processed.surveyQuestions,
template: template.id,
prompts: PromptService.processPromptValues({
launchConf: responses[1].data,
launchOptions: responses[0].data
}),
triggerModalOpen: true
};
});
}
else {
$scope.promptData = {
launchConf: responses[1].data,
launchOptions: responses[0].data,
template: template.id,
prompts: PromptService.processPromptValues({
launchConf: responses[1].data,
launchOptions: responses[0].data
}),
triggerModalOpen: true
};
}
}
});
} }
else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_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' }); InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'workflow_job_template' });
} }
else { else {
var alertStrings = { // Something went wrong - Let the user know that we're unable to launch because we don't know
header: 'Error: Unable to determine template type', // what type of job template this is
body: 'We were unable to determine this template\'s type while launching.' Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while launching.');
}
}
else {
Alert('Error: Unable to launch template', 'Template parameter is missing');
}
};
$scope.launchJob = () => {
let jobLaunchData = {
extra_vars: $scope.promptData.extraVars
};
let jobTemplate = new JobTemplate();
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 = {};
} }
Alert(strings.get('ALERT', alertStrings)); 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.postLaunch({
id: $scope.promptData.template,
launchData: jobLaunchData
}).then((launchRes) => {
$state.go('jobResult', { id: launchRes.data.job }, { reload: true });
}).catch(({data, status}) => {
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'),
msg: i18n.sprintf(i18n._('Failed to launch job template. POST returned: %d'), $scope.promptData.template, status) });
});
};
vm.scheduleJob = (template) => {
if(template) {
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
$state.go('jobTemplateSchedules', {id: template.id});
} }
} else { else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
var alertStrings = { $state.go('workflowJobTemplateSchedules', {id: template.id});
header: 'Error: Unable to launch template',
body: 'Template parameter is missing'
} }
Alert(strings.get('ALERT', alertStrings)); else {
// Something went wrong Let the user know that we're unable to redirect to schedule 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 routing to schedule.');
}
}
else {
Alert('Error: Unable to schedule job', 'Template parameter is missing');
} }
}; };
@@ -334,7 +463,11 @@ ListTemplatesController.$inject = [
'Prompt', 'Prompt',
'Wait', 'Wait',
'ProcessErrors', 'ProcessErrors',
'TemplateCopyService' 'TemplateCopyService',
'$q',
'Empty',
'i18n',
'PromptService'
]; ];
export default ListTemplatesController; export default ListTemplatesController;

View File

@@ -118,4 +118,5 @@
query-set="querySet"> query-set="querySet">
</paginate> </paginate>
</at-panel-body> </at-panel-body>
<prompt prompt-data="promptData" open-modal="openModal" on-finish="launchJob()"></launch>
</at-panel> </at-panel>

View File

@@ -6,7 +6,7 @@ function TemplatesStrings (BaseString) {
ns.state = { ns.state = {
LIST_BREADCRUMB_LABEL: t.s('TEMPLATES') LIST_BREADCRUMB_LABEL: t.s('TEMPLATES')
} };
ns.list = { ns.list = {
PANEL_TITLE: t.s('TEMPLATES'), PANEL_TITLE: t.s('TEMPLATES'),
@@ -19,7 +19,44 @@ function TemplatesStrings (BaseString) {
ROW_ITEM_LABEL_CREDENTIALS: t.s('Credentials'), ROW_ITEM_LABEL_CREDENTIALS: t.s('Credentials'),
ROW_ITEM_LABEL_MODIFIED: t.s('Last Modified'), ROW_ITEM_LABEL_MODIFIED: t.s('Last Modified'),
ROW_ITEM_LABEL_RAN: t.s('Last Ran'), ROW_ITEM_LABEL_RAN: t.s('Last Ran'),
} };
ns.prompt = {
INVENTORY: t.s('Inventory'),
CREDENTIAL: t.s('Credential'),
OTHER_PROMPTS: t.s('Other Prompts'),
SURVEY: t.s('Survey'),
PREVIEW: t.s('Preview'),
LAUNCH: t.s('LAUNCH'),
SELECTED: t.s('SELECTED'),
NO_CREDENTIALS_SELECTED: t.s('No credentials selected'),
NO_INVENTORY_SELECTED: t.s('No inventory selected'),
REVERT: t.s('REVERT'),
CREDENTIAL_TYPE: t.s('Credential Type'),
PASSWORDS_REQUIRED_HELP: t.s('Launching this job requires the passwords listed below. Enter and confirm each password before continuing.'),
PLEASE_ENTER_PASSWORD: t.s('Please enter a password.'),
credential_passwords: {
SSH_PASSWORD: t.s('SSH Password'),
PRIVATE_KEY_PASSPHRASE: t.s('Private Key Passphrase'),
PRIVILEGE_ESCALATION_PASSWORD: t.s('Privilege Escalation Password'),
VAULT_PASSWORD: t.s('Vault Password')
},
SHOW_CHANGES: t.s('Show Changes'),
SKIP_TAGS: t.s('Skip Tags'),
JOB_TAGS: t.s('Job Tags'),
LIMIT: t.s('Limit'),
JOB_TYPE: t.s('Job Type'),
VERBOSITY: t.s('Verbosity'),
CHOOSE_JOB_TYPE: t.s('Choose a job type'),
CHOOSE_VERBOSITY: t.s('Choose a verbosity'),
EXTRA_VARIABLES: t.s('Extra Variables'),
PLEASE_ENTER_ANSWER: t.s('Please enter an answer.'),
VALID_INTEGER: t.s('Please enter an answer that is a valid integer.'),
VALID_DECIMAL: t.s('Please enter an answer that is a decimal number.'),
PLAYBOOK_RUN: t.s('Playbook Run'),
CHECK: t.s('Check'),
NO_CREDS_MATCHING_TYPE: t.s('No Credentials Matching This Type Have Been Created')
};
} }
TemplatesStrings.$inject = ['BaseStringService']; TemplatesStrings.$inject = ['BaseStringService'];

View File

@@ -626,11 +626,20 @@ input[type='radio']:checked:before {
background-color: @submit-button-bg-dis; background-color: @submit-button-bg-dis;
} }
.Form-saveButton--disabled {
background-color: @submit-button-bg-dis;
cursor: not-allowed;
}
.Form-saveButton:hover, .Form-launchButton:hover { .Form-saveButton:hover, .Form-launchButton:hover {
background-color: @submit-button-bg-hov; background-color: @submit-button-bg-hov;
color: @submit-button-text; color: @submit-button-text;
} }
.Form-saveButton--disabled:hover {
background-color: @submit-button-bg-dis;
}
.Form-cancelButton { .Form-cancelButton {
background-color: @default-bg; background-color: @default-bg;
color: @btn-txt; color: @btn-txt;
@@ -668,6 +677,10 @@ input[type='radio']:checked:before {
background-color: @default-link; background-color: @default-link;
} }
.Form-primaryButton--noMargin {
margin-right: 0px;
}
.Form-formGroup--singleColumn { .Form-formGroup--singleColumn {
width: 100% !important; width: 100% !important;
padding-right: 0px; padding-right: 0px;

View File

@@ -16,14 +16,12 @@ function AtModalController (eventService, strings) {
const vm = this; const vm = this;
let overlay; let overlay;
let modal;
let listeners; let listeners;
vm.strings = strings; vm.strings = strings;
vm.init = (scope, el) => { vm.init = (scope, el) => {
overlay = el[0]; // eslint-disable-line prefer-destructuring overlay = el[0]; // eslint-disable-line prefer-destructuring
modal = el.find('.at-Modal-window')[0]; // eslint-disable-line prefer-destructuring
vm.modal = scope[scope.ns].modal; vm.modal = scope[scope.ns].modal;
vm.modal.show = vm.show; vm.modal.show = vm.show;
@@ -35,7 +33,7 @@ function AtModalController (eventService, strings) {
vm.modal.message = message; vm.modal.message = message;
listeners = eventService.addListeners([ listeners = eventService.addListeners([
[window, 'click', vm.clickToHide] [overlay, 'click', vm.clickToHide]
]); ]);
overlay.style.display = 'block'; overlay.style.display = 'block';
@@ -53,22 +51,10 @@ function AtModalController (eventService, strings) {
}; };
vm.clickToHide = event => { vm.clickToHide = event => {
if (vm.clickIsOutsideModal(event)) { if ($(event.target).hasClass('at-Modal')) {
vm.hide(); vm.hide();
} }
}; };
vm.clickIsOutsideModal = e => {
const m = modal.getBoundingClientRect();
const cx = e.clientX;
const cy = e.clientY;
if (cx < m.left || cx > m.right || cy > m.bottom || cy < m.top) {
return true;
}
return false;
};
} }
AtModalController.$inject = [ AtModalController.$inject = [

View File

@@ -10,6 +10,12 @@ function AtTabGroupController () {
vm.tabs.push(tab); vm.tabs.push(tab);
}; };
vm.clearActive = () => {
vm.tabs.forEach((tab) => {
tab.state._active = false;
});
};
} }
function atTabGroup () { function atTabGroup () {

View File

@@ -25,7 +25,12 @@ function AtTabController ($state) {
return; return;
} }
$state.go(scope.state._go, scope.state._params, { reload: true }); if (scope.state._go) {
$state.go(scope.state._go, scope.state._params, { reload: true });
} else {
group.clearActive();
scope.state._active = true;
}
}; };
} }

View File

@@ -0,0 +1,21 @@
let Base;
function JobModel (method, resource, config) {
Base.call(this, 'jobs');
this.Constructor = JobModel;
return this.create(method, resource, config);
}
function JobModelLoader (BaseModel) {
Base = BaseModel;
return JobModel;
}
JobModelLoader.$inject = [
'BaseModel'
];
export default JobModelLoader;

View File

@@ -1,5 +1,69 @@
let Base; let Base;
let WorkflowJobTemplateNode; let WorkflowJobTemplateNode;
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.ask_inventory_on_launch &&
!launchData.ask_credential_on_launch &&
!launchData.ask_verbosity_on_launch &&
!launchData.ask_job_type_on_launch &&
!launchData.ask_limit_on_launch &&
!launchData.ask_tags_on_launch &&
!launchData.ask_skip_tags_on_launch &&
!launchData.ask_variables_on_launch &&
!launchData.ask_diff_mode_on_launch &&
!launchData.survey_enabled
);
}
function setDependentResources (id) { function setDependentResources (id) {
this.dependentResources = [ this.dependentResources = [
@@ -17,20 +81,30 @@ function JobTemplateModel (method, resource, config) {
this.Constructor = JobTemplateModel; this.Constructor = JobTemplateModel;
this.setDependentResources = setDependentResources.bind(this); this.setDependentResources = setDependentResources.bind(this);
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 JobTemplateModelLoader (BaseModel, WorkflowJobTemplateNodeModel) { function JobTemplateModelLoader (BaseModel, WorkflowJobTemplateNodeModel, _$http_) {
Base = BaseModel; Base = BaseModel;
WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel; WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel;
$http = _$http_;
return JobTemplateModel; return JobTemplateModel;
} }
JobTemplateModelLoader.$inject = [ JobTemplateModelLoader.$inject = [
'BaseModel', 'BaseModel',
'WorkflowJobTemplateNodeModel' 'WorkflowJobTemplateNodeModel',
'$http',
'$state'
]; ];
export default JobTemplateModelLoader; export default JobTemplateModelLoader;

View File

@@ -0,0 +1,21 @@
let Base;
function WorkflowJobModel (method, resource, config) {
Base.call(this, 'workflow_jobs');
this.Constructor = WorkflowJobModel;
return this.create(method, resource, config);
}
function WorkflowJobModelLoader (BaseModel) {
Base = BaseModel;
return WorkflowJobModel;
}
WorkflowJobModelLoader.$inject = [
'BaseModel'
];
export default WorkflowJobModelLoader;

View File

@@ -14,6 +14,8 @@ import InstanceGroup from '~models/InstanceGroup';
import InventorySource from '~models/InventorySource'; import InventorySource from '~models/InventorySource';
import Inventory from '~models/Inventory'; import Inventory from '~models/Inventory';
import InventoryScript from '~models/InventoryScript'; import InventoryScript from '~models/InventoryScript';
import Job from '~models/Job';
import WorkflowJob from '~models/WorkflowJob';
import WorkflowJobTemplate from '~models/WorkflowJobTemplate'; import WorkflowJobTemplate from '~models/WorkflowJobTemplate';
import ModelsStrings from '~models/models.strings'; import ModelsStrings from '~models/models.strings';
@@ -41,6 +43,9 @@ angular
.service('InventoryScriptModel', InventoryScript) .service('InventoryScriptModel', InventoryScript)
.service('WorkflowJobTemplateModel', WorkflowJobTemplate) .service('WorkflowJobTemplateModel', WorkflowJobTemplate)
.service('ModelsStrings', ModelsStrings) .service('ModelsStrings', ModelsStrings)
.service('UnifiedJobTemplateModel', UnifiedJobTemplate); .service('UnifiedJobTemplateModel', UnifiedJobTemplate)
.service('JobModel', Job)
.service('WorkflowJobModel', WorkflowJob)
.service('WorkflowJobTemplateModel', WorkflowJobTemplate);
export default MODULE_NAME; export default MODULE_NAME;

View File

@@ -60,8 +60,14 @@ function BaseStringService (namespace) {
this.CANCEL = t.s('CANCEL'); this.CANCEL = t.s('CANCEL');
this.SAVE = t.s('SAVE'); this.SAVE = t.s('SAVE');
this.OK = t.s('OK'); this.OK = t.s('OK');
this.NEXT = t.s('NEXT');
this.SHOW = t.s('SHOW');
this.HIDE = t.s('HIDE');
this.ON = t.s('ON'); this.ON = t.s('ON');
this.OFF = t.s('OFF'); this.OFF = t.s('OFF');
this.YAML = t.s('YAML');
this.JSON = t.s('JSON');
this.deleteResource = { this.deleteResource = {
HEADER: t.s('Delete'), HEADER: t.s('Delete'),
USED_BY: resourceType => t.s('The {{ resourceType }} is currently being used by other resources.', { resourceType }), USED_BY: resourceType => t.s('The {{ resourceType }} is currently being used by other resources.', { resourceType }),

View File

@@ -122,6 +122,7 @@
@import '../../src/system-tracking/fact-module-filter.block.less'; @import '../../src/system-tracking/fact-module-filter.block.less';
@import '../../src/system-tracking/fact-module-pickers.block.less'; @import '../../src/system-tracking/fact-module-pickers.block.less';
@import '../../src/system-tracking/system-tracking-container.block.less'; @import '../../src/system-tracking/system-tracking-container.block.less';
@import '../../src/templates/prompt/prompt.block.less';
@import '../../src/templates/job_templates/multi-credential/multi-credential.block.less'; @import '../../src/templates/job_templates/multi-credential/multi-credential.block.less';
@import '../../src/templates/labels/labelsList.block.less'; @import '../../src/templates/labels/labelsList.block.less';
@import '../../src/templates/survey-maker/survey-maker.block.less'; @import '../../src/templates/survey-maker/survey-maker.block.less';

View File

@@ -1,12 +1,12 @@
<div class="LabelList-tagContainer" ng-repeat="related_group in related_groups"> <div class="LabelList-tagContainer" ng-repeat="related_group in related_groups">
<div class="LabelList-tag" ng-class="{'LabelList-tag--deletable' : (showDelete && host.summary_fields.user_capabilities.edit)}">
<span class="LabelList-name">{{ related_group.name }}</span>
</div>
<div class="LabelList-deleteContainer" <div class="LabelList-deleteContainer"
ng-click="deleteLabel(host, related_group)" ng-click="deleteLabel(host, related_group)"
ng-show="showDelete && host.summary_fields.user_capabilities.edit"> ng-show="showDelete && host.summary_fields.user_capabilities.edit">
<i class="fa fa-times LabelList-tagDelete"></i> <i class="fa fa-times LabelList-tagDelete"></i>
</div> </div>
<div class="LabelList-tag" ng-class="{'LabelList-tag--deletable' : (showDelete && host.summary_fields.user_capabilities.edit)}">
<span class="LabelList-name">{{ related_group.name }}</span>
</div>
</div> </div>
<div class="LabelList-seeMoreLess" ng-show="count > 5 && seeMoreInactive" <div class="LabelList-seeMoreLess" ng-show="count > 5 && seeMoreInactive"
ng-click="seeMore()" translate>View More</div> ng-click="seeMore()" translate>View More</div>

View File

@@ -394,20 +394,9 @@ export default
} }
}; };
$scope.toggle_inventory = function(id) { $scope.toggle_credential = function(cred) {
$scope.inventories.forEach(function(row, i) {
if (row.id === id) {
$scope.selected_inventory = angular.copy(row);
$scope.inventories[i].checked = 1;
} else {
$scope.inventories[i].checked = 0;
}
});
};
$scope.toggle_credential = function(id) {
$scope.credentials.forEach(function(row, i) { $scope.credentials.forEach(function(row, i) {
if (row.id === id) { if (row.id === cred.id) {
$scope.selected_credentials.machine = angular.copy(row); $scope.selected_credentials.machine = angular.copy(row);
$scope.credentials[i].checked = 1; $scope.credentials[i].checked = 1;
} else { } else {

View File

@@ -31,12 +31,12 @@
<div class="JobSubmission-previewTags--outer"> <div class="JobSubmission-previewTags--outer">
<div class="JobSubmission-previewTags--inner"> <div class="JobSubmission-previewTags--inner">
<div class="JobSubmission-previewTagContainer"> <div class="JobSubmission-previewTagContainer">
<div class="JobSubmission-previewTagContainerDelete" ng-click="deleteSelectedInventory()">
<i class="fa fa-times JobSubmission-previewTagContainerTagDelete"></i>
</div>
<div class="JobSubmission-previewTag JobSubmission-previewTag--deletable"> <div class="JobSubmission-previewTag JobSubmission-previewTag--deletable">
<span>{{selected_inventory.name}}</span> <span>{{selected_inventory.name}}</span>
</div> </div>
<div class="JobSubmission-previewTagContainerDelete" ng-click="deleteSelectedInventory()">
<i class="fa fa-times JobSubmission-previewTagContainerTagDelete"></i>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -58,22 +58,22 @@
<div class="JobSubmission-previewTags--outer"> <div class="JobSubmission-previewTags--outer">
<div class="JobSubmission-previewTags--inner"> <div class="JobSubmission-previewTags--inner">
<div class="JobSubmission-previewTagContainer u-wordwrap" ng-show="selected_credentials.machine"> <div class="JobSubmission-previewTagContainer u-wordwrap" ng-show="selected_credentials.machine">
<div class="JobSubmission-previewTagContainerDelete" ng-click="deleteMachineCred()" ng-show="ask_credential_on_launch">
<i class="fa fa-times JobSubmission-previewTagContainerTagDelete"></i>
</div>
<div class="JobSubmission-previewTag" ng-class="{'JobSubmission-previewTag--deletable': ask_credential_on_launch}"> <div class="JobSubmission-previewTag" ng-class="{'JobSubmission-previewTag--deletable': ask_credential_on_launch}">
<span><span class="JobSubmission-previewTagLabel" ng-class="{'JobSubmission-previewTagLabel--deletable': ask_credential_on_launch}"> <span><span class="JobSubmission-previewTagLabel" ng-class="{'JobSubmission-previewTagLabel--deletable': ask_credential_on_launch}">
MACHINE</span>:&nbsp;{{selected_credentials.machine.name}}</span> MACHINE</span>:&nbsp;{{selected_credentials.machine.name}}</span>
</div> </div>
</div> <div class="JobSubmission-previewTagContainerDelete" ng-click="deleteMachineCred()" ng-show="ask_credential_on_launch">
<div class="JobSubmission-previewTagContainer" ng-repeat="extraCredential in selected_credentials.extra">
<div class="JobSubmission-previewTagContainerDelete" ng-click="deleteExtraCred($index)" ng-show="ask_credential_on_launch">
<i class="fa fa-times JobSubmission-previewTagContainerTagDelete"></i> <i class="fa fa-times JobSubmission-previewTagContainerTagDelete"></i>
</div> </div>
</div>
<div class="JobSubmission-previewTagContainer" ng-repeat="extraCredential in selected_credentials.extra">
<div class="JobSubmission-previewTag" ng-class="{'JobSubmission-previewTag--deletable': ask_credential_on_launch}"> <div class="JobSubmission-previewTag" ng-class="{'JobSubmission-previewTag--deletable': ask_credential_on_launch}">
<span><span class="JobSubmission-previewTagLabel" ng-class="{'JobSubmission-previewTagLabel--deletable': ask_credential_on_launch}"> <span><span class="JobSubmission-previewTagLabel" ng-class="{'JobSubmission-previewTagLabel--deletable': ask_credential_on_launch}">
{{credential_types[extraCredential.credential_type].name | uppercase}}</span>:&nbsp;{{extraCredential.name}}</span> {{credential_types[extraCredential.credential_type].name | uppercase}}</span>:&nbsp;{{extraCredential.name}}</span>
</div> </div>
<div class="JobSubmission-previewTagContainerDelete" ng-click="deleteExtraCred($index)" ng-show="ask_credential_on_launch">
<i class="fa fa-times JobSubmission-previewTagContainerTagDelete"></i>
</div>
</div> </div>
<div class="JobSubmission-previewTagContainer JobSubmission-previewTagContainer--vault" ng-show="selected_credentials.vault"> <div class="JobSubmission-previewTagContainer JobSubmission-previewTagContainer--vault" ng-show="selected_credentials.vault">
<div class="JobSubmission-previewTag JobSubmission-previewTag--vault" ng-class="{'JobSubmission-previewTag--deletable': ask_credential_on_launch}"> <div class="JobSubmission-previewTag JobSubmission-previewTag--vault" ng-class="{'JobSubmission-previewTag--deletable': ask_credential_on_launch}">

View File

@@ -1,135 +0,0 @@
export default
function AddSchedule($location, $rootScope, $stateParams, SchedulerInit,
Wait, GetBasePath, Empty, SchedulePost, $state, Rest,
ProcessErrors) {
return function(params) {
var scope = params.scope,
callback= params.callback,
base = params.base || $location.path().replace(/^\//, '').split('/')[0],
url = params.url || null,
scheduler,
job_type;
job_type = scope.parentObject.job_type;
if (!Empty($stateParams.id) && base !== 'system_job_templates' && base !== 'inventories' && !url) {
url = GetBasePath(base) + $stateParams.id + '/schedules/';
}
else if(base === "inventories"){
if (!params.url){
url = GetBasePath('groups') + $stateParams.id + '/';
Rest.setUrl(url);
Rest.get().
then(function (data) {
url = data.data.related.inventory_source + 'schedules/';
}).catch(function (response) {
ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get inventory group info. GET returned status: ' +
response.status
});
});
}
else {
url = params.url;
}
}
else if (base === 'system_job_templates') {
url = GetBasePath(base) + $stateParams.id + '/schedules/';
if(job_type === "cleanup_facts"){
scope.isFactCleanup = true;
scope.keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
scope.granularity_keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
scope.prompt_for_days_facts_form.keep_amount.$setViewValue(30);
scope.prompt_for_days_facts_form.granularity_keep_amount.$setViewValue(1);
scope.keep_unit = scope.keep_unit_choices[0];
scope.granularity_keep_unit = scope.granularity_keep_unit_choices[1];
}
else {
scope.cleanupJob = true;
}
}
Wait('start');
$('#form-container').empty();
scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false });
if(scope.schedulerUTCTime) {
// The UTC time is already set
scope.processSchedulerEndDt();
}
else {
// We need to wait for it to be set by angular-scheduler because the following function depends
// on it
var schedulerUTCTimeWatcher = scope.$watch('schedulerUTCTime', function(newVal) {
if(newVal) {
// Remove the watcher
schedulerUTCTimeWatcher();
scope.processSchedulerEndDt();
}
});
}
scheduler.inject('form-container', false);
scheduler.injectDetail('occurrences', false);
scheduler.clear();
scope.$on("htmlDetailReady", function() {
$rootScope.$broadcast("ScheduleFormCreated", scope);
});
scope.showRRuleDetail = false;
if (scope.removeScheduleSaved) {
scope.removeScheduleSaved();
}
scope.removeScheduleSaved = scope.$on('ScheduleSaved', function(e, data) {
Wait('stop');
if (callback) {
scope.$emit(callback, data);
}
$state.go("^", null, {reload: true});
});
scope.saveSchedule = function() {
SchedulePost({
scope: scope,
url: url,
scheduler: scheduler,
callback: 'ScheduleSaved',
mode: 'add'
});
};
$('#scheduler-tabs li a').on('shown.bs.tab', function(e) {
if ($(e.target).text() === 'Details') {
if (!scheduler.isValid()) {
$('#scheduler-tabs a:first').tab('show');
}
}
});
};
}
AddSchedule.$inject =
[ '$location', '$rootScope', '$stateParams',
'SchedulerInit', 'Wait', 'GetBasePath',
'Empty', 'SchedulePost', '$state',
'Rest', 'ProcessErrors'
];

View File

@@ -1,154 +0,0 @@
export default
function EditSchedule(SchedulerInit, $rootScope, Wait, Rest, ProcessErrors,
GetBasePath, SchedulePost, $state) {
return function(params) {
var scope = params.scope,
id = params.id,
callback = params.callback,
schedule, scheduler,
url = GetBasePath('schedules') + id + '/';
delete scope.isFactCleanup;
delete scope.cleanupJob;
function setGranularity(){
var a,b, prompt_for_days,
keep_unit,
granularity,
granularity_keep_unit;
if(scope.cleanupJob){
scope.schedulerPurgeDays = Number(schedule.extra_data.days);
// scope.scheduler_form.schedulerPurgeDays.$setViewValue( Number(schedule.extra_data.days));
}
else if(scope.isFactCleanup){
scope.keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
scope.granularity_keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
// the API returns something like 20w or 1y
a = schedule.extra_data.older_than; // "20y"
b = schedule.extra_data.granularity; // "1w"
prompt_for_days = Number(_.initial(a,1).join('')); // 20
keep_unit = _.last(a); // "y"
granularity = Number(_.initial(b,1).join('')); // 1
granularity_keep_unit = _.last(b); // "w"
scope.keep_amount = prompt_for_days;
scope.granularity_keep_amount = granularity;
scope.keep_unit = _.find(scope.keep_unit_choices, function(i){
return i.value === keep_unit;
});
scope.granularity_keep_unit =_.find(scope.granularity_keep_unit_choices, function(i){
return i.value === granularity_keep_unit;
});
}
}
if (scope.removeScheduleFound) {
scope.removeScheduleFound();
}
scope.removeScheduleFound = scope.$on('ScheduleFound', function() {
$('#form-container').empty();
scheduler = SchedulerInit({ scope: scope, requireFutureStartTime: false });
scheduler.inject('form-container', false);
scheduler.injectDetail('occurrences', false);
if (!/DTSTART/.test(schedule.rrule)) {
schedule.rrule += ";DTSTART=" + schedule.dtstart.replace(/\.\d+Z$/,'Z');
}
schedule.rrule = schedule.rrule.replace(/ RRULE:/,';');
schedule.rrule = schedule.rrule.replace(/DTSTART:/,'DTSTART=');
scope.$on("htmlDetailReady", function() {
scheduler.setRRule(schedule.rrule);
scheduler.setName(schedule.name);
$rootScope.$broadcast("ScheduleFormCreated", scope);
});
scope.showRRuleDetail = false;
scheduler.setRRule(schedule.rrule);
scheduler.setName(schedule.name);
if(scope.isFactCleanup || scope.cleanupJob){
setGranularity();
}
});
if (scope.removeScheduleSaved) {
scope.removeScheduleSaved();
}
scope.removeScheduleSaved = scope.$on('ScheduleSaved', function(e, data) {
Wait('stop');
if (callback) {
scope.$emit(callback, data);
}
$state.go("^");
});
scope.saveSchedule = function() {
schedule.extra_data = scope.extraVars;
SchedulePost({
scope: scope,
url: url,
scheduler: scheduler,
callback: 'ScheduleSaved',
mode: 'edit',
schedule: schedule
});
};
Wait('start');
// Get the existing record
Rest.setUrl(url);
Rest.get()
.then(({data}) => {
schedule = data;
try {
schedule.extra_data = JSON.parse(schedule.extra_data);
} catch(e) {
// do nothing
}
scope.extraVars = data.extra_data === '' ? '---' : '---\n' + jsyaml.safeDump(data.extra_data);
if(schedule.extra_data.hasOwnProperty('granularity')){
scope.isFactCleanup = true;
}
if (schedule.extra_data.hasOwnProperty('days')){
scope.cleanupJob = true;
}
scope.schedule_obj = data;
scope.$emit('ScheduleFound');
})
.catch(({data, status}) => {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status });
});
};
}
EditSchedule.$inject =
[ 'SchedulerInit', '$rootScope', 'Wait', 'Rest',
'ProcessErrors', 'GetBasePath', 'SchedulePost', '$state'
];

View File

@@ -1,13 +1,14 @@
export default export default
function SchedulePost(Rest, ProcessErrors, RRuleToAPI, Wait) { function SchedulePost(Rest, ProcessErrors, RRuleToAPI, Wait, $q) {
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
url = params.url, url = params.url,
scheduler = params.scheduler, scheduler = params.scheduler,
mode = params.mode, mode = params.mode,
schedule = (params.schedule) ? params.schedule : {}, schedule = (params.schedule) ? params.schedule : {},
callback = params.callback, promptData = params.promptData,
newSchedule, rrule, extra_vars; newSchedule, rrule, extra_vars;
let deferred = $q.defer();
if (scheduler.isValid()) { if (scheduler.isValid()) {
Wait('start'); Wait('start');
newSchedule = scheduler.getValue(); newSchedule = scheduler.getValue();
@@ -32,41 +33,101 @@ export default
schedule.extra_data = scope.parseType === 'yaml' ? schedule.extra_data = scope.parseType === 'yaml' ?
(scope.extraVars === '---' ? "" : jsyaml.safeLoad(scope.extraVars)) : scope.extraVars; (scope.extraVars === '---' ? "" : jsyaml.safeLoad(scope.extraVars)) : scope.extraVars;
} }
if(promptData) {
if(promptData.launchConf.survey_enabled){
for (var i=0; i < promptData.surveyQuestions.length; i++){
var fld = promptData.surveyQuestions[i].variable;
// grab all survey questions that have answers
if(promptData.surveyQuestions[i].required || (promptData.surveyQuestions[i].required === false && promptData.surveyQuestions[i].model.toString()!=="")) {
if(!schedule.extra_data) {
schedule.extra_data = {};
}
schedule.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) {
schedule.extra_data[fld] = "";
}
break;
}
}
}
}
if(_.has(promptData, 'prompts.jobType.value.value') && _.get(promptData, 'launchConf.ask_job_type_on_launch')) {
schedule.job_type = promptData.prompts.jobType.templateDefault === promptData.prompts.jobType.value.value ? null : promptData.prompts.jobType.value.value;
}
if(_.has(promptData, 'prompts.tags.value') && _.get(promptData, 'launchConf.ask_tags_on_launch')){
let templateDefaultJobTags = promptData.prompts.tags.templateDefault.split(',');
schedule.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')){
let templateDefaultSkipTags = promptData.prompts.skipTags.templateDefault.split(',');
schedule.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')){
schedule.limit = promptData.prompts.limit.templateDefault === promptData.prompts.limit.value ? null : promptData.prompts.limit.value;
}
if(_.has(promptData, 'prompts.verbosity.value.value') && _.get(promptData, 'launchConf.ask_verbosity_on_launch')){
schedule.verbosity = promptData.prompts.verbosity.templateDefault === promptData.prompts.verbosity.value.value ? null : promptData.prompts.verbosity.value.value;
}
if(_.has(promptData, 'prompts.inventory.value') && _.get(promptData, 'launchConf.ask_inventory_on_launch')){
schedule.inventory = promptData.prompts.inventory.templateDefault.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')){
schedule.diff_mode = promptData.prompts.diffMode.templateDefault === promptData.prompts.diffMode.value ? null : promptData.prompts.diffMode.value;
}
// Credentials gets POST'd to a separate endpoint
// if($scope.promptData.launchConf.ask_credential_on_launch){
// jobLaunchData.credentials = [];
// promptData.credentials.value.forEach((credential) => {
// jobLaunchData.credentials.push(credential.id);
// });
// }
}
Rest.setUrl(url); Rest.setUrl(url);
if (mode === 'add') { if (mode === 'add') {
Rest.post(schedule) Rest.post(schedule)
.then(() => { .then(() => {
if (callback) { Wait('stop');
scope.$emit(callback); deferred.resolve();
}
else {
Wait('stop');
}
}) })
.catch(({data, status}) => { .catch(({data, status}) => {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'POST to ' + url + ' returned: ' + status }); msg: 'POST to ' + url + ' returned: ' + status });
deferred.reject();
}); });
} }
else { else {
Rest.put(schedule) Rest.put(schedule)
.then(() => { .then(() => {
if (callback) { Wait('stop');
scope.$emit(callback, schedule); deferred.resolve(schedule);
}
else {
Wait('stop');
}
}) })
.catch(({data, status}) => { .catch(({data, status}) => {
ProcessErrors(scope, data, status, null, { hdr: 'Error!', ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'POST to ' + url + ' returned: ' + status }); msg: 'POST to ' + url + ' returned: ' + status });
deferred.reject();
}); });
} }
} }
else { else {
return false; deferred.reject();
} }
return deferred.promise;
}; };
} }
@@ -74,5 +135,6 @@ SchedulePost.$inject =
[ 'Rest', [ 'Rest',
'ProcessErrors', 'ProcessErrors',
'RRuleToAPI', 'RRuleToAPI',
'Wait' 'Wait',
'$q'
]; ];

View File

@@ -10,9 +10,7 @@ import editController from './schedulerEdit.controller';
import {templateUrl} from '../shared/template-url/template-url.factory'; import {templateUrl} from '../shared/template-url/template-url.factory';
import schedulerDatePicker from './schedulerDatePicker.directive'; import schedulerDatePicker from './schedulerDatePicker.directive';
import { N_ } from '../i18n'; import { N_ } from '../i18n';
import AddSchedule from './factories/add-schedule.factory';
import DeleteSchedule from './factories/delete-schedule.factory'; import DeleteSchedule from './factories/delete-schedule.factory';
import EditSchedule from './factories/edit-schedule.factory';
import RRuleToAPI from './factories/r-rule-to-api.factory'; import RRuleToAPI from './factories/r-rule-to-api.factory';
import SchedulePost from './factories/schedule-post.factory'; import SchedulePost from './factories/schedule-post.factory';
import ToggleSchedule from './factories/toggle-schedule.factory'; import ToggleSchedule from './factories/toggle-schedule.factory';
@@ -24,9 +22,7 @@ export default
.controller('schedulerListController', listController) .controller('schedulerListController', listController)
.controller('schedulerAddController', addController) .controller('schedulerAddController', addController)
.controller('schedulerEditController', editController) .controller('schedulerEditController', editController)
.factory('AddSchedule', AddSchedule)
.factory('DeleteSchedule', DeleteSchedule) .factory('DeleteSchedule', DeleteSchedule)
.factory('EditSchedule', EditSchedule)
.factory('RRuleToAPI', RRuleToAPI) .factory('RRuleToAPI', RRuleToAPI)
.factory('SchedulePost', SchedulePost) .factory('SchedulePost', SchedulePost)
.factory('ToggleSchedule', ToggleSchedule) .factory('ToggleSchedule', ToggleSchedule)
@@ -47,10 +43,10 @@ export default
activityStreamTarget: 'job_template', activityStreamTarget: 'job_template',
activityStreamId: 'id' activityStreamId: 'id'
}, },
ncyBreadcrumb: { // ncyBreadcrumb: {
parent: 'templates.editJobTemplate({job_template_id: parentObject.id})', // parent: 'templates.editJobTemplate({job_template_id: parentObject.id})',
label: N_('SCHEDULES') // label: N_('SCHEDULES')
}, // },
resolve: { resolve: {
Dataset: ['ScheduleList', 'QuerySet', '$stateParams', 'GetBasePath', Dataset: ['ScheduleList', 'QuerySet', '$stateParams', 'GetBasePath',
function(list, qs, $stateParams, GetBasePath) { function(list, qs, $stateParams, GetBasePath) {

View File

@@ -4,12 +4,22 @@
* All Rights Reserved * All Rights Reserved
*************************************************/ *************************************************/
export default ['$filter', '$state', '$stateParams', 'AddSchedule', 'Wait', export default ['$filter', '$state', '$stateParams', 'Wait',
'$scope', '$rootScope', 'CreateSelect2', 'ParseTypeChange', 'GetBasePath', '$scope', '$rootScope', 'CreateSelect2', 'ParseTypeChange', 'GetBasePath',
'Rest', 'ParentObject', 'Rest', 'ParentObject', 'JobTemplateModel', '$q', 'Empty', 'SchedulePost',
function($filter, $state, $stateParams, AddSchedule, Wait, $scope, 'ProcessErrors', 'SchedulerInit', '$location', 'PromptService',
$rootScope, CreateSelect2, ParseTypeChange, GetBasePath, Rest, ParentObject) { function($filter, $state, $stateParams, Wait,
$scope.processSchedulerEndDt = function(){ $scope, $rootScope, CreateSelect2, ParseTypeChange, GetBasePath,
Rest, ParentObject, JobTemplate, $q, Empty, SchedulePost,
ProcessErrors, SchedulerInit, $location, PromptService) {
var base = $scope.base || $location.path().replace(/^\//, '').split('/')[0],
scheduler,
job_type;
var schedule_url = ParentObject.related.schedules || `${ParentObject.related.inventory_source}schedules`;
let processSchedulerEndDt = function(){
// set the schedulerEndDt to be equal to schedulerStartDt + 1 day @ midnight // set the schedulerEndDt to be equal to schedulerStartDt + 1 day @ midnight
var dt = new Date($scope.schedulerUTCTime); var dt = new Date($scope.schedulerUTCTime);
// increment date by 1 day // increment date by 1 day
@@ -19,12 +29,6 @@ export default ['$filter', '$state', '$stateParams', 'AddSchedule', 'Wait',
$scope.$parent.schedulerEndDt = month + '/' + day + '/' + dt.getFullYear(); $scope.$parent.schedulerEndDt = month + '/' + day + '/' + dt.getFullYear();
}; };
// initial end @ midnight values
$scope.schedulerEndHour = "00";
$scope.schedulerEndMinute = "00";
$scope.schedulerEndSecond = "00";
$scope.parentObject = ParentObject;
/* /*
* This is a workaround for the angular-scheduler library inserting `ll` into fields after an * This is a workaround for the angular-scheduler library inserting `ll` into fields after an
* invalid entry and never unsetting them. Presumably null is being truncated down to 2 chars * invalid entry and never unsetting them. Presumably null is being truncated down to 2 chars
@@ -50,9 +54,255 @@ export default ['$filter', '$state', '$stateParams', 'AddSchedule', 'Wait',
$scope.scheduleTimeChange(); $scope.scheduleTimeChange();
}; };
$scope.$on("ScheduleFormCreated", function(e, scope) { $scope.saveSchedule = function() {
SchedulePost({
scope: $scope,
url: schedule_url,
scheduler: scheduler,
promptData: $scope.promptData,
mode: 'add'
}).then(() => {
Wait('stop');
$state.go("^", null, {reload: true});
});
};
$scope.prompt = () => {
$scope.promptData.triggerModalOpen = true;
};
$scope.formCancel = function() {
$state.go("^");
};
// initial end @ midnight values
$scope.schedulerEndHour = "00";
$scope.schedulerEndMinute = "00";
$scope.schedulerEndSecond = "00";
$scope.parentObject = ParentObject;
$scope.hideForm = true;
// extra_data field is not manifested in the UI when scheduling a Management Job
if ($state.current.name === 'jobTemplateSchedules.add'){
$scope.parseType = 'yaml';
$scope.extraVars = '---';
ParseTypeChange({
scope: $scope,
variable: 'extraVars',
parse_variable: 'parseType',
field_id: 'SchedulerForm-extraVars'
});
let jobTemplate = new JobTemplate();
$q.all([jobTemplate.optionsLaunch(ParentObject.id), jobTemplate.getLaunch(ParentObject.id)])
.then((responses) => {
let launchConf = responses[1].data;
let watchForPromptChanges = () => {
let promptValuesToWatch = [
'promptData.prompts.inventory.value',
'promptData.prompts.verbosity.value',
'missingSurveyValue'
];
$scope.$watchGroup(promptValuesToWatch, function() {
let missingPromptValue = false;
if($scope.missingSurveyValue) {
missingPromptValue = true;
} else if(!$scope.promptData.prompts.inventory.value || !$scope.promptData.prompts.inventory.value.id) {
missingPromptValue = true;
}
$scope.promptModalMissingReqFields = missingPromptValue;
});
};
if(!launchConf.ask_variables_on_launch) {
$scope.noVars = true;
}
if(!launchConf.survey_enabled &&
!launchConf.ask_inventory_on_launch &&
!launchConf.ask_credential_on_launch &&
!launchConf.ask_verbosity_on_launch &&
!launchConf.ask_job_type_on_launch &&
!launchConf.ask_limit_on_launch &&
!launchConf.ask_tags_on_launch &&
!launchConf.ask_skip_tags_on_launch &&
!launchConf.ask_diff_mode_on_launch &&
!launchConf.survey_enabled &&
!launchConf.credential_needed_to_start &&
!launchConf.inventory_needed_to_start &&
launchConf.passwords_needed_to_start.length === 0 &&
launchConf.variables_needed_to_start.length === 0) {
$scope.showPromptButton = false;
} else {
$scope.showPromptButton = true;
// Ignore the fact that variables might be promptable on launch
// Promptable variables will happen in the schedule form
launchConf.ignore_ask_variables = true;
if(launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory')) {
$scope.promptModalMissingReqFields = true;
}
if(launchConf.survey_enabled) {
// go out and get the survey questions
jobTemplate.getSurveyQuestions(ParentObject.id)
.then((surveyQuestionRes) => {
let processed = PromptService.processSurveyQuestions({
surveyQuestions: surveyQuestionRes.data.spec
});
$scope.missingSurveyValue = processed.missingSurveyValue;
$scope.promptData = {
launchConf: responses[1].data,
launchOptions: responses[0].data,
surveyQuestions: processed.surveyQuestions,
template: ParentObject.id,
prompts: PromptService.processPromptValues({
launchConf: responses[1].data,
launchOptions: responses[0].data
}),
};
$scope.$watch('promptData.surveyQuestions', () => {
let missingSurveyValue = false;
_.each($scope.promptData.surveyQuestions, (question) => {
if(question.required && (Empty(question.model) || question.model === [])) {
missingSurveyValue = true;
}
});
$scope.missingSurveyValue = missingSurveyValue;
}, true);
watchForPromptChanges();
});
}
else {
$scope.promptData = {
launchConf: responses[1].data,
launchOptions: responses[0].data,
template: ParentObject.id,
prompts: PromptService.processPromptValues({
launchConf: responses[1].data,
launchOptions: responses[0].data
}),
};
watchForPromptChanges();
}
}
});
}
else if ($state.current.name === 'workflowJobTemplateSchedules.add'){
$scope.parseType = 'yaml';
// grab any existing extra_vars from parent workflow_job_template
let defaultUrl = GetBasePath('workflow_job_templates') + $stateParams.id + '/';
Rest.setUrl(defaultUrl);
Rest.get().then(function(res){
var data = res.data.extra_vars;
$scope.extraVars = data === '' ? '---' : data;
ParseTypeChange({
scope: $scope,
variable: 'extraVars',
parse_variable: 'parseType',
field_id: 'SchedulerForm-extraVars'
});
});
}
else if ($state.current.name === 'projectSchedules.add'){
$scope.noVars = true;
}
else if ($state.current.name === 'inventories.edit.inventory_sources.edit.schedules.add'){
$scope.noVars = true;
}
job_type = $scope.parentObject.job_type;
if (!Empty($stateParams.id) && base !== 'system_job_templates' && base !== 'inventories' && !schedule_url) {
schedule_url = GetBasePath(base) + $stateParams.id + '/schedules/';
}
else if(base === "inventories"){
if (!schedule_url){
Rest.setUrl(GetBasePath('groups') + $stateParams.id + '/');
Rest.get()
.then(function (data) {
schedule_url = data.data.related.inventory_source + 'schedules/';
}).catch(function (response) {
ProcessErrors(null, response.data, response.status, null, {
hdr: 'Error!',
msg: 'Failed to get inventory group info. GET returned status: ' +
response.status
});
});
}
}
else if (base === 'system_job_templates') {
schedule_url = GetBasePath(base) + $stateParams.id + '/schedules/';
if(job_type === "cleanup_facts"){
$scope.isFactCleanup = true;
$scope.keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
$scope.granularity_keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
$scope.prompt_for_days_facts_form.keep_amount.$setViewValue(30);
$scope.prompt_for_days_facts_form.granularity_keep_amount.$setViewValue(1);
$scope.keep_unit = $scope.keep_unit_choices[0];
$scope.granularity_keep_unit = $scope.granularity_keep_unit_choices[1];
}
else {
$scope.cleanupJob = true;
}
}
Wait('start');
$('#form-container').empty();
scheduler = SchedulerInit({ scope: $scope, requireFutureStartTime: false });
if($scope.schedulerUTCTime) {
// The UTC time is already set
processSchedulerEndDt();
}
else {
// We need to wait for it to be set by angular-scheduler because the following function depends
// on it
var schedulerUTCTimeWatcher = $scope.$watch('schedulerUTCTime', function(newVal) {
if(newVal) {
// Remove the watcher
schedulerUTCTimeWatcher();
processSchedulerEndDt();
}
});
}
scheduler.inject('form-container', false);
scheduler.injectDetail('occurrences', false);
scheduler.clear();
$scope.$on("htmlDetailReady", function() {
$scope.hideForm = false; $scope.hideForm = false;
$scope = angular.extend($scope, scope);
$scope.$on("formUpdated", function() { $scope.$on("formUpdated", function() {
$rootScope.$broadcast("loadSchedulerDetailPane"); $rootScope.$broadcast("loadSchedulerDetailPane");
}); });
@@ -91,72 +341,18 @@ export default ['$filter', '$state', '$stateParams', 'AddSchedule', 'Wait',
Wait('stop'); Wait('stop');
}); });
$scope.showRRuleDetail = false;
$scope.hideForm = true; $('#scheduler-tabs li a').on('shown.bs.tab', function(e) {
if ($(e.target).text() === 'Details') {
var schedule_url = ParentObject.related.schedules || `${ParentObject.related.inventory_source}schedules`; if (!scheduler.isValid()) {
$('#scheduler-tabs a:first').tab('show');
$scope.formCancel = function() { }
$state.go("^"); }
};
// extra_data field is not manifested in the UI when scheduling a Management Job
if ($state.current.name === 'jobTemplateSchedules.add'){
$scope.parseType = 'yaml';
// grab any existing extra_vars from parent job_template
let defaultUrl = GetBasePath('job_templates') + $stateParams.id + '/';
Rest.setUrl(defaultUrl);
Rest.get().then(function(res){
var data = res.data.extra_vars;
$scope.extraVars = data === '' ? '---' : data;
ParseTypeChange({
scope: $scope,
variable: 'extraVars',
parse_variable: 'parseType',
field_id: 'SchedulerForm-extraVars'
});
});
}
else if ($state.current.name === 'workflowJobTemplateSchedules.add'){
$scope.parseType = 'yaml';
// grab any existing extra_vars from parent workflow_job_template
let defaultUrl = GetBasePath('workflow_job_templates') + $stateParams.id + '/';
Rest.setUrl(defaultUrl);
Rest.get().then(function(res){
var data = res.data.extra_vars;
$scope.extraVars = data === '' ? '---' : data;
ParseTypeChange({
scope: $scope,
variable: 'extraVars',
parse_variable: 'parseType',
field_id: 'SchedulerForm-extraVars'
});
});
}
else if ($state.current.name === 'projectSchedules.add'){
$scope.noVars = true;
}
else if ($state.current.name === 'inventories.edit.inventory_sources.edit.schedules.add'){
$scope.noVars = true;
}
AddSchedule({
scope: $scope,
callback: 'SchedulesRefresh',
base: $scope.base ? $scope.base : null,
url: schedule_url
}); });
var callSelect2 = function() { CreateSelect2({
CreateSelect2({ element: '.MakeSelect2',
element: '.MakeSelect2', multiple: false
multiple: false
});
};
$scope.$on("updateSchedulerSelects", function() {
callSelect2();
}); });
callSelect2();
}]; }];

View File

@@ -1,5 +1,20 @@
export default ['$filter', '$state', '$stateParams', 'EditSchedule', 'Wait', '$scope', '$rootScope', 'CreateSelect2', 'ParseTypeChange', 'ParentObject', export default ['$filter', '$state', '$stateParams', 'Wait', '$scope',
function($filter, $state, $stateParams, EditSchedule, Wait, $scope, $rootScope, CreateSelect2, ParseTypeChange, ParentObject) { '$rootScope', 'CreateSelect2', 'ParseTypeChange', 'ParentObject', 'ProcessErrors', 'Rest',
'GetBasePath', 'SchedulerInit', 'SchedulePost', 'JobTemplateModel', '$q', 'Empty', 'PromptService',
function($filter, $state, $stateParams, Wait, $scope,
$rootScope, CreateSelect2, ParseTypeChange, ParentObject, ProcessErrors, Rest,
GetBasePath, SchedulerInit, SchedulePost, JobTemplate, $q, Empty, PromptService) {
let schedule, scheduler;
// initial end @ midnight values
$scope.schedulerEndHour = "00";
$scope.schedulerEndMinute = "00";
$scope.schedulerEndSecond = "00";
$scope.parentObject = ParentObject;
$scope.isEdit = true;
$scope.hideForm = true;
$scope.parseType = 'yaml';
$scope.processSchedulerEndDt = function(){ $scope.processSchedulerEndDt = function(){
// set the schedulerEndDt to be equal to schedulerStartDt + 1 day @ midnight // set the schedulerEndDt to be equal to schedulerStartDt + 1 day @ midnight
@@ -10,11 +25,10 @@ function($filter, $state, $stateParams, EditSchedule, Wait, $scope, $rootScope,
day = $filter('schZeroPad')(dt.getDate(), 2); day = $filter('schZeroPad')(dt.getDate(), 2);
$scope.$parent.schedulerEndDt = month + '/' + day + '/' + dt.getFullYear(); $scope.$parent.schedulerEndDt = month + '/' + day + '/' + dt.getFullYear();
}; };
// initial end @ midnight values
$scope.schedulerEndHour = "00"; $scope.formCancel = function() {
$scope.schedulerEndMinute = "00"; $state.go("^");
$scope.schedulerEndSecond = "00"; };
$scope.parentObject = ParentObject;
/* /*
* This is a workaround for the angular-scheduler library inserting `ll` into fields after an * This is a workaround for the angular-scheduler library inserting `ll` into fields after an
@@ -41,85 +55,24 @@ function($filter, $state, $stateParams, EditSchedule, Wait, $scope, $rootScope,
$scope.scheduleTimeChange(); $scope.scheduleTimeChange();
}; };
$scope.$on("ScheduleFormCreated", function(e, scope) { $scope.saveSchedule = function() {
$scope.hideForm = false; schedule.extra_data = $scope.extraVars;
$scope = angular.extend($scope, scope); SchedulePost({
scope: $scope,
$scope.$on("formUpdated", function() { url: GetBasePath('schedules') + parseInt($stateParams.schedule_id) + '/',
$rootScope.$broadcast("loadSchedulerDetailPane"); scheduler: scheduler,
mode: 'edit',
schedule: schedule,
promptData: $scope.promptData
}).then(() => {
Wait('stop');
$state.go("^", null, {reload: true});
}); });
$scope.$watchGroup(["schedulerName",
"schedulerStartDt",
"schedulerStartHour",
"schedulerStartMinute",
"schedulerStartSecond",
"schedulerTimeZone",
"schedulerFrequency",
"schedulerInterval",
"monthlyRepeatOption",
"monthDay",
"monthlyOccurrence",
"monthlyWeekDay",
"yearlyRepeatOption",
"yearlyMonth",
"yearlyMonthDay",
"yearlyOccurrence",
"yearlyWeekDay",
"yearlyOtherMonth",
"schedulerEnd",
"schedulerOccurrenceCount",
"schedulerEndDt"
], function() {
$scope.$emit("formUpdated");
}, true);
$scope.$watch("weekDays", function() {
$scope.$emit("formUpdated");
}, true);
$rootScope.$broadcast("loadSchedulerDetailPane");
Wait('stop');
});
$scope.isEdit = true;
$scope.hideForm = true;
$scope.parseType = 'yaml';
$scope.formCancel = function() {
$state.go("^");
}; };
// extra_data field is not manifested in the UI when scheduling a Management Job $scope.prompt = () => {
if ($state.current.name !== 'managementJobsList.schedule.add' && $state.current.name !== 'managementJobsList.schedule.edit'){ $scope.promptData.triggerModalOpen = true;
$scope.$on('ScheduleFound', function(){ };
if ($state.current.name === 'projectSchedules.edit'){
$scope.noVars = true;
}
else if ($state.current.name === 'inventories.edit.inventory_sources.edit.schedules.edit'){
$scope.noVars = true;
}
else {
let readOnly = !$scope.schedule_obj.summary_fields.user_capabilities
.edit;
ParseTypeChange({
scope: $scope,
variable: 'extraVars',
parse_variable: 'parseType',
field_id: 'SchedulerForm-extraVars',
readOnly: readOnly
});
}
});
}
EditSchedule({
scope: $scope,
id: parseInt($stateParams.schedule_id),
callback: 'SchedulesRefresh',
base: $scope.base ? $scope.base: null
});
var callSelect2 = function() { var callSelect2 = function() {
CreateSelect2({ CreateSelect2({
@@ -132,5 +85,284 @@ function($filter, $state, $stateParams, EditSchedule, Wait, $scope, $rootScope,
callSelect2(); callSelect2();
}); });
Wait('start');
// Get the existing record
Rest.setUrl(GetBasePath('schedules') + parseInt($stateParams.schedule_id) + '/');
Rest.get()
.then(({data}) => {
schedule = data;
try {
schedule.extra_data = JSON.parse(schedule.extra_data);
} catch(e) {
// do nothing
}
$scope.extraVars = (data.extra_data === '' || _.isEmpty(data.extra_data)) ? '---' : '---\n' + jsyaml.safeDump(data.extra_data);
if(schedule.extra_data.hasOwnProperty('granularity')){
$scope.isFactCleanup = true;
}
if (schedule.extra_data.hasOwnProperty('days')){
$scope.cleanupJob = true;
}
$scope.schedule_obj = data;
$('#form-container').empty();
scheduler = SchedulerInit({ scope: $scope, requireFutureStartTime: false });
scheduler.inject('form-container', false);
scheduler.injectDetail('occurrences', false);
if (!/DTSTART/.test(schedule.rrule)) {
schedule.rrule += ";DTSTART=" + schedule.dtstart.replace(/\.\d+Z$/,'Z');
}
schedule.rrule = schedule.rrule.replace(/ RRULE:/,';');
schedule.rrule = schedule.rrule.replace(/DTSTART:/,'DTSTART=');
$scope.$on("htmlDetailReady", function() {
scheduler.setRRule(schedule.rrule);
scheduler.setName(schedule.name);
$scope.hideForm = false;
$scope.$watchGroup(["schedulerName",
"schedulerStartDt",
"schedulerStartHour",
"schedulerStartMinute",
"schedulerStartSecond",
"schedulerTimeZone",
"schedulerFrequency",
"schedulerInterval",
"monthlyRepeatOption",
"monthDay",
"monthlyOccurrence",
"monthlyWeekDay",
"yearlyRepeatOption",
"yearlyMonth",
"yearlyMonthDay",
"yearlyOccurrence",
"yearlyWeekDay",
"yearlyOtherMonth",
"schedulerEnd",
"schedulerOccurrenceCount",
"schedulerEndDt"
], function() {
$rootScope.$broadcast("loadSchedulerDetailPane");
}, true);
$scope.$watch("weekDays", function() {
$rootScope.$broadcast("loadSchedulerDetailPane");
}, true);
$rootScope.$broadcast("loadSchedulerDetailPane");
Wait('stop');
});
$scope.showRRuleDetail = false;
scheduler.setRRule(schedule.rrule);
scheduler.setName(schedule.name);
if($scope.isFactCleanup || $scope.cleanupJob){
var a,b, prompt_for_days,
keep_unit,
granularity,
granularity_keep_unit;
if($scope.cleanupJob){
$scope.schedulerPurgeDays = Number(schedule.extra_data.days);
}
else if($scope.isFactCleanup){
$scope.keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
$scope.granularity_keep_unit_choices = [{
"label" : "Days",
"value" : "d"
},
{
"label": "Weeks",
"value" : "w"
},
{
"label" : "Years",
"value" : "y"
}];
// the API returns something like 20w or 1y
a = schedule.extra_data.older_than; // "20y"
b = schedule.extra_data.granularity; // "1w"
prompt_for_days = Number(_.initial(a,1).join('')); // 20
keep_unit = _.last(a); // "y"
granularity = Number(_.initial(b,1).join('')); // 1
granularity_keep_unit = _.last(b); // "w"
$scope.keep_amount = prompt_for_days;
$scope.granularity_keep_amount = granularity;
$scope.keep_unit = _.find($scope.keep_unit_choices, function(i){
return i.value === keep_unit;
});
$scope.granularity_keep_unit =_.find($scope.granularity_keep_unit_choices, function(i){
return i.value === granularity_keep_unit;
});
}
}
if ($state.current.name === 'jobTemplateSchedules.edit'){
let jobTemplate = new JobTemplate();
Rest.setUrl(data.related.credentials);
$q.all([jobTemplate.optionsLaunch(ParentObject.id), jobTemplate.getLaunch(ParentObject.id), Rest.get()])
.then((responses) => {
let launchOptions = responses[0].data,
launchConf = responses[1].data,
scheduleCredentials = responses[2].data;
let watchForPromptChanges = () => {
let promptValuesToWatch = [
// credential passwords...?
'promptData.prompts.inventory.value',
'promptData.prompts.verbosity.value',
'missingSurveyValue'
];
$scope.$watchGroup(promptValuesToWatch, function() {
let missingPromptValue = false;
if($scope.missingSurveyValue) {
missingPromptValue = true;
} else if(!$scope.promptData.prompts.inventory.value || !$scope.promptData.prompts.inventory.value.id) {
missingPromptValue = true;
}
$scope.promptModalMissingReqFields = missingPromptValue;
});
};
let prompts = PromptService.processPromptValues({
launchConf: responses[1].data,
launchOptions: responses[0].data,
currentValues: data
});
prompts.credentials.value = scheduleCredentials.results.length > 0 ? scheduleCredentials.results : prompts.credentials.value;
if(!launchConf.ask_variables_on_launch) {
$scope.noVars = true;
}
if(!launchConf.survey_enabled &&
!launchConf.ask_inventory_on_launch &&
!launchConf.ask_credential_on_launch &&
!launchConf.ask_verbosity_on_launch &&
!launchConf.ask_job_type_on_launch &&
!launchConf.ask_limit_on_launch &&
!launchConf.ask_tags_on_launch &&
!launchConf.ask_skip_tags_on_launch &&
!launchConf.ask_diff_mode_on_launch &&
!launchConf.survey_enabled &&
!launchConf.credential_needed_to_start &&
!launchConf.inventory_needed_to_start &&
launchConf.passwords_needed_to_start.length === 0 &&
launchConf.variables_needed_to_start.length === 0) {
$scope.showPromptButton = false;
} else {
$scope.showPromptButton = true;
// Ignore the fact that variables might be promptable on launch
// Promptable variables will happen in the schedule form
launchConf.ignore_ask_variables = true;
if(launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory') && !_.has(data, 'summary_fields.inventory')) {
$scope.promptModalMissingReqFields = true;
}
if(responses[1].data.survey_enabled) {
// go out and get the survey questions
jobTemplate.getSurveyQuestions(ParentObject.id)
.then((surveyQuestionRes) => {
let processed = PromptService.processSurveyQuestions({
surveyQuestions: surveyQuestionRes.data.spec,
extra_data: data.extra_data
});
$scope.missingSurveyValue = processed.missingSurveyValue;
$scope.extraVars = (processed.extra_data === '' || _.isEmpty(processed.extra_data)) ? '---' : '---\n' + jsyaml.safeDump(processed.extra_data);
ParseTypeChange({
scope: $scope,
variable: 'extraVars',
parse_variable: 'parseType',
field_id: 'SchedulerForm-extraVars',
readOnly: !$scope.schedule_obj.summary_fields.user_capabilities.edit
});
$scope.promptData = {
launchConf: launchConf,
launchOptions: launchOptions,
prompts: prompts,
surveyQuestions: surveyQuestionRes.data.spec,
template: ParentObject.id
};
$scope.$watch('promptData.surveyQuestions', () => {
let missingSurveyValue = false;
_.each($scope.promptData.surveyQuestions, (question) => {
if(question.required && (Empty(question.model) || question.model === [])) {
missingSurveyValue = true;
}
});
$scope.missingSurveyValue = missingSurveyValue;
}, true);
watchForPromptChanges();
});
}
else {
$scope.promptData = {
launchConf: launchConf,
launchOptions: launchOptions,
prompts: prompts,
template: ParentObject.id
};
watchForPromptChanges();
}
}
});
}
// extra_data field is not manifested in the UI when scheduling a Management Job
if ($state.current.name !== 'managementJobsList.schedule.add' && $state.current.name !== 'managementJobsList.schedule.edit'){
if ($state.current.name === 'projectSchedules.edit'){
$scope.noVars = true;
}
else if ($state.current.name === 'inventories.edit.inventory_sources.edit.schedules.edit'){
$scope.noVars = true;
}
else {
ParseTypeChange({
scope: $scope,
variable: 'extraVars',
parse_variable: 'parseType',
field_id: 'SchedulerForm-extraVars',
readOnly: !$scope.schedule_obj.summary_fields.user_capabilities.edit
});
}
}
})
.catch(({data, status}) => {
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
msg: 'Failed to retrieve schedule ' + parseInt($stateParams.schedule_id) + ' GET returned: ' + status });
});
callSelect2(); callSelect2();
}]; }];

View File

@@ -54,3 +54,15 @@
margin-right: 0px; margin-right: 0px;
} }
} }
.SchedulerForm-promptSaveTooltip {
position: absolute;
height: 100%;
display: block;
margin-left: 20px;
width: ~"calc(100% - 20px)";
}
.SchedulerForm-promptSave {
position: relative;
}

View File

@@ -661,23 +661,30 @@
</div> </div>
</div> </div>
</div> </div>
<div class="buttons Form-buttons"> <div class="buttons Form-buttons">
<button type="button" <button type="button"
class="btn btn-sm Form-cancelButton" class="btn btn-sm Form-primaryButton Form-primaryButton--noMargin"
id="project_cancel_btn" id="schedule_prompt_btn"
ng-show="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)" ng-show="(schedule_obj.summary_fields.user_capabilities.edit || canAdd) && showPromptButton"
ng-click="formCancel()">Close</button> ng-click="prompt()">Prompt</button>
<button type="button" <button type="button"
class="btn btn-sm Form-cancelButton" class="btn btn-sm Form-cancelButton"
id="project_cancel_btn" id="schedule_cancel_btn"
ng-show="(schedule_obj.summary_fields.user_capabilities.edit || canAdd)" ng-show="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)"
ng-click="formCancel()">Cancel</button> ng-click="formCancel()">Close</button>
<button type="button" <button type="button"
class="btn btn-sm Form-saveButton" class="btn btn-sm Form-cancelButton"
id="project_save_btn" id="schedule_cancel_btn"
ng-click="saveSchedule()" ng-show="(schedule_obj.summary_fields.user_capabilities.edit || canAdd)"
ng-show="(schedule_obj.summary_fields.user_capabilities.edit || canAdd)" ng-click="formCancel()">Cancel</button>
ng-disabled="!schedulerIsValid"> Save</button> <div class="SchedulerForm-promptSave" ng-show="(schedule_obj.summary_fields.user_capabilities.edit || canAdd)">
<div ng-if="promptModalMissingReqFields" class="SchedulerForm-promptSaveTooltip" aw-tool-tip="Additional information required in the Prompt area before saving" data-placement="top"></div>
</div> <button type="button"
class="btn btn-sm Form-saveButton"
id="schedule_save_btn"
ng-click="saveSchedule()"
ng-disabled="!schedulerIsValid || promptModalMissingReqFields"> Save</button>
</div>
</div> </div>
<prompt prompt-data="promptData" action-text="CONFIRM"></launch>
</div>

View File

@@ -8,13 +8,13 @@
<span id="InstanceGroups" class="form-control Form-textInput Form-textInput--variableHeight input-medium lookup LabelList-lookupTags" <span id="InstanceGroups" class="form-control Form-textInput Form-textInput--variableHeight input-medium lookup LabelList-lookupTags"
ng-disabled="fieldIsDisabled"> ng-disabled="fieldIsDisabled">
<div class="LabelList-tagContainer" ng-repeat="tag in instanceGroupsTags"> <div class="LabelList-tagContainer" ng-repeat="tag in instanceGroupsTags">
<div class="LabelList-tag LabelList-tag--deletable">
<span class="LabelList-name">{{ tag.name }}</span>
</div>
<div class="LabelList-deleteContainer" <div class="LabelList-deleteContainer"
ng-click="deleteTag(tag)"> ng-click="deleteTag(tag)">
<i class="fa fa-times LabelList-tagDelete"></i> <i class="fa fa-times LabelList-tagDelete"></i>
</div> </div>
<div class="LabelList-tag LabelList-tag--deletable">
<span class="LabelList-name">{{ tag.name }}</span>
</div>
</div> </div>
</span> </span>
</div> </div>

View File

@@ -344,7 +344,7 @@ export default ['$compile', 'Attr', 'Icon',
} }
else { // its assumed that options.input_type = checkbox else { // its assumed that options.input_type = checkbox
innerTable += "<td class=\"List-tableCell select-column List-staticColumn--smallStatus\"><input type=\"checkbox\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" + innerTable += "<td class=\"List-tableCell select-column List-staticColumn--smallStatus\"><input type=\"checkbox\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" +
list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ".id, true)\" ng-true-value=\"1\" " + list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ", true)\" ng-true-value=\"1\" " +
"ng-false-value=\"0\" id=\"check_" + list.iterator + "_{{" + list.iterator + ".id}}\" /></td>"; "ng-false-value=\"0\" id=\"check_" + list.iterator + "_{{" + list.iterator + ".id}}\" /></td>";
} }
} }
@@ -368,11 +368,11 @@ export default ['$compile', 'Attr', 'Icon',
if (options.mode === 'select') { if (options.mode === 'select') {
if (options.input_type === "radio") { //added by JT so that lookup forms can be either radio inputs or check box inputs if (options.input_type === "radio") { //added by JT so that lookup forms can be either radio inputs or check box inputs
innerTable += "<td class=\"List-tableCell\"><input type=\"radio\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" + innerTable += "<td class=\"List-tableCell\"><input type=\"radio\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" +
list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ".id, true)\" ng-value=\"1\" " + list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ", true)\" ng-value=\"1\" " +
"ng-false-value=\"0\" id=\"check_{{" + list.iterator + ".id}}\" /></td>"; "ng-false-value=\"0\" id=\"check_{{" + list.iterator + ".id}}\" /></td>";
} else { // its assumed that options.input_type = checkbox } else { // its assumed that options.input_type = checkbox
innerTable += "<td class=\"List-tableCell\"><input type=\"checkbox\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" + innerTable += "<td class=\"List-tableCell\"><input type=\"checkbox\" ng-model=\"" + list.iterator + ".checked\" name=\"check_{{" +
list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ".id, true)\" ng-true-value=\"1\" " + list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ", true)\" ng-true-value=\"1\" " +
"ng-false-value=\"0\" id=\"check_{{" + list.iterator + ".id}}\" /></td>"; "ng-false-value=\"0\" id=\"check_{{" + list.iterator + ".id}}\" /></td>";
} }
} else if ((options.mode === 'edit' || options.mode === 'summary') && list.fieldActions) { } else if ((options.mode === 'edit' || options.mode === 'summary') && list.fieldActions) {

View File

@@ -48,6 +48,7 @@ export default
scrollbarStyle: null scrollbarStyle: null
} }
}; };
scope[fld + 'codeMirror'] = AngularCodeMirror(readOnly); scope[fld + 'codeMirror'] = AngularCodeMirror(readOnly);
scope[fld + 'codeMirror'].addModes(variableEditModes); scope[fld + 'codeMirror'].addModes(variableEditModes);
scope[fld + 'codeMirror'].showTextArea({ scope[fld + 'codeMirror'].showTextArea({
@@ -95,6 +96,7 @@ export default
// convert json to yaml // convert json to yaml
try { try {
removeField(fld); removeField(fld);
json_obj = JSON.parse(scope[fld]); json_obj = JSON.parse(scope[fld]);
if ($.isEmptyObject(json_obj)) { if ($.isEmptyObject(json_obj)) {
scope[fld] = '---'; scope[fld] = '---';

View File

@@ -21,12 +21,7 @@
<div class="MultiCredential-tags"> <div class="MultiCredential-tags">
<div class="MultiCredential-tagSection"> <div class="MultiCredential-tagSection">
<div class="MultiCredential-flexContainer"> <div class="MultiCredential-flexContainer">
<div class="MultiCredential-tagContainer ng-scope" ng-repeat="tag in tags track by $index"> <div class="MultiCredential-tagContainer ng-scope" ng-repeat="tag in tags track by $index" ng-class="{'MultiCredential-tagContainer--disabled': tag.readOnly}">
<div class="MultiCredential-deleteContainer"
ng-click="vm.removeCredential(tag)"
ng-hide="fieldIsDisabled || tag.readOnly">
<i class="fa fa-times MultiCredential-tagDelete"></i>
</div>
<div class="MultiCredential-iconContainer--disabled" ng-switch="tag.kind" ng-if="fieldIsDisabled || tag.readOnly"> <div class="MultiCredential-iconContainer--disabled" ng-switch="tag.kind" ng-if="fieldIsDisabled || tag.readOnly">
<i class="fa fa-cloud MultiCredential-tagIcon" ng-switch-when="cloud"></i> <i class="fa fa-cloud MultiCredential-tagIcon" ng-switch-when="cloud"></i>
<i class="fa fa-info MultiCredential-tagIcon" ng-switch-when="insights"></i> <i class="fa fa-info MultiCredential-tagIcon" ng-switch-when="insights"></i>
@@ -52,6 +47,11 @@
{{ tag.name }} | {{ tag.info }} {{ tag.name }} | {{ tag.info }}
</span> </span>
</div> </div>
<div class="MultiCredential-deleteContainer"
ng-click="vm.removeCredential(tag)"
ng-hide="fieldIsDisabled || tag.readOnly">
<i class="fa fa-times MultiCredential-tagDelete"></i>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -34,25 +34,28 @@
.MultiCredential-tagContainer { .MultiCredential-tagContainer {
display: flex; display: flex;
max-width: 100%; max-width: 100%;
background-color: @default-link;
color: @default-bg;
border-radius: 5px;
padding: 0px 0px 0px 10px;
margin: 3px 10px 3px 0px;
}
.MultiCredential-tagContainer--disabled {
background-color: @default-icon;
} }
.MultiCredential-tag { .MultiCredential-tag {
border-radius: 5px;
padding: 2px 10px;
margin: 3px 0px;
font-size: 12px; font-size: 12px;
margin-right: 10px; margin-right: 10px;
max-width: 100%; max-width: 100%;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
background-color: @default-link; padding: 2px 0px 2px 15px;
color: @default-bg;
padding-left: 15px;
} }
.MultiCredential-tag--disabled { .MultiCredential-tag--disabled {
background-color: @default-icon;
border-top-left-radius: 0px; border-top-left-radius: 0px;
border-bottom-left-radius: 0px; border-bottom-left-radius: 0px;
padding-left: 10px; padding-left: 10px;
@@ -60,22 +63,17 @@
.MultiCredential-tag--deletable { .MultiCredential-tag--deletable {
margin-right: 0px; margin-right: 0px;
border-top-left-radius: 0px; border-top-right-radius: 0px;
border-bottom-left-radius: 0px; border-bottom-right-radius: 0px;
border-right: 0; border-right: 0;
max-width: ~"calc(100% - 23px)"; max-width: ~"calc(100% - 23px)";
margin-right: 10px;
padding-left: 10px; padding-left: 10px;
} }
.MultiCredential-deleteContainer { .MultiCredential-deleteContainer {
background-color: @default-link!important; border-top-right-radius: 5px;
color: white; border-bottom-right-radius: 5px;
background-color: @default-bg; padding: 2px 5px;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
padding: 0 5px;
margin: 3px 0px;
align-items: center; align-items: center;
display: flex; display: flex;
cursor: pointer; cursor: pointer;
@@ -86,8 +84,6 @@
} }
.MultiCredential-iconContainer { .MultiCredential-iconContainer {
background-color: @default-link!important;
color: @default-bg;
border-top-left-radius: 0px; border-top-left-radius: 0px;
border-bottom-left-radius: 0px; border-bottom-left-radius: 0px;
padding: 0px 5px; padding: 0px 5px;
@@ -98,8 +94,6 @@
} }
.MultiCredential-iconContainer--disabled { .MultiCredential-iconContainer--disabled {
background-color: @default-icon;
color: @default-bg;
border-top-left-radius: 5px; border-top-left-radius: 5px;
border-bottom-left-radius: 5px; border-bottom-left-radius: 5px;
padding: 0 5px; padding: 0 5px;
@@ -112,7 +106,7 @@
.MultiCredential-tagIcon { .MultiCredential-tagIcon {
margin: 0px 0px; margin: 0px 0px;
font-size: 10px; font-size: 12px;
} }
.MultiCredential-name { .MultiCredential-name {
@@ -123,10 +117,9 @@
.MultiCredential-name--label { .MultiCredential-name--label {
color: @default-list-header-bg; color: @default-list-header-bg;
font-size: 10px; font-size: 12px;
margin-left: -8px; margin-left: -8px;
margin-right: 5px; margin-right: 5px;
text-transform: uppercase;
} }
.MultiCredential-tag--deletable > .MultiCredential-name { .MultiCredential-tag--deletable > .MultiCredential-name {

View File

@@ -16,12 +16,8 @@
<div class="MultiCredential-tagSection"> <div class="MultiCredential-tagSection">
<div class="MultiCredential-flexContainer"> <div class="MultiCredential-flexContainer">
<div class="MultiCredential-tagContainer ng-scope" <div class="MultiCredential-tagContainer ng-scope"
ng-class="{'MultiCredential-tagContainer--disabled': tag.readOnly}"
ng-repeat="tag in tags track by $index"> ng-repeat="tag in tags track by $index">
<div class="MultiCredential-deleteContainer"
ng-click="vm.deselectCredential(tag)"
ng-hide="fieldIsDisabled || tag.readOnly">
<i class="fa fa-times MultiCredential-tagDelete"></i>
</div>
<div class="MultiCredential-iconContainer--disabled" ng-switch="tag.kind" ng-if="fieldIsDisabled || tag.readOnly"> <div class="MultiCredential-iconContainer--disabled" ng-switch="tag.kind" ng-if="fieldIsDisabled || tag.readOnly">
<i class="fa fa-cloud MultiCredential-tagIcon" ng-switch-when="cloud"></i> <i class="fa fa-cloud MultiCredential-tagIcon" ng-switch-when="cloud"></i>
<i class="fa fa-info MultiCredential-tagIcon" ng-switch-when="insights"></i> <i class="fa fa-info MultiCredential-tagIcon" ng-switch-when="insights"></i>
@@ -47,6 +43,11 @@
{{ tag.name }} | {{ tag.info }} {{ tag.name }} | {{ tag.info }}
</span> </span>
</div> </div>
<div class="MultiCredential-deleteContainer"
ng-click="vm.deselectCredential(tag)"
ng-hide="fieldIsDisabled || tag.readOnly">
<i class="fa fa-times MultiCredential-tagDelete"></i>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -39,27 +39,27 @@
color: @default-link-hov; color: @default-link-hov;
} }
.LabelList-tag--deletable, .JobSubmission-previewTag--deletable { .LabelList-tag--deletable, .JobSubmission-previewTag--deletable, .Prompt-previewTag--deletable {
color: @default-bg; color: @default-bg;
background-color: @default-link; background-color: @default-link;
margin-right: 0px; margin-right: 0px;
border-top-left-radius: 0px; border-top-right-radius: 0px;
border-bottom-left-radius: 0px; border-bottom-right-radius: 0px;
border-right: 0; border-right: 0;
max-width: ~"calc(100% - 23px)"; max-width: ~"calc(100% - 23px)";
margin-right: 5px;
} }
.LabelList-deleteContainer, .JobSubmission-previewTagContainerDelete { .LabelList-deleteContainer, .JobSubmission-previewTagContainerDelete, .Prompt-previewTagContainerDelete {
background-color: @default-link; background-color: @default-link;
border-top-left-radius: 5px; border-top-right-radius: 5px;
border-bottom-left-radius: 5px; border-bottom-right-radius: 5px;
color: @default-bg; color: @default-bg;
padding: 0 5px; padding: 0 5px;
margin: 3px 0px; margin: 3px 0px;
align-items: center; align-items: center;
display: flex; display: flex;
cursor: pointer; cursor: pointer;
margin-right: 5px;
} }
.LabelList-tagDelete { .LabelList-tagDelete {
@@ -76,12 +76,12 @@
max-width: ~"calc(100% - 23px)"; max-width: ~"calc(100% - 23px)";
} }
.LabelList-deleteContainer:hover, .JobSubmission-previewTagContainerDelete:hover { .LabelList-deleteContainer:hover, .JobSubmission-previewTagContainerDelete:hover, .Prompt-previewTagContainerDelete:hover {
border-color: @default-err; border-color: @default-err;
background-color: @default-err; background-color: @default-err;
} }
.LabelList-deleteContainer:hover > .LabelList-tagDelete, .JobSubmission-previewTagContainerDelete:hover > .JobSubmission-previewTagContainerTagDelete { .LabelList-deleteContainer:hover > .LabelList-tagDelete, .JobSubmission-previewTagContainerDelete:hover > .JobSubmission-previewTagContainerTagDelete, .Prompt-previewTagContainerDelete:hover > .Prompt-previewTagContainerTagDelete {
color: @default-bg; color: @default-bg;
} }

View File

@@ -17,14 +17,14 @@
Labels Labels
</div> </div>
<div class="LabelList-tagContainer" ng-repeat="label in labels"> <div class="LabelList-tagContainer" ng-repeat="label in labels">
<div class="LabelList-tag" ng-class="{'LabelList-tag--deletable' : (showDelete && template.summary_fields.user_capabilities.edit)}">
<span class="LabelList-name">{{ label.name }}</span>
</div>
<div class="LabelList-deleteContainer" <div class="LabelList-deleteContainer"
ng-click="deleteLabel(template, label)" ng-click="deleteLabel(template, label)"
ng-show="showDelete && template.summary_fields.user_capabilities.edit"> ng-show="showDelete && template.summary_fields.user_capabilities.edit">
<i class="fa fa-times LabelList-tagDelete"></i> <i class="fa fa-times LabelList-tagDelete"></i>
</div> </div>
<div class="LabelList-tag" ng-class="{'LabelList-tag--deletable' : (showDelete && template.summary_fields.user_capabilities.edit)}">
<span class="LabelList-name">{{ label.name }}</span>
</div>
</div> </div>
<div class="LabelList-seeMoreLess" ng-show="count > 5 && seeMoreInactive" <div class="LabelList-seeMoreLess" ng-show="count > 5 && seeMoreInactive"
ng-click="seeMore()">View More</div> ng-click="seeMore()">View More</div>

View File

@@ -10,6 +10,7 @@ import jobTemplates from './job_templates/main';
import workflowAdd from './workflows/add-workflow/main'; import workflowAdd from './workflows/add-workflow/main';
import workflowEdit from './workflows/edit-workflow/main'; import workflowEdit from './workflows/edit-workflow/main';
import labels from './labels/main'; import labels from './labels/main';
import prompt from './prompt/main';
import workflowChart from './workflows/workflow-chart/main'; import workflowChart from './workflows/workflow-chart/main';
import workflowMaker from './workflows/workflow-maker/main'; import workflowMaker from './workflows/workflow-maker/main';
import workflowControls from './workflows/workflow-controls/main'; import workflowControls from './workflows/workflow-controls/main';
@@ -23,7 +24,7 @@ import TemplatesStrings from './templates.strings';
import listRoute from '~features/templates/list.route.js'; import listRoute from '~features/templates/list.route.js';
export default export default
angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, workflowAdd.name, workflowEdit.name, angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, prompt.name, workflowAdd.name, workflowEdit.name,
workflowChart.name, workflowMaker.name, workflowControls.name workflowChart.name, workflowMaker.name, workflowControls.name
]) ])
.service('TemplatesService', templatesService) .service('TemplatesService', templatesService)

View File

@@ -0,0 +1,17 @@
import promptDirective from './prompt.directive';
import promptInventory from './steps/inventory/prompt-inventory.directive';
import promptCredential from './steps/credential/prompt-credential.directive';
import promptOtherPrompts from './steps/other-prompts/prompt-other-prompts.directive';
import promptSurvey from './steps/survey/prompt-survey.directive';
import promptPreview from './steps/preview/prompt-preview.directive';
import promptService from './prompt.service';
export default
angular.module('prompt', [])
.directive('prompt', promptDirective)
.directive('promptInventory', promptInventory)
.directive('promptCredential', promptCredential)
.directive('promptOtherPrompts', promptOtherPrompts)
.directive('promptSurvey', promptSurvey)
.directive('promptPreview', promptPreview)
.service('PromptService', promptService);

View File

@@ -0,0 +1,166 @@
.Prompt .modal-dialog {
width: 700px;
}
.Prompt-step {
margin-top: 20px;
}
.Prompt-footer {
display: flex;
flex: 0 0 auto;
margin-top: 15px;
justify-content: flex-end;
align-items: flex-end;
}
.Prompt-actionButton {
background-color: @submit-button-bg;
border: 1px solid @submit-button-bg;
color: @submit-button-text;
text-transform: uppercase;
border-radius: 5px;
height: 30px;
padding-left:15px;
padding-right: 15px;
min-width: 85px;
}
.Prompt-actionButton:disabled {
background-color: @d7grey;
border-color: @d7grey;
opacity: 0.65;
}
.Prompt-actionButton:enabled:hover,
.Prompt-actionButton:enabled:focus {
background-color: @submit-button-bg-hov;
border: 1px solid @submit-button-bg-hov;
}
.Prompt-defaultButton{
background-color: @default-bg;
color: @btn-txt;
text-transform: uppercase;
border-radius: 5px;
border: 1px solid @btn-bord;
padding-left:15px;
padding-right: 15px;
height: 30px;
min-width: 85px;
margin-right: 20px;
}
.Prompt-defaultButton:hover{
background-color: @btn-bg-hov;
color: @btn-txt;
}
.Prompt-revertLink {
font-size: 12px;
}
.Prompt-selectedItem {
display: flex;
flex: 1 0 auto;
margin-bottom: 15px;
align-items: baseline;
}
.Prompt-selectedItemInfo {
display: flex;
flex: 0 0 100%;
background-color: @default-no-items-bord;
border: 1px solid @default-border;
padding: 10px;
border-radius: 5px;
max-height: 120px;
overflow-y: scroll;
}
.Prompt-selectedItemRevert {
display: flex;
flex: 0 0 auto;
}
.Prompt-credentialSubSection {
display: flex;
justify-content: flex-end;
align-items: center;
margin-bottom: 15px;
}
.Prompt-selectedItemLabel, .Prompt-label {
color: @default-interface-txt;
margin-right: 10px;
}
.Prompt-label {
line-height: 24px;
}
.Prompt-selectedItemNone {
color: @default-icon;
}
.Prompt-selectedItemContainer {
display: block;
width: 100%;
}
.Prompt-instructions {
color: @default-interface-txt;
margin-top: 25px;
margin-bottom: 15px;
}
.Prompt-passwordButton {
padding: 4px 13px;
}
.Prompt .List-noItems {
margin-top: auto;
}
.Prompt-selectedItemLabel {
flex: 0 0 80px;
line-height: 29px;
}
.Prompt-previewTags--outer {
flex: 1 0 auto;
max-width: ~"calc(100% - 140px)";
}
.Prompt-previewTags--inner {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
}
.Prompt-previewTagLabel {
color: @default-interface-txt;
}
.Prompt-previewTagLabel--deletable{
color: @default-list-header-bg;
}
.Prompt-previewTagRevert {
flex: 0 0 60px;
line-height: 29px;
}
.Prompt-previewTagContainer {
display: flex;
}
.Prompt-previewRow--flex {
display: flex;
margin-bottom: 10px;
}
.Prompt-previewRow--noflex {
margin-bottom: 10px;
}
.Prompt-previewRowTitle {
width: 150px;
color: @default-interface-txt;
text-transform: uppercase;
}
.Prompt-previewRowValue {
flex: 1 0 auto;
}
.Prompt-noSelectedItem {
height: 30px;
line-height: 30px;
font-style: italic;
color: @default-interface-txt;
}
.Prompt-previewTag {
border-radius: 5px;
padding: 2px 10px;
margin: 3px 0px;
font-size: 12px;
color: @default-bg;
background-color: @default-link;
margin-right: 5px;
max-width: 100%;
display: inline-block;
}
.Prompt-credentialSubSection .select2 {
width: 50% !important;
}

View File

@@ -0,0 +1,188 @@
export default [ 'Rest', 'GetBasePath', 'ProcessErrors', 'CredentialTypeModel', 'TemplatesStrings',
function (Rest, GetBasePath, ProcessErrors, CredentialType, strings) {
// strings.get('deleteResource.HEADER')
// ${strings.get('deleteResource.CONFIRM', 'template')}
const vm = this || {};
vm.strings = strings;
let scope;
let modal;
vm.init = (_scope_) => {
scope = _scope_;
({ modal } = scope[scope.ns]);
scope.$watch('vm.promptData.triggerModalOpen', () => {
if(vm.promptData && vm.promptData.triggerModalOpen) {
vm.steps = {
inventory: {
includeStep: false
},
credential: {
includeStep: false
},
other_prompts: {
includeStep: false
},
survey: {
includeStep: false
},
preview: {
includeStep: true,
tab: {
_active: false,
_disabled: true
}
}
};
let order = 1;
vm.actionText = vm.actionText ? vm.actionText : strings.get('prompt.LAUNCH');
vm.forms = {};
let credentialType = new CredentialType();
credentialType.http.get()
.then( (response) => {
vm.promptData.prompts.credentials.credentialTypes = {};
vm.promptData.prompts.credentials.credentialTypeOptions = [];
response.data.results.forEach((credentialTypeRow => {
vm.promptData.prompts.credentials.credentialTypes[credentialTypeRow.id] = credentialTypeRow.kind;
if(credentialTypeRow.kind.match(/^(cloud|net|ssh|vault)$/)) {
if(credentialTypeRow.kind === 'ssh') {
vm.promptData.prompts.credentials.credentialKind = credentialTypeRow.id.toString();
}
vm.promptData.prompts.credentials.credentialTypeOptions.push({
name: credentialTypeRow.name,
value: credentialTypeRow.id
});
}
}));
vm.promptData.prompts.inventory.templateDefault = _.has(vm, 'promptData.launchConf.defaults.inventory') ? vm.promptData.launchConf.defaults.inventory : null;
vm.promptData.prompts.credentials.templateDefault = _.has(vm, 'promptData.launchConf.defaults.credentials') ? angular.copy(vm.promptData.launchConf.defaults.credentials) : [];
vm.promptData.prompts.credentials.passwordsNeededToStart = vm.promptData.launchConf.passwords_needed_to_start;
vm.promptData.prompts.credentials.passwords = {};
vm.promptData.prompts.credentials.value.forEach((credential) => {
if (credential.passwords_needed && credential.passwords_needed.length > 0) {
credential.passwords_needed.forEach(passwordNeeded => {
let credPassObj = {
id: credential.id,
name: credential.name
};
if(passwordNeeded === "ssh_password") {
vm.promptData.prompts.credentials.passwords.ssh = credPassObj;
}
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 = [];
}
vm.promptData.prompts.credentials.passwords.vault.push(credPassObj);
}
});
}
});
vm.promptData.prompts.variables.ignore = vm.promptData.launchConf.ignore_ask_variables;
vm.promptData.prompts.verbosity.templateDefault = vm.promptData.launchConf.defaults.verbosity;
vm.promptData.prompts.jobType.templateDefault = vm.promptData.launchConf.defaults.job_type;
vm.promptData.prompts.limit.templateDefault = vm.promptData.launchConf.defaults.limit;
vm.promptData.prompts.tags.templateDefault = vm.promptData.launchConf.defaults.job_tags;
vm.promptData.prompts.skipTags.templateDefault = vm.promptData.launchConf.defaults.skip_tags;
vm.promptData.prompts.diffMode.templateDefault = vm.promptData.launchConf.defaults.diff_mode;
if(vm.promptData.launchConf.ask_inventory_on_launch) {
vm.steps.inventory.includeStep = true;
vm.steps.inventory.tab = {
_active: true,
order: order
};
order++;
}
if(vm.promptData.launchConf.ask_credential_on_launch || (vm.promptData.launchConf.passwords_needed_to_start && vm.promptData.launchConf.passwords_needed_to_start.length > 0)) {
vm.steps.credential.includeStep = true;
vm.steps.credential.tab = {
_active: order === 1 ? true : false,
_disabled: order === 1 ? false : true,
order: order
};
order++;
}
if(vm.promptData.launchConf.ask_verbosity_on_launch || vm.promptData.launchConf.ask_job_type_on_launch || vm.promptData.launchConf.ask_limit_on_launch || vm.promptData.launchConf.ask_tags_on_launch || vm.promptData.launchConf.ask_skip_tags_on_launch || (vm.promptData.launchConf.ask_variables_on_launch && !vm.promptData.launchConf.ignore_ask_variables) || vm.promptData.launchConf.ask_diff_mode_on_launch) {
vm.steps.other_prompts.includeStep = true;
vm.steps.other_prompts.tab = {
_active: order === 1 ? true : false,
_disabled: order === 1 ? false : true,
order: order
};
order++;
}
if(vm.promptData.launchConf.survey_enabled) {
vm.steps.survey.includeStep = true;
vm.steps.survey.tab = {
_active: order === 1 ? true : false,
_disabled: order === 1 ? false : true,
order: order
};
order++;
}
vm.steps.preview.tab.order = order;
modal.show('PROMPT');
vm.promptData.triggerModalOpen = false;
})
.catch(({data, status}) => {
ProcessErrors(scope, data, status, null, {
hdr: 'Error!',
msg: 'Failed to get credential types. GET status: ' + status
});
});
}
}, true);
};
vm.next = (currentTab) => {
Object.keys(vm.steps).forEach(step => {
if(vm.steps[step].tab) {
if(vm.steps[step].tab.order === currentTab.order) {
vm.steps[step].tab._active = false;
} else if(vm.steps[step].tab.order === currentTab.order + 1) {
vm.steps[step].tab._active = true;
vm.steps[step].tab._disabled = false;
}
}
});
};
vm.finish = () => {
vm.promptData.triggerModalOpen = false;
if(vm.onFinish) {
vm.onFinish();
}
modal.hide();
};
vm.cancel = () => {
vm.promptData.triggerModalOpen = false;
modal.hide();
};
}];

View File

@@ -0,0 +1,24 @@
import promptController from './prompt.controller';
export default [ 'templateUrl',
function(templateUrl) {
return {
scope: {
promptData: '=',
onFinish: '&',
actionText: '@actionText'
},
templateUrl: templateUrl('templates/prompt/prompt'),
replace: true,
transclude: true,
restrict: 'E',
controller: promptController,
controllerAs: 'vm',
bindToController: true,
link: function(scope, el, attrs, promptController) {
scope.ns = 'launch';
scope[scope.ns] = { modal: {} };
promptController.init(scope);
}
};
}];

View File

@@ -0,0 +1,36 @@
<div class="Prompt">
<at-modal>
<at-tab-group>
<at-tab ng-if="vm.steps.inventory.tab" state="vm.steps.inventory.tab">{{:: vm.strings.get('prompt.INVENTORY') }}</at-tab>
<at-tab ng-if="vm.steps.credential.tab" state="vm.steps.credential.tab">{{:: vm.strings.get('prompt.CREDENTIAL') }}</at-tab>
<at-tab ng-if="vm.steps.other_prompts.tab" state="vm.steps.other_prompts.tab">{{:: vm.strings.get('prompt.OTHER_PROMPTS') }}</at-tab>
<at-tab ng-if="vm.steps.survey.tab" state="vm.steps.survey.tab">{{:: vm.strings.get('prompt.SURVEY') }}</at-tab>
<at-tab state="vm.steps.preview.tab">{{:: vm.strings.get('prompt.PREVIEW') }}</at-tab>
</at-tab-group>
<div class="Prompt-step">
<div ng-if="vm.steps.inventory.includeStep" ng-show="vm.steps.inventory.tab._active">
<prompt-inventory prompt-data="vm.promptData"></prompt-inventory>
</div>
<div ng-if="vm.steps.credential.includeStep" ng-show="vm.steps.credential.tab._active">
<prompt-credential prompt-data="vm.promptData" credential-passwords-form="vm.forms.credentialPasswords"></prompt-credential>
</div>
<div ng-if="vm.steps.other_prompts.includeStep" ng-show="vm.steps.other_prompts.tab._active">
<prompt-other-prompts prompt-data="vm.promptData" other-prompts-form="vm.forms.otherPrompts" is-active-step="vm.steps.other_prompts.tab._active"></prompt-other-prompts>
</div>
<div ng-if="vm.steps.survey.includeStep" ng-show="vm.steps.survey.tab._active">
<prompt-survey prompt-data="vm.promptData" survey-form="vm.forms.survey"></prompt-survey>
</div>
<div ng-if="vm.steps.preview.tab._active">
<prompt-preview prompt-data="vm.promptData"></prompt-preview>
</div>
</div>
<div class="Prompt-footer">
<button class="Prompt-defaultButton" ng-click="vm.cancel()">{{:: vm.strings.get('CANCEL') }}</button>
<button class="Prompt-actionButton" ng-show="vm.steps.inventory.tab._active" ng-click="vm.next(vm.steps.inventory.tab)" ng-disabled="!vm.promptData.prompts.inventory.value.id">{{:: vm.strings.get('NEXT') }}</button>
<button class="Prompt-actionButton" ng-show="vm.steps.credential.tab._active" ng-click="vm.next(vm.steps.credential.tab)" ng-disabled="!vm.forms.credentialPasswords.$valid">{{:: vm.strings.get('NEXT') }}</button>
<button class="Prompt-actionButton" ng-show="vm.steps.other_prompts.tab._active" ng-click="vm.next(vm.steps.other_prompts.tab)" ng-disabled="!vm.forms.otherPrompts.$valid">{{:: vm.strings.get('NEXT') }}</button>
<button class="Prompt-actionButton" ng-show="vm.steps.survey.tab._active" ng-click="vm.next(vm.steps.survey.tab)" ng-disabled="!vm.forms.survey.$valid">{{:: vm.strings.get('NEXT') }}</button>
<button class="Prompt-actionButton" ng-show="vm.steps.preview.tab._active" ng-click="vm.finish()" ng-bind="vm.actionText"></button>
</div>
</at-modal>
</div>

View File

@@ -0,0 +1,124 @@
function PromptService (Empty, $filter) {
this.processPromptValues = (params) => {
let prompts = {
credentials: {},
inventory: {},
variables: {},
verbosity: {},
jobType: {},
limit: {},
tags: {},
skipTags: {},
diffMode: {}
};
prompts.credentials.value = _.has(params, 'launchConf.defaults.credentials') ? 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);
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 : "");
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 : "");
prompts.variables.value = _.has(params, 'launchConf.defaults.extra_vars') ? params.launchConf.defaults.extra_vars : "---";
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.jobType.choices = _.get(params, 'launchOptions.actions.POST.job_type.choices', []).map(c => ({label: c[1], value: c[0]}));
prompts.jobType.value = _.has(params, 'currentValues.job_type') && params.currentValues.job_type ? _.find(prompts.jobType.choices, item => item.value === params.currentValues.job_type) : _.find(prompts.jobType.choices, item => item.value === params.launchConf.defaults.job_type);
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.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);
return prompts;
};
this.processSurveyQuestions = (params) => {
let missingSurveyValue = false;
for(let i=0; i<params.surveyQuestions.length; i++){
var question = params.surveyQuestions[i];
question.index = i;
question.question_name = $filter('sanitize')(question.question_name);
question.question_description = (question.question_description) ? $filter('sanitize')(question.question_description) : undefined;
if(question.type === "textarea" && (!Empty(question.default_textarea) || (params.extra_data && params.extra_data[question.variable]))) {
if(params.extra_data && params.extra_data[question.variable]) {
question.model = params.extra_data[question.variable];
delete params.extra_data[question.variable];
} else {
question.model = angular.copy(question.default_textarea);
}
}
else if(question.type === "multiselect") {
if(params.extra_data && params.extra_data[question.variable]) {
question.model = params.extra_data[question.variable];
delete params.extra_data[question.variable];
} else {
question.model = question.default.split(/\n/);
}
question.choices = question.choices.split(/\n/);
}
else if(question.type === "multiplechoice") {
if(params.extra_data && params.extra_data[question.variable]) {
question.model = params.extra_data[question.variable];
delete params.extra_data[question.variable];
} else {
question.model = question.default ? angular.copy(question.default) : "";
}
question.choices = question.choices.split(/\n/);
// Add a default empty string option to the choices array. If this choice is
// selected then the extra var will not be sent when we POST to the launch
// endpoint
if(!question.required) {
question.choices.unshift('');
}
}
else if(question.type === "float"){
if(params.extra_data && params.extra_data[question.variable]) {
question.model = !Empty(params.extra_data[question.variable]) ? params.extra_data[question.variable] : ((!Empty(question.default)) ? angular.copy(question.default) : (!Empty(question.default_float)) ? angular.copy(question.default_float) : "");
delete params.extra_data[question.variable];
} else {
question.model = (!Empty(question.default)) ? angular.copy(question.default) : (!Empty(question.default_float)) ? angular.copy(question.default_float) : "";
}
}
else {
if(params.extra_data && params.extra_data[question.variable]) {
question.model = params.extra_data[question.variable];
delete params.extra_data[question.variable];
} else {
question.model = question.default ? angular.copy(question.default) : "";
}
}
if(question.type === "text" || question.type === "textarea" || question.type === "password") {
question.minlength = (!Empty(question.min)) ? Number(question.min) : "";
question.maxlength = (!Empty(question.max)) ? Number(question.max) : "" ;
}
else if(question.type === "integer") {
question.minValue = (!Empty(question.min)) ? Number(question.min) : "";
question.maxValue = (!Empty(question.max)) ? Number(question.max) : "" ;
}
else if(question.type === "float") {
question.minValue = (!Empty(question.min)) ? question.min : "";
question.maxValue = (!Empty(question.max)) ? question.max : "" ;
}
if(question.required && (Empty(question.model) || question.model === [])) {
missingSurveyValue = true;
}
}
return {
surveyQuestions: params.surveyQuestions,
extra_data: params.extra_data,
missingSurveyValue: missingSurveyValue
};
};
}
PromptService.$inject = ['Empty', '$filter'];
export default PromptService;

View File

@@ -0,0 +1,273 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
[ 'CredentialList', 'QuerySet', 'GetBasePath', 'CreateSelect2', 'TemplatesStrings',
function(CredentialList, qs, GetBasePath, CreateSelect2, strings) {
const vm = this;
vm.strings = strings;
let scope;
let launch;
let updateSelectedRow = () => {
if(scope.credentials && scope.credentials.length > 0) {
scope.credentials.forEach((credential, i) => {
scope.credentials[i].checked = 0;
});
scope.promptData.prompts.credentials.value.forEach((selectedCredential) => {
if(selectedCredential.credential_type === parseInt(scope.promptData.prompts.credentials.credentialKind)) {
scope.credentials.forEach((credential, i) => {
if(scope.credentials[i].id === selectedCredential.id) {
scope.credentials[i].checked = 1;
}
});
}
});
}
};
let wipePasswords = (cred) => {
if(cred.passwords_needed) {
cred.passwords_needed.forEach((passwordNeeded => {
if(passwordNeeded === 'ssh_password') {
delete scope.promptData.prompts.credentials.passwords.ssh;
} else if(passwordNeeded === 'become_password') {
delete scope.promptData.prompts.credentials.passwords.become;
} else if(passwordNeeded === 'ssh_key_unlock') {
delete scope.promptData.prompts.credentials.passwords.ssh_key_unlock;
} else if(passwordNeeded.startsWith("vault_password")) {
for (let i = scope.promptData.prompts.credentials.passwords.vault.length - 1; i >= 0; i--) {
if(cred.id === scope.promptData.prompts.credentials.passwords.vault[i].id) {
scope.promptData.prompts.credentials.passwords.vault.splice(i, 1);
}
}
}
}));
} else if(cred.inputs && !_.isEmpty(cred.inputs)) {
if(cred.inputs.password && cred.inputs.password === "ASK") {
delete scope.promptData.prompts.credentials.passwords.ssh;
} else if(cred.inputs.become_password && cred.inputs.become_password === "ASK") {
delete scope.promptData.prompts.credentials.passwords.become;
} else if(cred.inputs.ssh_key_unlock && cred.inputs.ssh_key_unlock === "ASK") {
delete scope.promptData.prompts.credentials.passwords.ssh_key_unlock;
} else if(cred.inputs.vault_password && cred.inputs.vault_password === "ASK") {
for (let i = scope.promptData.prompts.credentials.passwords.vault.length - 1; i >= 0; i--) {
if(cred.id === scope.promptData.prompts.credentials.passwords.vault[i].id) {
scope.promptData.prompts.credentials.passwords.vault.splice(i, 1);
}
}
}
}
};
let updateNeededPasswords = (cred) => {
if(cred.inputs) {
let credPassObj = {
id: cred.id,
name: cred.name
};
if(cred.inputs.password && cred.inputs.password === "ASK") {
scope.promptData.prompts.credentials.passwords.ssh = credPassObj;
} else if(cred.inputs.become_password && cred.inputs.become_password === "ASK") {
scope.promptData.prompts.credentials.passwords.become = credPassObj;
} else if(cred.inputs.ssh_key_unlock && cred.inputs.ssh_key_unlock === "ASK") {
scope.promptData.prompts.credentials.passwords.ssh_key_unlock = credPassObj;
} else if(cred.inputs.vault_password && cred.inputs.vault_password === "ASK") {
credPassObj.vault_id = cred.inputs.vault_id;
if(!scope.promptData.prompts.credentials.passwords.vault) {
scope.promptData.prompts.credentials.passwords.vault = [];
}
scope.promptData.prompts.credentials.passwords.vault.push(credPassObj);
}
}
};
vm.init = (_scope_, _launch_) => {
scope = _scope_;
launch = _launch_;
scope.toggle_row = (selectedRow) => {
for (let i = scope.promptData.prompts.credentials.value.length - 1; i >= 0; i--) {
if(scope.promptData.prompts.credentials.value[i].credential_type === parseInt(scope.promptData.prompts.credentials.credentialKind)) {
wipePasswords(scope.promptData.prompts.credentials.value[i]);
scope.promptData.prompts.credentials.value.splice(i, 1);
}
}
scope.promptData.prompts.credentials.value.push(_.cloneDeep(selectedRow));
updateNeededPasswords(selectedRow);
};
scope.toggle_credential = (cred) => {
// This is a checkbox click. At the time of writing this the only
// multi-select credentials on launch are vault credentials so this
// logic should only get executed when a vault credential checkbox
// is clicked.
let uncheck = false;
let removeCredential = (credentialToRemove, index) => {
wipePasswords(credentialToRemove);
scope.promptData.prompts.credentials.value.splice(index, 1);
};
// Only one vault credential per vault_id is allowed so we need to check
// to see if one has already been selected and if so replace it.
for (let i = scope.promptData.prompts.credentials.value.length - 1; i >= 0; i--) {
if(cred.credential_type === scope.promptData.prompts.credentials.value[i].credential_type) {
if(scope.promptData.prompts.credentials.value[i].id === cred.id) {
removeCredential(scope.promptData.prompts.credentials.value[i], i);
i = -1;
uncheck = true;
}
else if(scope.promptData.prompts.credentials.value[i].inputs) {
if(cred.inputs.vault_id === scope.promptData.prompts.credentials.value[i].inputs.vault_id) {
removeCredential(scope.promptData.prompts.credentials.value[i], i);
}
} else if(scope.promptData.prompts.credentials.value[i].vault_id) {
if(cred.inputs.vault_id === scope.promptData.prompts.credentials.value[i].vault_id) {
removeCredential(scope.promptData.prompts.credentials.value[i], i);
}
} else {
// The currently selected vault credential does not have a vault_id
if(!cred.inputs.vault_id || cred.inputs.vault_id === "") {
removeCredential(scope.promptData.prompts.credentials.value[i], i);
}
}
}
}
if(!uncheck) {
scope.promptData.prompts.credentials.value.push(cred);
updateNeededPasswords(cred);
}
};
scope.credential_dataset = [];
scope.credentials = [];
let credList = _.cloneDeep(CredentialList);
credList.emptyListText = strings.get('prompt.NO_CREDS_MATCHING_TYPE');
scope.list = credList;
scope.generateCredentialList(scope.promptData.prompts.credentials.credentialKind);
scope.credential_default_params = {
order_by: 'name',
page_size: 5
};
scope.credential_queryset = {
order_by: 'name',
page_size: 5
};
scope.$watch('promptData.prompts.credentials.credentialKind', (oldKind, newKind) => {
if (scope.promptData.prompts.credentials && scope.promptData.prompts.credentials.credentialKind) {
if(scope.promptData.prompts.credentials.credentialTypes[oldKind] === "vault" || scope.promptData.prompts.credentials.credentialTypes[newKind] === "vault") {
scope.generateCredentialList(scope.promptData.prompts.credentials.credentialKind);
}
scope.credential_queryset.page = 1;
scope.credential_default_params.credential_type = scope.credential_queryset.credential_type = parseInt(scope.promptData.prompts.credentials.credentialKind);
qs.search(GetBasePath('credentials'), scope.credential_default_params)
.then(res => {
scope.credential_dataset = res.data;
scope.credentials = scope.credential_dataset.results;
});
}
});
scope.$watchCollection('promptData.prompts.credentials.value', () => {
updateSelectedRow();
});
scope.$watchCollection('credentials', () => {
updateSelectedRow();
});
CreateSelect2({
element: '#launch-kind-select',
multiple: false
});
};
vm.deleteSelectedCredential = (credentialToDelete) => {
for (let i = scope.promptData.prompts.credentials.value.length - 1; i >= 0; i--) {
if(scope.promptData.prompts.credentials.value[i].id === credentialToDelete.id) {
wipePasswords(credentialToDelete);
scope.promptData.prompts.credentials.value.splice(i, 1);
}
}
scope.credentials.forEach((credential, i) => {
if(credential.id === credentialToDelete.id) {
scope.credentials[i].checked = 0;
}
});
};
vm.revert = () => {
scope.promptData.prompts.credentials.value = scope.promptData.prompts.credentials.templateDefault;
scope.promptData.prompts.credentials.passwords = {
vault: []
};
scope.promptData.prompts.credentials.value.forEach((credential) => {
if (credential.passwords_needed && credential.passwords_needed.length > 0) {
credential.passwords_needed.forEach(passwordNeeded => {
let credPassObj = {
id: credential.id,
name: credential.name
};
if(passwordNeeded === "ssh_password") {
scope.promptData.prompts.credentials.passwords.ssh = credPassObj;
}
if(passwordNeeded === "become_password") {
scope.promptData.prompts.credentials.passwords.become = credPassObj;
}
if(passwordNeeded === "ssh_key_unlock") {
scope.promptData.prompts.credentials.passwords.ssh_key_unlock = credPassObj;
}
if(passwordNeeded.startsWith("vault_password")) {
credPassObj.vault_id = credential.vault_id;
scope.promptData.prompts.credentials.passwords.vault.push(credPassObj);
}
});
}
});
};
vm.showRevertCredentials = () => {
if(scope.promptData.launchConf.ask_credential_on_launch) {
if(scope.promptData.prompts.credentials.value && scope.promptData.prompts.credentials.templateDefault && (scope.promptData.prompts.credentials.value.length === scope.promptData.prompts.credentials.templateDefault.length)) {
let selectedIds = scope.promptData.prompts.credentials.value.map((x) => { return x.id; }).sort();
let defaultIds = scope.promptData.prompts.credentials.templateDefault.map((x) => { return x.id; }).sort();
return !selectedIds.every((e, i) => { return defaultIds.indexOf(e) === i; });
} else {
return true;
}
} else {
return false;
}
};
vm.togglePassword = (id) => {
var buttonId = id + "_show_input_button",
inputId = id;
if ($(inputId).attr("type") === "password") {
$(buttonId).html(strings.get('HIDE'));
$(inputId).attr("type", "text");
} else {
$(buttonId).html(strings.get('SHOW'));
$(inputId).attr("type", "password");
}
};
}
];

View File

@@ -0,0 +1,56 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import promptCredentialController from './prompt-credential.controller';
export default [ 'templateUrl', '$compile', 'generateList',
(templateUrl, $compile, GenerateList) => {
return {
scope: {
promptData: '=',
credentialPasswordsForm: '='
},
templateUrl: templateUrl('templates/prompt/steps/credential/prompt-credential'),
controller: promptCredentialController,
controllerAs: 'vm',
require: ['^^prompt', 'promptCredential'],
restrict: 'E',
replace: true,
transclude: true,
link: (scope, el, attrs, controllers) => {
const launchController = controllers[0];
const promptCredentialController = controllers[1];
scope.generateCredentialList = (credKind) => {
let inputType = (credKind && scope.promptData.prompts.credentials.credentialTypes[credKind] === "vault") ? null : 'radio';
let list = _.cloneDeep(scope.list);
if(credKind && scope.promptData.prompts.credentials.credentialTypes[credKind] === "vault") {
list.fields.name.modalColumnClass = 'col-md-6';
list.fields.info = {
label: 'Vault ID',
ngBind: 'credential.inputs.vault_id',
key: false,
nosort: true,
modalColumnClass: 'col-md-6',
infoHeaderClass: '',
dataPlacement: 'top',
};
}
let html = GenerateList.build({
list: list,
input_type: inputType,
mode: 'lookup'
});
$('#prompt-credential').empty().append($compile(html)(scope));
};
promptCredentialController.init(scope, launchController);
}
};
}];

View File

@@ -0,0 +1,113 @@
<div>
<div class="Prompt-selectedItem">
<div class="Prompt-selectedItemInfo" ng-hide="promptData.prompts.credentials.templateDefault.length === 0 && promptData.prompts.credentials.value.length === 0">
<div class="Prompt-selectedItemLabel">
<span>{{:: vm.strings.get('prompt.SELECTED') }}</span>
</div>
<div class="Prompt-previewTags--outer">
<div ng-show="promptData.prompts.credentials.templateDefault.length > 0 && promptData.prompts.credentials.value.length === 0" class="Prompt-noSelectedItem">{{:: vm.strings.get('prompt.NO_CREDENTIALS_SELECTED') }}</div>
<div class="Prompt-previewTags--inner">
<div class="MultiCredential-tagContainer"
ng-class="{'MultiCredential-tagContainer--disabled': !promptData.launchConf.ask_credential_on_launch}"
ng-repeat="credential in promptData.prompts.credentials.value">
<div class="MultiCredential-iconContainer" ng-switch="promptData.prompts.credentials.credentialTypes[credential.credential_type]">
<i class="fa fa-cloud MultiCredential-tagIcon" ng-switch-when="cloud"></i>
<i class="fa fa-info MultiCredential-tagIcon" ng-switch-when="insights"></i>
<i class="fa fa-sitemap MultiCredential-tagIcon" ng-switch-when="net"></i>
<i class="fa fa-code-fork MultiCredential-tagIcon" ng-switch-when="scm"></i>
<i class="fa fa-key MultiCredential-tagIcon" ng-switch-when="ssh"></i>
<i class="fa fa-archive MultiCredential-tagIcon" ng-switch-when="vault"></i>
</div>
<div class="MultiCredential-tag" ng-class="promptData.launchConf.ask_credential_on_launch ? 'MultiCredential-tag--deletable' : 'MultiCredential-tag--disabled'">
<span ng-if="!credential.inputs.vault_id && !credential.vault_id" class="MultiCredential-name--label">
{{ credential.name }}
</span>
<span ng-if="credential.inputs.vault_id || credential.vault_id" class="MultiCredential-name--label">
{{ credential.name }} | {{ credential.vault_id ? credential.vault_id : credential.inputs.vault_id }}
</span>
</div>
<div class="MultiCredential-deleteContainer"
ng-click="vm.deleteSelectedCredential(credential)"
ng-hide="!promptData.launchConf.ask_credential_on_launch">
<i class="fa fa-times MultiCredential-tagDelete"></i>
</div>
</div>
</div>
</div>
<div class="Prompt-previewTagRevert">
<a class="Prompt-revertLink" href="" ng-show="vm.showRevertCredentials()" ng-click="vm.revert()">{{:: vm.strings.get('prompt.REVERT') }}</a>
</div>
</div>
</div>
<span ng-show="promptData.launchConf.ask_credential_on_launch">
<div class="Prompt-credentialSubSection">
<span class="Prompt-label">{{:: vm.strings.get('prompt.CREDENTIAL_TYPE') }}:</span>
<select id="launch-kind-select" ng-model="promptData.prompts.credentials.credentialKind">
<option ng-repeat="option in promptData.prompts.credentials.credentialTypeOptions" value="{{option.value}}">{{option.name}}</option>
</select>
</div>
<div id="prompt-credential"></div>
</span>
<div ng-show="promptData.prompts.credentials.passwords.ssh || promptData.prompts.credentials.passwords.become || promptData.prompts.credentials.passwords.ssh_key_unlock || (promptData.prompts.credentials.passwords.vault && promptData.prompts.credentials.passwords.vault.length > 0)">
<div class="Prompt-instructions">{{:: vm.strings.get('prompt.PASSWORDS_REQUIRED_HELP') }}</div>
<form name="credentialPasswordsForm" autocomplete="off" novalidate>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="promptData.prompts.credentials.passwords.ssh">
<label for="ssh_password" class="Form-inputLabelContainer">
<span class="Form-requiredAsterisk">*</span>
<span class="Form-inputLabel">{{:: vm.strings.get('prompt.credential_passwords.SSH_PASSWORD') }}</span>
</label>
<div class="input-group">
<span class="input-group-btn">
<button type="button" class="btn btn-default show_input_button Prompt-passwordButton" id="prompt-ssh-password_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="vm.togglePassword('#prompt-ssh-password')" data-container="job-launch-modal" data-original-title="" title="">{{:: vm.strings.get('SHOW') }}</button>
</span>
<input id="prompt-ssh-password" type="password" ng-model="promptData.prompts.credentials.passwords.ssh.value" name="ssh_password" class="password-field form-control input-sm Form-textInput" required>
</div>
<div class="error" ng-show="credentialPasswordsForm.ssh_password.$dirty && credentialPasswordsForm.ssh_password.$error.required">{{:: vm.strings.get('prompt.PLEASE_ENTER_PASSWORD') }}</div>
<div class="error api-error" ng-bind="ssh_password_api_error"></div>
</div>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="promptData.prompts.credentials.passwords.ssh_key_unlock">
<label for="ssh_key_unlock" class="Form-inputLabelContainer">
<span class="Form-requiredAsterisk">*</span>
<span class="Form-inputLabel">{{:: vm.strings.get('prompt.credential_passwords.PRIVATE_KEY_PASSPHRASE') }}</span>
</label>
<div class="input-group">
<span class="input-group-btn">
<button type="button" class="btn btn-default show_input_button Prompt-passwordButton" id="prompt-ssh-key-unlock_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="vm.togglePassword('#prompt-ssh-key-unlock')" data-container="job-launch-modal" data-original-title="" title="">{{:: vm.strings.get('SHOW') }}</button>
</span>
<input id="prompt-ssh-key-unlock" type="password" ng-model="promptData.prompts.credentials.passwords.ssh_key_unlock.value" name="ssh_key_unlock" class="password-field form-control input-sm Form-textInput" required>
</div>
<div class="error" ng-show="credentialPasswordsForm.ssh_key_unlock.$dirty && credentialPasswordsForm.ssh_key_unlock.$error.required">{{:: vm.strings.get('prompt.PLEASE_ENTER_PASSWORD') }}</div>
<div class="error api-error" ng-bind="ssh_key_unlock_api_error"></div>
</div>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="promptData.prompts.credentials.passwords.become">
<label for="become_password" class="Form-inputLabelContainer">
<span class="Form-requiredAsterisk">*</span>
<span class="Form-inputLabel">{{:: vm.strings.get('prompt.credential_passwords.PRIVILEGE_ESCALATION_PASSWORD') }}</span>
</label>
<div class="input-group">
<span class="input-group-btn">
<button type="button" class="btn btn-default show_input_button Prompt-passwordButton" id="prompt-become-password_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="vm.togglePassword('#prompt-become-password')" data-container="job-launch-modal" data-original-title="" title="">{{:: vm.strings.get('SHOW') }}</button>
</span>
<input id="prompt-become-password" type="password" ng-model="promptData.prompts.credentials.passwords.become.value" name="become_password" class="password-field form-control input-sm Form-textInput" required>
</div>
<div class="error" ng-show="credentialPasswordsForm.become_password.$dirty && credentialPasswordsForm.become_password.$error.required">{{:: vm.strings.get('prompt.PLEASE_ENTER_PASSWORD') }}</div>
<div class="error api-error" ng-bind="become_password_api_error"></div>
</div>
<span ng-if="promptData.prompts.credentials.passwords.vault && promptData.prompts.credentials.passwords.vault.length > 0">
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-repeat="vault_credential in promptData.prompts.credentials.passwords.vault track by $index">
<label for="vault_password" class="Form-inputLabelContainer">
<span class="Form-requiredAsterisk">*</span>
<span class="Form-inputLabel">{{:: vm.strings.get('prompt.credential_passwords.VAULT_PASSWORD') }} - {{vault_credential.name}}</span>
</label>
<div class="input-group">
<span class="input-group-btn">
<button type="button" class="btn btn-default show_input_button Prompt-passwordButton" id="prompt-vault-password_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="vm.togglePassword('#prompt-vault-password')" data-container="job-launch-modal" data-original-title="" title="">{{:: vm.strings.get('SHOW') }}</button>
</span>
<input id="prompt-vault-password" type="password" ng-model="promptData.prompts.credentials.passwords.vault[$index].value" name="vault_password" class="password-field form-control input-sm Form-textInput" required>
</div>
<div class="error" ng-show="credentialPasswordsForm.vault_password.$dirty && credentialPasswordsForm.vault_password.$error.required">{{:: vm.strings.get('prompt.PLEASE_ENTER_PASSWORD') }}</div>
<div class="error api-error" ng-bind="vault_password_api_error"></div>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,37 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
[ 'TemplatesStrings', function(strings) {
const vm = this;
vm.strings = strings;
let scope;
let launch;
vm.init = (_scope_, _launch_) => {
scope = _scope_;
launch = _launch_;
scope.toggle_row = (row) => {
scope.promptData.prompts.inventory.value = row;
};
};
vm.deleteSelectedInventory = () => {
scope.promptData.prompts.inventory.value = null;
scope.inventories.forEach((inventory) => {
inventory.checked = 0;
});
};
vm.revert = () => {
scope.promptData.prompts.inventory.value = scope.promptData.prompts.inventory.templateDefault;
};
}
];

View File

@@ -0,0 +1,72 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import promptInventoryController from './prompt-inventory.controller';
export default [ 'templateUrl', 'QuerySet', 'GetBasePath', 'generateList', '$compile', 'InventoryList',
(templateUrl, qs, GetBasePath, GenerateList, $compile, InventoryList) => {
return {
scope: {
promptData: '='
},
templateUrl: templateUrl('templates/prompt/steps/inventory/prompt-inventory'),
controller: promptInventoryController,
controllerAs: 'vm',
require: ['^^prompt', 'promptInventory'],
restrict: 'E',
replace: true,
transclude: true,
link: (scope, el, attrs, controllers) => {
const launchController = controllers[0];
const promptInventoryController = controllers[1];
promptInventoryController.init(scope, launchController);
scope.inventory_default_params = {
order_by: 'name',
page_size: 5
};
scope.inventory_queryset = {
order_by: 'name',
page_size: 5
};
// Fire off the initial search
qs.search(GetBasePath('inventory'), scope.inventory_default_params)
.then(res => {
scope.inventory_dataset = res.data;
scope.inventories = scope.inventory_dataset.results;
let invList = _.cloneDeep(InventoryList);
let html = GenerateList.build({
list: invList,
input_type: 'radio',
mode: 'lookup'
});
scope.list = invList;
$('#prompt-inventory').append($compile(html)(scope));
scope.$watch('promptData.prompts.inventory.value', () => {
if(scope.promptData.prompts.inventory.value && scope.promptData.prompts.inventory.value.id) {
// Loop across the inventories and see if one of them should be "checked"
scope.inventories.forEach((row, i) => {
if (row.id === scope.promptData.prompts.inventory.value.id) {
scope.inventories[i].checked = 1;
}
else {
scope.inventories[i].checked = 0;
}
});
}
});
});
}
};
}];

View File

@@ -0,0 +1,26 @@
<div>
<div class="Prompt-selectedItem">
<div class="Prompt-selectedItemInfo" ng-hide="!promptData.prompts.inventory.value.id && !promptData.prompts.inventory.templateDefault.id">
<div class="Prompt-selectedItemLabel">
<span>{{:: vm.strings.get('prompt.SELECTED') }}</span>
</div>
<div class="Prompt-previewTags--outer">
<div ng-show="promptData.prompts.inventory.templateDefault.id && !promptData.prompts.inventory.value.id" class="Prompt-noSelectedItem">{{:: vm.strings.get('prompt.NO_INVENTORY_SELECTED') }}</div>
<div class="Prompt-previewTags--inner" ng-hide="!promptData.prompts.inventory.value.id">
<div class="Prompt-previewTagContainer">
<div class="Prompt-previewTag Prompt-previewTag--deletable">
<span>{{promptData.prompts.inventory.value.name}}</span>
</div>
<div class="Prompt-previewTagContainerDelete" ng-click="vm.deleteSelectedInventory()">
<i class="fa fa-times Prompt-previewTagContainerTagDelete"></i>
</div>
</div>
</div>
</div>
<div class="Prompt-previewTagRevert">
<a class="Prompt-revertLink" href="" ng-hide="promptData.prompts.inventory.value.id === promptData.prompts.inventory.templateDefault.id" ng-click="vm.revert()">{{:: vm.strings.get('prompt.REVERT') }}</a>
</div>
</div>
</div>
<div id="prompt-inventory"></div>
</div>

View File

@@ -0,0 +1,92 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
['ParseTypeChange', 'CreateSelect2', 'TemplatesStrings', function(ParseTypeChange, CreateSelect2, strings) {
const vm = this;
vm.strings = strings;
let scope;
let launch;
vm.init = (_scope_, _launch_) => {
scope = _scope_;
launch = _launch_;
scope.parseType = 'yaml';
// Can't pass otherPrompts.variables.value into ParseTypeChange
// due to the fact that Angular CodeMirror uses scope[string]
// notation.
scope.extraVariables = scope.promptData.prompts.variables.value;
scope.$watch('extraVariables', () => {
scope.promptData.prompts.variables.value = scope.extraVariables;
});
let codemirrorExtraVars = () => {
if(scope.promptData.launchConf.ask_variables_on_launch && !scope.promptData.prompts.variables.ignore) {
ParseTypeChange({
scope: scope,
variable: 'extraVariables',
field_id: 'job_launch_variables'
});
}
};
if(scope.promptData.launchConf.ask_job_type_on_launch) {
CreateSelect2({
element: '#job_launch_job_type',
multiple: false
});
}
if(scope.promptData.launchConf.ask_verbosity_on_launch) {
CreateSelect2({
element: '#job_launch_verbosity',
multiple: false
});
}
if(scope.promptData.launchConf.ask_tags_on_launch) {
CreateSelect2({
element: '#job_launch_job_tags',
multiple: true,
addNew: true
});
}
if(scope.promptData.launchConf.ask_skip_tags_on_launch) {
CreateSelect2({
element: '#job_launch_skip_tags',
multiple: true,
addNew: true
});
}
if(scope.isActiveStep) {
codemirrorExtraVars();
}
scope.$watch('isActiveStep', () => {
if(scope.isActiveStep) {
codemirrorExtraVars();
}
});
};
vm.toggleDiff = () => {
scope.promptData.prompts.diffMode.value = !scope.promptData.prompts.diffMode.value;
};
vm.updateParseType = (parseType) => {
scope.parseType = parseType;
// This function gets added to scope by the ParseTypeChange factory
scope.parseTypeChange('parseType', 'extraVariables');
};
}
];

View File

@@ -0,0 +1,32 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import promptOtherPrompts from './prompt-other-prompts.controller';
export default [ 'templateUrl',
(templateUrl) => {
return {
scope: {
promptData: '=',
otherPromptsForm: '=',
isActiveStep: '='
},
templateUrl: templateUrl('templates/prompt/steps/other-prompts/prompt-other-prompts'),
controller: promptOtherPrompts,
controllerAs: 'vm',
require: ['^^prompt', 'promptOtherPrompts'],
restrict: 'E',
replace: true,
transclude: true,
link: (scope, el, attrs, controllers) => {
const launchController = controllers[0];
const promptOtherPromptsController = controllers[1];
promptOtherPromptsController.init(scope, launchController);
}
};
}];

View File

@@ -0,0 +1,110 @@
<form name="otherPromptsForm" autocomplete="off" novalidate>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="promptData.launchConf.ask_job_type_on_launch">
<label for="job_type" class="Form-inputLabelContainer">
<span class="Form-requiredAsterisk">*</span>
<span class="Form-inputLabel" translate>{{:: vm.strings.get('prompt.JOB_TYPE') }}</span>
</label>
<div>
<select
id="job_launch_job_type"
ng-options="v.label for v in promptData.prompts.jobType.choices track by v.value"
ng-model="promptData.prompts.jobType.value"
class="form-control Form-dropDown"
name="job_type"
tabindex="-1"
aria-hidden="true"
required>
<option value="" class="" selected="selected">{{:: vm.strings.get('prompt.CHOOSE_JOB_TYPE') }}</option>
</select>
</div>
</div>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="promptData.launchConf.ask_limit_on_launch">
<label for="limit">
<span class="Form-inputLabel">{{:: vm.strings.get('prompt.LIMIT') }}</span>
</label>
<div>
<input type="text" ng-model="promptData.prompts.limit.value" name="limit" class="form-control Form-textInput">
</div>
</div>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="promptData.launchConf.ask_verbosity_on_launch">
<label for="verbosity" class="Form-inputLabelContainer">
<span class="Form-requiredAsterisk">*</span>
<span class="Form-inputLabel">{{:: vm.strings.get('prompt.VERBOSITY') }}</span>
</label>
<div>
<select
id="job_launch_verbosity"
ng-options="v.label for v in promptData.prompts.verbosity.choices track by v.value"
ng-model="promptData.prompts.verbosity.value"
class="form-control Form-dropDown"
name="verbosity"
tabindex="-1"
aria-hidden="true"
required>
<option value="" class="" selected="selected">{{:: vm.strings.get('prompt.CHOOSE_VERBOSITY') }}</option>
</select>
</div>
</div>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="promptData.launchConf.ask_tags_on_launch">
<label for="tags">
<span class="Form-inputLabel">{{:: vm.strings.get('prompt.JOB_TAGS') }}</span>
</label>
<div>
<select
id="job_launch_job_tags"
ng-options="v.label for v in promptData.prompts.tags.options track by v.value"
ng-model="promptData.prompts.tags.value"
class="form-control Form-dropDown"
name="job_tags"
tabindex="-1"
aria-hidden="true"
multiple>
</select>
</div>
</div>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="promptData.launchConf.ask_skip_tags_on_launch">
<label for="skip_tags">
<span class="Form-inputLabel">{{:: vm.strings.get('prompt.SKIP_TAGS') }}</span>
</label>
<div>
<select
id="job_launch_skip_tags"
ng-options="v.label for v in promptData.prompts.skipTags.options track by v.value"
ng-model="promptData.prompts.skipTags.value"
class="form-control Form-dropDown"
name="skip_tags"
tabindex="-1"
aria-hidden="true"
multiple>
</select>
</div>
</div>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="promptData.launchConf.ask_diff_mode_on_launch">
<label for="diff_mode">
<span class="Form-inputLabel">{{:: vm.strings.get('prompt.SHOW_CHANGES') }}</span>
</label>
<div>
<div class="ScheduleToggle" ng-class="{'is-on': promptData.prompts.diffMode.value}" aw-tool-tip="" data-placement="top" data-original-title="" title="" ng-click="vm.toggleDiff()">
<button ng-show="promptData.prompts.diffMode.value" class="ScheduleToggle-switch is-on">{{:: vm.strings.get('ON') }}</button>
<button ng-show="!promptData.prompts.diffMode.value" class="ScheduleToggle-switch ng-hide">{{:: vm.strings.get('OFF') }}</button>
</div>
</div>
</div>
<div class="form-group Form-formGroup Form-formGroup--singleColumn" ng-if="promptData.launchConf.ask_variables_on_launch && !promptData.prompts.variables.ignore">
<label for="variables">
<span class="Form-inputLabel">{{:: vm.strings.get('prompt.EXTRA_VARIABLES') }}</span>
<a id="awp-variables" href="" aw-pop-over="<p>Pass extra command line variables to the playbook. This is the -e or --extra-vars command line parameter for ansible-playbook. Provide key/value pairs using either YAML or JSON.</p>JSON:<br /><blockquote>{<br />&quot;somevar&quot;: &quot;somevalue&quot;,<br />&quot;password&quot;: &quot;magic&quot;<br /> }</blockquote>YAML:<br /><blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>" data-placement="right" data-container="body" over-title="Extra Variables" class="help-link" data-original-title="" title="" tabindex="-1">
<i class="fa fa-question-circle"></i>
</a>
<div class="parse-selection" id="job_launch_variables_parse_type">
<input type="radio" ng-model="parseType" value="yaml" ng-change="vm.updateParseType(parseType)">
<span class="parse-label">{{:: vm.strings.get('YAML') }}</span>
<input type="radio" ng-model="parseType" value="json" ng-change="vm.updateParseType(parseType)">
<span class="parse-label">{{:: vm.strings.get('JSON') }}</span>
</div>
</label>
<div>
<textarea rows="6" ng-model="promptData.prompts.variables.value" name="variables" class="form-control Form-textArea Form-textAreaLabel" id="job_launch_variables"></textarea>
</div>
</div>
</form>

View File

@@ -0,0 +1,85 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
[ 'ParseTypeChange', 'ToJSON', 'TemplatesStrings', function(ParseTypeChange, ToJSON, strings) {
const vm = this;
vm.strings = strings;
let scope;
let launch;
let consolidateTags = (tagModel, tagId) => {
let tags = angular.copy(tagModel);
$(tagId).siblings(".select2").first().find(".select2-selection__choice").each((optionIndex, option) => {
tags.push({
value: option.title,
name: option.title,
label: option.title
});
});
return [...tags.reduce((map, tag) => map.has(tag.value) ? map : map.set(tag.value, tag), new Map()).values()];
};
vm.init = (_scope_, _launch_) => {
scope = _scope_;
launch = _launch_;
vm.showJobTags = true;
vm.showSkipTags = true;
scope.parseType = 'yaml';
scope.promptData.extraVars = ToJSON(scope.parseType, scope.promptData.prompts.variables.value, false);
if(scope.promptData.launchConf.ask_tags_on_launch) {
scope.promptData.prompts.tags.value = consolidateTags(scope.promptData.prompts.tags.value, "#job_launch_job_tags");
}
if(scope.promptData.launchConf.ask_skip_tags_on_launch) {
scope.promptData.prompts.skipTags.value = consolidateTags(scope.promptData.prompts.skipTags.value, "#job_launch_skip_tags");
}
if(scope.promptData.launchConf.survey_enabled){
scope.promptData.surveyQuestions.forEach(surveyQuestion => {
// grab all survey questions that have answers
if(surveyQuestion.required || (surveyQuestion.required === false && surveyQuestion.model.toString()!=="")) {
if(!scope.promptData.extraVars) {
scope.promptData.extraVars = {};
}
scope.promptData.extraVars[surveyQuestion.variable] = surveyQuestion.model;
}
if(surveyQuestion.required === false && _.isEmpty(surveyQuestion.model)) {
switch (surveyQuestion.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 (surveyQuestion.min === 0) {
scope.promptData.extraVars[surveyQuestion.variable] = "";
}
break;
}
}
});
}
scope.promptExtraVars = $.isEmptyObject(scope.promptData.extraVars) ? '---' : jsyaml.safeDump(scope.promptData.extraVars);
ParseTypeChange({
scope: scope,
variable: 'promptExtraVars',
field_id: 'job_launch_preview_variables',
readOnly: true
});
};
}
];

View File

@@ -0,0 +1,30 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import promptPreview from './prompt-preview.controller';
export default [ 'templateUrl',
(templateUrl) => {
return {
scope: {
promptData: '='
},
templateUrl: templateUrl('templates/prompt/steps/preview/prompt-preview'),
controller: promptPreview,
controllerAs: 'vm',
require: ['^^prompt', 'promptPreview'],
restrict: 'E',
replace: true,
transclude: true,
link: (scope, el, attrs, controllers) => {
const launchController = controllers[0];
const promptPreviewController = controllers[1];
promptPreviewController.init(scope, launchController);
}
};
}];

View File

@@ -0,0 +1,84 @@
<div>
<div class="Prompt-previewRow--flex">
<div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.JOB_TYPE') }}</div>
<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 === 'check'">{{:: vm.strings.get('prompt.CHECK') }}</span>
</div>
</div>
<div class="Prompt-previewRow--flex">
<div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.CREDENTIAL') }}</div>
<div class="Prompt-previewRowValue" style="display: flex">
<div class="Prompt-previewTagContainer u-wordwrap" ng-repeat="credential in promptData.prompts.credentials.value">
<div class="Prompt-previewTag">
<span ng-switch="promptData.prompts.credentials.credentialTypes[credential.credential_type]">
<span class="fa fa-cloud" ng-switch-when="cloud"></span>
<span class="fa fa-info" ng-switch-when="insights"></span>
<span class="fa fa-sitemap" ng-switch-when="net"></span>
<span class="fa fa-code-fork" ng-switch-when="scm"></span>
<span class="fa fa-key" ng-switch-when="ssh"></span>
<span class="fa fa-archive" ng-switch-when="vault"></span>
</span>
<span ng-bind="credential.name"></span>
</div>
</div>
</div>
</div>
<div class="Prompt-previewRow--flex">
<div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.INVENTORY') }}</div>
<div class="Prompt-previewRowValue" ng-bind="promptData.prompts.inventory.value.name"></div>
</div>
<div class="Prompt-previewRow--flex">
<div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.LIMIT') }}</div>
<div class="Prompt-previewRowValue" ng-bind="promptData.prompts.limit.value"></div>
</div>
<div class="Prompt-previewRow--flex">
<div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.VERBOSITY') }}</div>
<div class="Prompt-previewRowValue" ng-bind="promptData.prompts.verbosity.value.label"></div>
</div>
<div class="Prompt-previewRow--noflex">
<div class="Prompt-previewRowTitle">
<span>{{:: vm.strings.get('prompt.JOB_TAGS') }}&nbsp;</span>
<span ng-click="vm.showJobTags = !vm.showJobTags">
<span class="fa fa-caret-down" ng-show="vm.showJobTags" ></span>
<span class="fa fa-caret-left" ng-show="!vm.showJobTags"></span>
</span>
</div>
<div ng-show="vm.showJobTags" style="display: flex">
<div class="Prompt-previewTagContainer u-wordwrap" ng-repeat="tag in promptData.prompts.tags.value">
<div class="LabelList-tag">
<span>{{tag.name}}</span>
</div>
</div>
</div>
</div>
<div class="Prompt-previewRow--noflex">
<div class="Prompt-previewRowTitle">
<span>{{:: vm.strings.get('prompt.SKIP_TAGS') }}&nbsp;</span>
<span ng-click="vm.showSkipTags = !vm.showSkipTags">
<span class="fa fa-caret-down" ng-show="vm.showSkipTags" ></span>
<span class="fa fa-caret-left" ng-show="!vm.showSkipTags"></span>
</span>
</div>
<div ng-show="vm.showSkipTags" style="display: flex">
<div class="Prompt-previewTagContainer u-wordwrap" ng-repeat="tag in promptData.prompts.skipTags.value">
<div class="LabelList-tag">
<span>{{tag.name}}</span>
</div>
</div>
</div>
</div>
<div class="Prompt-previewRow--flex">
<div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.SHOW_CHANGES') }}</div>
<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('OFF') }}</span>
</div>
</div>
<div class="Prompt-previewRow--noflex">
<div class="Prompt-previewRowTitle">{{:: vm.strings.get('prompt.EXTRA_VARIABLES') }}</div>
<div>
<textarea rows="6" ng-model="promptExtraVars" name="preview_variables" class="form-control Form-textArea Form-textAreaLabel" id="job_launch_preview_variables"></textarea>
</div>
</div>
</div>

View File

@@ -0,0 +1,35 @@
/*************************************************
* Copyright (c) 2017 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
[ 'TemplatesStrings', function(strings) {
const vm = this;
vm.strings = strings;
let scope;
let launch;
vm.init = (_scope_, _launch_) => {
scope = _scope_;
launch = _launch_;
};
// This function is used to hide/show the contents of a password
// within a form
vm.togglePassword = (id) => {
var buttonId = id + "_show_input_button",
inputId = id;
if ($(inputId).attr("type") === "password") {
$(buttonId).html(strings.get('HIDE'));
$(inputId).attr("type", "text");
} else {
$(buttonId).html(strings.get('SHOW'));
$(inputId).attr("type", "password");
}
};
}
];

View File

@@ -0,0 +1,31 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import promptSurvey from './prompt-survey.controller';
export default [ 'templateUrl',
(templateUrl) => {
return {
scope: {
promptData: '=',
surveyForm: '='
},
templateUrl: templateUrl('templates/prompt/steps/survey/prompt-survey'),
controller: promptSurvey,
controllerAs: 'vm',
require: ['^^prompt', 'promptSurvey'],
restrict: 'E',
replace: true,
transclude: true,
link: (scope, el, attrs, controllers) => {
const launchController = controllers[0];
const promptSurveyController = controllers[1];
promptSurveyController.init(scope, launchController);
}
};
}];

View File

@@ -0,0 +1,65 @@
<form name="surveyForm" autocomplete="off" novalidate>
<div ng-repeat="question in promptData.surveyQuestions track by $index" id="taker_{{$index}}" class="form-group Form-formGroup Form-formGroup--singleColumn">
<label ng-attr-for="{{question.variable}}" class="Form-inputLabelContainer">
<span ng-show="question.required===true" class="Form-requiredAsterisk">*</span>
<span class="label-text Form-inputLabel"> {{question.question_name}}</span>
</label>
<div class="survey_taker_description" ng-if="question.question_description">
<i ng-bind-html="question.question_description"></i>
</div>
<div ng-if="question.type === 'text'">
<input type="text" id="survey_question_{{$index}}" ng-model="question.model" name="survey_question_{{$index}}" ng-minlength="question.minlength" ng-maxlength="question.maxlength" class="form-control Form-textInput" ng-required="question.required">
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$dirty && forms.survey.survey_question_{{$index}}.$error.required">{{:: vm.strings.get('prompt.PLEASE_ENTER_ANSWER') }}</div>
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.minlength || forms.survey.survey_question_{{$index}}.$error.maxlength"><span translate>Please enter an answer between</span> {{question.minlength}} <span translate>to</span> {{question.maxlength}} <span translate>characters long.</span></div>
</div>
<div ng-if="question.type === 'textarea'">
<textarea id="survey_question_{{$index}}" name="survey_question_{{$index}}" ng-model="question.model" ng-minlength="question.minlength" ng-maxlength="question.maxlength" class="form-control final Form-textArea" ng-required="question.required" rows="3"></textarea>
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$dirty && forms.survey.survey_question_{{$index}}.$error.required">{{:: vm.strings.get('prompt.PLEASE_ENTER_ANSWER') }}</div>
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.minlength || forms.survey.survey_question_{{$index}}.$error.maxlength"><span translate>Please enter an answer between</span> {{question.minlength}} <span translate>to</span> {{question.maxlength}} <span translate>characters long.</span></div>
</div>
<div ng-if="question.type === 'password'">
<div class="input-group">
<span class="input-group-btn">
<button type="button" class="btn btn-default show_input_button Prompt-passwordButton" id="survey_question_{{$index}}_show_input_button" aw-tool-tip="Toggle the display of plaintext." aw-tip-placement="top" ng-click="vm.togglePassword('#survey_question_' + $index)" data-original-title="" data-container="job-launch-modal" title="" translate>Show</button>
</span>
<input id="survey_question_{{$index}}" ng-if="!question.default" type="password" ng-model="question.model" name="survey_question_{{$index}}" ng-required="question.required" ng-minlength="question.minlength" ng-maxlength="question.maxlength" class="form-control Form-textInput" autocomplete="false">
<input id="survey_question_{{$index}}" ng-if="question.default" type="password" ng-model="question.model" name="survey_question_{{$index}}" ng-required="question.required" aw-password-min="question.minlength" aw-password-max="question.maxlength" class="form-control Form-textInput" autocomplete="false">
</div>
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$dirty && forms.survey.survey_question_{{$index}}.$error.required">{{:: vm.strings.get('prompt.PLEASE_ENTER_ANSWER') }}</div>
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.awPasswordMin || forms.survey.survey_question_{{$index}}.$error.awPasswordMax || forms.survey.survey_question_{{$index}}.$error.minlength || forms.survey.survey_question_{{$index}}.$error.maxlength"><span translate>Please enter an answer between</span> {{question.minlength}} <span translate>to</span> {{question.maxlength}} <span translate>characters long.</span></div>
</div>
<div ng-if="question.type === 'integer'">
<input type="number" id="survey_question_{{$index}}" ng-model="question.model" class="form-control Form-textInput" name="survey_question_{{$index}}" ng-required="question.required" integer aw-min="question.minValue" aw-max="question.maxValue"/>
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$dirty && forms.survey.survey_question_{{$index}}.$error.required">{{:: vm.strings.get('prompt.PLEASE_ENTER_ANSWER') }}</div>
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.number || forms.survey.survey_question_{{$index}}.$error.integer">{{:: vm.strings.get('prompt.VALID_INTEGER') }}</div>
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.awMin || forms.survey.survey_question_{{$index}}.$error.awMax"><span translate>Please enter an answer between</span> {{question.minValue}} <span>and</span> {{question.maxValue}}.</div>
</div>
<div ng-if="question.type === 'float'">
<input type="number" id="survey_question_{{$index}}" ng-model="question.model" class="form-control Form-textInput" name="survey_question_{{$index}}" ng-required="question.required" smart-float aw-min="question.minValue" aw-max="question.maxValue"/>
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$dirty && forms.survey.survey_question_{{$index}}.$error.required">{{:: vm.strings.get('prompt.PLEASE_ENTER_ANSWER') }}</div>
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.number || forms.survey.survey_question_{{$index}}.$error.float">{{:: vm.strings.get('prompt.VALID_DECIMAL') }}</div>
<div class="error survey_error" ng-show="forms.survey.survey_question_{{$index}}.$error.awMin || forms.survey.survey_question_{{$index}}.$error.awMax"><span translate>Please enter an answer between</span> {{question.minValue}} <span translate>and</span> {{question.maxValue}}.</div>
</div>
<div ng-if="question.type === 'multiplechoice'">
<div class="survey_taker_input">
<multiple-choice
multi-select="false"
question="question"
choices="question.choices"
ng-required="question.required"
ng-model="question.model">
</multiple-choice>
</div>
</div>
<div ng-if="question.type === 'multiselect'">
<multiple-choice
multi-select="true"
question="question"
choices="question.choices"
ng-required="question.required"
ng-model="question.model">
</multiple-choice>
</div>
</div>
</form>