diff --git a/awx/ui/client/src/scheduler/factories/add-schedule.factory.js b/awx/ui/client/src/scheduler/factories/add-schedule.factory.js
deleted file mode 100644
index c1870dd469..0000000000
--- a/awx/ui/client/src/scheduler/factories/add-schedule.factory.js
+++ /dev/null
@@ -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'
- ];
diff --git a/awx/ui/client/src/scheduler/factories/edit-schedule.factory.js b/awx/ui/client/src/scheduler/factories/edit-schedule.factory.js
deleted file mode 100644
index fe8c2eff24..0000000000
--- a/awx/ui/client/src/scheduler/factories/edit-schedule.factory.js
+++ /dev/null
@@ -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'
- ];
diff --git a/awx/ui/client/src/scheduler/factories/schedule-post.factory.js b/awx/ui/client/src/scheduler/factories/schedule-post.factory.js
index e62388368e..3033219275 100644
--- a/awx/ui/client/src/scheduler/factories/schedule-post.factory.js
+++ b/awx/ui/client/src/scheduler/factories/schedule-post.factory.js
@@ -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'
];
diff --git a/awx/ui/client/src/scheduler/main.js b/awx/ui/client/src/scheduler/main.js
index 6dbaf01971..7671614054 100644
--- a/awx/ui/client/src/scheduler/main.js
+++ b/awx/ui/client/src/scheduler/main.js
@@ -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) {
diff --git a/awx/ui/client/src/scheduler/schedulerAdd.controller.js b/awx/ui/client/src/scheduler/schedulerAdd.controller.js
index 9485315078..d7f82c32ca 100644
--- a/awx/ui/client/src/scheduler/schedulerAdd.controller.js
+++ b/awx/ui/client/src/scheduler/schedulerAdd.controller.js
@@ -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();
-
}];
diff --git a/awx/ui/client/src/scheduler/schedulerEdit.controller.js b/awx/ui/client/src/scheduler/schedulerEdit.controller.js
index c6030fdd94..a762b06072 100644
--- a/awx/ui/client/src/scheduler/schedulerEdit.controller.js
+++ b/awx/ui/client/src/scheduler/schedulerEdit.controller.js
@@ -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();
}];
diff --git a/awx/ui/client/src/scheduler/schedulerForm.block.less b/awx/ui/client/src/scheduler/schedulerForm.block.less
index 6512efa0f6..d40eb0e15e 100644
--- a/awx/ui/client/src/scheduler/schedulerForm.block.less
+++ b/awx/ui/client/src/scheduler/schedulerForm.block.less
@@ -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;
+}
diff --git a/awx/ui/client/src/scheduler/schedulerForm.partial.html b/awx/ui/client/src/scheduler/schedulerForm.partial.html
index ca6d0558fc..b06b1ea7eb 100644
--- a/awx/ui/client/src/scheduler/schedulerForm.partial.html
+++ b/awx/ui/client/src/scheduler/schedulerForm.partial.html
@@ -661,23 +661,30 @@