diff --git a/awx/ui/client/features/templates/templates.strings.js b/awx/ui/client/features/templates/templates.strings.js index 6f338c873f..0b4e6be732 100644 --- a/awx/ui/client/features/templates/templates.strings.js +++ b/awx/ui/client/features/templates/templates.strings.js @@ -89,6 +89,10 @@ function TemplatesStrings (BaseString) { ns.warnings = { WORKFLOW_RESTRICTED_COPY: t.s('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.') }; + + ns.workflows = { + INVALID_JOB_TEMPLATE: t.s('This Job Template is missing a default inventory or project. This must be addressed in the Job Template form before this node can be saved.') + }; } TemplatesStrings.$inject = ['BaseStringService']; diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js index 35f8cd5965..dc83056fe7 100644 --- a/awx/ui/client/src/templates/main.js +++ b/awx/ui/client/src/templates/main.js @@ -18,7 +18,6 @@ import workflowService from './workflows/workflow.service'; import WorkflowForm from './workflows.form'; import InventorySourcesList from './inventory-sources.list'; import TemplateList from './templates.list'; -import TemplatesStrings from './templates.strings'; import listRoute from '~features/templates/routes/templatesList.route.js'; import templateCompletedJobsRoute from '~features/jobs/routes/templateCompletedJobs.route.js'; @@ -32,7 +31,6 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p // TODO: currently being kept arround for rbac selection, templates within projects and orgs, etc. .factory('TemplateList', TemplateList) .value('InventorySourcesList', InventorySourcesList) - .service('TemplatesStrings', TemplatesStrings) .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) { let stateTree, addJobTemplate, editJobTemplate, addWorkflow, editWorkflow, diff --git a/awx/ui/client/src/templates/templates.strings.js b/awx/ui/client/src/templates/templates.strings.js deleted file mode 100644 index 8eada73742..0000000000 --- a/awx/ui/client/src/templates/templates.strings.js +++ /dev/null @@ -1,7 +0,0 @@ -function TemplatesStrings (BaseString) { - BaseString.call(this, 'templates'); -} - -TemplatesStrings.$inject = ['BaseStringService']; - -export default TemplatesStrings; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/main.js b/awx/ui/client/src/templates/workflows/workflow-maker/main.js index 4cca4bf693..821dfe18aa 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/main.js +++ b/awx/ui/client/src/templates/workflows/workflow-maker/main.js @@ -1,11 +1,9 @@ import workflowMaker from './workflow-maker.directive'; import WorkflowMakerController from './workflow-maker.controller'; -import WorkflowMakerForm from './workflow-maker.form'; export default angular.module('templates.workflowMaker', []) // In order to test this controller I had to expose it at the module level // like so. Is this correct? Is there a better pattern for doing this? .controller('WorkflowMakerController', WorkflowMakerController) - .factory('WorkflowMakerForm', WorkflowMakerForm) .directive('workflowMaker', workflowMaker); diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less index 7a6dfdd650..a189cfae12 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less @@ -275,6 +275,10 @@ height: 100%; overflow: hidden; } +.WorkflowMaker-invalidJobTemplateWarning { + margin-bottom: 5px; + color: @default-err; +} .Key-list { margin: 0; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js index 5037f15644..51c5f31e22 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js @@ -5,15 +5,16 @@ *************************************************/ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', - '$state', 'ProcessErrors', 'CreateSelect2', 'WorkflowMakerForm', '$q', 'JobTemplateModel', - 'Empty', 'PromptService', 'Rest', - function($scope, WorkflowService, GetBasePath, TemplatesService, $state, - ProcessErrors, CreateSelect2, WorkflowMakerForm, $q, JobTemplate, - Empty, PromptService, Rest) { + '$state', 'ProcessErrors', 'CreateSelect2', '$q', 'JobTemplateModel', + 'Empty', 'PromptService', 'Rest', 'TemplatesStrings', + function($scope, WorkflowService, GetBasePath, TemplatesService, + $state, ProcessErrors, CreateSelect2, $q, JobTemplate, + Empty, PromptService, Rest, TemplatesStrings) { - let form = WorkflowMakerForm(); let promptWatcher, surveyQuestionWatcher; + $scope.strings = TemplatesStrings; + $scope.workflowMakerFormConfig = { nodeMode: "idle", activeTab: "jobs", @@ -184,7 +185,7 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', params.node.isNew = false; continueRecursing(data.data.id); }, function(error) { - ProcessErrors($scope, error.data, error.status, form, { + ProcessErrors($scope, error.data, error.status, null, { hdr: 'Error!', msg: 'Failed to add workflow node. ' + 'POST returned status: ' + @@ -403,7 +404,11 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', $q.all(associatePromises.concat(credentialPromises)) .then(function() { $scope.closeDialog(); + }).catch(({data, status}) => { + ProcessErrors($scope, data, status, null); }); + }).catch(({data, status}) => { + ProcessErrors($scope, data, status, null); }); }; @@ -552,6 +557,8 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', } $scope.promptData = null; + $scope.selectedTemplateInvalid = false; + $scope.showPromptButton = false; // Reset the edgeConflict flag resetEdgeConflict(); @@ -647,6 +654,12 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', prompts.credentials.value = workflowNodeCredentials.concat(defaultCredsWithoutOverrides); + if ((!$scope.nodeBeingEdited.unifiedJobTemplate.inventory && !launchConf.ask_inventory_on_launch) || !$scope.nodeBeingEdited.unifiedJobTemplate.project) { + $scope.selectedTemplateInvalid = true; + } else { + $scope.selectedTemplateInvalid = false; + } + if (!launchConf.survey_enabled && !launchConf.ask_inventory_on_launch && !launchConf.ask_credential_on_launch && @@ -658,7 +671,6 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', !launchConf.ask_diff_mode_on_launch && !launchConf.survey_enabled && !launchConf.credential_needed_to_start && - !launchConf.inventory_needed_to_start && launchConf.passwords_needed_to_start.length === 0 && launchConf.variables_needed_to_start.length === 0) { $scope.showPromptButton = false; @@ -794,7 +806,7 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', $scope.nodeBeingEdited.unifiedJobTemplate = _.clone(data.data.results[0]); finishConfiguringEdit(); }, function(error) { - ProcessErrors($scope, error.data, error.status, form, { + ProcessErrors($scope, error.data, error.status, null, { hdr: 'Error!', msg: 'Failed to get unified job template. GET returned ' + 'status: ' + error.status @@ -946,8 +958,6 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', $scope.templateManuallySelected = function(selectedTemplate) { - $scope.selectedTemplate = angular.copy(selectedTemplate); - if (selectedTemplate.type === "job_template") { let jobTemplate = new JobTemplate(); @@ -955,6 +965,14 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', .then((responses) => { let launchConf = responses[1].data; + if ((!selectedTemplate.inventory && !launchConf.ask_inventory_on_launch) || !selectedTemplate.project) { + $scope.selectedTemplateInvalid = true; + } else { + $scope.selectedTemplateInvalid = false; + } + + $scope.selectedTemplate = angular.copy(selectedTemplate); + if (!launchConf.survey_enabled && !launchConf.ask_inventory_on_launch && !launchConf.ask_credential_on_launch && @@ -966,7 +984,6 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', !launchConf.ask_diff_mode_on_launch && !launchConf.survey_enabled && !launchConf.credential_needed_to_start && - !launchConf.inventory_needed_to_start && launchConf.passwords_needed_to_start.length === 0 && launchConf.variables_needed_to_start.length === 0) { $scope.showPromptButton = false; @@ -1028,6 +1045,8 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', }); } else { // TODO - clear out prompt data? + $scope.selectedTemplate = angular.copy(selectedTemplate); + $scope.selectedTemplateInvalid = false; $scope.showPromptButton = false; } }; @@ -1114,7 +1133,7 @@ export default ['$scope', 'WorkflowService', 'GetBasePath', 'TemplatesService', buildTreeFromNodes(); } }, function(error){ - ProcessErrors($scope, error.data, error.status, form, { + ProcessErrors($scope, error.data, error.status, null, { hdr: 'Error!', msg: 'Failed to get workflow job template nodes. GET returned ' + 'status: ' + error.status diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.form.js b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.form.js deleted file mode 100644 index 598f1678cd..0000000000 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.form.js +++ /dev/null @@ -1,183 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name forms.function:JobTemplate - * @description This form is for adding/editing a Job Template -*/ - -export default ['NotificationsList', 'i18n', '$rootScope', function(NotificationsList, i18n, $rootScope) { - return function() { - var WorkflowMakerFormObject = { - - addTitle: '', - editTitle: '', - name: 'workflow_maker', - basePath: 'job_templates', - tabs: false, - cancelButton: false, - showHeader: false, - - fields: { - edgeType: { - label: i18n._('Type'), - type: 'radio_group', - ngShow: 'selectedTemplate && edgeFlags.showTypeOptions', - ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)', - options: [ - { - label: i18n._('On Success'), - value: 'success', - ngShow: '!edgeFlags.typeRestriction || edgeFlags.typeRestriction === "successFailure"' - }, - { - label: i18n._('On Failure'), - value: 'failure', - ngShow: '!edgeFlags.typeRestriction || edgeFlags.typeRestriction === "successFailure"' - }, - { - label: i18n._('Always'), - value: 'always', - ngShow: '!edgeFlags.typeRestriction || edgeFlags.typeRestriction === "always"' - } - ], - awRequiredWhen: { - reqExpression: 'edgeFlags.showTypeOptions' - } - }, - credential: { - label: i18n._('Credential'), - type: 'lookup', - sourceModel: 'credential', - sourceField: 'name', - ngClick: 'lookUpCredential()', - requiredErrorMsg: i18n._("Please select a Credential."), - class: 'Form-formGroup--fullWidth', - awPopOver: "
" + i18n._("Select the credential you want the job to use when accessing the remote hosts. Choose the credential containing " + - " the username and SSH key or password that Ansible will need to log into the remote hosts.") + "
", - dataTitle: i18n._('Credential'), - dataPlacement: 'right', - dataContainer: "body", - ngShow: "selectedTemplate.ask_credential_on_launch", - ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)', - awRequiredWhen: { - reqExpression: 'selectedTemplate && selectedTemplate.ask_credential_on_launch' - } - }, - inventory: { - label: i18n._('Inventory'), - type: 'lookup', - sourceModel: 'inventory', - sourceField: 'name', - list: 'OrganizationList', - basePath: 'organization', - ngClick: 'lookUpInventory()', - requiredErrorMsg: i18n._("Please select an Inventory."), - class: 'Form-formGroup--fullWidth', - awPopOver: "" + i18n._("Select the inventory containing the hosts you want this job to manage.") + "
", - dataTitle: i18n._('Inventory'), - dataPlacement: 'right', - dataContainer: "body", - ngShow: "selectedTemplate.ask_inventory_on_launch", - ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)', - awRequiredWhen: { - reqExpression: 'selectedTemplate && selectedTemplate.ask_inventory_on_launch' - } - }, - job_type: { - label: i18n._('Job Type'), - type: 'select', - ngOptions: 'type.label for type in job_type_options track by type.value', - "default": 0, - class: 'Form-formGroup--fullWidth', - awPopOver: "" + i18n.sprintf(i18n._("When this template is submitted as a job, setting the type to %s will execute the playbook, running tasks " + - " on the selected hosts."), "run") + "
" +
- i18n.sprintf(i18n._("Setting the type to %s will not execute the playbook. Instead, %s will check playbook " +
- " syntax, test environment setup and report problems."), "check", "ansible
") + "
" + - i18n.sprintf(i18n._("Setting the type to %s will execute the playbook and store any " + - " scanned facts for use with " + $rootScope.BRAND_NAME + "'s System Tracking feature."), "scan") + "
", - dataTitle: i18n._('Job Type'), - dataPlacement: 'right', - dataContainer: "body", - ngShow: "selectedTemplate.ask_job_type_on_launch", - ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)', - awRequiredWhen: { - reqExpression: 'selectedTemplate && selectedTemplate.ask_job_type_on_launch' - } - }, - limit: { - label: i18n._('Limit'), - type: 'text', - class: 'Form-formGroup--fullWidth', - awPopOver: "" + i18n.sprintf(i18n._("Provide a host pattern to further constrain the list of hosts that will be managed or affected by the playbook. " + - "Multiple patterns can be separated by %s %s or %s"), ";", ":", ",") + "
" + - i18n.sprintf(i18n._("For more information and examples see " + - "%sthe Patterns topic at docs.ansible.com%s."), "", "") + "
", - dataTitle: i18n._('Limit'), - dataPlacement: 'right', - dataContainer: "body", - ngShow: "selectedTemplate.ask_limit_on_launch", - ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)' - }, - job_tags: { - label: i18n._('Job Tags'), - type: 'textarea', - rows: 5, - 'elementClass': 'Form-textInput', - class: 'Form-formGroup--fullWidth', - awPopOver: "" + i18n._("Provide a comma separated list of tags.") + "
\n" + - "" + i18n._("Tags are useful when you have a large playbook, and you want to run a specific part of a play or task.") + "
" + - "" + i18n._("Consult the Ansible documentation for further details on the usage of tags.") + "
", - dataTitle: i18n._("Job Tags"), - dataPlacement: "right", - dataContainer: "body", - ngShow: "selectedTemplate.ask_tags_on_launch", - ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)' - }, - skip_tags: { - label: i18n._('Skip Tags'), - type: 'textarea', - rows: 5, - 'elementClass': 'Form-textInput', - class: 'Form-formGroup--fullWidth', - awPopOver: "" + i18n._("Provide a comma separated list of tags.") + "
\n" + - "" + i18n._("Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task.") + "
" + - "" + i18n._("Consult the Ansible documentation for further details on the usage of tags.") + "
", - dataTitle: i18n._("Skip Tags"), - dataPlacement: "right", - dataContainer: "body", - ngShow: "selectedTemplate.ask_skip_tags_on_launch", - ngDisabled: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)' - } - }, - buttons: { - cancel: { - ngClick: 'cancelNodeForm()', - ngShow: '(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)' - }, - close: { - ngClick: 'cancelNodeForm()', - ngShow: '!(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)' - }, - select: { - ngClick: 'saveNodeForm()', - ngDisabled: "workflow_maker_form.$invalid || !selectedTemplate", - ngShow: '(workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)' - } - } - }; - - var itm; - for (itm in WorkflowMakerFormObject.related) { - if (WorkflowMakerFormObject.related[itm].include === "NotificationsList") { - WorkflowMakerFormObject.related[itm] = NotificationsList; - WorkflowMakerFormObject.related[itm].generateList = true; // tell form generator to call list generator and inject a list - } - } - return WorkflowMakerFormObject; - }; -}]; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html index 7016e57ebd..ad0a5fc9d5 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html @@ -93,31 +93,35 @@ -