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 @@
-