diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js
index a23e803de2..cbf50b22b6 100644
--- a/awx/ui/client/src/app.js
+++ b/awx/ui/client/src/app.js
@@ -47,7 +47,7 @@ import login from './login/main';
import activityStream from './activity-stream/main';
import standardOut from './standard-out/main';
import lookUpHelper from './lookup/main';
-import {JobTemplatesList, JobTemplatesAdd, JobTemplatesEdit} from './controllers/JobTemplates';
+import JobTemplates from './job-templates/main';
import {ScheduleEditController} from './controllers/Schedules';
import {ProjectsList, ProjectsAdd, ProjectsEdit} from './controllers/Projects';
import {OrganizationsList, OrganizationsAdd, OrganizationsEdit} from './controllers/Organizations';
@@ -65,7 +65,6 @@ import './shared/directives';
import './shared/filters';
import './shared/InventoryTree';
import './shared/Socket';
-import './job-templates/main';
import './shared/features/main';
import './login/authenticationServices/pendo/ng-pendo';
import footer from './footer/main';
@@ -101,6 +100,7 @@ var tower = angular.module('Tower', [
jobDetail.name,
notifications.name,
standardOut.name,
+ JobTemplates.name,
'templates',
'Utilities',
'OrganizationFormDefinition',
@@ -297,52 +297,6 @@ var tower = angular.module('Tower', [
}
}).
- state('jobTemplates', {
- url: '/job_templates',
- templateUrl: urlPrefix + 'partials/job_templates.html',
- controller: JobTemplatesList,
- data: {
- activityStream: true,
- activityStreamTarget: 'job_template'
- },
- ncyBreadcrumb: {
- label: "JOB TEMPLATES"
- },
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
- state('jobTemplates.add', {
- url: '/add',
- templateUrl: urlPrefix + 'partials/job_templates.html',
- controller: JobTemplatesAdd,
- ncyBreadcrumb: {
- parent: "jobTemplates",
- label: "CREATE JOB TEMPLATE"
- },
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
- state('jobTemplates.edit', {
- url: '/:template_id',
- templateUrl: urlPrefix + 'partials/job_templates.html',
- controller: JobTemplatesEdit,
- data: {
- activityStreamId: 'template_id'
- },
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
state('projects', {
url: '/projects',
templateUrl: urlPrefix + 'partials/projects.html',
@@ -458,28 +412,6 @@ var tower = angular.module('Tower', [
}
}).
- state('inventoryJobTemplateAdd', {
- url: '/inventories/:inventory_id/job_templates/add',
- templateUrl: urlPrefix + 'partials/job_templates.html',
- controller: JobTemplatesAdd,
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
- state('inventoryJobTemplateEdit', {
- url: '/inventories/:inventory_id/job_templates/:template_id',
- templateUrl: urlPrefix + 'partials/job_templates.html',
- controller: JobTemplatesEdit,
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
state('inventoryManage', {
url: '/inventories/:inventory_id/manage?groups',
templateUrl: urlPrefix + 'partials/inventory-manage.html',
diff --git a/awx/ui/client/src/controllers/JobTemplates.js b/awx/ui/client/src/controllers/JobTemplates.js
deleted file mode 100644
index b01dc04a48..0000000000
--- a/awx/ui/client/src/controllers/JobTemplates.js
+++ /dev/null
@@ -1,1273 +0,0 @@
-/*************************************************
- * Copyright (c) 2015 Ansible, Inc.
- *
- * All Rights Reserved
- *************************************************/
-
-/**
- * @ngdoc function
- * @name controllers.function:JobTemplate
- * @description This controller's for the Job Template page
-*/
-
-
-export function JobTemplatesList($scope, $rootScope, $location, $log,
- $stateParams, Rest, Alert, JobTemplateList, GenerateList, Prompt,
- SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors,
- GetBasePath, JobTemplateForm, CredentialList, LookUpInit, PlaybookRun,
- Wait, CreateDialog, $compile, $state) {
-
- ClearScope();
-
- var list = JobTemplateList,
- defaultUrl = GetBasePath('job_templates'),
- view = GenerateList,
- base = $location.path().replace(/^\//, '').split('/')[0],
- mode = (base === 'job_templates') ? 'edit' : 'select';
-
- view.inject(list, { mode: mode, scope: $scope });
- $rootScope.flashMessage = null;
-
- if ($scope.removePostRefresh) {
- $scope.removePostRefresh();
- }
- $scope.removePostRefresh = $scope.$on('PostRefresh', function () {
- // Cleanup after a delete
- Wait('stop');
- $('#prompt-modal').modal('hide');
- });
-
- SearchInit({
- scope: $scope,
- set: 'job_templates',
- list: list,
- url: defaultUrl
- });
- PaginateInit({
- scope: $scope,
- list: list,
- url: defaultUrl
- });
-
- // Called from Inventories tab, host failed events link:
- if ($stateParams.name) {
- $scope[list.iterator + 'SearchField'] = 'name';
- $scope[list.iterator + 'SearchValue'] = $stateParams.name;
- $scope[list.iterator + 'SearchFieldLabel'] = list.fields.name.label;
- }
-
- $scope.search(list.iterator);
-
- $scope.addJobTemplate = function () {
- $state.transitionTo('jobTemplates.add');
- };
-
- $scope.editJobTemplate = function (id) {
- $state.transitionTo('jobTemplates.edit', {template_id: id});
- };
-
- $scope.deleteJobTemplate = function (id, name) {
- var action = function () {
- $('#prompt-modal').modal('hide');
- Wait('start');
- var url = defaultUrl + id + '/';
- Rest.setUrl(url);
- Rest.destroy()
- .success(function () {
- $scope.search(list.iterator);
- })
- .error(function (data) {
- Wait('stop');
- ProcessErrors($scope, data, status, null, { hdr: 'Error!',
- msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
- });
- };
-
- Prompt({
- hdr: 'Delete',
- body: '
Are you sure you want to delete the job template below?
' + name + '
',
- action: action,
- actionText: 'DELETE'
- });
- };
-
- $scope.copyJobTemplate = function(id, name){
- var element,
- buttons = [{
- "label": "Cancel",
- "onClick": function() {
- $(this).dialog('close');
- },
- "icon": "fa-times",
- "class": "btn btn-default",
- "id": "copy-close-button"
- },{
- "label": "Copy",
- "onClick": function() {
- copyAction();
- // setTimeout(function(){
- // scope.$apply(function(){
- // if(mode==='survey-taker'){
- // scope.$emit('SurveyTakerCompleted');
- // } else{
- // scope.saveSurvey();
- // }
- // });
- // });
- },
- "icon": "fa-copy",
- "class": "btn btn-primary",
- "id": "job-copy-button"
- }],
- copyAction = function () {
- // retrieve the copy of the job template object from the api, then overwrite the name and throw away the id
- Wait('start');
- var url = defaultUrl + id + '/';
- Rest.setUrl(url);
- Rest.get()
- .success(function (data) {
- data.name = $scope.new_copy_name;
- delete data.id;
- $scope.$emit('GoToCopy', data);
- })
- .error(function (data) {
- Wait('stop');
- ProcessErrors($scope, data, status, null, { hdr: 'Error!',
- msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
- });
- };
-
-
- CreateDialog({
- id: 'copy-job-modal',
- title: "Copy",
- scope: $scope,
- buttons: buttons,
- width: 500,
- height: 300,
- minWidth: 200,
- callback: 'CopyDialogReady'
- });
-
- $('#job_name').text(name);
- $('#copy-job-modal').show();
-
-
- if ($scope.removeCopyDialogReady) {
- $scope.removeCopyDialogReady();
- }
- $scope.removeCopyDialogReady = $scope.$on('CopyDialogReady', function() {
- //clear any old remaining text
- $scope.new_copy_name = "" ;
- $scope.copy_form.$setPristine();
- $('#copy-job-modal').dialog('open');
- $('#job-copy-button').attr('ng-disabled', "!copy_form.$valid");
- element = angular.element(document.getElementById('job-copy-button'));
- $compile(element)($scope);
-
- });
-
- if ($scope.removeGoToCopy) {
- $scope.removeGoToCopy();
- }
- $scope.removeGoToCopy = $scope.$on('GoToCopy', function(e, data) {
- var url = defaultUrl,
- old_survey_url = (data.related.survey_spec) ? data.related.survey_spec : "" ;
- Rest.setUrl(url);
- Rest.post(data)
- .success(function (data) {
- if(data.survey_enabled===true){
- $scope.$emit("CopySurvey", data, old_survey_url);
- }
- else {
- $('#copy-job-modal').dialog('close');
- Wait('stop');
- $location.path($location.path() + '/' + data.id);
- }
-
- })
- .error(function (data) {
- Wait('stop');
- ProcessErrors($scope, data, status, null, { hdr: 'Error!',
- msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
- });
- });
-
- if ($scope.removeCopySurvey) {
- $scope.removeCopySurvey();
- }
- $scope.removeCopySurvey = $scope.$on('CopySurvey', function(e, new_data, old_url) {
- // var url = data.related.survey_spec;
- Rest.setUrl(old_url);
- Rest.get()
- .success(function (survey_data) {
-
- Rest.setUrl(new_data.related.survey_spec);
- Rest.post(survey_data)
- .success(function () {
- $('#copy-job-modal').dialog('close');
- Wait('stop');
- $location.path($location.path() + '/' + new_data.id);
- })
- .error(function (data) {
- Wait('stop');
- ProcessErrors($scope, data, status, null, { hdr: 'Error!',
- msg: 'Call to ' + new_data.related.survey_spec + ' failed. DELETE returned status: ' + status });
- });
-
-
- })
- .error(function (data) {
- Wait('stop');
- ProcessErrors($scope, data, status, null, { hdr: 'Error!',
- msg: 'Call to ' + old_url + ' failed. DELETE returned status: ' + status });
- });
-
- });
-
- };
-
- $scope.submitJob = function (id) {
- PlaybookRun({ scope: $scope, id: id });
- };
-
- $scope.scheduleJob = function (id) {
- $state.go('jobTemplateSchedules', {id: id});
- };
-}
-
-JobTemplatesList.$inject = ['$scope', '$rootScope', '$location', '$log',
- '$stateParams', 'Rest', 'Alert', 'JobTemplateList', 'generateList',
- 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
- 'ProcessErrors', 'GetBasePath', 'JobTemplateForm', 'CredentialList',
- 'LookUpInit', 'PlaybookRun', 'Wait', 'CreateDialog' , '$compile',
- '$state'
-];
-
-export function JobTemplatesAdd(Refresh, $filter, $scope, $rootScope, $compile,
- $location, $log, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
- ProcessErrors, ReturnToCaller, ClearScope, GetBasePath, InventoryList,
- CredentialList, ProjectList, LookUpInit, md5Setup, ParseTypeChange, Wait,
- Empty, ToJSON, CallbackHelpInit, SurveyControllerInit, Prompt, GetChoices,
- $state, CreateSelect2) {
-
- ClearScope();
-
- // Inject dynamic view
- var defaultUrl = GetBasePath('job_templates'),
- form = JobTemplateForm(),
- generator = GenerateForm,
- master = {},
- CloudCredentialList = {},
- selectPlaybook, checkSCMStatus,
- callback,
- base = $location.path().replace(/^\//, '').split('/')[0],
- context = (base === 'job_templates') ? 'job_template' : 'inv';
-
- CallbackHelpInit({ scope: $scope });
- $scope.can_edit = true;
- generator.inject(form, { mode: 'add', related: false, scope: $scope });
-
- callback = function() {
- // Make sure the form controller knows there was a change
- $scope[form.name + '_form'].$setDirty();
- };
- $scope.mode = "add";
- $scope.parseType = 'yaml';
- ParseTypeChange({ scope: $scope, field_id: 'job_templates_variables', onChange: callback });
-
- $scope.playbook_options = [];
- $scope.allow_callbacks = false;
-
- generator.reset();
-
- md5Setup({
- scope: $scope,
- master: master,
- check_field: 'allow_callbacks',
- default_val: false
- });
-
- LookUpInit({
- scope: $scope,
- form: form,
- current_item: ($stateParams.inventory_id !== undefined) ? $stateParams.inventory_id : null,
- list: InventoryList,
- field: 'inventory',
- input_type: "radio"
- });
-
-
- // Clone the CredentialList object for use with cloud_credential. Cloning
- // and changing properties to avoid collision.
- jQuery.extend(true, CloudCredentialList, CredentialList);
- CloudCredentialList.name = 'cloudcredentials';
- CloudCredentialList.iterator = 'cloudcredential';
-
- SurveyControllerInit({
- scope: $scope,
- parent_scope: $scope
- });
-
- if ($scope.removeLookUpInitialize) {
- $scope.removeLookUpInitialize();
- }
- $scope.removeLookUpInitialize = $scope.$on('lookUpInitialize', function () {
- LookUpInit({
- url: GetBasePath('credentials') + '?cloud=true',
- scope: $scope,
- form: form,
- current_item: null,
- list: CloudCredentialList,
- field: 'cloud_credential',
- hdr: 'Select Cloud Credential',
- input_type: 'radio'
- });
-
- LookUpInit({
- url: GetBasePath('credentials') + '?kind=ssh',
- scope: $scope,
- form: form,
- current_item: null,
- list: CredentialList,
- field: 'credential',
- hdr: 'Select Machine Credential',
- input_type: "radio"
- });
- });
-
- var selectCount = 0;
-
- if ($scope.removeChoicesReady) {
- $scope.removeChoicesReady();
- }
- $scope.removeChoicesReady = $scope.$on('choicesReadyVerbosity', function () {
- selectCount++;
- if (selectCount === 2) {
- var verbosity;
- // this sets the default options for the selects as specified by the controller.
- for (verbosity in $scope.verbosity_options) {
- if ($scope.verbosity_options[verbosity].isDefault) {
- $scope.verbosity = $scope.verbosity_options[verbosity];
- }
- }
- $scope.job_type = $scope.job_type_options[$scope.job_type_field.default];
-
- // if you're getting to the form from the scan job section on inventories,
- // set the job type select to be scan
- if ($stateParams.inventory_id) {
- // This means that the job template form was accessed via inventory prop's
- // This also means the job is a scan job.
- $scope.job_type.value = 'scan';
- $scope.jobTypeChange();
- $scope.inventory = $stateParams.inventory_id;
- Rest.setUrl(GetBasePath('inventory') + $stateParams.inventory_id + '/');
- Rest.get()
- .success(function (data) {
- $scope.inventory_name = data.name;
- })
- .error(function (data, status) {
- ProcessErrors($scope, data, status, form, { hdr: 'Error!',
- msg: 'Failed to lookup inventory: ' + data.id + '. GET returned status: ' + status });
- });
- }
- CreateSelect2({
- element:'#job_templates_job_type',
- multiple: false
- });
-
- CreateSelect2({
- element:'#playbook-select',
- multiple: false
- });
-
- CreateSelect2({
- element:'#job_templates_verbosity',
- multiple: false
- });
-
- $scope.$emit('lookUpInitialize');
- }
- });
-
- // setup verbosity options select
- GetChoices({
- scope: $scope,
- url: defaultUrl,
- field: 'verbosity',
- variable: 'verbosity_options',
- callback: 'choicesReadyVerbosity'
- });
-
- // setup job type options select
- GetChoices({
- scope: $scope,
- url: defaultUrl,
- field: 'job_type',
- variable: 'job_type_options',
- callback: 'choicesReadyVerbosity'
- });
-
- // Update playbook select whenever project value changes
- selectPlaybook = function (oldValue, newValue) {
- var url;
- if($scope.job_type.value === 'scan' && $scope.project_name === "Default"){
- $scope.playbook_options = ['Default'];
- $scope.playbook = 'Default';
- Wait('stop');
- }
- else if (oldValue !== newValue) {
- if ($scope.project) {
- Wait('start');
- url = GetBasePath('projects') + $scope.project + '/playbooks/';
- Rest.setUrl(url);
- Rest.get()
- .success(function (data) {
- var i, opts = [];
- for (i = 0; i < data.length; i++) {
- opts.push(data[i]);
- }
- $scope.playbook_options = opts;
- Wait('stop');
- })
- .error(function (data, status) {
- ProcessErrors($scope, data, status, form, { hdr: 'Error!',
- msg: 'Failed to get playbook list for ' + url + '. GET returned status: ' + status });
- });
- }
- }
- };
-
- $scope.jobTypeChange = function(){
- if($scope.job_type){
- if($scope.job_type.value === 'scan'){
- $scope.toggleScanInfo();
- }
- else if($scope.project_name === "Default"){
- $scope.project_name = null;
- $scope.playbook_options = [];
- // $scope.playbook = 'null';
- $scope.job_templates_form.playbook.$setPristine();
- }
- }
- };
-
- $scope.toggleScanInfo = function() {
- $scope.project_name = 'Default';
- if($scope.project === null){
- selectPlaybook();
- }
- else {
- $scope.project = null;
- }
- };
-
- // Detect and alert user to potential SCM status issues
- checkSCMStatus = function (oldValue, newValue) {
- if (oldValue !== newValue && !Empty($scope.project)) {
- Rest.setUrl(GetBasePath('projects') + $scope.project + '/');
- Rest.get()
- .success(function (data) {
- var msg;
- switch (data.status) {
- case 'failed':
- msg = "The selected project has a failed status. Review the project's SCM settings" +
- " and run an update before adding it to a template.";
- break;
- case 'never updated':
- msg = 'The selected project has a never updated status. You will need to run a successful' +
- ' update in order to selected a playbook. Without a valid playbook you will not be able ' +
- ' to save this template.';
- break;
- case 'missing':
- msg = 'The selected project has a status of missing. Please check the server and make sure ' +
- ' the directory exists and file permissions are set correctly.';
- break;
- }
- if (msg) {
- Alert('Warning', msg, 'alert-info', null, null, null, null, true);
- }
- })
- .error(function (data, status) {
- ProcessErrors($scope, data, status, form, { hdr: 'Error!',
- msg: 'Failed to get project ' + $scope.project + '. GET returned status: ' + status });
- });
- }
- };
-
-
- // $scope.selectPlaybookUnregister = $scope.$watch('project_name', function (newval, oldval) {
- // selectPlaybook(oldval, newval);
- // checkSCMStatus(oldval, newval);
- // });
-
- // Register a watcher on project_name
- if ($scope.selectPlaybookUnregister) {
- $scope.selectPlaybookUnregister();
- }
- $scope.selectPlaybookUnregister = $scope.$watch('project', function (newValue, oldValue) {
- if (newValue !== oldValue) {
- selectPlaybook(oldValue, newValue);
- checkSCMStatus();
- }
- });
-
- LookUpInit({
- scope: $scope,
- form: form,
- current_item: null,
- list: ProjectList,
- field: 'project',
- input_type: "radio",
- autopopulateLookup: (context === 'inv') ? false : true
- });
-
- if ($scope.removeSurveySaved) {
- $scope.rmoveSurveySaved();
- }
- $scope.removeSurveySaved = $scope.$on('SurveySaved', function() {
- Wait('stop');
- $scope.survey_exists = true;
- $scope.invalid_survey = false;
- $('#job_templates_survey_enabled_chbox').attr('checked', true);
- $('#job_templates_delete_survey_btn').show();
- $('#job_templates_edit_survey_btn').show();
- $('#job_templates_create_survey_btn').hide();
-
- });
-
-
- function saveCompleted() {
- setTimeout(function() {
- $scope.$apply(function() {
- var base = $location.path().replace(/^\//, '').split('/')[0];
- if (base === 'job_templates') {
- ReturnToCaller();
- }
- else {
- ReturnToCaller(1);
- }
- });
- }, 500);
- }
-
- if ($scope.removeTemplateSaveSuccess) {
- $scope.removeTemplateSaveSuccess();
- }
- $scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) {
- Wait('stop');
- if (data.related && data.related.callback) {
- Alert('Callback URL', 'Host callbacks are enabled for this template. The callback URL is:
'+
- '' + $scope.callback_server_path + data.related.callback + '
'+
- 'The host configuration key is: ' + $filter('sanitize')(data.host_config_key) + '
', 'alert-info', saveCompleted, null, null, null, true);
- }
- else {
- saveCompleted();
- }
- });
-
- // Save
- $scope.formSave = function () {
- $scope.invalid_survey = false;
- if ($scope.removeGatherFormFields) {
- $scope.removeGatherFormFields();
- }
- $scope.removeGatherFormFields = $scope.$on('GatherFormFields', function(e, data) {
- generator.clearApiErrors();
- Wait('start');
- data = {};
- var fld;
- try {
- for (fld in form.fields) {
- if (form.fields[fld].type === 'select' && fld !== 'playbook') {
- data[fld] = $scope[fld].value;
- } else {
- if (fld !== 'variables') {
- data[fld] = $scope[fld];
- }
- }
- }
- data.extra_vars = ToJSON($scope.parseType, $scope.variables, true);
- if(data.job_type === 'scan' && $scope.default_scan === true){
- data.project = "";
- data.playbook = "";
- }
- Rest.setUrl(defaultUrl);
- Rest.post(data)
- .success(function(data) {
- $scope.$emit('templateSaveSuccess', data);
-
- $scope.addedItem = data.id;
-
- Refresh({
- scope: $scope,
- set: 'job_templates',
- iterator: 'job_template',
- url: $scope.current_url
- });
-
- if(data.survey_enabled===true){
- //once the job template information is saved we submit the survey info to the correct endpoint
- var url = data.url+ 'survey_spec/';
- Rest.setUrl(url);
- Rest.post({ name: $scope.survey_name, description: $scope.survey_description, spec: $scope.survey_questions })
- .success(function () {
- Wait('stop');
-
- })
- .error(function (data, status) {
- ProcessErrors($scope, data, status, form, { hdr: 'Error!',
- msg: 'Failed to add new survey. Post returned status: ' + status });
- });
- }
-
-
- })
- .error(function (data, status) {
- ProcessErrors($scope, data, status, form, { hdr: 'Error!',
- msg: 'Failed to add new job template. POST returned status: ' + status
- });
- });
-
- } catch (err) {
- Wait('stop');
- Alert("Error", "Error parsing extra variables. Parser returned: " + err);
- }
- });
-
-
- if ($scope.removePromptForSurvey) {
- $scope.removePromptForSurvey();
- }
- $scope.removePromptForSurvey = $scope.$on('PromptForSurvey', function() {
- var action = function () {
- // $scope.$emit("GatherFormFields");
- Wait('start');
- $('#prompt-modal').modal('hide');
- $scope.addSurvey();
-
- };
- Prompt({
- hdr: 'Incomplete Survey',
- body: 'Do you want to create a survey before proceeding?
',
- action: action
- });
- });
-
- // users can't save a survey with a scan job
- if($scope.job_type.value === "scan" && $scope.survey_enabled === true){
- $scope.survey_enabled = false;
- }
- if($scope.survey_enabled === true && $scope.survey_exists!==true){
- // $scope.$emit("PromptForSurvey");
-
- // The original design for this was a pop up that would prompt the user if they wanted to create a
- // survey, because they had enabled one but not created it yet. We switched this for now so that
- // an error message would be displayed by the survey buttons that tells the user to add a survey or disabled
- // surveys.
- $scope.invalid_survey = true;
- return;
- } else {
- $scope.$emit("GatherFormFields");
- }
-
-
- };
-
- $scope.formCancel = function () {
- $state.transitionTo('jobTemplates');
- };
-}
-
-JobTemplatesAdd.$inject = ['Refresh', '$filter', '$scope', '$rootScope', '$compile',
- '$location', '$log', '$stateParams', 'JobTemplateForm', 'GenerateForm',
- 'Rest', 'Alert', 'ProcessErrors', 'ReturnToCaller', 'ClearScope',
- 'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList',
- 'LookUpInit', 'md5Setup', 'ParseTypeChange', 'Wait', 'Empty', 'ToJSON',
- 'CallbackHelpInit', 'initSurvey', 'Prompt', 'GetChoices', '$state',
- 'CreateSelect2'
-];
-
-
-export function JobTemplatesEdit($filter, $scope, $rootScope, $compile,
- $location, $log, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
- ProcessErrors, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller,
- ClearScope, InventoryList, CredentialList, ProjectList, LookUpInit,
- GetBasePath, md5Setup, ParseTypeChange, JobStatusToolTip, FormatDate, Wait,
- Empty, Prompt, ParseVariableString, ToJSON, SchedulesControllerInit,
- JobsControllerInit, JobsListUpdate, GetChoices, SchedulesListInit,
- SchedulesList, CallbackHelpInit, PlaybookRun, SurveyControllerInit, $state,
- CreateSelect2){
-
- ClearScope();
-
- var defaultUrl = GetBasePath('job_templates'),
- generator = GenerateForm,
- form = JobTemplateForm(),
- base = $location.path().replace(/^\//, '').split('/')[0],
- master = {},
- id = $stateParams.template_id,
- relatedSets = {},
- checkSCMStatus, getPlaybooks, callback,
- choicesCount = 0;
-
-
- CallbackHelpInit({ scope: $scope });
-
- SchedulesList.well = false;
- generator.inject(form, { mode: 'edit', related: true, scope: $scope });
- $scope.mode = 'edit';
- $scope.parseType = 'yaml';
- $scope.showJobType = false;
-
- SurveyControllerInit({
- scope: $scope,
- parent_scope: $scope,
- id: id
- });
-
- callback = function() {
- // Make sure the form controller knows there was a change
- $scope[form.name + '_form'].$setDirty();
- };
-
- $scope.playbook_options = null;
- $scope.playbook = null;
- generator.reset();
-
- getPlaybooks = function (project) {
- var url;
- if($scope.job_type.value === 'scan' && $scope.project_name === "Default"){
- $scope.playbook_options = ['Default'];
- $scope.playbook = 'Default';
- Wait('stop');
- }
- else if (!Empty(project)) {
- url = GetBasePath('projects') + project + '/playbooks/';
- Wait('start');
- Rest.setUrl(url);
- Rest.get()
- .success(function (data) {
- var i;
- $scope.playbook_options = [];
- for (i = 0; i < data.length; i++) {
- $scope.playbook_options.push(data[i]);
- if (data[i] === $scope.playbook) {
- $scope.job_templates_form.playbook.$setValidity('required', true);
- }
- }
- if ($scope.playbook) {
- $scope.$emit('jobTemplateLoadFinished');
- } else {
- Wait('stop');
- }
- })
- .error(function () {
- Wait('stop');
- Alert('Missing Playbooks', 'Unable to retrieve the list of playbooks for this project. Choose a different ' +
- ' project or make the playbooks available on the file system.', 'alert-info');
- });
- }
- else {
- Wait('stop');
- }
- };
-
- $scope.jobTypeChange = function(){
- if($scope.job_type){
- if($scope.job_type.value === 'scan'){
- $scope.toggleScanInfo();
- }
- else if($scope.project_name === "Default"){
- $scope.project_name = null;
- $scope.playbook_options = [];
- // $scope.playbook = 'null';
- $scope.job_templates_form.playbook.$setPristine();
- }
-
- }
- };
-
- $scope.toggleScanInfo = function() {
- $scope.project_name = 'Default';
- if($scope.project === null){
- getPlaybooks();
- }
- else {
- $scope.project = null;
- }
- };
-
- // Detect and alert user to potential SCM status issues
- checkSCMStatus = function () {
- if (!Empty($scope.project)) {
- Wait('start');
- Rest.setUrl(GetBasePath('projects') + $scope.project + '/');
- Rest.get()
- .success(function (data) {
- var msg;
- switch (data.status) {
- case 'failed':
- msg = "The selected project has a failed status. Review the project's SCM settings" +
- " and run an update before adding it to a template.";
- break;
- case 'never updated':
- msg = 'The selected project has a never updated status. You will need to run a successful' +
- ' update in order to selected a playbook. Without a valid playbook you will not be able ' +
- ' to save this template.';
- break;
- case 'missing':
- msg = 'The selected project has a status of missing. Please check the server and make sure ' +
- ' the directory exists and file permissions are set correctly.';
- break;
- }
- Wait('stop');
- if (msg) {
- Alert('Warning', msg, 'alert-info', null, null, null, null, true);
- }
- })
- .error(function (data, status) {
- ProcessErrors($scope, data, status, form, { hdr: 'Error!', msg: 'Failed to get project ' + $scope.project +
- '. GET returned status: ' + status });
- });
- }
- };
-
- if ($scope.removerelatedschedules) {
- $scope.removerelatedschedules();
- }
- $scope.removerelatedschedules = $scope.$on('relatedschedules', function() {
- SchedulesListInit({
- scope: $scope,
- list: SchedulesList,
- choices: null,
- related: true
- });
- });
-
- // Register a watcher on project_name. Refresh the playbook list on change.
- if ($scope.watchProjectUnregister) {
- $scope.watchProjectUnregister();
- }
- $scope.watchProjectUnregister = $scope.$watch('project', function (newValue, oldValue) {
- if (newValue !== oldValue) {
- getPlaybooks($scope.project);
- checkSCMStatus();
- }
- });
-
-
-
- // Turn off 'Wait' after both cloud credential and playbook list come back
- if ($scope.removeJobTemplateLoadFinished) {
- $scope.removeJobTemplateLoadFinished();
- }
- $scope.removeJobTemplateLoadFinished = $scope.$on('jobTemplateLoadFinished', function () {
- CreateSelect2({
- element:'#job_templates_job_type',
- multiple: false
- });
-
- CreateSelect2({
- element:'#playbook-select',
- multiple: false
- });
-
- CreateSelect2({
- element:'#job_templates_verbosity',
- multiple: false
- });
-
- for (var set in relatedSets) {
- $scope.search(relatedSets[set].iterator);
- }
- SchedulesControllerInit({
- scope: $scope,
- parent_scope: $scope,
- iterator: 'schedule'
- });
-
- });
-
- // Set the status/badge for each related job
- if ($scope.removeRelatedCompletedJobs) {
- $scope.removeRelatedCompletedJobs();
- }
- $scope.removeRelatedCompletedJobs = $scope.$on('relatedcompleted_jobs', function () {
- JobsControllerInit({
- scope: $scope,
- parent_scope: $scope,
- iterator: form.related.completed_jobs.iterator
- });
- JobsListUpdate({
- scope: $scope,
- parent_scope: $scope,
- list: form.related.completed_jobs
- });
- });
-
- if ($scope.cloudCredentialReadyRemove) {
- $scope.cloudCredentialReadyRemove();
- }
- $scope.cloudCredentialReadyRemove = $scope.$on('cloudCredentialReady', function (e, name) {
- var CloudCredentialList = {};
- $scope.cloud_credential_name = name;
- master.cloud_credential_name = name;
- // Clone the CredentialList object for use with cloud_credential. Cloning
- // and changing properties to avoid collision.
- jQuery.extend(true, CloudCredentialList, CredentialList);
- CloudCredentialList.name = 'cloudcredentials';
- CloudCredentialList.iterator = 'cloudcredential';
- LookUpInit({
- url: GetBasePath('credentials') + '?cloud=true',
- scope: $scope,
- form: form,
- current_item: $scope.cloud_credential,
- list: CloudCredentialList,
- field: 'cloud_credential',
- hdr: 'Select Cloud Credential',
- input_type: "radio"
- });
- $scope.$emit('jobTemplateLoadFinished');
- });
-
-
- // Retrieve each related set and populate the playbook list
- if ($scope.jobTemplateLoadedRemove) {
- $scope.jobTemplateLoadedRemove();
- }
- $scope.jobTemplateLoadedRemove = $scope.$on('jobTemplateLoaded', function (e, related_cloud_credential, masterObject, relatedSets) {
- var dft, set;
- master = masterObject;
- getPlaybooks($scope.project);
-
- for (set in relatedSets) {
- $scope.search(relatedSets[set].iterator);
- }
-
- dft = ($scope.host_config_key === "" || $scope.host_config_key === null) ? false : true;
- md5Setup({
- scope: $scope,
- master: master,
- check_field: 'allow_callbacks',
- default_val: dft
- });
-
- ParseTypeChange({ scope: $scope, field_id: 'job_templates_variables', onChange: callback });
-
- if (related_cloud_credential) {
- Rest.setUrl(related_cloud_credential);
- Rest.get()
- .success(function (data) {
- $scope.$emit('cloudCredentialReady', data.name);
- })
- .error(function (data, status) {
- ProcessErrors($scope, data, status, null, {hdr: 'Error!',
- msg: 'Failed to related cloud credential. GET returned status: ' + status });
- });
- } else {
- // No existing cloud credential
- $scope.$emit('cloudCredentialReady', null);
- }
- });
-
- Wait('start');
-
- if ($scope.removeEnableSurvey) {
- $scope.removeEnableSurvey();
- }
- $scope.removeEnableSurvey = $scope.$on('EnableSurvey', function(fld) {
-
- $('#job_templates_survey_enabled_chbox').attr('checked', $scope[fld]);
- Rest.setUrl(defaultUrl + id+ '/survey_spec/');
- Rest.get()
- .success(function (data) {
- if(!data || !data.name){
- $('#job_templates_delete_survey_btn').hide();
- $('#job_templates_edit_survey_btn').hide();
- $('#job_templates_create_survey_btn').show();
- }
- else {
- $scope.survey_exists = true;
- $('#job_templates_delete_survey_btn').show();
- $('#job_templates_edit_survey_btn').show();
- $('#job_templates_create_survey_btn').hide();
- }
- })
- .error(function (data, status) {
- ProcessErrors($scope, data, status, form, {
- hdr: 'Error!',
- msg: 'Failed to retrieve job template: ' + $stateParams.template_id + '. GET status: ' + status
- });
- });
- });
-
- if ($scope.removeSurveySaved) {
- $scope.rmoveSurveySaved();
- }
- $scope.removeSurveySaved = $scope.$on('SurveySaved', function() {
- Wait('stop');
- $scope.survey_exists = true;
- $scope.invalid_survey = false;
- $('#job_templates_survey_enabled_chbox').attr('checked', true);
- $('#job_templates_delete_survey_btn').show();
- $('#job_templates_edit_survey_btn').show();
- $('#job_templates_create_survey_btn').hide();
-
- });
-
- if ($scope.removeLoadJobs) {
- $scope.rmoveLoadJobs();
- }
- $scope.removeLoadJobs = $scope.$on('LoadJobs', function() {
- $scope.fillJobTemplate();
- });
-
- if ($scope.removeChoicesReady) {
- $scope.removeChoicesReady();
- }
- $scope.removeChoicesReady = $scope.$on('choicesReady', function() {
- choicesCount++;
- if (choicesCount === 4) {
- $scope.$emit('LoadJobs');
- }
- });
-
- GetChoices({
- scope: $scope,
- url: GetBasePath('unified_jobs'),
- field: 'status',
- variable: 'status_choices',
- callback: 'choicesReady'
- });
-
- GetChoices({
- scope: $scope,
- url: GetBasePath('unified_jobs'),
- field: 'type',
- variable: 'type_choices',
- callback: 'choicesReady'
- });
-
- // setup verbosity options lookup
- GetChoices({
- scope: $scope,
- url: defaultUrl,
- field: 'verbosity',
- variable: 'verbosity_options',
- callback: 'choicesReady'
- });
-
- // setup job type options lookup
- GetChoices({
- scope: $scope,
- url: defaultUrl,
- field: 'job_type',
- variable: 'job_type_options',
- callback: 'choicesReady'
- });
-
- function saveCompleted() {
- setTimeout(function() {
- $scope.$apply(function() {
- var base = $location.path().replace(/^\//, '').split('/')[0];
- if (base === 'job_templates') {
- ReturnToCaller();
- }
- else {
- ReturnToCaller(1);
- }
- });
- }, 500);
- }
-
- if ($scope.removeTemplateSaveSuccess) {
- $scope.removeTemplateSaveSuccess();
- }
- $scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) {
- Wait('stop');
- if ($scope.allow_callbacks && ($scope.host_config_key !== master.host_config_key || $scope.callback_url !== master.callback_url)) {
- if (data.related && data.related.callback) {
- Alert('Callback URL', 'Host callbacks are enabled for this template. The callback URL is:
'+
- '' + $scope.callback_server_path + data.related.callback + '
'+
- 'The host configuration key is: ' + $filter('sanitize')(data.host_config_key) + '
', 'alert-info', saveCompleted, null, null, null, true);
- }
- else {
- saveCompleted();
- }
- }
- else {
- saveCompleted();
- }
- });
-
-
-
- // Save changes to the parent
- $scope.formSave = function () {
- $scope.invalid_survey = false;
- if ($scope.removeGatherFormFields) {
- $scope.removeGatherFormFields();
- }
- $scope.removeGatherFormFields = $scope.$on('GatherFormFields', function(e, data) {
- generator.clearApiErrors();
- Wait('start');
- data = {};
- var fld;
- try {
- // Make sure we have valid variable data
- data.extra_vars = ToJSON($scope.parseType, $scope.variables, true);
- if(data.extra_vars === undefined ){
- throw 'undefined variables';
- }
- for (fld in form.fields) {
- if (form.fields[fld].type === 'select' && fld !== 'playbook') {
- data[fld] = $scope[fld].value;
- } else {
- if (fld !== 'variables' && fld !== 'callback_url') {
- data[fld] = $scope[fld];
- }
- }
- }
- Rest.setUrl(defaultUrl + id + '/');
- Rest.put(data)
- .success(function (data) {
- $scope.$emit('templateSaveSuccess', data);
- })
- .error(function (data, status) {
- ProcessErrors($scope, data, status, form, { hdr: 'Error!',
- msg: 'Failed to update job template. PUT returned status: ' + status });
- });
-
- } catch (err) {
- Wait('stop');
- Alert("Error", "Error parsing extra variables. Parser returned: " + err);
- }
- });
-
-
- if ($scope.removePromptForSurvey) {
- $scope.removePromptForSurvey();
- }
- $scope.removePromptForSurvey = $scope.$on('PromptForSurvey', function() {
- var action = function () {
- // $scope.$emit("GatherFormFields");
- Wait('start');
- $('#prompt-modal').modal('hide');
- $scope.addSurvey();
-
- };
- Prompt({
- hdr: 'Incomplete Survey',
- body: 'Do you want to create a survey before proceeding?
',
- action: action
- });
- });
-
- // users can't save a survey with a scan job
- if($scope.job_type.value === "scan" && $scope.survey_enabled === true){
- $scope.survey_enabled = false;
- }
- if($scope.survey_enabled === true && $scope.survey_exists!==true){
- // $scope.$emit("PromptForSurvey");
-
- // The original design for this was a pop up that would prompt the user if they wanted to create a
- // survey, because they had enabled one but not created it yet. We switched this for now so that
- // an error message would be displayed by the survey buttons that tells the user to add a survey or disabled
- // surveys.
- $scope.invalid_survey = true;
- return;
- } else {
- $scope.$emit("GatherFormFields");
- }
-
- };
-
- $scope.formCancel = function () {
- $state.transitionTo('jobTemplates');
- };
-
- // Related set: Add button
- $scope.add = function (set) {
- $rootScope.flashMessage = null;
- $location.path('/' + base + '/' + $stateParams.template_id + '/' + set);
- };
-
- // Related set: Edit button
- $scope.edit = function (set, id) {
- $rootScope.flashMessage = null;
- $location.path('/' + set + '/' + id);
- };
-
- // Launch a job using the selected template
- $scope.launch = function() {
-
- if ($scope.removePromptForSurvey) {
- $scope.removePromptForSurvey();
- }
- $scope.removePromptForSurvey = $scope.$on('PromptForSurvey', function() {
- var action = function () {
- // $scope.$emit("GatherFormFields");
- Wait('start');
- $('#prompt-modal').modal('hide');
- $scope.addSurvey();
-
- };
- Prompt({
- hdr: 'Incomplete Survey',
- body: 'Do you want to create a survey before proceeding?
',
- action: action
- });
- });
- if($scope.survey_enabled === true && $scope.survey_exists!==true){
- $scope.$emit("PromptForSurvey");
- }
- else {
-
- PlaybookRun({
- scope: $scope,
- id: id
- });
- }
- };
-
- // handler for 'Enable Survey' button
- $scope.surveyEnabled = function(){
- Rest.setUrl(defaultUrl + id+ '/');
- Rest.patch({"survey_enabled": $scope.survey_enabled})
- .success(function (data) {
-
- if(Empty(data.summary_fields.survey)){
- $('#job_templates_delete_survey_btn').hide();
- $('#job_templates_edit_survey_btn').hide();
- $('#job_templates_create_survey_btn').show();
- }
- else{
- $scope.survey_exists = true;
- $('#job_templates_delete_survey_btn').show();
- $('#job_templates_edit_survey_btn').show();
- $('#job_templates_create_survey_btn').hide();
- }
- })
- .error(function (data, status) {
- ProcessErrors($scope, data, status, form, {
- hdr: 'Error!',
- msg: 'Failed to retrieve save survey_enabled: ' + $stateParams.template_id + '. GET status: ' + status
- });
- });
- };
-
-
-}
-
-JobTemplatesEdit.$inject = ['$filter', '$scope', '$rootScope', '$compile',
- '$location', '$log', '$stateParams', 'JobTemplateForm', 'GenerateForm',
- 'Rest', 'Alert', 'ProcessErrors', 'RelatedSearchInit',
- 'RelatedPaginateInit','ReturnToCaller', 'ClearScope', 'InventoryList',
- 'CredentialList', 'ProjectList', 'LookUpInit', 'GetBasePath', 'md5Setup',
- 'ParseTypeChange', 'JobStatusToolTip', 'FormatDate', 'Wait',
- 'Empty', 'Prompt', 'ParseVariableString', 'ToJSON',
- 'SchedulesControllerInit', 'JobsControllerInit', 'JobsListUpdate',
- 'GetChoices', 'SchedulesListInit', 'SchedulesList', 'CallbackHelpInit',
- 'PlaybookRun' , 'initSurvey', '$state', 'CreateSelect2'
-];
diff --git a/awx/ui/client/src/job-templates/add/inventory-job-templates-add.route.js b/awx/ui/client/src/job-templates/add/inventory-job-templates-add.route.js
new file mode 100644
index 0000000000..c273fcbbf3
--- /dev/null
+++ b/awx/ui/client/src/job-templates/add/inventory-job-templates-add.route.js
@@ -0,0 +1,19 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+
+export default {
+ name: 'inventoryJobTemplateAdd',
+ url: '/inventories/:inventory_id/job_templates/add',
+ templateUrl: templateUrl('job-templates/add/job-templates-add'),
+ controller: 'JobTemplatesAdd',
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/job-templates/add/job-templates-add.controller.js b/awx/ui/client/src/job-templates/add/job-templates-add.controller.js
new file mode 100644
index 0000000000..1c7000d399
--- /dev/null
+++ b/awx/ui/client/src/job-templates/add/job-templates-add.controller.js
@@ -0,0 +1,452 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+ export default
+ [ 'Refresh', '$filter', '$scope', '$rootScope', '$compile',
+ '$location', '$log', '$stateParams', 'JobTemplateForm', 'GenerateForm',
+ 'Rest', 'Alert', 'ProcessErrors', 'ReturnToCaller', 'ClearScope',
+ 'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList',
+ 'LookUpInit', 'md5Setup', 'ParseTypeChange', 'Wait', 'Empty', 'ToJSON',
+ 'CallbackHelpInit', 'initSurvey', 'Prompt', 'GetChoices', '$state',
+ 'CreateSelect2',
+ function(
+ Refresh, $filter, $scope, $rootScope, $compile,
+ $location, $log, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
+ ProcessErrors, ReturnToCaller, ClearScope, GetBasePath, InventoryList,
+ CredentialList, ProjectList, LookUpInit, md5Setup, ParseTypeChange, Wait,
+ Empty, ToJSON, CallbackHelpInit, SurveyControllerInit, Prompt, GetChoices,
+ $state, CreateSelect2
+ ) {
+
+ ClearScope();
+
+ // Inject dynamic view
+ var defaultUrl = GetBasePath('job_templates'),
+ form = JobTemplateForm(),
+ generator = GenerateForm,
+ master = {},
+ CloudCredentialList = {},
+ selectPlaybook, checkSCMStatus,
+ callback,
+ base = $location.path().replace(/^\//, '').split('/')[0],
+ context = (base === 'job_templates') ? 'job_template' : 'inv';
+
+ CallbackHelpInit({ scope: $scope });
+ $scope.can_edit = true;
+ generator.inject(form, { mode: 'add', related: false, scope: $scope });
+
+ callback = function() {
+ // Make sure the form controller knows there was a change
+ $scope[form.name + '_form'].$setDirty();
+ };
+ $scope.mode = "add";
+ $scope.parseType = 'yaml';
+ ParseTypeChange({ scope: $scope, field_id: 'job_templates_variables', onChange: callback });
+
+ $scope.playbook_options = [];
+ $scope.allow_callbacks = false;
+
+ generator.reset();
+
+ md5Setup({
+ scope: $scope,
+ master: master,
+ check_field: 'allow_callbacks',
+ default_val: false
+ });
+
+ LookUpInit({
+ scope: $scope,
+ form: form,
+ current_item: ($stateParams.inventory_id !== undefined) ? $stateParams.inventory_id : null,
+ list: InventoryList,
+ field: 'inventory',
+ input_type: "radio"
+ });
+
+
+ // Clone the CredentialList object for use with cloud_credential. Cloning
+ // and changing properties to avoid collision.
+ jQuery.extend(true, CloudCredentialList, CredentialList);
+ CloudCredentialList.name = 'cloudcredentials';
+ CloudCredentialList.iterator = 'cloudcredential';
+
+ SurveyControllerInit({
+ scope: $scope,
+ parent_scope: $scope
+ });
+
+ if ($scope.removeLookUpInitialize) {
+ $scope.removeLookUpInitialize();
+ }
+ $scope.removeLookUpInitialize = $scope.$on('lookUpInitialize', function () {
+ LookUpInit({
+ url: GetBasePath('credentials') + '?cloud=true',
+ scope: $scope,
+ form: form,
+ current_item: null,
+ list: CloudCredentialList,
+ field: 'cloud_credential',
+ hdr: 'Select Cloud Credential',
+ input_type: 'radio'
+ });
+
+ LookUpInit({
+ url: GetBasePath('credentials') + '?kind=ssh',
+ scope: $scope,
+ form: form,
+ current_item: null,
+ list: CredentialList,
+ field: 'credential',
+ hdr: 'Select Machine Credential',
+ input_type: "radio"
+ });
+ });
+
+ var selectCount = 0;
+
+ if ($scope.removeChoicesReady) {
+ $scope.removeChoicesReady();
+ }
+ $scope.removeChoicesReady = $scope.$on('choicesReadyVerbosity', function () {
+ selectCount++;
+ if (selectCount === 2) {
+ var verbosity;
+ // this sets the default options for the selects as specified by the controller.
+ for (verbosity in $scope.verbosity_options) {
+ if ($scope.verbosity_options[verbosity].isDefault) {
+ $scope.verbosity = $scope.verbosity_options[verbosity];
+ }
+ }
+ $scope.job_type = $scope.job_type_options[$scope.job_type_field.default];
+
+ // if you're getting to the form from the scan job section on inventories,
+ // set the job type select to be scan
+ if ($stateParams.inventory_id) {
+ // This means that the job template form was accessed via inventory prop's
+ // This also means the job is a scan job.
+ $scope.job_type.value = 'scan';
+ $scope.jobTypeChange();
+ $scope.inventory = $stateParams.inventory_id;
+ Rest.setUrl(GetBasePath('inventory') + $stateParams.inventory_id + '/');
+ Rest.get()
+ .success(function (data) {
+ $scope.inventory_name = data.name;
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, form, { hdr: 'Error!',
+ msg: 'Failed to lookup inventory: ' + data.id + '. GET returned status: ' + status });
+ });
+ }
+ CreateSelect2({
+ element:'#job_templates_job_type',
+ multiple: false
+ });
+
+ CreateSelect2({
+ element:'#playbook-select',
+ multiple: false
+ });
+
+ CreateSelect2({
+ element:'#job_templates_verbosity',
+ multiple: false
+ });
+
+ $scope.$emit('lookUpInitialize');
+ }
+ });
+
+ // setup verbosity options select
+ GetChoices({
+ scope: $scope,
+ url: defaultUrl,
+ field: 'verbosity',
+ variable: 'verbosity_options',
+ callback: 'choicesReadyVerbosity'
+ });
+
+ // setup job type options select
+ GetChoices({
+ scope: $scope,
+ url: defaultUrl,
+ field: 'job_type',
+ variable: 'job_type_options',
+ callback: 'choicesReadyVerbosity'
+ });
+
+ // Update playbook select whenever project value changes
+ selectPlaybook = function (oldValue, newValue) {
+ var url;
+ if($scope.job_type.value === 'scan' && $scope.project_name === "Default"){
+ $scope.playbook_options = ['Default'];
+ $scope.playbook = 'Default';
+ Wait('stop');
+ }
+ else if (oldValue !== newValue) {
+ if ($scope.project) {
+ Wait('start');
+ url = GetBasePath('projects') + $scope.project + '/playbooks/';
+ Rest.setUrl(url);
+ Rest.get()
+ .success(function (data) {
+ var i, opts = [];
+ for (i = 0; i < data.length; i++) {
+ opts.push(data[i]);
+ }
+ $scope.playbook_options = opts;
+ Wait('stop');
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, form, { hdr: 'Error!',
+ msg: 'Failed to get playbook list for ' + url + '. GET returned status: ' + status });
+ });
+ }
+ }
+ };
+
+ $scope.jobTypeChange = function(){
+ if($scope.job_type){
+ if($scope.job_type.value === 'scan'){
+ $scope.toggleScanInfo();
+ }
+ else if($scope.project_name === "Default"){
+ $scope.project_name = null;
+ $scope.playbook_options = [];
+ // $scope.playbook = 'null';
+ $scope.job_templates_form.playbook.$setPristine();
+ }
+ }
+ };
+
+ $scope.toggleScanInfo = function() {
+ $scope.project_name = 'Default';
+ if($scope.project === null){
+ selectPlaybook();
+ }
+ else {
+ $scope.project = null;
+ }
+ };
+
+ // Detect and alert user to potential SCM status issues
+ checkSCMStatus = function (oldValue, newValue) {
+ if (oldValue !== newValue && !Empty($scope.project)) {
+ Rest.setUrl(GetBasePath('projects') + $scope.project + '/');
+ Rest.get()
+ .success(function (data) {
+ var msg;
+ switch (data.status) {
+ case 'failed':
+ msg = "The selected project has a failed status. Review the project's SCM settings" +
+ " and run an update before adding it to a template.";
+ break;
+ case 'never updated':
+ msg = 'The selected project has a never updated status. You will need to run a successful' +
+ ' update in order to selected a playbook. Without a valid playbook you will not be able ' +
+ ' to save this template.';
+ break;
+ case 'missing':
+ msg = 'The selected project has a status of missing. Please check the server and make sure ' +
+ ' the directory exists and file permissions are set correctly.';
+ break;
+ }
+ if (msg) {
+ Alert('Warning', msg, 'alert-info', null, null, null, null, true);
+ }
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, form, { hdr: 'Error!',
+ msg: 'Failed to get project ' + $scope.project + '. GET returned status: ' + status });
+ });
+ }
+ };
+
+
+ // $scope.selectPlaybookUnregister = $scope.$watch('project_name', function (newval, oldval) {
+ // selectPlaybook(oldval, newval);
+ // checkSCMStatus(oldval, newval);
+ // });
+
+ // Register a watcher on project_name
+ if ($scope.selectPlaybookUnregister) {
+ $scope.selectPlaybookUnregister();
+ }
+ $scope.selectPlaybookUnregister = $scope.$watch('project', function (newValue, oldValue) {
+ if (newValue !== oldValue) {
+ selectPlaybook(oldValue, newValue);
+ checkSCMStatus();
+ }
+ });
+
+ LookUpInit({
+ scope: $scope,
+ form: form,
+ current_item: null,
+ list: ProjectList,
+ field: 'project',
+ input_type: "radio",
+ autopopulateLookup: (context === 'inv') ? false : true
+ });
+
+ if ($scope.removeSurveySaved) {
+ $scope.rmoveSurveySaved();
+ }
+ $scope.removeSurveySaved = $scope.$on('SurveySaved', function() {
+ Wait('stop');
+ $scope.survey_exists = true;
+ $scope.invalid_survey = false;
+ $('#job_templates_survey_enabled_chbox').attr('checked', true);
+ $('#job_templates_delete_survey_btn').show();
+ $('#job_templates_edit_survey_btn').show();
+ $('#job_templates_create_survey_btn').hide();
+
+ });
+
+
+ function saveCompleted() {
+ setTimeout(function() {
+ $scope.$apply(function() {
+ var base = $location.path().replace(/^\//, '').split('/')[0];
+ if (base === 'job_templates') {
+ ReturnToCaller();
+ }
+ else {
+ ReturnToCaller(1);
+ }
+ });
+ }, 500);
+ }
+
+ if ($scope.removeTemplateSaveSuccess) {
+ $scope.removeTemplateSaveSuccess();
+ }
+ $scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) {
+ Wait('stop');
+ if (data.related && data.related.callback) {
+ Alert('Callback URL', 'Host callbacks are enabled for this template. The callback URL is:
'+
+ '' + $scope.callback_server_path + data.related.callback + '
'+
+ 'The host configuration key is: ' + $filter('sanitize')(data.host_config_key) + '
', 'alert-info', saveCompleted, null, null, null, true);
+ }
+ else {
+ saveCompleted();
+ }
+ });
+
+ // Save
+ $scope.formSave = function () {
+ $scope.invalid_survey = false;
+ if ($scope.removeGatherFormFields) {
+ $scope.removeGatherFormFields();
+ }
+ $scope.removeGatherFormFields = $scope.$on('GatherFormFields', function(e, data) {
+ generator.clearApiErrors();
+ Wait('start');
+ data = {};
+ var fld;
+ try {
+ for (fld in form.fields) {
+ if (form.fields[fld].type === 'select' && fld !== 'playbook') {
+ data[fld] = $scope[fld].value;
+ } else {
+ if (fld !== 'variables') {
+ data[fld] = $scope[fld];
+ }
+ }
+ }
+ data.extra_vars = ToJSON($scope.parseType, $scope.variables, true);
+ if(data.job_type === 'scan' && $scope.default_scan === true){
+ data.project = "";
+ data.playbook = "";
+ }
+ Rest.setUrl(defaultUrl);
+ Rest.post(data)
+ .success(function(data) {
+ $scope.$emit('templateSaveSuccess', data);
+
+ $scope.addedItem = data.id;
+
+ Refresh({
+ scope: $scope,
+ set: 'job_templates',
+ iterator: 'job_template',
+ url: $scope.current_url
+ });
+
+ if(data.survey_enabled===true){
+ //once the job template information is saved we submit the survey info to the correct endpoint
+ var url = data.url+ 'survey_spec/';
+ Rest.setUrl(url);
+ Rest.post({ name: $scope.survey_name, description: $scope.survey_description, spec: $scope.survey_questions })
+ .success(function () {
+ Wait('stop');
+
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, form, { hdr: 'Error!',
+ msg: 'Failed to add new survey. Post returned status: ' + status });
+ });
+ }
+
+
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, form, { hdr: 'Error!',
+ msg: 'Failed to add new job template. POST returned status: ' + status
+ });
+ });
+
+ } catch (err) {
+ Wait('stop');
+ Alert("Error", "Error parsing extra variables. Parser returned: " + err);
+ }
+ });
+
+
+ if ($scope.removePromptForSurvey) {
+ $scope.removePromptForSurvey();
+ }
+ $scope.removePromptForSurvey = $scope.$on('PromptForSurvey', function() {
+ var action = function () {
+ // $scope.$emit("GatherFormFields");
+ Wait('start');
+ $('#prompt-modal').modal('hide');
+ $scope.addSurvey();
+
+ };
+ Prompt({
+ hdr: 'Incomplete Survey',
+ body: 'Do you want to create a survey before proceeding?
',
+ action: action
+ });
+ });
+
+ // users can't save a survey with a scan job
+ if($scope.job_type.value === "scan" && $scope.survey_enabled === true){
+ $scope.survey_enabled = false;
+ }
+ if($scope.survey_enabled === true && $scope.survey_exists!==true){
+ // $scope.$emit("PromptForSurvey");
+
+ // The original design for this was a pop up that would prompt the user if they wanted to create a
+ // survey, because they had enabled one but not created it yet. We switched this for now so that
+ // an error message would be displayed by the survey buttons that tells the user to add a survey or disabled
+ // surveys.
+ $scope.invalid_survey = true;
+ return;
+ } else {
+ $scope.$emit("GatherFormFields");
+ }
+
+
+ };
+
+ $scope.formCancel = function () {
+ $state.transitionTo('jobTemplates');
+ };
+ }
+
+ ];
diff --git a/awx/ui/client/src/job-templates/add/job-templates-add.partial.html b/awx/ui/client/src/job-templates/add/job-templates-add.partial.html
new file mode 100644
index 0000000000..6c3956cfb8
--- /dev/null
+++ b/awx/ui/client/src/job-templates/add/job-templates-add.partial.html
@@ -0,0 +1,5 @@
+
diff --git a/awx/ui/client/src/job-templates/add/job-templates-add.route.js b/awx/ui/client/src/job-templates/add/job-templates-add.route.js
new file mode 100644
index 0000000000..7d79f00763
--- /dev/null
+++ b/awx/ui/client/src/job-templates/add/job-templates-add.route.js
@@ -0,0 +1,23 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+
+export default {
+ name: 'jobTemplates.add',
+ url: '/add',
+ templateUrl: templateUrl('job-templates/add/job-templates-add'),
+ controller: 'JobTemplatesAdd',
+ ncyBreadcrumb: {
+ parent: "jobTemplates",
+ label: "CREATE JOB TEMPLATE"
+ },
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/job-templates/add/main.js b/awx/ui/client/src/job-templates/add/main.js
new file mode 100644
index 0000000000..b618873933
--- /dev/null
+++ b/awx/ui/client/src/job-templates/add/main.js
@@ -0,0 +1,17 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import jobTemplateAddRoute from './job-templates-add.route';
+import inventoryJobTemplateAddRoute from './inventory-job-templates-add.route';
+import controller from './job-templates-add.controller';
+
+export default
+ angular.module('jobTemplatesAdd', [])
+ .controller('JobTemplatesAdd', controller)
+ .run(['$stateExtender', function($stateExtender) {
+ $stateExtender.addState(jobTemplateAddRoute);
+ $stateExtender.addState(inventoryJobTemplateAddRoute);
+ }]);
diff --git a/awx/ui/client/src/job-templates/edit/inventory-job-templates-edit.route.js b/awx/ui/client/src/job-templates/edit/inventory-job-templates-edit.route.js
new file mode 100644
index 0000000000..6b0d476302
--- /dev/null
+++ b/awx/ui/client/src/job-templates/edit/inventory-job-templates-edit.route.js
@@ -0,0 +1,22 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+
+export default {
+ name: 'inventoryJobTemplateEdit',
+ url: '/inventories/:inventory_id/job_templates/:template_id',
+ templateUrl: templateUrl('job-templates/edit/job-templates-edit'),
+ controller: 'JobTemplatesEdit',
+ data: {
+ activityStreamId: 'template_id'
+ },
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/job-templates/edit/job-templates-edit.controller.js b/awx/ui/client/src/job-templates/edit/job-templates-edit.controller.js
new file mode 100644
index 0000000000..786358d52d
--- /dev/null
+++ b/awx/ui/client/src/job-templates/edit/job-templates-edit.controller.js
@@ -0,0 +1,596 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/**
+ * @ngdoc function
+ * @name controllers.function:JobTemplatesEdit
+ * @description This controller's for Job Template Edit
+*/
+
+export default
+ [ '$filter', '$scope', '$rootScope', '$compile',
+ '$location', '$log', '$stateParams', 'JobTemplateForm', 'GenerateForm',
+ 'Rest', 'Alert', 'ProcessErrors', 'RelatedSearchInit',
+ 'RelatedPaginateInit','ReturnToCaller', 'ClearScope', 'InventoryList',
+ 'CredentialList', 'ProjectList', 'LookUpInit', 'GetBasePath', 'md5Setup',
+ 'ParseTypeChange', 'JobStatusToolTip', 'FormatDate', 'Wait',
+ 'Empty', 'Prompt', 'ParseVariableString', 'ToJSON',
+ 'SchedulesControllerInit', 'JobsControllerInit', 'JobsListUpdate',
+ 'GetChoices', 'SchedulesListInit', 'SchedulesList', 'CallbackHelpInit',
+ 'PlaybookRun' , 'initSurvey', '$state', 'CreateSelect2',
+ function(
+ $filter, $scope, $rootScope, $compile,
+ $location, $log, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
+ ProcessErrors, RelatedSearchInit, RelatedPaginateInit, ReturnToCaller,
+ ClearScope, InventoryList, CredentialList, ProjectList, LookUpInit,
+ GetBasePath, md5Setup, ParseTypeChange, JobStatusToolTip, FormatDate, Wait,
+ Empty, Prompt, ParseVariableString, ToJSON, SchedulesControllerInit,
+ JobsControllerInit, JobsListUpdate, GetChoices, SchedulesListInit,
+ SchedulesList, CallbackHelpInit, PlaybookRun, SurveyControllerInit, $state,
+ CreateSelect2
+ ) {
+
+ ClearScope();
+
+ var defaultUrl = GetBasePath('job_templates'),
+ generator = GenerateForm,
+ form = JobTemplateForm(),
+ base = $location.path().replace(/^\//, '').split('/')[0],
+ master = {},
+ id = $stateParams.template_id,
+ relatedSets = {},
+ checkSCMStatus, getPlaybooks, callback,
+ choicesCount = 0;
+
+
+ CallbackHelpInit({ scope: $scope });
+
+ SchedulesList.well = false;
+ generator.inject(form, { mode: 'edit', related: true, scope: $scope });
+ $scope.mode = 'edit';
+ $scope.parseType = 'yaml';
+ $scope.showJobType = false;
+
+ SurveyControllerInit({
+ scope: $scope,
+ parent_scope: $scope,
+ id: id
+ });
+
+ callback = function() {
+ // Make sure the form controller knows there was a change
+ $scope[form.name + '_form'].$setDirty();
+ };
+
+ $scope.playbook_options = null;
+ $scope.playbook = null;
+ generator.reset();
+
+ getPlaybooks = function (project) {
+ var url;
+ if($scope.job_type.value === 'scan' && $scope.project_name === "Default"){
+ $scope.playbook_options = ['Default'];
+ $scope.playbook = 'Default';
+ Wait('stop');
+ }
+ else if (!Empty(project)) {
+ url = GetBasePath('projects') + project + '/playbooks/';
+ Wait('start');
+ Rest.setUrl(url);
+ Rest.get()
+ .success(function (data) {
+ var i;
+ $scope.playbook_options = [];
+ for (i = 0; i < data.length; i++) {
+ $scope.playbook_options.push(data[i]);
+ if (data[i] === $scope.playbook) {
+ $scope.job_templates_form.playbook.$setValidity('required', true);
+ }
+ }
+ if ($scope.playbook) {
+ $scope.$emit('jobTemplateLoadFinished');
+ } else {
+ Wait('stop');
+ }
+ })
+ .error(function () {
+ Wait('stop');
+ Alert('Missing Playbooks', 'Unable to retrieve the list of playbooks for this project. Choose a different ' +
+ ' project or make the playbooks available on the file system.', 'alert-info');
+ });
+ }
+ else {
+ Wait('stop');
+ }
+ };
+
+ $scope.jobTypeChange = function(){
+ if($scope.job_type){
+ if($scope.job_type.value === 'scan'){
+ $scope.toggleScanInfo();
+ }
+ else if($scope.project_name === "Default"){
+ $scope.project_name = null;
+ $scope.playbook_options = [];
+ // $scope.playbook = 'null';
+ $scope.job_templates_form.playbook.$setPristine();
+ }
+
+ }
+ };
+
+ $scope.toggleScanInfo = function() {
+ $scope.project_name = 'Default';
+ if($scope.project === null){
+ getPlaybooks();
+ }
+ else {
+ $scope.project = null;
+ }
+ };
+
+ // Detect and alert user to potential SCM status issues
+ checkSCMStatus = function () {
+ if (!Empty($scope.project)) {
+ Wait('start');
+ Rest.setUrl(GetBasePath('projects') + $scope.project + '/');
+ Rest.get()
+ .success(function (data) {
+ var msg;
+ switch (data.status) {
+ case 'failed':
+ msg = "The selected project has a failed status. Review the project's SCM settings" +
+ " and run an update before adding it to a template.";
+ break;
+ case 'never updated':
+ msg = 'The selected project has a never updated status. You will need to run a successful' +
+ ' update in order to selected a playbook. Without a valid playbook you will not be able ' +
+ ' to save this template.';
+ break;
+ case 'missing':
+ msg = 'The selected project has a status of missing. Please check the server and make sure ' +
+ ' the directory exists and file permissions are set correctly.';
+ break;
+ }
+ Wait('stop');
+ if (msg) {
+ Alert('Warning', msg, 'alert-info', null, null, null, null, true);
+ }
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, form, { hdr: 'Error!', msg: 'Failed to get project ' + $scope.project +
+ '. GET returned status: ' + status });
+ });
+ }
+ };
+
+ if ($scope.removerelatedschedules) {
+ $scope.removerelatedschedules();
+ }
+ $scope.removerelatedschedules = $scope.$on('relatedschedules', function() {
+ SchedulesListInit({
+ scope: $scope,
+ list: SchedulesList,
+ choices: null,
+ related: true
+ });
+ });
+
+ // Register a watcher on project_name. Refresh the playbook list on change.
+ if ($scope.watchProjectUnregister) {
+ $scope.watchProjectUnregister();
+ }
+ $scope.watchProjectUnregister = $scope.$watch('project', function (newValue, oldValue) {
+ if (newValue !== oldValue) {
+ getPlaybooks($scope.project);
+ checkSCMStatus();
+ }
+ });
+
+
+
+ // Turn off 'Wait' after both cloud credential and playbook list come back
+ if ($scope.removeJobTemplateLoadFinished) {
+ $scope.removeJobTemplateLoadFinished();
+ }
+ $scope.removeJobTemplateLoadFinished = $scope.$on('jobTemplateLoadFinished', function () {
+ CreateSelect2({
+ element:'#job_templates_job_type',
+ multiple: false
+ });
+
+ CreateSelect2({
+ element:'#playbook-select',
+ multiple: false
+ });
+
+ CreateSelect2({
+ element:'#job_templates_verbosity',
+ multiple: false
+ });
+
+ for (var set in relatedSets) {
+ $scope.search(relatedSets[set].iterator);
+ }
+ SchedulesControllerInit({
+ scope: $scope,
+ parent_scope: $scope,
+ iterator: 'schedule'
+ });
+
+ });
+
+ // Set the status/badge for each related job
+ if ($scope.removeRelatedCompletedJobs) {
+ $scope.removeRelatedCompletedJobs();
+ }
+ $scope.removeRelatedCompletedJobs = $scope.$on('relatedcompleted_jobs', function () {
+ JobsControllerInit({
+ scope: $scope,
+ parent_scope: $scope,
+ iterator: form.related.completed_jobs.iterator
+ });
+ JobsListUpdate({
+ scope: $scope,
+ parent_scope: $scope,
+ list: form.related.completed_jobs
+ });
+ });
+
+ if ($scope.cloudCredentialReadyRemove) {
+ $scope.cloudCredentialReadyRemove();
+ }
+ $scope.cloudCredentialReadyRemove = $scope.$on('cloudCredentialReady', function (e, name) {
+ var CloudCredentialList = {};
+ $scope.cloud_credential_name = name;
+ master.cloud_credential_name = name;
+ // Clone the CredentialList object for use with cloud_credential. Cloning
+ // and changing properties to avoid collision.
+ jQuery.extend(true, CloudCredentialList, CredentialList);
+ CloudCredentialList.name = 'cloudcredentials';
+ CloudCredentialList.iterator = 'cloudcredential';
+ LookUpInit({
+ url: GetBasePath('credentials') + '?cloud=true',
+ scope: $scope,
+ form: form,
+ current_item: $scope.cloud_credential,
+ list: CloudCredentialList,
+ field: 'cloud_credential',
+ hdr: 'Select Cloud Credential',
+ input_type: "radio"
+ });
+ $scope.$emit('jobTemplateLoadFinished');
+ });
+
+
+ // Retrieve each related set and populate the playbook list
+ if ($scope.jobTemplateLoadedRemove) {
+ $scope.jobTemplateLoadedRemove();
+ }
+ $scope.jobTemplateLoadedRemove = $scope.$on('jobTemplateLoaded', function (e, related_cloud_credential, masterObject, relatedSets) {
+ var dft, set;
+ master = masterObject;
+ getPlaybooks($scope.project);
+
+ for (set in relatedSets) {
+ $scope.search(relatedSets[set].iterator);
+ }
+
+ dft = ($scope.host_config_key === "" || $scope.host_config_key === null) ? false : true;
+ md5Setup({
+ scope: $scope,
+ master: master,
+ check_field: 'allow_callbacks',
+ default_val: dft
+ });
+
+ ParseTypeChange({ scope: $scope, field_id: 'job_templates_variables', onChange: callback });
+
+ if (related_cloud_credential) {
+ Rest.setUrl(related_cloud_credential);
+ Rest.get()
+ .success(function (data) {
+ $scope.$emit('cloudCredentialReady', data.name);
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, null, {hdr: 'Error!',
+ msg: 'Failed to related cloud credential. GET returned status: ' + status });
+ });
+ } else {
+ // No existing cloud credential
+ $scope.$emit('cloudCredentialReady', null);
+ }
+ });
+
+ Wait('start');
+
+ if ($scope.removeEnableSurvey) {
+ $scope.removeEnableSurvey();
+ }
+ $scope.removeEnableSurvey = $scope.$on('EnableSurvey', function(fld) {
+
+ $('#job_templates_survey_enabled_chbox').attr('checked', $scope[fld]);
+ Rest.setUrl(defaultUrl + id+ '/survey_spec/');
+ Rest.get()
+ .success(function (data) {
+ if(!data || !data.name){
+ $('#job_templates_delete_survey_btn').hide();
+ $('#job_templates_edit_survey_btn').hide();
+ $('#job_templates_create_survey_btn').show();
+ }
+ else {
+ $scope.survey_exists = true;
+ $('#job_templates_delete_survey_btn').show();
+ $('#job_templates_edit_survey_btn').show();
+ $('#job_templates_create_survey_btn').hide();
+ }
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, form, {
+ hdr: 'Error!',
+ msg: 'Failed to retrieve job template: ' + $stateParams.template_id + '. GET status: ' + status
+ });
+ });
+ });
+
+ if ($scope.removeSurveySaved) {
+ $scope.rmoveSurveySaved();
+ }
+ $scope.removeSurveySaved = $scope.$on('SurveySaved', function() {
+ Wait('stop');
+ $scope.survey_exists = true;
+ $scope.invalid_survey = false;
+ $('#job_templates_survey_enabled_chbox').attr('checked', true);
+ $('#job_templates_delete_survey_btn').show();
+ $('#job_templates_edit_survey_btn').show();
+ $('#job_templates_create_survey_btn').hide();
+
+ });
+
+ if ($scope.removeLoadJobs) {
+ $scope.rmoveLoadJobs();
+ }
+ $scope.removeLoadJobs = $scope.$on('LoadJobs', function() {
+ $scope.fillJobTemplate();
+ });
+
+ if ($scope.removeChoicesReady) {
+ $scope.removeChoicesReady();
+ }
+ $scope.removeChoicesReady = $scope.$on('choicesReady', function() {
+ choicesCount++;
+ if (choicesCount === 4) {
+ $scope.$emit('LoadJobs');
+ }
+ });
+
+ GetChoices({
+ scope: $scope,
+ url: GetBasePath('unified_jobs'),
+ field: 'status',
+ variable: 'status_choices',
+ callback: 'choicesReady'
+ });
+
+ GetChoices({
+ scope: $scope,
+ url: GetBasePath('unified_jobs'),
+ field: 'type',
+ variable: 'type_choices',
+ callback: 'choicesReady'
+ });
+
+ // setup verbosity options lookup
+ GetChoices({
+ scope: $scope,
+ url: defaultUrl,
+ field: 'verbosity',
+ variable: 'verbosity_options',
+ callback: 'choicesReady'
+ });
+
+ // setup job type options lookup
+ GetChoices({
+ scope: $scope,
+ url: defaultUrl,
+ field: 'job_type',
+ variable: 'job_type_options',
+ callback: 'choicesReady'
+ });
+
+ function saveCompleted() {
+ setTimeout(function() {
+ $scope.$apply(function() {
+ var base = $location.path().replace(/^\//, '').split('/')[0];
+ if (base === 'job_templates') {
+ ReturnToCaller();
+ }
+ else {
+ ReturnToCaller(1);
+ }
+ });
+ }, 500);
+ }
+
+ if ($scope.removeTemplateSaveSuccess) {
+ $scope.removeTemplateSaveSuccess();
+ }
+ $scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) {
+ Wait('stop');
+ if ($scope.allow_callbacks && ($scope.host_config_key !== master.host_config_key || $scope.callback_url !== master.callback_url)) {
+ if (data.related && data.related.callback) {
+ Alert('Callback URL', 'Host callbacks are enabled for this template. The callback URL is:
'+
+ '' + $scope.callback_server_path + data.related.callback + '
'+
+ 'The host configuration key is: ' + $filter('sanitize')(data.host_config_key) + '
', 'alert-info', saveCompleted, null, null, null, true);
+ }
+ else {
+ saveCompleted();
+ }
+ }
+ else {
+ saveCompleted();
+ }
+ });
+
+
+
+ // Save changes to the parent
+ $scope.formSave = function () {
+ $scope.invalid_survey = false;
+ if ($scope.removeGatherFormFields) {
+ $scope.removeGatherFormFields();
+ }
+ $scope.removeGatherFormFields = $scope.$on('GatherFormFields', function(e, data) {
+ generator.clearApiErrors();
+ Wait('start');
+ data = {};
+ var fld;
+ try {
+ // Make sure we have valid variable data
+ data.extra_vars = ToJSON($scope.parseType, $scope.variables, true);
+ if(data.extra_vars === undefined ){
+ throw 'undefined variables';
+ }
+ for (fld in form.fields) {
+ if (form.fields[fld].type === 'select' && fld !== 'playbook') {
+ data[fld] = $scope[fld].value;
+ } else {
+ if (fld !== 'variables' && fld !== 'callback_url') {
+ data[fld] = $scope[fld];
+ }
+ }
+ }
+ Rest.setUrl(defaultUrl + id + '/');
+ Rest.put(data)
+ .success(function (data) {
+ $scope.$emit('templateSaveSuccess', data);
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, form, { hdr: 'Error!',
+ msg: 'Failed to update job template. PUT returned status: ' + status });
+ });
+
+ } catch (err) {
+ Wait('stop');
+ Alert("Error", "Error parsing extra variables. Parser returned: " + err);
+ }
+ });
+
+
+ if ($scope.removePromptForSurvey) {
+ $scope.removePromptForSurvey();
+ }
+ $scope.removePromptForSurvey = $scope.$on('PromptForSurvey', function() {
+ var action = function () {
+ // $scope.$emit("GatherFormFields");
+ Wait('start');
+ $('#prompt-modal').modal('hide');
+ $scope.addSurvey();
+
+ };
+ Prompt({
+ hdr: 'Incomplete Survey',
+ body: 'Do you want to create a survey before proceeding?
',
+ action: action
+ });
+ });
+
+ // users can't save a survey with a scan job
+ if($scope.job_type.value === "scan" && $scope.survey_enabled === true){
+ $scope.survey_enabled = false;
+ }
+ if($scope.survey_enabled === true && $scope.survey_exists!==true){
+ // $scope.$emit("PromptForSurvey");
+
+ // The original design for this was a pop up that would prompt the user if they wanted to create a
+ // survey, because they had enabled one but not created it yet. We switched this for now so that
+ // an error message would be displayed by the survey buttons that tells the user to add a survey or disabled
+ // surveys.
+ $scope.invalid_survey = true;
+ return;
+ } else {
+ $scope.$emit("GatherFormFields");
+ }
+
+ };
+
+ $scope.formCancel = function () {
+ $state.transitionTo('jobTemplates');
+ };
+
+ // Related set: Add button
+ $scope.add = function (set) {
+ $rootScope.flashMessage = null;
+ $location.path('/' + base + '/' + $stateParams.template_id + '/' + set);
+ };
+
+ // Related set: Edit button
+ $scope.edit = function (set, id) {
+ $rootScope.flashMessage = null;
+ $location.path('/' + set + '/' + id);
+ };
+
+ // Launch a job using the selected template
+ $scope.launch = function() {
+
+ if ($scope.removePromptForSurvey) {
+ $scope.removePromptForSurvey();
+ }
+ $scope.removePromptForSurvey = $scope.$on('PromptForSurvey', function() {
+ var action = function () {
+ // $scope.$emit("GatherFormFields");
+ Wait('start');
+ $('#prompt-modal').modal('hide');
+ $scope.addSurvey();
+
+ };
+ Prompt({
+ hdr: 'Incomplete Survey',
+ body: 'Do you want to create a survey before proceeding?
',
+ action: action
+ });
+ });
+ if($scope.survey_enabled === true && $scope.survey_exists!==true){
+ $scope.$emit("PromptForSurvey");
+ }
+ else {
+
+ PlaybookRun({
+ scope: $scope,
+ id: id
+ });
+ }
+ };
+
+ // handler for 'Enable Survey' button
+ $scope.surveyEnabled = function(){
+ Rest.setUrl(defaultUrl + id+ '/');
+ Rest.patch({"survey_enabled": $scope.survey_enabled})
+ .success(function (data) {
+
+ if(Empty(data.summary_fields.survey)){
+ $('#job_templates_delete_survey_btn').hide();
+ $('#job_templates_edit_survey_btn').hide();
+ $('#job_templates_create_survey_btn').show();
+ }
+ else{
+ $scope.survey_exists = true;
+ $('#job_templates_delete_survey_btn').show();
+ $('#job_templates_edit_survey_btn').show();
+ $('#job_templates_create_survey_btn').hide();
+ }
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, form, {
+ hdr: 'Error!',
+ msg: 'Failed to retrieve save survey_enabled: ' + $stateParams.template_id + '. GET status: ' + status
+ });
+ });
+ };
+
+
+ }
+ ];
diff --git a/awx/ui/client/src/job-templates/edit/job-templates-edit.partial.html b/awx/ui/client/src/job-templates/edit/job-templates-edit.partial.html
new file mode 100644
index 0000000000..cb55a8a700
--- /dev/null
+++ b/awx/ui/client/src/job-templates/edit/job-templates-edit.partial.html
@@ -0,0 +1,5 @@
+
diff --git a/awx/ui/client/src/job-templates/edit/job-templates-edit.route.js b/awx/ui/client/src/job-templates/edit/job-templates-edit.route.js
new file mode 100644
index 0000000000..2a4ab960e4
--- /dev/null
+++ b/awx/ui/client/src/job-templates/edit/job-templates-edit.route.js
@@ -0,0 +1,22 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+
+export default {
+ name: 'jobTemplates.edit',
+ url: '/:template_id',
+ templateUrl: templateUrl('job-templates/edit/job-templates-edit'),
+ controller: 'JobTemplatesEdit',
+ data: {
+ activityStreamId: 'template_id'
+ },
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/job-templates/edit/main.js b/awx/ui/client/src/job-templates/edit/main.js
new file mode 100644
index 0000000000..b7a48404ec
--- /dev/null
+++ b/awx/ui/client/src/job-templates/edit/main.js
@@ -0,0 +1,17 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import jobTemplateEditRoute from './job-templates-edit.route';
+import inventoryJobTemplateEditRoute from './inventory-job-templates-edit.route';
+import controller from './job-templates-edit.controller';
+
+export default
+ angular.module('jobTemplatesEdit', [])
+ .controller('JobTemplatesEdit', controller)
+ .run(['$stateExtender', function($stateExtender) {
+ $stateExtender.addState(jobTemplateEditRoute);
+ $stateExtender.addState(inventoryJobTemplateEditRoute);
+ }]);
diff --git a/awx/ui/client/src/job-templates/list/job-templates-list.controller.js b/awx/ui/client/src/job-templates/list/job-templates-list.controller.js
new file mode 100644
index 0000000000..c23258017f
--- /dev/null
+++ b/awx/ui/client/src/job-templates/list/job-templates-list.controller.js
@@ -0,0 +1,241 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+export default
+ [ '$scope', '$rootScope', '$location', '$log',
+ '$stateParams', 'Rest', 'Alert', 'JobTemplateList', 'generateList',
+ 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
+ 'ProcessErrors', 'GetBasePath', 'JobTemplateForm', 'CredentialList',
+ 'LookUpInit', 'PlaybookRun', 'Wait', 'CreateDialog' , '$compile',
+ '$state',
+
+ function(
+ $scope, $rootScope, $location, $log,
+ $stateParams, Rest, Alert, JobTemplateList, GenerateList, Prompt,
+ SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors,
+ GetBasePath, JobTemplateForm, CredentialList, LookUpInit, PlaybookRun,
+ Wait, CreateDialog, $compile, $state
+ ) {
+
+ ClearScope();
+
+ var list = JobTemplateList,
+ defaultUrl = GetBasePath('job_templates'),
+ view = GenerateList,
+ base = $location.path().replace(/^\//, '').split('/')[0],
+ mode = (base === 'job_templates') ? 'edit' : 'select';
+
+ view.inject(list, { mode: mode, scope: $scope });
+ $rootScope.flashMessage = null;
+
+ if ($scope.removePostRefresh) {
+ $scope.removePostRefresh();
+ }
+ $scope.removePostRefresh = $scope.$on('PostRefresh', function () {
+ // Cleanup after a delete
+ Wait('stop');
+ $('#prompt-modal').modal('hide');
+ });
+
+ SearchInit({
+ scope: $scope,
+ set: 'job_templates',
+ list: list,
+ url: defaultUrl
+ });
+ PaginateInit({
+ scope: $scope,
+ list: list,
+ url: defaultUrl
+ });
+
+ // Called from Inventories tab, host failed events link:
+ if ($stateParams.name) {
+ $scope[list.iterator + 'SearchField'] = 'name';
+ $scope[list.iterator + 'SearchValue'] = $stateParams.name;
+ $scope[list.iterator + 'SearchFieldLabel'] = list.fields.name.label;
+ }
+
+ $scope.search(list.iterator);
+
+ $scope.addJobTemplate = function () {
+ $state.transitionTo('jobTemplates.add');
+ };
+
+ $scope.editJobTemplate = function (id) {
+ $state.transitionTo('jobTemplates.edit', {template_id: id});
+ };
+
+ $scope.deleteJobTemplate = function (id, name) {
+ var action = function () {
+ $('#prompt-modal').modal('hide');
+ Wait('start');
+ var url = defaultUrl + id + '/';
+ Rest.setUrl(url);
+ Rest.destroy()
+ .success(function () {
+ $scope.search(list.iterator);
+ })
+ .error(function (data) {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
+ });
+ };
+
+ Prompt({
+ hdr: 'Delete',
+ body: 'Are you sure you want to delete the job template below?
' + name + '
',
+ action: action,
+ actionText: 'DELETE'
+ });
+ };
+
+ $scope.copyJobTemplate = function(id, name){
+ var element,
+ buttons = [{
+ "label": "Cancel",
+ "onClick": function() {
+ $(this).dialog('close');
+ },
+ "icon": "fa-times",
+ "class": "btn btn-default",
+ "id": "copy-close-button"
+ },{
+ "label": "Copy",
+ "onClick": function() {
+ copyAction();
+ // setTimeout(function(){
+ // scope.$apply(function(){
+ // if(mode==='survey-taker'){
+ // scope.$emit('SurveyTakerCompleted');
+ // } else{
+ // scope.saveSurvey();
+ // }
+ // });
+ // });
+ },
+ "icon": "fa-copy",
+ "class": "btn btn-primary",
+ "id": "job-copy-button"
+ }],
+ copyAction = function () {
+ // retrieve the copy of the job template object from the api, then overwrite the name and throw away the id
+ Wait('start');
+ var url = defaultUrl + id + '/';
+ Rest.setUrl(url);
+ Rest.get()
+ .success(function (data) {
+ data.name = $scope.new_copy_name;
+ delete data.id;
+ $scope.$emit('GoToCopy', data);
+ })
+ .error(function (data) {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
+ });
+ };
+
+
+ CreateDialog({
+ id: 'copy-job-modal',
+ title: "Copy",
+ scope: $scope,
+ buttons: buttons,
+ width: 500,
+ height: 300,
+ minWidth: 200,
+ callback: 'CopyDialogReady'
+ });
+
+ $('#job_name').text(name);
+ $('#copy-job-modal').show();
+
+
+ if ($scope.removeCopyDialogReady) {
+ $scope.removeCopyDialogReady();
+ }
+ $scope.removeCopyDialogReady = $scope.$on('CopyDialogReady', function() {
+ //clear any old remaining text
+ $scope.new_copy_name = "" ;
+ $scope.copy_form.$setPristine();
+ $('#copy-job-modal').dialog('open');
+ $('#job-copy-button').attr('ng-disabled', "!copy_form.$valid");
+ element = angular.element(document.getElementById('job-copy-button'));
+ $compile(element)($scope);
+
+ });
+
+ if ($scope.removeGoToCopy) {
+ $scope.removeGoToCopy();
+ }
+ $scope.removeGoToCopy = $scope.$on('GoToCopy', function(e, data) {
+ var url = defaultUrl,
+ old_survey_url = (data.related.survey_spec) ? data.related.survey_spec : "" ;
+ Rest.setUrl(url);
+ Rest.post(data)
+ .success(function (data) {
+ if(data.survey_enabled===true){
+ $scope.$emit("CopySurvey", data, old_survey_url);
+ }
+ else {
+ $('#copy-job-modal').dialog('close');
+ Wait('stop');
+ $location.path($location.path() + '/' + data.id);
+ }
+
+ })
+ .error(function (data) {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
+ });
+ });
+
+ if ($scope.removeCopySurvey) {
+ $scope.removeCopySurvey();
+ }
+ $scope.removeCopySurvey = $scope.$on('CopySurvey', function(e, new_data, old_url) {
+ // var url = data.related.survey_spec;
+ Rest.setUrl(old_url);
+ Rest.get()
+ .success(function (survey_data) {
+
+ Rest.setUrl(new_data.related.survey_spec);
+ Rest.post(survey_data)
+ .success(function () {
+ $('#copy-job-modal').dialog('close');
+ Wait('stop');
+ $location.path($location.path() + '/' + new_data.id);
+ })
+ .error(function (data) {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + new_data.related.survey_spec + ' failed. DELETE returned status: ' + status });
+ });
+
+
+ })
+ .error(function (data) {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + old_url + ' failed. DELETE returned status: ' + status });
+ });
+
+ });
+
+ };
+
+ $scope.submitJob = function (id) {
+ PlaybookRun({ scope: $scope, id: id });
+ };
+
+ $scope.scheduleJob = function (id) {
+ $state.go('jobTemplateSchedules', {id: id});
+ };
+ }
+ ];
diff --git a/awx/ui/client/src/partials/job_templates.html b/awx/ui/client/src/job-templates/list/job-templates-list.partial.html
similarity index 95%
rename from awx/ui/client/src/partials/job_templates.html
rename to awx/ui/client/src/job-templates/list/job-templates-list.partial.html
index 9aa018d9bc..4fcfdeb6e9 100644
--- a/awx/ui/client/src/partials/job_templates.html
+++ b/awx/ui/client/src/job-templates/list/job-templates-list.partial.html
@@ -3,7 +3,6 @@
-