mirror of
https://github.com/ansible/awx.git
synced 2024-10-28 02:25:27 +03:00
Merge pull request #1096 from mabashian/169-v1
UI support for prompting on job template schedules
This commit is contained in:
commit
b2b519e48d
@ -42,4 +42,3 @@
|
||||
</at-panel>
|
||||
|
||||
<div ng-if="$state.current.name.includes('permissions.add')" ui-view="modal"></div>
|
||||
|
||||
|
@ -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 || {},
|
||||
unifiedJobTemplate = model,
|
||||
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
|
||||
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) {
|
||||
if(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')) {
|
||||
InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'workflow_job_template' });
|
||||
}
|
||||
else {
|
||||
var alertStrings = {
|
||||
header: 'Error: Unable to determine template type',
|
||||
body: 'We were unable to determine this template\'s type while launching.'
|
||||
// Something went wrong - Let the user know that we're unable to launch because we don't know
|
||||
// what type of job template this is
|
||||
Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while launching.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
Alert('Error: Unable to launch template', 'Template parameter is missing');
|
||||
}
|
||||
};
|
||||
|
||||
$scope.launchJob = () => {
|
||||
|
||||
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 {
|
||||
var alertStrings = {
|
||||
header: 'Error: Unable to launch template',
|
||||
body: 'Template parameter is missing'
|
||||
else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
|
||||
$state.go('workflowJobTemplateSchedules', {id: template.id});
|
||||
}
|
||||
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',
|
||||
'Wait',
|
||||
'ProcessErrors',
|
||||
'TemplateCopyService'
|
||||
'TemplateCopyService',
|
||||
'$q',
|
||||
'Empty',
|
||||
'i18n',
|
||||
'PromptService'
|
||||
];
|
||||
|
||||
export default ListTemplatesController;
|
||||
|
@ -118,4 +118,5 @@
|
||||
query-set="querySet">
|
||||
</paginate>
|
||||
</at-panel-body>
|
||||
<prompt prompt-data="promptData" open-modal="openModal" on-finish="launchJob()"></launch>
|
||||
</at-panel>
|
||||
|
@ -6,7 +6,7 @@ function TemplatesStrings (BaseString) {
|
||||
|
||||
ns.state = {
|
||||
LIST_BREADCRUMB_LABEL: t.s('TEMPLATES')
|
||||
}
|
||||
};
|
||||
|
||||
ns.list = {
|
||||
PANEL_TITLE: t.s('TEMPLATES'),
|
||||
@ -19,7 +19,44 @@ function TemplatesStrings (BaseString) {
|
||||
ROW_ITEM_LABEL_CREDENTIALS: t.s('Credentials'),
|
||||
ROW_ITEM_LABEL_MODIFIED: t.s('Last Modified'),
|
||||
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'];
|
||||
|
@ -626,11 +626,20 @@ input[type='radio']:checked:before {
|
||||
background-color: @submit-button-bg-dis;
|
||||
}
|
||||
|
||||
.Form-saveButton--disabled {
|
||||
background-color: @submit-button-bg-dis;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.Form-saveButton:hover, .Form-launchButton:hover {
|
||||
background-color: @submit-button-bg-hov;
|
||||
color: @submit-button-text;
|
||||
}
|
||||
|
||||
.Form-saveButton--disabled:hover {
|
||||
background-color: @submit-button-bg-dis;
|
||||
}
|
||||
|
||||
.Form-cancelButton {
|
||||
background-color: @default-bg;
|
||||
color: @btn-txt;
|
||||
@ -668,6 +677,10 @@ input[type='radio']:checked:before {
|
||||
background-color: @default-link;
|
||||
}
|
||||
|
||||
.Form-primaryButton--noMargin {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.Form-formGroup--singleColumn {
|
||||
width: 100% !important;
|
||||
padding-right: 0px;
|
||||
|
@ -16,14 +16,12 @@ function AtModalController (eventService, strings) {
|
||||
const vm = this;
|
||||
|
||||
let overlay;
|
||||
let modal;
|
||||
let listeners;
|
||||
|
||||
vm.strings = strings;
|
||||
|
||||
vm.init = (scope, el) => {
|
||||
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.show = vm.show;
|
||||
@ -35,7 +33,7 @@ function AtModalController (eventService, strings) {
|
||||
vm.modal.message = message;
|
||||
|
||||
listeners = eventService.addListeners([
|
||||
[window, 'click', vm.clickToHide]
|
||||
[overlay, 'click', vm.clickToHide]
|
||||
]);
|
||||
|
||||
overlay.style.display = 'block';
|
||||
@ -53,22 +51,10 @@ function AtModalController (eventService, strings) {
|
||||
};
|
||||
|
||||
vm.clickToHide = event => {
|
||||
if (vm.clickIsOutsideModal(event)) {
|
||||
if ($(event.target).hasClass('at-Modal')) {
|
||||
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 = [
|
||||
|
@ -10,6 +10,12 @@ function AtTabGroupController () {
|
||||
|
||||
vm.tabs.push(tab);
|
||||
};
|
||||
|
||||
vm.clearActive = () => {
|
||||
vm.tabs.forEach((tab) => {
|
||||
tab.state._active = false;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function atTabGroup () {
|
||||
|
@ -25,7 +25,12 @@ function AtTabController ($state) {
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
21
awx/ui/client/lib/models/Job.js
Normal file
21
awx/ui/client/lib/models/Job.js
Normal 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;
|
@ -1,5 +1,69 @@
|
||||
let Base;
|
||||
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) {
|
||||
this.dependentResources = [
|
||||
@ -17,20 +81,30 @@ function JobTemplateModel (method, resource, config) {
|
||||
|
||||
this.Constructor = JobTemplateModel;
|
||||
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);
|
||||
}
|
||||
|
||||
function JobTemplateModelLoader (BaseModel, WorkflowJobTemplateNodeModel) {
|
||||
function JobTemplateModelLoader (BaseModel, WorkflowJobTemplateNodeModel, _$http_) {
|
||||
Base = BaseModel;
|
||||
WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel;
|
||||
$http = _$http_;
|
||||
|
||||
return JobTemplateModel;
|
||||
}
|
||||
|
||||
JobTemplateModelLoader.$inject = [
|
||||
'BaseModel',
|
||||
'WorkflowJobTemplateNodeModel'
|
||||
'WorkflowJobTemplateNodeModel',
|
||||
'$http',
|
||||
'$state'
|
||||
];
|
||||
|
||||
export default JobTemplateModelLoader;
|
||||
|
21
awx/ui/client/lib/models/WorkflowJob.js
Normal file
21
awx/ui/client/lib/models/WorkflowJob.js
Normal 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;
|
@ -14,6 +14,8 @@ import InstanceGroup from '~models/InstanceGroup';
|
||||
import InventorySource from '~models/InventorySource';
|
||||
import Inventory from '~models/Inventory';
|
||||
import InventoryScript from '~models/InventoryScript';
|
||||
import Job from '~models/Job';
|
||||
import WorkflowJob from '~models/WorkflowJob';
|
||||
import WorkflowJobTemplate from '~models/WorkflowJobTemplate';
|
||||
|
||||
import ModelsStrings from '~models/models.strings';
|
||||
@ -41,6 +43,9 @@ angular
|
||||
.service('InventoryScriptModel', InventoryScript)
|
||||
.service('WorkflowJobTemplateModel', WorkflowJobTemplate)
|
||||
.service('ModelsStrings', ModelsStrings)
|
||||
.service('UnifiedJobTemplateModel', UnifiedJobTemplate);
|
||||
.service('UnifiedJobTemplateModel', UnifiedJobTemplate)
|
||||
.service('JobModel', Job)
|
||||
.service('WorkflowJobModel', WorkflowJob)
|
||||
.service('WorkflowJobTemplateModel', WorkflowJobTemplate);
|
||||
|
||||
export default MODULE_NAME;
|
||||
|
@ -60,8 +60,14 @@ function BaseStringService (namespace) {
|
||||
this.CANCEL = t.s('CANCEL');
|
||||
this.SAVE = t.s('SAVE');
|
||||
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.OFF = t.s('OFF');
|
||||
this.YAML = t.s('YAML');
|
||||
this.JSON = t.s('JSON');
|
||||
|
||||
this.deleteResource = {
|
||||
HEADER: t.s('Delete'),
|
||||
USED_BY: resourceType => t.s('The {{ resourceType }} is currently being used by other resources.', { resourceType }),
|
||||
|
@ -122,6 +122,7 @@
|
||||
@import '../../src/system-tracking/fact-module-filter.block.less';
|
||||
@import '../../src/system-tracking/fact-module-pickers.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/labels/labelsList.block.less';
|
||||
@import '../../src/templates/survey-maker/survey-maker.block.less';
|
||||
|
@ -1,12 +1,12 @@
|
||||
<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"
|
||||
ng-click="deleteLabel(host, related_group)"
|
||||
ng-show="showDelete && host.summary_fields.user_capabilities.edit">
|
||||
<i class="fa fa-times LabelList-tagDelete"></i>
|
||||
</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 class="LabelList-seeMoreLess" ng-show="count > 5 && seeMoreInactive"
|
||||
ng-click="seeMore()" translate>View More</div>
|
||||
|
@ -394,20 +394,9 @@ export default
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggle_inventory = function(id) {
|
||||
$scope.inventories.forEach(function(row, i) {
|
||||
if (row.id === id) {
|
||||
$scope.selected_inventory = angular.copy(row);
|
||||
$scope.inventories[i].checked = 1;
|
||||
} else {
|
||||
$scope.inventories[i].checked = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggle_credential = function(id) {
|
||||
$scope.toggle_credential = function(cred) {
|
||||
$scope.credentials.forEach(function(row, i) {
|
||||
if (row.id === id) {
|
||||
if (row.id === cred.id) {
|
||||
$scope.selected_credentials.machine = angular.copy(row);
|
||||
$scope.credentials[i].checked = 1;
|
||||
} else {
|
||||
|
@ -31,12 +31,12 @@
|
||||
<div class="JobSubmission-previewTags--outer">
|
||||
<div class="JobSubmission-previewTags--inner">
|
||||
<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">
|
||||
<span>{{selected_inventory.name}}</span>
|
||||
</div>
|
||||
<div class="JobSubmission-previewTagContainerDelete" ng-click="deleteSelectedInventory()">
|
||||
<i class="fa fa-times JobSubmission-previewTagContainerTagDelete"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -58,22 +58,22 @@
|
||||
<div class="JobSubmission-previewTags--outer">
|
||||
<div class="JobSubmission-previewTags--inner">
|
||||
<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}">
|
||||
<span><span class="JobSubmission-previewTagLabel" ng-class="{'JobSubmission-previewTagLabel--deletable': ask_credential_on_launch}">
|
||||
MACHINE</span>: {{selected_credentials.machine.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<div class="JobSubmission-previewTagContainerDelete" ng-click="deleteMachineCred()" ng-show="ask_credential_on_launch">
|
||||
<i class="fa fa-times JobSubmission-previewTagContainerTagDelete"></i>
|
||||
</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}">
|
||||
<span><span class="JobSubmission-previewTagLabel" ng-class="{'JobSubmission-previewTagLabel--deletable': ask_credential_on_launch}">
|
||||
{{credential_types[extraCredential.credential_type].name | uppercase}}</span>: {{extraCredential.name}}</span>
|
||||
</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 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}">
|
||||
|
@ -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'
|
||||
];
|
@ -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'
|
||||
];
|
@ -1,13 +1,14 @@
|
||||
export default
|
||||
function SchedulePost(Rest, ProcessErrors, RRuleToAPI, Wait) {
|
||||
function SchedulePost(Rest, ProcessErrors, RRuleToAPI, Wait, $q) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
url = params.url,
|
||||
scheduler = params.scheduler,
|
||||
mode = params.mode,
|
||||
schedule = (params.schedule) ? params.schedule : {},
|
||||
callback = params.callback,
|
||||
promptData = params.promptData,
|
||||
newSchedule, rrule, extra_vars;
|
||||
let deferred = $q.defer();
|
||||
if (scheduler.isValid()) {
|
||||
Wait('start');
|
||||
newSchedule = scheduler.getValue();
|
||||
@ -32,41 +33,101 @@ export default
|
||||
schedule.extra_data = scope.parseType === 'yaml' ?
|
||||
(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);
|
||||
if (mode === 'add') {
|
||||
Rest.post(schedule)
|
||||
.then(() => {
|
||||
if (callback) {
|
||||
scope.$emit(callback);
|
||||
}
|
||||
else {
|
||||
Wait('stop');
|
||||
}
|
||||
Wait('stop');
|
||||
deferred.resolve();
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'POST to ' + url + ' returned: ' + status });
|
||||
|
||||
deferred.reject();
|
||||
});
|
||||
}
|
||||
else {
|
||||
Rest.put(schedule)
|
||||
.then(() => {
|
||||
if (callback) {
|
||||
scope.$emit(callback, schedule);
|
||||
}
|
||||
else {
|
||||
Wait('stop');
|
||||
}
|
||||
Wait('stop');
|
||||
deferred.resolve(schedule);
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'POST to ' + url + ' returned: ' + status });
|
||||
|
||||
deferred.reject();
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
deferred.reject();
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
}
|
||||
|
||||
@ -74,5 +135,6 @@ SchedulePost.$inject =
|
||||
[ 'Rest',
|
||||
'ProcessErrors',
|
||||
'RRuleToAPI',
|
||||
'Wait'
|
||||
'Wait',
|
||||
'$q'
|
||||
];
|
||||
|
@ -10,9 +10,7 @@ import editController from './schedulerEdit.controller';
|
||||
import {templateUrl} from '../shared/template-url/template-url.factory';
|
||||
import schedulerDatePicker from './schedulerDatePicker.directive';
|
||||
import { N_ } from '../i18n';
|
||||
import AddSchedule from './factories/add-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 SchedulePost from './factories/schedule-post.factory';
|
||||
import ToggleSchedule from './factories/toggle-schedule.factory';
|
||||
@ -24,9 +22,7 @@ export default
|
||||
.controller('schedulerListController', listController)
|
||||
.controller('schedulerAddController', addController)
|
||||
.controller('schedulerEditController', editController)
|
||||
.factory('AddSchedule', AddSchedule)
|
||||
.factory('DeleteSchedule', DeleteSchedule)
|
||||
.factory('EditSchedule', EditSchedule)
|
||||
.factory('RRuleToAPI', RRuleToAPI)
|
||||
.factory('SchedulePost', SchedulePost)
|
||||
.factory('ToggleSchedule', ToggleSchedule)
|
||||
@ -47,10 +43,10 @@ export default
|
||||
activityStreamTarget: 'job_template',
|
||||
activityStreamId: 'id'
|
||||
},
|
||||
ncyBreadcrumb: {
|
||||
parent: 'templates.editJobTemplate({job_template_id: parentObject.id})',
|
||||
label: N_('SCHEDULES')
|
||||
},
|
||||
// ncyBreadcrumb: {
|
||||
// parent: 'templates.editJobTemplate({job_template_id: parentObject.id})',
|
||||
// label: N_('SCHEDULES')
|
||||
// },
|
||||
resolve: {
|
||||
Dataset: ['ScheduleList', 'QuerySet', '$stateParams', 'GetBasePath',
|
||||
function(list, qs, $stateParams, GetBasePath) {
|
||||
|
@ -4,12 +4,22 @@
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['$filter', '$state', '$stateParams', 'AddSchedule', 'Wait',
|
||||
export default ['$filter', '$state', '$stateParams', 'Wait',
|
||||
'$scope', '$rootScope', 'CreateSelect2', 'ParseTypeChange', 'GetBasePath',
|
||||
'Rest', 'ParentObject',
|
||||
function($filter, $state, $stateParams, AddSchedule, Wait, $scope,
|
||||
$rootScope, CreateSelect2, ParseTypeChange, GetBasePath, Rest, ParentObject) {
|
||||
$scope.processSchedulerEndDt = function(){
|
||||
'Rest', 'ParentObject', 'JobTemplateModel', '$q', 'Empty', 'SchedulePost',
|
||||
'ProcessErrors', 'SchedulerInit', '$location', 'PromptService',
|
||||
function($filter, $state, $stateParams, Wait,
|
||||
$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
|
||||
var dt = new Date($scope.schedulerUTCTime);
|
||||
// increment date by 1 day
|
||||
@ -19,12 +29,6 @@ export default ['$filter', '$state', '$stateParams', 'AddSchedule', 'Wait',
|
||||
$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
|
||||
* 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.$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 = angular.extend($scope, scope);
|
||||
$scope.$on("formUpdated", function() {
|
||||
$rootScope.$broadcast("loadSchedulerDetailPane");
|
||||
});
|
||||
@ -91,72 +341,18 @@ export default ['$filter', '$state', '$stateParams', 'AddSchedule', 'Wait',
|
||||
|
||||
Wait('stop');
|
||||
});
|
||||
$scope.showRRuleDetail = false;
|
||||
|
||||
$scope.hideForm = true;
|
||||
|
||||
var schedule_url = ParentObject.related.schedules || `${ParentObject.related.inventory_source}schedules`;
|
||||
|
||||
$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
|
||||
$('#scheduler-tabs li a').on('shown.bs.tab', function(e) {
|
||||
if ($(e.target).text() === 'Details') {
|
||||
if (!scheduler.isValid()) {
|
||||
$('#scheduler-tabs a:first').tab('show');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var callSelect2 = function() {
|
||||
CreateSelect2({
|
||||
element: '.MakeSelect2',
|
||||
multiple: false
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$on("updateSchedulerSelects", function() {
|
||||
callSelect2();
|
||||
CreateSelect2({
|
||||
element: '.MakeSelect2',
|
||||
multiple: false
|
||||
});
|
||||
|
||||
callSelect2();
|
||||
|
||||
}];
|
||||
|
@ -1,5 +1,20 @@
|
||||
export default ['$filter', '$state', '$stateParams', 'EditSchedule', 'Wait', '$scope', '$rootScope', 'CreateSelect2', 'ParseTypeChange', 'ParentObject',
|
||||
function($filter, $state, $stateParams, EditSchedule, Wait, $scope, $rootScope, CreateSelect2, ParseTypeChange, ParentObject) {
|
||||
export default ['$filter', '$state', '$stateParams', 'Wait', '$scope',
|
||||
'$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(){
|
||||
// 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);
|
||||
$scope.$parent.schedulerEndDt = month + '/' + day + '/' + dt.getFullYear();
|
||||
};
|
||||
// initial end @ midnight values
|
||||
$scope.schedulerEndHour = "00";
|
||||
$scope.schedulerEndMinute = "00";
|
||||
$scope.schedulerEndSecond = "00";
|
||||
$scope.parentObject = ParentObject;
|
||||
|
||||
$scope.formCancel = function() {
|
||||
$state.go("^");
|
||||
};
|
||||
|
||||
/*
|
||||
* 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.$on("ScheduleFormCreated", function(e, scope) {
|
||||
$scope.hideForm = false;
|
||||
$scope = angular.extend($scope, scope);
|
||||
|
||||
$scope.$on("formUpdated", function() {
|
||||
$rootScope.$broadcast("loadSchedulerDetailPane");
|
||||
$scope.saveSchedule = function() {
|
||||
schedule.extra_data = $scope.extraVars;
|
||||
SchedulePost({
|
||||
scope: $scope,
|
||||
url: GetBasePath('schedules') + parseInt($stateParams.schedule_id) + '/',
|
||||
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
|
||||
if ($state.current.name !== 'managementJobsList.schedule.add' && $state.current.name !== 'managementJobsList.schedule.edit'){
|
||||
$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
|
||||
});
|
||||
$scope.prompt = () => {
|
||||
$scope.promptData.triggerModalOpen = true;
|
||||
};
|
||||
|
||||
var callSelect2 = function() {
|
||||
CreateSelect2({
|
||||
@ -132,5 +85,284 @@ function($filter, $state, $stateParams, EditSchedule, Wait, $scope, $rootScope,
|
||||
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();
|
||||
}];
|
||||
|
@ -54,3 +54,15 @@
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.SchedulerForm-promptSaveTooltip {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
display: block;
|
||||
margin-left: 20px;
|
||||
width: ~"calc(100% - 20px)";
|
||||
}
|
||||
|
||||
.SchedulerForm-promptSave {
|
||||
position: relative;
|
||||
}
|
||||
|
@ -661,23 +661,30 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons Form-buttons">
|
||||
<button type="button"
|
||||
class="btn btn-sm Form-cancelButton"
|
||||
id="project_cancel_btn"
|
||||
ng-show="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)"
|
||||
ng-click="formCancel()">Close</button>
|
||||
<button type="button"
|
||||
class="btn btn-sm Form-cancelButton"
|
||||
id="project_cancel_btn"
|
||||
ng-show="(schedule_obj.summary_fields.user_capabilities.edit || canAdd)"
|
||||
ng-click="formCancel()">Cancel</button>
|
||||
<button type="button"
|
||||
class="btn btn-sm Form-saveButton"
|
||||
id="project_save_btn"
|
||||
ng-click="saveSchedule()"
|
||||
ng-show="(schedule_obj.summary_fields.user_capabilities.edit || canAdd)"
|
||||
ng-disabled="!schedulerIsValid"> Save</button>
|
||||
|
||||
</div>
|
||||
<div class="buttons Form-buttons">
|
||||
<button type="button"
|
||||
class="btn btn-sm Form-primaryButton Form-primaryButton--noMargin"
|
||||
id="schedule_prompt_btn"
|
||||
ng-show="(schedule_obj.summary_fields.user_capabilities.edit || canAdd) && showPromptButton"
|
||||
ng-click="prompt()">Prompt</button>
|
||||
<button type="button"
|
||||
class="btn btn-sm Form-cancelButton"
|
||||
id="schedule_cancel_btn"
|
||||
ng-show="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)"
|
||||
ng-click="formCancel()">Close</button>
|
||||
<button type="button"
|
||||
class="btn btn-sm Form-cancelButton"
|
||||
id="schedule_cancel_btn"
|
||||
ng-show="(schedule_obj.summary_fields.user_capabilities.edit || canAdd)"
|
||||
ng-click="formCancel()">Cancel</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>
|
||||
<button type="button"
|
||||
class="btn btn-sm Form-saveButton"
|
||||
id="schedule_save_btn"
|
||||
ng-click="saveSchedule()"
|
||||
ng-disabled="!schedulerIsValid || promptModalMissingReqFields"> Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<prompt prompt-data="promptData" action-text="CONFIRM"></launch>
|
||||
</div>
|
||||
|
@ -8,13 +8,13 @@
|
||||
<span id="InstanceGroups" class="form-control Form-textInput Form-textInput--variableHeight input-medium lookup LabelList-lookupTags"
|
||||
ng-disabled="fieldIsDisabled">
|
||||
<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"
|
||||
ng-click="deleteTag(tag)">
|
||||
<i class="fa fa-times LabelList-tagDelete"></i>
|
||||
</div>
|
||||
<div class="LabelList-tag LabelList-tag--deletable">
|
||||
<span class="LabelList-name">{{ tag.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -344,7 +344,7 @@ export default ['$compile', 'Attr', 'Icon',
|
||||
}
|
||||
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_{{" +
|
||||
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>";
|
||||
}
|
||||
}
|
||||
@ -368,11 +368,11 @@ export default ['$compile', 'Attr', 'Icon',
|
||||
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
|
||||
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>";
|
||||
} else { // its assumed that options.input_type = checkbox
|
||||
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>";
|
||||
}
|
||||
} else if ((options.mode === 'edit' || options.mode === 'summary') && list.fieldActions) {
|
||||
|
@ -48,6 +48,7 @@ export default
|
||||
scrollbarStyle: null
|
||||
}
|
||||
};
|
||||
|
||||
scope[fld + 'codeMirror'] = AngularCodeMirror(readOnly);
|
||||
scope[fld + 'codeMirror'].addModes(variableEditModes);
|
||||
scope[fld + 'codeMirror'].showTextArea({
|
||||
@ -95,6 +96,7 @@ export default
|
||||
// convert json to yaml
|
||||
try {
|
||||
removeField(fld);
|
||||
|
||||
json_obj = JSON.parse(scope[fld]);
|
||||
if ($.isEmptyObject(json_obj)) {
|
||||
scope[fld] = '---';
|
||||
|
@ -21,12 +21,7 @@
|
||||
<div class="MultiCredential-tags">
|
||||
<div class="MultiCredential-tagSection">
|
||||
<div class="MultiCredential-flexContainer">
|
||||
<div class="MultiCredential-tagContainer ng-scope" ng-repeat="tag in tags track by $index">
|
||||
<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-tagContainer ng-scope" ng-repeat="tag in tags track by $index" ng-class="{'MultiCredential-tagContainer--disabled': 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-info MultiCredential-tagIcon" ng-switch-when="insights"></i>
|
||||
@ -52,6 +47,11 @@
|
||||
{{ tag.name }} | {{ tag.info }}
|
||||
</span>
|
||||
</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>
|
||||
|
@ -34,25 +34,28 @@
|
||||
.MultiCredential-tagContainer {
|
||||
display: flex;
|
||||
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 {
|
||||
border-radius: 5px;
|
||||
padding: 2px 10px;
|
||||
margin: 3px 0px;
|
||||
font-size: 12px;
|
||||
margin-right: 10px;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
background-color: @default-link;
|
||||
color: @default-bg;
|
||||
padding-left: 15px;
|
||||
padding: 2px 0px 2px 15px;
|
||||
}
|
||||
|
||||
.MultiCredential-tag--disabled {
|
||||
background-color: @default-icon;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
padding-left: 10px;
|
||||
@ -60,22 +63,17 @@
|
||||
|
||||
.MultiCredential-tag--deletable {
|
||||
margin-right: 0px;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
border-right: 0;
|
||||
max-width: ~"calc(100% - 23px)";
|
||||
margin-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.MultiCredential-deleteContainer {
|
||||
background-color: @default-link!important;
|
||||
color: white;
|
||||
background-color: @default-bg;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
padding: 0 5px;
|
||||
margin: 3px 0px;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
padding: 2px 5px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
@ -86,8 +84,6 @@
|
||||
}
|
||||
|
||||
.MultiCredential-iconContainer {
|
||||
background-color: @default-link!important;
|
||||
color: @default-bg;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
padding: 0px 5px;
|
||||
@ -98,8 +94,6 @@
|
||||
}
|
||||
|
||||
.MultiCredential-iconContainer--disabled {
|
||||
background-color: @default-icon;
|
||||
color: @default-bg;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
padding: 0 5px;
|
||||
@ -112,7 +106,7 @@
|
||||
|
||||
.MultiCredential-tagIcon {
|
||||
margin: 0px 0px;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.MultiCredential-name {
|
||||
@ -123,10 +117,9 @@
|
||||
|
||||
.MultiCredential-name--label {
|
||||
color: @default-list-header-bg;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
margin-left: -8px;
|
||||
margin-right: 5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.MultiCredential-tag--deletable > .MultiCredential-name {
|
||||
|
@ -16,12 +16,8 @@
|
||||
<div class="MultiCredential-tagSection">
|
||||
<div class="MultiCredential-flexContainer">
|
||||
<div class="MultiCredential-tagContainer ng-scope"
|
||||
ng-class="{'MultiCredential-tagContainer--disabled': tag.readOnly}"
|
||||
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">
|
||||
<i class="fa fa-cloud MultiCredential-tagIcon" ng-switch-when="cloud"></i>
|
||||
<i class="fa fa-info MultiCredential-tagIcon" ng-switch-when="insights"></i>
|
||||
@ -47,6 +43,11 @@
|
||||
{{ tag.name }} | {{ tag.info }}
|
||||
</span>
|
||||
</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>
|
||||
|
@ -39,27 +39,27 @@
|
||||
color: @default-link-hov;
|
||||
}
|
||||
|
||||
.LabelList-tag--deletable, .JobSubmission-previewTag--deletable {
|
||||
.LabelList-tag--deletable, .JobSubmission-previewTag--deletable, .Prompt-previewTag--deletable {
|
||||
color: @default-bg;
|
||||
background-color: @default-link;
|
||||
margin-right: 0px;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
border-right: 0;
|
||||
max-width: ~"calc(100% - 23px)";
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.LabelList-deleteContainer, .JobSubmission-previewTagContainerDelete {
|
||||
.LabelList-deleteContainer, .JobSubmission-previewTagContainerDelete, .Prompt-previewTagContainerDelete {
|
||||
background-color: @default-link;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
color: @default-bg;
|
||||
padding: 0 5px;
|
||||
margin: 3px 0px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.LabelList-tagDelete {
|
||||
@ -76,12 +76,12 @@
|
||||
max-width: ~"calc(100% - 23px)";
|
||||
}
|
||||
|
||||
.LabelList-deleteContainer:hover, .JobSubmission-previewTagContainerDelete:hover {
|
||||
.LabelList-deleteContainer:hover, .JobSubmission-previewTagContainerDelete:hover, .Prompt-previewTagContainerDelete:hover {
|
||||
border-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;
|
||||
}
|
||||
|
||||
|
@ -17,14 +17,14 @@
|
||||
Labels
|
||||
</div>
|
||||
<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"
|
||||
ng-click="deleteLabel(template, label)"
|
||||
ng-show="showDelete && template.summary_fields.user_capabilities.edit">
|
||||
<i class="fa fa-times LabelList-tagDelete"></i>
|
||||
</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 class="LabelList-seeMoreLess" ng-show="count > 5 && seeMoreInactive"
|
||||
ng-click="seeMore()">View More</div>
|
||||
|
@ -10,6 +10,7 @@ import jobTemplates from './job_templates/main';
|
||||
import workflowAdd from './workflows/add-workflow/main';
|
||||
import workflowEdit from './workflows/edit-workflow/main';
|
||||
import labels from './labels/main';
|
||||
import prompt from './prompt/main';
|
||||
import workflowChart from './workflows/workflow-chart/main';
|
||||
import workflowMaker from './workflows/workflow-maker/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';
|
||||
|
||||
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
|
||||
])
|
||||
.service('TemplatesService', templatesService)
|
||||
|
17
awx/ui/client/src/templates/prompt/main.js
Normal file
17
awx/ui/client/src/templates/prompt/main.js
Normal 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);
|
166
awx/ui/client/src/templates/prompt/prompt.block.less
Normal file
166
awx/ui/client/src/templates/prompt/prompt.block.less
Normal 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;
|
||||
}
|
188
awx/ui/client/src/templates/prompt/prompt.controller.js
Normal file
188
awx/ui/client/src/templates/prompt/prompt.controller.js
Normal 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();
|
||||
};
|
||||
}];
|
24
awx/ui/client/src/templates/prompt/prompt.directive.js
Normal file
24
awx/ui/client/src/templates/prompt/prompt.directive.js
Normal 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);
|
||||
}
|
||||
};
|
||||
}];
|
36
awx/ui/client/src/templates/prompt/prompt.partial.html
Normal file
36
awx/ui/client/src/templates/prompt/prompt.partial.html
Normal 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>
|
124
awx/ui/client/src/templates/prompt/prompt.service.js
Normal file
124
awx/ui/client/src/templates/prompt/prompt.service.js
Normal 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;
|
@ -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");
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
@ -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);
|
||||
}
|
||||
};
|
||||
}];
|
@ -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>
|
@ -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;
|
||||
};
|
||||
}
|
||||
];
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}];
|
@ -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>
|
@ -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');
|
||||
};
|
||||
}
|
||||
];
|
@ -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);
|
||||
}
|
||||
};
|
||||
}];
|
@ -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 />"somevar": "somevalue",<br />"password": "magic"<br /> }</blockquote>YAML:<br /><blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>" data-placement="right" data-container="body" over-title="Extra Variables" class="help-link" data-original-title="" title="" tabindex="-1">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
</a>
|
||||
<div class="parse-selection" id="job_launch_variables_parse_type">
|
||||
<input type="radio" ng-model="parseType" value="yaml" ng-change="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>
|
@ -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
|
||||
});
|
||||
};
|
||||
}
|
||||
];
|
@ -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);
|
||||
}
|
||||
};
|
||||
}];
|
@ -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') }} </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') }} </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>
|
@ -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");
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
@ -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);
|
||||
}
|
||||
};
|
||||
}];
|
@ -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>
|
Loading…
Reference in New Issue
Block a user