diff --git a/awx/ui/client/features/templates/list-templates.controller.js b/awx/ui/client/features/templates/list-templates.controller.js index 9e5d2b18ef..927e807656 100644 --- a/awx/ui/client/features/templates/list-templates.controller.js +++ b/awx/ui/client/features/templates/list-templates.controller.js @@ -1,148 +1,75 @@ /** *********************************************** - * Copyright (c) 2017 Ansible, Inc. + * Copyright (c) 2018 Ansible, Inc. * * All Rights Reserved ************************************************ */ - -const ALERT_MISSING = 'Template parameter is missing'; -const ALERT_NO_PERMISSION = 'You do not have permission to perform this action.'; -const ALERT_UNKNOWN = 'We were unable to determine this template\'s type'; -const ALERT_UNKNOWN_COPY = `${ALERT_UNKNOWN} while copying.`; -const ALERT_UNKNOWN_DELETE = `${ALERT_UNKNOWN} while deleting.`; -const ALERT_UNKNOWN_EDIT = `${ALERT_UNKNOWN} while routing to edit.`; -const ALERT_UNKNOWN_LAUNCH = `${ALERT_UNKNOWN} while launching.`; -const ALERT_UNKNOWN_SCHEDULE = `${ALERT_UNKNOWN} while routing to schedule.`; -const ERROR_EDIT = 'Error: Unable to edit template'; -const ERROR_DELETE = 'Error: Unable to delete template'; -const ERROR_LAUNCH = 'Error: Unable to launch template'; -const ERROR_UNKNOWN = 'Error: Unable to determine template type'; -const ERROR_JOB_SCHEDULE = 'Error: Unable to schedule job'; -const ERROR_TEMPLATE_COPY = 'Error: Unable to copy job template'; -const ERROR_WORKFLOW_COPY = 'Error: Unable to copy workflow job template'; - const JOB_TEMPLATE_ALIASES = ['job_template', 'Job Template']; const WORKFLOW_TEMPLATE_ALIASES = ['workflow_job_template', 'Workflow Job Template']; -const isJobTemplate = obj => _.includes(JOB_TEMPLATE_ALIASES, _.get(obj, 'type')); -const isWorkflowTemplate = obj => _.includes(WORKFLOW_TEMPLATE_ALIASES, _.get(obj, 'type')); +const isJobTemplate = ({ type }) => JOB_TEMPLATE_ALIASES.indexOf(type) > -1; +const isWorkflowTemplate = ({ type }) => WORKFLOW_TEMPLATE_ALIASES.indexOf(type) > -1; +const mapChoices = choices => Object.assign(...choices.map(([k, v]) => ({[k]: v}))); -function TemplatesListController ( - $scope, - $rootScope, - Alert, - TemplateList, - Prompt, - ProcessErrors, - GetBasePath, - InitiatePlaybookRun, - Wait, - $state, +function ListTemplatesController( $filter, + $scope, + $state, + Alert, Dataset, - rbacUiControlService, - TemplatesService, - qs, - i18n, - JobTemplate, - WorkflowJobTemplate, - TemplatesStrings, - $q, - Empty, - i18n, + InitiatePlaybookRun, + ProcessErrors, + Prompt, PromptService, + resolvedModels, + strings, + Wait, ) { - const jobTemplate = new JobTemplate(); - const list = TemplateList; + const vm = this || {}; + const [jobTemplate, workflowTemplate] = resolvedModels; - init(); + const choices = workflowTemplate.options('actions.GET.type.choices') + .concat(jobTemplate.options('actions.GET.type.choices')); - function init () { - $scope.canAdd = false; + vm.strings = strings; + vm.templateTypes = mapChoices(choices); + vm.activeId = parseInt($state.params.job_template_id || $state.params.workflow_template_id); - rbacUiControlService.canAdd('job_templates').then(params => { - $scope.canAddJobTemplate = params.canAdd; - }); + $scope.canAddJobTemplate = jobTemplate.options('actions.POST') + $scope.canAddWorkflowJobTemplate = workflowTemplate.options('actions.POST') + $scope.canAdd = ($scope.canAddJobTemplate || $scope.canAddWorkflowJobTemplate); - rbacUiControlService.canAdd('workflow_job_templates').then(params => { - $scope.canAddWorkflowJobTemplate = params.canAdd; - }); + // smart-search + const name = 'templates'; + const iterator = 'template'; + const key = 'template_dataset'; - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - $scope.options = {}; - - $rootScope.flashMessage = null; - } - - $scope.$on(`${list.iterator}_options`, (event, data) => { - $scope.options = data.data.actions.GET; - optionsRequestDataProcessing(); + $scope.list = { iterator, name }; + $scope.collection = { iterator, basePath: 'unified_job_templates' }; + $scope[key] = Dataset.data; + $scope[name] = Dataset.data.results; + $scope.$on('updateDataset', (e, dataset) => { + $scope[key] = dataset; + $scope[name] = dataset.results; }); - $scope.$watchCollection('templates', () => { - optionsRequestDataProcessing(); - }); - - $scope.$on('ws-jobs', () => { - const path = GetBasePath(list.basePath) || GetBasePath(list.name); - qs.search(path, $state.params[`${list.iterator}_search`]) - .then(searchResponse => { - $scope[`${list.iterator}_dataset`] = searchResponse.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - }); - }); - - // iterate over the list and add fields like type label, after the - // OPTIONS request returns, or the list is sorted/paginated/searched - function optionsRequestDataProcessing () { - $scope[list.name].forEach((item, idx) => { - const itm = $scope[list.name][idx]; - // Set the item type label - if (list.fields.type && _.has($scope.options, 'type.choices')) { - $scope.options.type.choices.forEach(choice => { - if (choice[0] === item.type) { - [itm.type_label] = choice; - } - }); - } - }); - } - - $scope.editJobTemplate = template => { + vm.runTemplate = template => { if (!template) { - Alert(ERROR_EDIT, ALERT_MISSING); + Alert(strings.get('error.LAUNCH'), strings.get('alert.MISSING_PARAMETER')); return; } if (isJobTemplate(template)) { - $state.transitionTo('templates.editJobTemplate', { job_template_id: template.id }); + runJobTemplate(template); } else if (isWorkflowTemplate(template)) { - $state.transitionTo('templates.editWorkflowJobTemplate', { workflow_job_template_id: template.id }); + runWorkflowTemplate(template); } else { - Alert(ERROR_UNKNOWN, ALERT_UNKNOWN_EDIT); + Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_LAUNCH')); } }; - $scope.submitJob = template => { + vm.scheduleTemplate = template => { if (!template) { - Alert(ERROR_LAUNCH, ALERT_MISSING); - return; - } - - if (isJobTemplate(template)) { - submitJobTemplate(template) - } else if (isWorkflowTemplate(template)) { - InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'workflow_job_template' }); - } else { - Alert(ERROR_UNKNOWN, ALERT_UNKNOWN_LAUNCH); - } - }; - - $scope.scheduleJob = template => { - if (!template) { - Alert(ERROR_JOB_SCHEDULE, ALERT_MISSING); + Alert(strings.get('error.SCHEDULE'), strings.get('alert.MISSING_PARAMETER')); return; } @@ -151,362 +78,329 @@ function TemplatesListController ( } else if (isWorkflowTemplate(template)) { $state.go('workflowJobTemplateSchedules', { id: template.id }); } else { - Alert(ERROR_UNKNOWN, ALERT_UNKNOWN_SCHEDULE); + Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_SCHEDULE')); } }; - $scope.deleteJobTemplate = template => { + vm.deleteTemplate = template => { if (!template) { - Alert(ERROR_DELETE, ALERT_MISSING); + Alert(strings.get('error.DELETE'), strings.get('alert.MISSING_PARAMETER')); return; } if (isWorkflowTemplate(template)) { - const body = TemplatesStrings.get('deleteResource.CONFIRM', 'workflow job template'); - $scope.displayTemplateDeletePrompt(template, body); + displayWorkflowTemplateDeletePrompt(template); } else if (isJobTemplate(template)) { - jobTemplate.getDependentResourceCounts(template.id) - .then(counts => { - const body = buildTemplateDeletePromptHTML(counts); - $scope.displayTemplateDeletePrompt(template, body); - }); + jobTemplate.getDependentResourceCounts(template.id) + .then(counts => displayJobTemplateDeletePrompt(template, counts)); } else { - Alert(ERROR_UNKNOWN, ALERT_UNKNOWN_DELETE); + Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_DELETE')); } }; - $scope.copyTemplate = template => { + vm.copyTemplate = template => { if (!template) { - Alert(ERROR_TEMPLATE_COPY, ALERT_MISSING); + Alert(strings.get('error.COPY'), strings.get('alert.MISSING_PARAMETER')); return; } if (isJobTemplate(template)) { - $scope.copyJobTemplate(template); + copyJobTemplate(template); } else if (isWorkflowTemplate(template)) { - $scope.copyWorkflowJobTemplate(template); + copyWorkflowTemplate(template); } else { - Alert(ERROR_UNKNOWN, ALERT_UNKNOWN_COPY); + Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_COPY')); } }; - $scope.copyJobTemplate = template => { + vm.getModified = template => { + const modified = _.get(template, 'modified'); + + if (!modified) { + return undefined; + } + + let html = $filter('longDate')(modified); + + const { username, id } = _.get(template, 'summary_fields.modified_by', {}); + + if (username && id) { + html += ` by ${$filter('sanitize')(username)}`; + } + + return html; + }; + + vm.getLastRan = template => { + const lastJobRun = _.get(template, 'last_job_run'); + + if (!lastJobRun) { + return undefined; + } + + let html = $filter('longDate')(modified); + + // TODO: uncomment and update when last job run user is returned by api + // const { username, id } = _.get(template, 'summary_fields.last_job_run_by', {}); + + // if (username && id) { + // html += ` by ${$filter('sanitize')(username)}`; + //} + + return html; + }; + + function createErrorHandler(path, action) { + return ({ data, status }) => { + const hdr = strings.get('error.HEADER'); + const msg = strings.get('error.CALL', { path, action, status }); + ProcessErrors($scope, data, status, null, { hdr, msg }); + }; + } + + function copyJobTemplate(template) { Wait('start'); - new JobTemplate('get', template.id) + jobTemplate + .create('get', template.id) .then(model => model.copy()) .then(({ id }) => { const params = { job_template_id: id }; $state.go('templates.editJobTemplate', params, { reload: true }); }) - .catch(({ data, status }) => { - const params = { hdr: 'Error!', msg: `Call to copy failed. Return status: ${status}` }; - ProcessErrors($scope, data, status, null, params); - }) + .catch(createErrorHandler('copy job template', 'POST')) .finally(() => Wait('stop')); }; - $scope.copyWorkflowJobTemplate = template => { + function copyWorkflowTemplate(template) { Wait('start'); - new WorkflowJobTemplate('get', template.id) - .then(model => model.extend('GET', 'copy')) + workflowTemplate + .create('get', template.id) + .then(model => model.extend('get', 'copy')) .then(model => { const action = () => { + Wait('start'); model.copy() .then(({ id }) => { const params = { workflow_job_template_id: id }; $state.go('templates.editWorkflowJobTemplate', params, { reload: true }); }) - .catch(({ data, status }) => { - const params = { hdr: 'Error!', msg: `Call to copy failed. Return status: ${status}` }; - ProcessErrors($scope, data, status, null, params); - }); + .catch(createErrorHandler('copy workflow', 'POST')) + .finally(() => Wait('stop')); }; + if (model.get('related.copy.can_copy_without_user_input')) { action(); } else if (model.get('related.copy.can_copy')) { Prompt({ - hdr: 'Copy Workflow', action, - actionText: 'COPY', + actionText: strings.get('COPY'), + body: buildWorkflowCopyPromptHTML(model.get('related.copy')), class: 'Modal-primaryButton', - body: buildWorkflowCopyPromptHTML(model.get('related.copy')) + hdr: strings.get('actions.COPY_WORKFLOW'), }); } else { - Alert(ERROR_WORKFLOW_COPY, ALERT_NO_PERMISSION); + Alert(strings.get('error.COPY'), strings.get('alert.NO_PERMISSION')); } }) - .catch(({ data, status }) => { - const params = { hdr: 'Error!', msg: `Call to copy failed. Return status: ${status}` }; - ProcessErrors($scope, data, status, null, params); - }) + .catch(createErrorHandler('copy workflow', 'GET')) .finally(() => Wait('stop')); - }; - - $scope.displayTemplateDeletePrompt = (template, body) => { - const action = () => { - function handleSuccessfulDelete (isWorkflow) { - let reloadListStateParams = null; - let stateParamID; - - if (isWorkflow) { - stateParamID = $state.params.workflow_job_template_id; - } else { - stateParamID = $state.params.job_template_id; - } - - const templateSearch = _.get($state.params, 'template_search'); - const { page } = templateSearch; - - if ($scope.templates.length === 1 && !_.isEmpty(page) && page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - - const pageNum = (parseInt(reloadListStateParams.template_search.page, 0) - 1); - reloadListStateParams.template_search.page = pageNum.toString(); - } - - if (parseInt(stateParamID, 0) === template.id) { - $state.go('templates', reloadListStateParams, { reload: true }); - } else { - $state.go('.', reloadListStateParams, { reload: true }); - } - - Wait('stop'); - } // end handler - - let deleteServiceMethod; - let failMsg; - - if (isWorkflowTemplate(template)) { - deleteServiceMethod = TemplatesService.deleteWorkflowJobTemplate; - failMsg = 'Call to delete workflow job template failed. DELETE returned status: '; - } else if (isJobTemplate(template)) { - deleteServiceMethod = TemplatesService.deleteJobTemplate; - failMsg = 'Call to delete job template failed. DELETE returned status: '; - } else { - Alert(ERROR_UNKNOWN, ALERT_UNKNOWN_DELETE); - return; - } - - $('#prompt-modal').modal('hide'); - Wait('start'); - - deleteServiceMethod(template.id) - .then(() => handleSuccessfulDelete(isWorkflowTemplate(template))) - .catch(res => { - ProcessErrors($scope, res.data, res.status, null, { - hdr: 'Error!', - msg: `${failMsg} ${res.status}.` - }); - }); - }; // end action - - Prompt({ - action, - actionText: 'DELETE', - body, - hdr: i18n._('Delete'), - resourceName: $filter('sanitize')(template.name) - }); - }; - - function buildTemplateDeletePromptHTML (dependentResourceCounts) { - const invalidateRelatedLines = []; - - let bodyHTML = ` -
- ${TemplatesStrings.get('deleteResource.CONFIRM', 'job template')} -
`; - - dependentResourceCounts.forEach(countObj => { - if (countObj.count && countObj.count > 0) { - invalidateRelatedLines.push(`
- - ${countObj.label} - - - ${countObj.count} - -
`); - } - }); - - if (invalidateRelatedLines && invalidateRelatedLines.length > 0) { - bodyHTML = ` -
- ${TemplatesStrings.get('deleteResource.USED_BY', 'job template')} - ${TemplatesStrings.get('deleteResource.CONFIRM', 'job template')} -
`; - invalidateRelatedLines.forEach(invalidateRelatedLine => { - bodyHTML += invalidateRelatedLine; - }); - } - - return bodyHTML; } - function buildWorkflowCopyPromptHTML (data) { - const { - credentials_unable_to_copy, - inventories_unable_to_copy, - job_templates_unable_to_copy - } = data; + function handleSuccessfulDelete(template) { + const { page } = _.get($state.params, 'template_search'); + let reloadListStateParams = null; - let itemsHTML = ''; - - if (job_templates_unable_to_copy.length > 0) { - itemsHTML += '
Unified Job Templates that cannot be copied
'; + if ($scope.templates.length === 1 && !_.isEmpty(page) && page !== '1') { + reloadListStateParams = _.cloneDeep($state.params); + const pageNumber = (parseInt(reloadListStateParams.template_search.page, 0) - 1); + reloadListStateParams.template_search.page = pageNumber.toString(); } - if (inventories_unable_to_copy.length > 0) { - itemsHTML += '
Node prompted inventories that cannot be copied
'; + if (parseInt($state.params.job_template_id, 0) === template.id) { + $state.go('templates', reloadListStateParams, { reload: true }); + } else if (parseInt($state.params.workflow_job_template_id, 0) === template.id) { + $state.go('templates', reloadListStateParams, { reload: true }); + } else { + $state.go('.', reloadListStateParams, { reload: true }); } + } - if (credentials_unable_to_copy.length > 0) { - itemsHTML += '
Node prompted credentials that cannot be copied
'; - } + function displayJobTemplateDeletePrompt(template, counts) { + Prompt({ + action() { + $('#prompt-modal').modal('hide'); + Wait('start'); + jobTemplate + .request('delete', template.id) + .then(() => handleSuccessfulDelete(template)) + .catch(createErrorHandler('delete template', 'DELETE')) + .finally(() => Wait('stop')); + }, + hdr: strings.get('DELETE'), + resourceName: $filter('sanitize')(template.name), + body: buildJobTemplateDeletePromptHTML(counts), + }); + } - const bodyHTML = ` + function displayWorkflowTemplateDeletePrompt(template) { + Prompt({ + action() { + $('#prompt-modal').modal('hide'); + Wait('start'); + workflowTemplate + .request('delete', template.id) + .then(() => handleSuccessfulDelete(template)) + .catch(createErrorHandler('delete template', 'DELETE')) + .finally(() => Wait('stop')) + }, + hdr: strings.get('DELETE'), + resourceName: $filter('sanitize')(template.name), + body: strings.get('deleteResource.CONFIRM', 'workflow template'), + }); + } + + function buildJobTemplateDeletePromptHTML(counts) { + const buildCount = count => `${count}`; + const buildLabel = label => ` + ${$filter('sanitize')(label)}`; + const buildCountLabel = ({ count, label }) => `
+ ${buildLabel(label)}${buildCount(count)}
`; + + const displayedCounts = counts.filter(({ count }) => count > 0); + + const html = ` + ${displayedCounts.length ? strings.get('deleteResource.USED_BY', 'job template') : ''} + ${strings.get('deleteResource.CONFIRM', 'job template')} + ${displayedCounts.map(buildCountLabel).join('')} + `; + + return html; + } + + function buildWorkflowCopyPromptHTML(data) { + const pull = (data, param) => _.get(data, param, []).map($filter('sanitize')); + + const credentials = pull(data, 'credentials_unable_to_copy'); + const inventories = pull(data, 'inventories_unable_to_copy'); + const templates = pull(data, 'templates_unable_to_copy'); + + const html = `
- You do not have access to all resources used by this workflow. - Resources that you don't have access to will not be copied - and will result in an incomplete workflow. + ${strings.get('warnings.WORKFLOW_RESTRICTED_COPY')}
- ${itemsHTML} + ${templates.length ? `
Unified Job Templates
` : ''} +
+
+ ${credentials.length ? `
Credentials
` : ''} +
+
+ ${inventories.length ? `
Inventories
` : ''}
`; - return bodyHTML; + return html; } - function submitJobTemplate(template) { - if(template) { - if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) { - let jobTemplate = new JobTemplate(); + function runJobTemplate(template) { + const selectedJobTemplate = jobTemplate.create(); + const preLaunchPromises = [ + selectedJobTemplate.getLaunch(template.id), + selectedJobTemplate.optionsLaunch(template.id), + ]; - $q.all([jobTemplate.optionsLaunch(template.id), jobTemplate.getLaunch(template.id)]) - .then((responses) => { - if(jobTemplate.canLaunchWithoutPrompt()) { - jobTemplate.postLaunch({id: template.id}) - .then((launchRes) => { - $state.go('jobResult', { id: launchRes.data.job }, { reload: true }); - }); - } else { + Promise.all(preLaunchPromises) + .then(([launchData, launchOptions]) => { + if (selectedJobTemplate.canLaunchWithoutPrompt()) { + return selectedJobTemplate + .postLaunch({ id: template.id }) + .then(({ data }) => { + $state.go('jobResult', { id: data.job }, { reload: true }) + }); + } - if(responses[1].data.survey_enabled) { + const promptData = { + launchConf: launchData.data, + launchOptions: launchOptions.data, + template: template.id, + prompts: PromptService.processPromptValues({ + launchConf: launchData.data, + launchOptions: launchOptions.data + }), + triggerModalOpen: true, + }; - // go out and get the survey questions - jobTemplate.getSurveyQuestions(template.id) - .then((surveyQuestionRes) => { + if (launchData.data.survey_enabled) { + selectedJobTemplate.getSurveyQuestions(template.id) + .then(({ data }) => { + const processed = PromptService.processSurveyQuestions({ surveyQuestions: data.spec }); + promptData.surveyQuestions = processed.surveyQuestions; + $scope.promptData = promptData; + }); + } else { + $scope.promptData = promptData; + } + }); + } - let processed = PromptService.processSurveyQuestions({ - surveyQuestions: surveyQuestionRes.data.spec - }); - - $scope.promptData = { - launchConf: responses[1].data, - launchOptions: responses[0].data, - surveyQuestions: processed.surveyQuestions, - template: template.id, - prompts: PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data - }), - triggerModalOpen: true - }; - }); - } - else { - $scope.promptData = { - launchConf: responses[1].data, - launchOptions: responses[0].data, - template: template.id, - prompts: PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data - }), - triggerModalOpen: true - }; - } - } - }); - } - else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) { - InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'workflow_job_template' }); - } - else { - // Something went wrong - Let the user know that we're unable to launch because we don't know - // what type of job template this is - Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while launching.'); - } - } - else { - Alert('Error: Unable to launch template', 'Template parameter is missing'); - } + function runWorkflowTemplate(template) { + InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'workflow_job_template' }); } $scope.launchJob = () => { - - let jobLaunchData = { + const jobLaunchData = { extra_vars: $scope.promptData.extraVars }; - let jobTemplate = new JobTemplate(); - - if($scope.promptData.launchConf.ask_tags_on_launch){ + if ($scope.promptData.launchConf.ask_tags_on_launch){ jobLaunchData.job_tags = $scope.promptData.prompts.tags.value.map(a => a.value).join(); } - if($scope.promptData.launchConf.ask_skip_tags_on_launch){ + + if ($scope.promptData.launchConf.ask_skip_tags_on_launch){ jobLaunchData.skip_tags = $scope.promptData.prompts.skipTags.value.map(a => a.value).join(); } - if($scope.promptData.launchConf.ask_limit_on_launch && _.has($scope, 'promptData.prompts.limit.value')){ + + if ($scope.promptData.launchConf.ask_limit_on_launch && _.has($scope, 'promptData.prompts.limit.value')){ jobLaunchData.limit = $scope.promptData.prompts.limit.value; } - if($scope.promptData.launchConf.ask_job_type_on_launch && _.has($scope, 'promptData.prompts.jobType.value.value')) { + + if ($scope.promptData.launchConf.ask_job_type_on_launch && _.has($scope, 'promptData.prompts.jobType.value.value')) { jobLaunchData.job_type = $scope.promptData.prompts.jobType.value.value; } - if($scope.promptData.launchConf.ask_verbosity_on_launch && _.has($scope, 'promptData.prompts.verbosity.value.value')) { + + if ($scope.promptData.launchConf.ask_verbosity_on_launch && _.has($scope, 'promptData.prompts.verbosity.value.value')) { jobLaunchData.verbosity = $scope.promptData.prompts.verbosity.value.value; } - if($scope.promptData.launchConf.ask_inventory_on_launch && !Empty($scope.promptData.prompts.inventory.value.id)){ + + if ($scope.promptData.launchConf.ask_inventory_on_launch && !_.isEmpty($scope.promptData.prompts.inventory.value.id)){ jobLaunchData.inventory_id = $scope.promptData.prompts.inventory.value.id; } - if($scope.promptData.launchConf.ask_credential_on_launch){ + + if ($scope.promptData.launchConf.ask_credential_on_launch){ jobLaunchData.credentials = []; $scope.promptData.prompts.credentials.value.forEach((credential) => { jobLaunchData.credentials.push(credential.id); }); } - if($scope.promptData.launchConf.ask_diff_mode_on_launch && _.has($scope, 'promptData.prompts.diffMode.value')) { + + if ($scope.promptData.launchConf.ask_diff_mode_on_launch && _.has($scope, 'promptData.prompts.diffMode.value')) { jobLaunchData.diff_mode = $scope.promptData.prompts.diffMode.value; } - if($scope.promptData.prompts.credentials.passwords) { + if ($scope.promptData.prompts.credentials.passwords) { _.forOwn($scope.promptData.prompts.credentials.passwords, (val, key) => { - if(!jobLaunchData.credential_passwords) { + if (!jobLaunchData.credential_passwords) { jobLaunchData.credential_passwords = {}; } - if(key === "ssh_key_unlock") { + if (key === "ssh_key_unlock") { jobLaunchData.credential_passwords.ssh_key_unlock = val.value; - } else if(key !== "vault") { + } else if (key !== "vault") { jobLaunchData.credential_passwords[`${key}_password`] = val.value; } else { _.each(val, (vaultCred) => { @@ -521,42 +415,30 @@ function TemplatesListController ( delete jobLaunchData.extra_vars; } - jobTemplate.postLaunch({ + jobTemplate.create().postLaunch({ id: $scope.promptData.template, launchData: jobLaunchData - }).then((launchRes) => { + }) + .then((launchRes) => { $state.go('jobResult', { id: launchRes.data.job }, { reload: true }); - }).catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), - msg: i18n.sprintf(i18n._('Failed to launch job template. POST returned: %d'), $scope.promptData.template, status) }); - }); + }) + .catch(createErrorHandler('launch job template', 'POST')) }; } -TemplatesListController.$inject = [ - '$scope', - '$rootScope', - 'Alert', - 'TemplateList', - 'Prompt', - 'ProcessErrors', - 'GetBasePath', - 'InitiatePlaybookRun', - 'Wait', - '$state', +ListTemplatesController.$inject = [ '$filter', + '$scope', + '$state', + 'Alert', 'Dataset', - 'rbacUiControlService', - 'TemplatesService', - 'QuerySet', - 'i18n', - 'JobTemplateModel', - 'WorkflowJobTemplateModel', - 'TemplatesStrings', - '$q,' - 'Empty', - 'i18n', + 'InitiatePlaybookRun', + 'ProcessErrors', + 'Prompt', 'PromptService', + 'resolvedModels', + 'TemplatesStrings', + 'Wait', ]; -export default TemplatesListController; \ No newline at end of file +export default ListTemplatesController; diff --git a/awx/ui/client/features/templates/list.route.js b/awx/ui/client/features/templates/list.route.js index 384164b421..2750a0e8a7 100644 --- a/awx/ui/client/features/templates/list.route.js +++ b/awx/ui/client/features/templates/list.route.js @@ -2,14 +2,6 @@ import ListController from './list-templates.controller'; const listTemplate = require('~features/templates/list.view.html'); import { N_ } from '../../src/i18n'; -function TemplatesResolve (UnifiedJobTemplate) { - return new UnifiedJobTemplate(['get', 'options']); -} - -TemplatesResolve.$inject = [ - 'UnifiedJobTemplateModel' -]; - export default { name: 'templates', route: '/templates', @@ -32,10 +24,10 @@ export default { }, params: { template_search: { + dynamic: true, value: { - type: 'workflow_job_template,job_template' - }, - dynamic: true + type: 'workflow_job_template,job_template', + }, } }, searchPrefix: 'template', @@ -43,16 +35,34 @@ export default { '@': { controller: ListController, templateUrl: listTemplate, - controllerAs: 'vm' + controllerAs: 'vm', } }, resolve: { - resolvedModels: TemplatesResolve, - Dataset: ['TemplateList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || GetBasePath(list.name); - return qs.search(path, $stateParams[`${list.iterator}_search`]); + resolvedModels: [ + 'JobTemplateModel', + 'WorkflowJobTemplateModel', + (JobTemplate, WorkflowJobTemplate) => { + const models = [ + new JobTemplate(['options']), + new WorkflowJobTemplate(['options']), + ]; + return Promise.all(models); + }, + ], + Dataset: [ + '$stateParams', + 'Wait', + 'GetBasePath', + 'QuerySet', + ($stateParams, Wait, GetBasePath, qs) => { + const searchParam = $stateParams.template_search; + const searchPath = GetBasePath('unified_job_templates'); + + Wait('start'); + return qs.search(searchPath, searchParam) + .finally(() => Wait('stop')) } - ] + ], } }; diff --git a/awx/ui/client/features/templates/list.view.html b/awx/ui/client/features/templates/list.view.html index d22d64cfb4..7759287085 100644 --- a/awx/ui/client/features/templates/list.view.html +++ b/awx/ui/client/features/templates/list.view.html @@ -89,16 +89,16 @@ + value="{{ vm.getLastRan(template) }}">
- - t.s('The {{ resourceType }} is currently being used by other resources.', { resourceType }), CONFIRM: resourceType => t.s('Are you sure you want to delete this {{ resourceType }}?', { resourceType }) }; + this.error = { HEADER: t.s('Error!'), - CALL: ({ path, status }) => t.s('Call to {{ path }} failed. DELETE returned status: {{ status }}.', { path, status }) + CALL: ({ path, action, status }) => t.s('Call to {{ path }} failed. {{ action }} returned status: {{ status }}.', { path, action, status }), }; this.ALERT = ({ header, body }) => t.s('{{ header }} {{ body }}', { header, body }); diff --git a/awx/ui/client/src/templates/copy-template/template-copy.service.js b/awx/ui/client/src/templates/copy-template/template-copy.service.js deleted file mode 100644 index e55e0d2da3..0000000000 --- a/awx/ui/client/src/templates/copy-template/template-copy.service.js +++ /dev/null @@ -1,77 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default - ['$rootScope', 'Rest', 'ProcessErrors', 'GetBasePath', 'moment', - function($rootScope, Rest, ProcessErrors, GetBasePath, moment){ - return { - get: function(id){ - var defaultUrl = GetBasePath('job_templates') + '?id=' + id; - Rest.setUrl(defaultUrl); - return Rest.get() - .then(response => response) - .catch((error) => { - ProcessErrors($rootScope, error.response, error.status, null, {hdr: 'Error!', - msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status}); - }); - }, - getSurvey: function(endpoint){ - Rest.setUrl(endpoint); - return Rest.get(); - }, - copySurvey: function(source, target){ - return this.getSurvey(source.related.survey_spec).then( (response) => { - Rest.setUrl(target.related.survey_spec); - return Rest.post(response.data); - }); - }, - set: function(results){ - var defaultUrl = GetBasePath('job_templates'); - var self = this; - Rest.setUrl(defaultUrl); - var name = this.buildName(results[0].name); - results[0].name = name + ' @ ' + moment().format('h:mm:ss a'); // 2:49:11 pm - return Rest.post(results[0]) - .then((response) => { - // also copy any associated survey_spec - if (results[0].summary_fields.survey){ - return self.copySurvey(results[0], response.data).then( () => response.data); - } - else { - return response.data; - } - }) - .catch(({res, status}) => { - ProcessErrors($rootScope, res, status, null, {hdr: 'Error!', - msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status}); - }); - }, - buildName: function(name){ - var result = name.split('@')[0]; - return result; - }, - getWorkflowCopy: function(id) { - let url = GetBasePath('workflow_job_templates'); - - url = url + id + '/copy'; - - Rest.setUrl(url); - return Rest.get(); - }, - getWorkflowCopyName: function(baseName) { - return `${baseName}@${moment().format('h:mm:ss a')}`; - }, - copyWorkflow: function(id, name) { - let url = GetBasePath('workflow_job_templates'); - - url = url + id + '/copy'; - - Rest.setUrl(url); - return Rest.post({ name }); - } - }; - } - ]; diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js index abc320451a..5d1eee77d8 100644 --- a/awx/ui/client/src/templates/main.js +++ b/awx/ui/client/src/templates/main.js @@ -15,7 +15,6 @@ import workflowChart from './workflows/workflow-chart/main'; import workflowMaker from './workflows/workflow-maker/main'; import workflowControls from './workflows/workflow-controls/main'; import workflowService from './workflows/workflow.service'; -import templateCopyService from './copy-template/template-copy.service'; import WorkflowForm from './workflows.form'; import CompletedJobsList from './completed-jobs.list'; import InventorySourcesList from './inventory-sources.list'; @@ -29,7 +28,6 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p ]) .service('TemplatesService', templatesService) .service('WorkflowService', workflowService) - .service('TemplateCopyService', templateCopyService) .factory('WorkflowForm', WorkflowForm) .factory('CompletedJobsList', CompletedJobsList) // TODO: currently being kept arround for rbac selection, templates within projects and orgs, etc.