From 3cd5cd518430479db596665282c7fb03eb91f8e8 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Mon, 16 Mar 2015 10:55:44 -0400 Subject: [PATCH 01/12] added scan job type to job type dropdown on the job template form. --- awx/ui/static/js/controllers/JobTemplates.js | 10 ++++++++-- awx/ui/static/js/forms/JobTemplates.js | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js index 063b376df9..990bb31e02 100644 --- a/awx/ui/static/js/controllers/JobTemplates.js +++ b/awx/ui/static/js/controllers/JobTemplates.js @@ -276,7 +276,8 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $ $scope.job_type_options = [ { value: 'run', label: 'Run' }, - { value: 'check', label: 'Check' } + { value: 'check', label: 'Check' }, + { value: 'scan' , label: 'Scan'} ]; $scope.verbosity_options = [ @@ -340,6 +341,10 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $ parent_scope: $scope }); + // $scope.jobTypeChange = function(){ + // + // }; + // Update playbook select whenever project value changes selectPlaybook = function (oldValue, newValue) { var url; @@ -591,7 +596,8 @@ export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, // Our job type options $scope.job_type_options = [ { value: 'run', label: 'Run' }, - { value: 'check', label: 'Check' } + { value: 'check', label: 'Check' }, + { value: 'scan', label: 'Scan'} ]; $scope.verbosity_options = [ diff --git a/awx/ui/static/js/forms/JobTemplates.js b/awx/ui/static/js/forms/JobTemplates.js index f78f556474..404e5f2729 100644 --- a/awx/ui/static/js/forms/JobTemplates.js +++ b/awx/ui/static/js/forms/JobTemplates.js @@ -66,6 +66,7 @@ export default label: 'Job Type', type: 'select', ngOptions: 'type.label for type in job_type_options track by type.value', + ngChange: 'jobTypeChange()', "default": 0, addRequired: true, editRequired: true, From 09488465308be380c892d76367b36961057f31a3 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 18 Mar 2015 14:08:54 -0400 Subject: [PATCH 02/12] Scan job templates Implemented saving a job template as type = scan. Can add/edit these jobs. Also on inventory properties accordion you can see the list of scan job templates for the inventory. --- awx/ui/static/js/app.js | 5 + awx/ui/static/js/controllers/Inventories.js | 43 ++++++++- awx/ui/static/js/controllers/JobTemplates.js | 99 ++++++++++++++++++-- awx/ui/static/js/forms/Inventories.js | 29 +++--- awx/ui/static/js/forms/JobTemplates.js | 19 +++- awx/ui/static/js/helpers/JobTemplates.js | 9 +- awx/ui/static/js/lists/ScanJobs.js | 4 +- awx/ui/static/js/shared/form-generator.js | 5 + 8 files changed, 179 insertions(+), 34 deletions(-) diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index fc0964a028..9256860cb2 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -267,6 +267,11 @@ var tower = angular.module('Tower', [ controller: JobTemplatesAdd }). + when('/inventories/:inventory_id/job_templates/:template_id', { + templateUrl: urlPrefix + 'partials/job_templates.html', + controller: JobTemplatesEdit + }). + when('/inventories/:inventory_id/manage', { templateUrl: urlPrefix + 'partials/inventory-manage.html', controller: InventoriesManage diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index 6cf2d5474a..67cd2cc271 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -482,7 +482,7 @@ InventoriesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit, PaginateInit, - LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, ParseVariableString, Stream) { + LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, ParseVariableString, Stream, RelatedSearchInit, RelatedPaginateInit) { ClearScope(); @@ -492,17 +492,30 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ generator = GenerateForm, inventory_id = $routeParams.inventory_id, master = {}, - fld, json_data, data; + fld, json_data, data, + relatedSets = {}; form.well = true; form.formLabelSize = null; form.formFieldSize = null; - + $scope.inventory_id = inventory_id; generator.inject(form, { mode: 'edit', related: true, scope: $scope }); generator.reset(); LoadBreadCrumbs(); + // After the project is loaded, retrieve each related set + if ($scope.inventoryLoadedRemove) { + $scope.inventoryLoadedRemove(); + } + $scope.projectLoadedRemove = $scope.$on('inventoryLoaded', function () { + var set, opts=[]; + + for (set in relatedSets) { + $scope.search(relatedSets[set].iterator); + } + }); + Wait('start'); Rest.setUrl(GetBasePath('inventory') + inventory_id + '/'); Rest.get() @@ -530,6 +543,19 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; } } + relatedSets = form.relatedSets(data.related); + + // Initialize related search functions. Doing it here to make sure relatedSets object is populated. + RelatedSearchInit({ + scope: $scope, + form: form, + relatedSets: relatedSets + }); + RelatedPaginateInit({ + scope: $scope, + relatedSets: relatedSets + }); + Wait('stop'); $scope.parseType = 'yaml'; ParseTypeChange({ @@ -546,6 +572,7 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ field: 'organization', input_type: 'radio' }); + $scope.$emit('inventoryLoaded'); }) .error(function (data, status) { ProcessErrors($scope, data, status, null, { hdr: 'Error!', @@ -627,11 +654,19 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ $scope.addScanJob = function(){ $location.path($location.path()+'/job_templates/add'); }; + + $scope.editScanJob = function(){ + $location.path($location.path()+'/job_templates/'+this.scan_job_template.id); + }; + + $scope.deleteScanJob = function(){ + $location.path($location.path()+'/job_templates/add'); + }; } InventoriesEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'generateList', 'OrganizationList', 'SearchInit', - 'PaginateInit', 'LookUpInit', 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', 'Stream' + 'PaginateInit', 'LookUpInit', 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', 'Stream', 'RelatedSearchInit', 'RelatedPaginateInit' ]; diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js index 990bb31e02..b2a0f26331 100644 --- a/awx/ui/static/js/controllers/JobTemplates.js +++ b/awx/ui/static/js/controllers/JobTemplates.js @@ -302,12 +302,13 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $ LookUpInit({ scope: $scope, form: form, - current_item: null, + current_item: ($routeParams.inventory_id !== undefined) ? $routeParams.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); @@ -341,14 +342,62 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $ parent_scope: $scope }); - // $scope.jobTypeChange = function(){ - // - // }; + + $scope.jobTypeChange = function(){ + if($scope.job_type){ + if($scope.job_type.value === 'scan'){ + $scope.default_scan = true; + $scope.project_name = 'Default'; + $scope.project = null; + } + else{ + $scope.default_scan = false; + $scope.project_name = null; + $scope.project = null; + $scope.playbook_options = []; + $scope.playbook = 'null'; + } + } + }; + + $scope.toggleScanInfo = function() { + if($scope.default_scan){ + $scope.project_name = 'Default'; + $scope.project = null; + } + if(!$scope.default_scan){ + $scope.project_name = null; + $scope.playbook_options = []; + $scope.playbook = 'null'; + } + }; + + if ($routeParams.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 = $routeParams.inventory_id; + Rest.setUrl(GetBasePath('inventory') + $routeParams.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 }); + }); + } // Update playbook select whenever project value changes selectPlaybook = function (oldValue, newValue) { var url; - if (oldValue !== newValue) { + if($scope.job_type.value === 'scan' && $scope.default_scan === true){ + $scope.playbook_options = ['Default']; + $scope.playbook = 'Default'; + Wait('stop'); + } + else if (oldValue !== newValue) { if ($scope.project) { Wait('start'); url = GetBasePath('projects') + $scope.project + '/playbooks/'; @@ -477,7 +526,10 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $ } } 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) { @@ -621,9 +673,42 @@ export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $scope.playbook = null; generator.reset(); + $scope.jobTypeChange = function(){ + if($scope.job_type){ + if($scope.job_type.value === 'scan'){ + $scope.default_scan = true; + $scope.project_name = 'Default'; + $scope.project = null; + } + else{ + $scope.default_scan = false; + $scope.project_name = null; + $scope.playbook_options = []; + $scope.playbook = 'null'; + } + } + }; + + $scope.toggleScanInfo = function() { + if($scope.default_scan){ + $scope.project_name = 'Default'; + $scope.project = null; + } + if(!$scope.default_scan){ + $scope.project_name = null; + $scope.playbook_options = []; + $scope.playbook = 'null'; + } + }; + getPlaybooks = function (project) { var url; - if (!Empty(project)) { + if($scope.job_type.value === 'scan' && $scope.default_scan === true){ + $scope.playbook_options = ['Default']; + $scope.playbook = 'Default'; + Wait('stop'); + } + else if (!Empty(project)) { url = GetBasePath('projects') + project + '/playbooks/'; Wait('start'); Rest.setUrl(url); diff --git a/awx/ui/static/js/forms/Inventories.js b/awx/ui/static/js/forms/Inventories.js index 5d0bd79d60..a67919453d 100644 --- a/awx/ui/static/js/forms/Inventories.js +++ b/awx/ui/static/js/forms/Inventories.js @@ -97,10 +97,10 @@ export default }, related: { - scan_jobs: { + scan_job_templates: { type: 'collection', title: 'Scan Jobs', - iterator: 'scan_job', + iterator: 'scan_job_template', index: false, open: false, @@ -109,14 +109,15 @@ export default ngClick: "addScanJob(inventory_id)", icon: 'icon-plus', label: 'Add', - awToolTip: 'Add a scan job' + awToolTip: 'Add a scan job template' } }, fields: { name: { key: true, - label: 'Name' + label: 'Name', + linkTo: '/#/inventories/{{inventory_id}}/job_templates/{{scan_job_template.id}}' }, description: { label: 'Description' @@ -126,17 +127,17 @@ export default fieldActions: { edit: { label: 'Edit', - ngClick: "edit('organizations', organization.id, organization.name)", + ngClick: "editScanJob(inventory_id)", icon: 'icon-edit', - awToolTip: 'Edit the organization', + awToolTip: 'Edit the scan job template', 'class': 'btn btn-default' }, "delete": { label: 'Delete', - ngClick: "delete('organizations', organization.id, organization.name, 'organizations')", + ngClick: "deleteScanJob(inventory_id)", icon: 'icon-trash', "class": 'btn-danger', - awToolTip: 'Delete the organization' + awToolTip: 'Delete the scan job template' } } } @@ -144,14 +145,10 @@ export default relatedSets: function(urls) { return { - scan_jobs: { - iterator: 'scan_job', - url: urls.organizations - }, - // schedules: { - // iterator: 'schedule', - // url: urls.schedules - // } + scan_job_templates: { + iterator: 'scan_job_template', + url: urls.scan_job_templates + } }; } diff --git a/awx/ui/static/js/forms/JobTemplates.js b/awx/ui/static/js/forms/JobTemplates.js index 404e5f2729..96e6db7d4f 100644 --- a/awx/ui/static/js/forms/JobTemplates.js +++ b/awx/ui/static/js/forms/JobTemplates.js @@ -102,7 +102,9 @@ export default awPopOver: "

Select the project containing the playbook you want this job to execute.

", dataTitle: 'Project', dataPlacement: 'right', - dataContainer: "body" + dataContainer: "body", + ngDisabled: 'default_scan === true', + ngRequired: 'default_scan === false' }, playbook: { label: 'Playbook', @@ -114,7 +116,20 @@ export default awPopOver: "

Select the playbook to be executed by this job.

", dataTitle: 'Playbook', dataPlacement: 'right', - dataContainer: "body" + dataContainer: "body", + ngDisabled: 'default_scan === true', + ngRequired: 'default_scan === false' + }, + default_scan: { + label: "Use default scan job project and playbook", + type: 'checkbox', + ngChange: 'toggleScanInfo()', + ngShow: 'job_type.value === "scan"', + column: 1, + awPopOver: "

Scan jobs templates use a default project and default playbook. Uncheck this box to override these defaults.

", + dataTitle: 'Scan jobs', + dataPlacement: 'right', + dataContainer: "body" }, credential: { label: 'Machine Credential', diff --git a/awx/ui/static/js/helpers/JobTemplates.js b/awx/ui/static/js/helpers/JobTemplates.js index 5e55668282..7ab79fc385 100644 --- a/awx/ui/static/js/helpers/JobTemplates.js +++ b/awx/ui/static/js/helpers/JobTemplates.js @@ -171,6 +171,12 @@ angular.module('JobTemplatesHelper', ['Utilities']) input_type: "radio" }); + + if(scope.project === "" && scope.playbook === ""){ + scope.default_scan = true; + scope.toggleScanInfo(); + } + RelatedSearchInit({ scope: scope, form: form, @@ -193,7 +199,4 @@ angular.module('JobTemplatesHelper', ['Utilities']) }; }; - - - }]); diff --git a/awx/ui/static/js/lists/ScanJobs.js b/awx/ui/static/js/lists/ScanJobs.js index c13b864f4f..33b1d6b0f8 100644 --- a/awx/ui/static/js/lists/ScanJobs.js +++ b/awx/ui/static/js/lists/ScanJobs.js @@ -15,8 +15,8 @@ export default angular.module('ScanJobsListDefinition', []) .value( 'ScanJobsList', { - name: 'scan_jobs', - iterator: 'scan_job', + name: 'scan_job_templates', + iterator: 'scan_job_template', editTitle: 'Scan Jobs', 'class': 'table-condensed', index: false, diff --git a/awx/ui/static/js/shared/form-generator.js b/awx/ui/static/js/shared/form-generator.js index 9affa6dcd8..516b478081 100644 --- a/awx/ui/static/js/shared/form-generator.js +++ b/awx/ui/static/js/shared/form-generator.js @@ -1107,6 +1107,8 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += "\" "; html += this.attr(field, 'ngOptions'); html += (field.ngChange) ? this.attr(field, 'ngChange') : ""; + html += (field.ngDisabled) ? this.attr(field, 'ngDisabled'): ""; + html += (field.ngRequired) ? this.attr(field, 'ngRequired') : ""; html += buildId(field, fld, this.form); html += (options.mode === 'edit' && field.editRequired) ? "required " : ""; html += (options.mode === 'add' && field.addRequired) ? "required " : ""; @@ -1331,6 +1333,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += "\n"; html += "\n"; html += "\n"; html += " Date: Wed, 18 Mar 2015 15:15:48 -0400 Subject: [PATCH 03/12] On Job Template save: redirect to proper dest return to job template list if adding/editing a job template from the list; likewise return to hte inventory properties if adding/editing from that page --- awx/ui/static/js/controllers/Inventories.js | 39 ++++++++++++++++---- awx/ui/static/js/controllers/JobTemplates.js | 24 +++++++++++- awx/ui/static/js/forms/Inventories.js | 8 ++-- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index 67cd2cc271..8360d1463b 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -482,7 +482,7 @@ InventoriesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit, PaginateInit, - LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, ParseVariableString, Stream, RelatedSearchInit, RelatedPaginateInit) { + LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, ParseVariableString, Stream, RelatedSearchInit, RelatedPaginateInit, Prompt) { ClearScope(); @@ -490,6 +490,7 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ var defaultUrl = GetBasePath('inventory'), form = InventoryForm(), generator = GenerateForm, + jobtemplateUrl = GetBasePath('job_templates'), inventory_id = $routeParams.inventory_id, master = {}, fld, json_data, data, @@ -498,7 +499,7 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ form.well = true; form.formLabelSize = null; form.formFieldSize = null; - $scope.inventory_id = inventory_id; + $scope.inventory_id = inventory_id; generator.inject(form, { mode: 'edit', related: true, scope: $scope }); generator.reset(); @@ -509,8 +510,7 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ $scope.inventoryLoadedRemove(); } $scope.projectLoadedRemove = $scope.$on('inventoryLoaded', function () { - var set, opts=[]; - + var set; for (set in relatedSets) { $scope.search(relatedSets[set].iterator); } @@ -659,14 +659,37 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ $location.path($location.path()+'/job_templates/'+this.scan_job_template.id); }; - $scope.deleteScanJob = function(){ - $location.path($location.path()+'/job_templates/add'); - }; + $scope.deleteScanJob = function () { + var id = this.scan_job_template.id , + action = function () { + $('#prompt-modal').modal('hide'); + Wait('start'); + var url = jobtemplateUrl+id; + Rest.setUrl(url); + Rest.destroy() + .success(function () { + $('#prompt-modal').modal('hide'); + $scope.search(form.related.scan_job_templates.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: '
Delete job template ' + this.scan_job_template.name + '?
', + action: action + }); + }; + } InventoriesEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'generateList', 'OrganizationList', 'SearchInit', - 'PaginateInit', 'LookUpInit', 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', 'Stream', 'RelatedSearchInit', 'RelatedPaginateInit' + 'PaginateInit', 'LookUpInit', 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', 'Stream', 'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt' ]; diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js index b2a0f26331..a898c50308 100644 --- a/awx/ui/static/js/controllers/JobTemplates.js +++ b/awx/ui/static/js/controllers/JobTemplates.js @@ -486,7 +486,17 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $ function saveCompleted() { - setTimeout(function() { $scope.$apply(function() { $location.path('/job_templates'); }); }, 500); + setTimeout(function() { + $scope.$apply(function() { + var base = $location.path().replace(/^\//, '').split('/')[0]; + if (base === 'job_templates') { + ReturnToCaller(); + } + else { + ReturnToCaller(1); + } + }); + }, 500); } if ($scope.removeTemplateSaveSuccess) { @@ -979,7 +989,17 @@ export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, }); function saveCompleted() { - setTimeout(function() { $scope.$apply(function() { $location.path('/job_templates'); }); }, 500); + setTimeout(function() { + $scope.$apply(function() { + var base = $location.path().replace(/^\//, '').split('/')[0]; + if (base === 'job_templates') { + ReturnToCaller(); + } + else { + ReturnToCaller(1); + } + }); + }, 500); } if ($scope.removeTemplateSaveSuccess) { diff --git a/awx/ui/static/js/forms/Inventories.js b/awx/ui/static/js/forms/Inventories.js index a67919453d..00db33e3e6 100644 --- a/awx/ui/static/js/forms/Inventories.js +++ b/awx/ui/static/js/forms/Inventories.js @@ -99,14 +99,14 @@ export default related: { scan_job_templates: { type: 'collection', - title: 'Scan Jobs', + title: 'Scan Job Templates', iterator: 'scan_job_template', index: false, open: false, actions: { add: { - ngClick: "addScanJob(inventory_id)", + ngClick: "addScanJob()", icon: 'icon-plus', label: 'Add', awToolTip: 'Add a scan job template' @@ -127,14 +127,14 @@ export default fieldActions: { edit: { label: 'Edit', - ngClick: "editScanJob(inventory_id)", + ngClick: "editScanJob()", icon: 'icon-edit', awToolTip: 'Edit the scan job template', 'class': 'btn btn-default' }, "delete": { label: 'Delete', - ngClick: "deleteScanJob(inventory_id)", + ngClick: "deleteScanJob()", icon: 'icon-trash', "class": 'btn-danger', awToolTip: 'Delete the scan job template' From 2885ad4cb28c1e62789ce9cc535098fc75465186 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 18 Mar 2015 21:41:20 -0400 Subject: [PATCH 04/12] fixed breadcrumb for when editing a job template from an inventory properties page --- awx/ui/static/js/controllers/Inventories.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index 8360d1463b..d872b58fe4 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -503,7 +503,7 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ generator.inject(form, { mode: 'edit', related: true, scope: $scope }); generator.reset(); - LoadBreadCrumbs(); + // After the project is loaded, retrieve each related set if ($scope.inventoryLoadedRemove) { @@ -556,6 +556,11 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ relatedSets: relatedSets }); + LoadBreadCrumbs({ + path: $location.path(), + title: $scope.inventory_name + }); + Wait('stop'); $scope.parseType = 'yaml'; ParseTypeChange({ From 8b710c191b740e2dffe6ad8c9acaf4a22759746c Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Thu, 19 Mar 2015 11:22:54 -0400 Subject: [PATCH 05/12] fix small error w/ playbook options when type=scan --- awx/ui/static/js/controllers/JobTemplates.js | 59 ++++++++++---------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js index a898c50308..8de864f0ca 100644 --- a/awx/ui/static/js/controllers/JobTemplates.js +++ b/awx/ui/static/js/controllers/JobTemplates.js @@ -349,6 +349,7 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $ $scope.default_scan = true; $scope.project_name = 'Default'; $scope.project = null; + $scope.toggleScanInfo(); } else{ $scope.default_scan = false; @@ -683,34 +684,6 @@ export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $scope.playbook = null; generator.reset(); - $scope.jobTypeChange = function(){ - if($scope.job_type){ - if($scope.job_type.value === 'scan'){ - $scope.default_scan = true; - $scope.project_name = 'Default'; - $scope.project = null; - } - else{ - $scope.default_scan = false; - $scope.project_name = null; - $scope.playbook_options = []; - $scope.playbook = 'null'; - } - } - }; - - $scope.toggleScanInfo = function() { - if($scope.default_scan){ - $scope.project_name = 'Default'; - $scope.project = null; - } - if(!$scope.default_scan){ - $scope.project_name = null; - $scope.playbook_options = []; - $scope.playbook = 'null'; - } - }; - getPlaybooks = function (project) { var url; if($scope.job_type.value === 'scan' && $scope.default_scan === true){ @@ -749,6 +722,36 @@ export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, } }; + $scope.jobTypeChange = function(){ + if($scope.job_type){ + if($scope.job_type.value === 'scan'){ + $scope.default_scan = true; + $scope.project_name = 'Default'; + $scope.project = null; + $scope.toggleScanInfo(); + } + else{ + $scope.default_scan = false; + $scope.project_name = null; + $scope.playbook_options = []; + $scope.playbook = 'null'; + } + } + }; + + $scope.toggleScanInfo = function() { + if($scope.default_scan){ + $scope.project_name = 'Default'; + $scope.project = null; + getPlaybooks(); + } + if(!$scope.default_scan){ + $scope.project_name = null; + $scope.playbook_options = []; + $scope.playbook = 'null'; + } + }; + // Detect and alert user to potential SCM status issues checkSCMStatus = function () { if (!Empty($scope.project)) { From 2883a70cbafc552295842fc9f60a3279205958a1 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Thu, 19 Mar 2015 11:50:44 -0400 Subject: [PATCH 06/12] adding launch button for scan jobs on inventory properties page. I also adjusted the indentation on the job submission page so that it was completly crazy. --- awx/ui/static/js/controllers/Inventories.js | 8 +- awx/ui/static/js/forms/Inventories.js | 6 + awx/ui/static/js/helpers/JobSubmission.js | 2098 +++++++++---------- 3 files changed, 1061 insertions(+), 1051 deletions(-) diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index d872b58fe4..830d8c1f1e 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -482,7 +482,7 @@ InventoriesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit, PaginateInit, - LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, ParseVariableString, Stream, RelatedSearchInit, RelatedPaginateInit, Prompt) { + LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, ParseVariableString, Stream, RelatedSearchInit, RelatedPaginateInit, Prompt, PlaybookRun) { ClearScope(); @@ -660,6 +660,10 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ $location.path($location.path()+'/job_templates/add'); }; + $scope.launchScanJob = function(){ + PlaybookRun({ scope: $scope, id: this.scan_job_template.id }); + }; + $scope.editScanJob = function(){ $location.path($location.path()+'/job_templates/'+this.scan_job_template.id); }; @@ -694,7 +698,7 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ InventoriesEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'generateList', 'OrganizationList', 'SearchInit', - 'PaginateInit', 'LookUpInit', 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', 'Stream', 'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt' + 'PaginateInit', 'LookUpInit', 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', 'Stream', 'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt', 'PlaybookRun' ]; diff --git a/awx/ui/static/js/forms/Inventories.js b/awx/ui/static/js/forms/Inventories.js index 00db33e3e6..1b20e523f4 100644 --- a/awx/ui/static/js/forms/Inventories.js +++ b/awx/ui/static/js/forms/Inventories.js @@ -125,6 +125,12 @@ export default }, fieldActions: { + submit: { + label: 'Launch', + ngClick: "launchScanJob()", + awToolTip: 'Launch the scan job template', + 'class': 'btn btn-default' + }, edit: { label: 'Edit', ngClick: "editScanJob()", diff --git a/awx/ui/static/js/helpers/JobSubmission.js b/awx/ui/static/js/helpers/JobSubmission.js index c673bb3122..41626056ad 100644 --- a/awx/ui/static/js/helpers/JobSubmission.js +++ b/awx/ui/static/js/helpers/JobSubmission.js @@ -1,9 +1,9 @@ /********************************************* - * Copyright (c) 2014 AnsibleWorks, Inc. - * - * JobSubmission.js - * - */ +* Copyright (c) 2014 AnsibleWorks, Inc. +* +* JobSubmission.js +* +*/ /** * @ngdoc function * @name helpers.function:JobSubmission @@ -13,1047 +13,1047 @@ export default angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'CredentialFormDefinition', 'CredentialsListDefinition', - 'LookUpHelper', 'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog', 'FormGenerator', 'JobVarsPromptFormDefinition']) - - .factory('LaunchJob', ['Rest', 'Wait', 'ProcessErrors', 'ToJSON', 'Empty', 'GetBasePath', - function(Rest, Wait, ProcessErrors, ToJSON, Empty, GetBasePath) { - return function(params) { - var scope = params.scope, - callback = params.callback || 'JobLaunched', - job_launch_data = {}, - url = params.url, - vars_url = GetBasePath('job_templates')+scope.job_template_id + '/', - // fld, - extra_vars; - - //found it easier to assume that there will be extra vars, and then check for a blank object at the end - job_launch_data.extra_vars = {}; - - //gather the extra vars from the job template if survey is enabled and prompt for vars is false - if (scope.removeGetExtraVars) { - scope.removeGetExtraVars(); - } - scope.removeGetExtraVars = scope.$on('GetExtraVars', function() { - - Rest.setUrl(vars_url); - Rest.get() - .success(function (data) { - if(!Empty(data.extra_vars)){ - data.extra_vars = ToJSON('yaml', data.extra_vars, false); - $.each(data.extra_vars, function(key,value){ - job_launch_data.extra_vars[key] = value; - }); - } - scope.$emit('BuildData'); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, { hdr: 'Error!', - msg: 'Failed to retrieve job template extra variables.' }); - }); - }); - - //build the data object to be sent to the job launch endpoint. Any variables gathered from the survey and the extra variables text editor are inserted into the extra_vars dict of the job_launch_data - if (scope.removeBuildData) { - scope.removeBuildData(); - } - scope.removeBuildData = scope.$on('BuildData', function() { - if(!Empty(scope.passwords_needed_to_start) && scope.passwords_needed_to_start.length>0){ - scope.passwords.forEach(function(password) { - job_launch_data[password] = scope[password]; - scope.passwords_needed_to_start.push(password+'_confirm'); // i'm pushing these values into this array for use during the survey taker parsing - }); - } - if(scope.prompt_for_vars===true){ - extra_vars = ToJSON(scope.parseType, scope.extra_vars, false); - if(!Empty(extra_vars)){ - $.each(extra_vars, function(key,value){ - job_launch_data.extra_vars[key] = value; - }); - } - - } - - if(scope.survey_enabled===true){ - for (var i=0; i < scope.survey_questions.length; i++){ - var fld = scope.survey_questions[i].variable; - // grab all survey questions that have answers - if(scope.survey_questions[i].required || (scope.survey_questions[i].required === false && scope[fld].toString()!=="")) { - job_launch_data.extra_vars[fld] = scope[fld]; - } - // for optional text and text-areas, submit a blank string if min length is 0 - if(scope.survey_questions[i].required === false && (scope.survey_questions[i].type === "text" || scope.survey_questions[i].type === "textarea") && scope.survey_questions[i].min === 0 && (scope[fld] === "" || scope[fld] === undefined)){ - job_launch_data.extra_vars[fld] = ""; - } - } - } - - // include the credential used if the user was prompted to choose a cred - if(!Empty(scope.credential)){ - job_launch_data.credential_id = scope.credential; - } - - // If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything. - if(jQuery.isEmptyObject(job_launch_data.extra_vars)===true && scope.prompt_for_vars===false){ - delete job_launch_data.extra_vars; - } - - Rest.setUrl(url); - Rest.post(job_launch_data) - .success(function(data) { - Wait('stop'); - if(!$('#password-modal').is(':hidden')){ - $('#password-modal').dialog('close'); - } - scope.$emit(callback, data); - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed updating job ' + scope.job_template_id + ' with variables. POST returned: ' + status }); - }); - }); - - // if the user has a survey and does not have 'prompt for vars' selected, then we want to - // include the extra vars from the job template in the job launch. so first check for these conditions - // and then overlay any survey vars over those. - if(scope.prompt_for_vars===false && scope.survey_enabled===true){ - scope.$emit('GetExtraVars'); - } - else { - scope.$emit('BuildData'); - } - - - }; - }]) - - .factory('PromptForCredential', ['$location', 'Wait', 'GetBasePath', 'LookUpInit', 'JobTemplateForm', 'CredentialList', 'Rest', 'Prompt', 'ProcessErrors', - function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList, Rest, Prompt, ProcessErrors) { - return function(params) { - - var scope = params.scope, - callback = params.callback || 'CredentialReady', - selectionMade; - - Wait('stop'); - scope.credential = ''; - - if (scope.removeShowLookupDialog) { - scope.removeShowLookupDialog(); - } - scope.removeShowLookupDialog = scope.$on('ShowLookupDialog', function() { - selectionMade = function () { - scope.$emit(callback, scope.credential); - }; - - LookUpInit({ - url: GetBasePath('credentials') + '?kind=ssh', - scope: scope, - form: JobTemplateForm(), - current_item: null, - list: CredentialList, - field: 'credential', - hdr: 'Credential Required', - instructions: "Launching this job requires a machine credential. Please select your machine credential now or Cancel to quit.", - postAction: selectionMade, - input_type: 'radio' - }); - scope.lookUpCredential(); - }); - - if (scope.removeAlertNoCredentials) { - scope.removeAlertNoCredentials(); - } - scope.removeAlertNoCredentials = scope.$on('AlertNoCredentials', function() { - var action = function () { - $('#prompt-modal').modal('hide'); - $location.url('/credentials/add'); - }; - - Prompt({ - hdr: 'Machine Credential Required', - body: "
There are no machine credentials defined in Tower. Launching this job requires a machine credential. " + - "Create one now?", - action: action - }); - }); - - Rest.setUrl(GetBasePath('credentials') + '?kind=ssh'); - Rest.get() - .success(function(data) { - if (data.results.length > 0) { - scope.$emit('ShowLookupDialog'); - } - else { - scope.$emit('AlertNoCredentials'); - } - }) - .error(function(data,status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Checking for machine credentials failed. GET returned: ' + status }); - }); - }; - }]) - - - - .factory('CreateLaunchDialog', ['$compile', 'Rest', 'GetBasePath', 'TextareaResize', 'CreateDialog', 'GenerateForm', - 'JobVarsPromptForm', 'Wait', 'ParseTypeChange', - function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm, - JobVarsPromptForm, Wait, ParseTypeChange) { - return function(params) { - var buttons, - scope = params.scope, - html = params.html, - // job_launch_data = {}, - callback = params.callback || 'PlaybookLaunchFinished', - // url = params.url, - e; - - // html+='
job_launch_form.$valid = {{job_launch_form.$valid}}
'; - html+=''; - $('#password-modal').empty().html(html); - $('#password-modal').find('#job_extra_vars').before(scope.helpContainer); - e = angular.element(document.getElementById('password-modal')); - $compile(e)(scope); - - if(scope.prompt_for_vars===true){ - ParseTypeChange({ scope: scope, field_id: 'job_extra_vars' , variable: "extra_vars"}); - } - - buttons = [{ - label: "Cancel", - onClick: function() { - $('#password-modal').dialog('close'); - // scope.$emit('CancelJob'); - // scope.$destroy(); - }, - icon: "fa-times", - "class": "btn btn-default", - "id": "password-cancel-button" - },{ - label: "Launch", - onClick: function() { - scope.$emit(callback); - }, - icon: "fa-check", - "class": "btn btn-primary", - "id": "password-accept-button" - }]; - - CreateDialog({ - id: 'password-modal', - scope: scope, - buttons: buttons, - width: 620, - height: 700, //(scope.passwords.length > 1) ? 700 : 500, - minWidth: 500, - title: 'Launch Configuration', - callback: 'DialogReady', - onOpen: function(){ - Wait('stop'); - } - }); - - if (scope.removeDialogReady) { - scope.removeDialogReady(); - } - scope.removeDialogReady = scope.$on('DialogReady', function() { - $('#password-modal').dialog('open'); - $('#password-accept-button').attr('ng-disabled', 'job_launch_form.$invalid' ); - e = angular.element(document.getElementById('password-accept-button')); - $compile(e)(scope); - // if(scope.prompt_for_vars===true){ - // setTimeout(function() { - // TextareaResize({ - // scope: scope, - // textareaId: 'job_variables', - // modalId: 'password-modal', - // formId: 'job_launch_form', - // parse: true - // }); - // }, 300); - // } - - }); - }; - - }]) - - - - - .factory('PromptForPasswords', ['$compile', 'Wait', 'Alert', 'CredentialForm', - function($compile, Wait, Alert, CredentialForm) { - return function(params) { - var scope = params.scope, - callback = params.callback || 'PasswordsAccepted', - url = params.url, - form = CredentialForm, - // acceptedPasswords = {}, - fld, field, - html=params.html || ""; - - scope.passwords = params.passwords; - // Wait('stop'); - - - html += "
Launching this job requires the passwords listed below. Enter and confirm each password before continuing.
\n"; - // html += "
\n"; - - scope.passwords.forEach(function(password) { - // Prompt for password - field = form.fields[password]; - fld = password; - scope[fld] = ''; - html += "
\n"; - html += "\n"; - html += "Please enter a password.
\n"; - html += "
\n"; - html += "
\n"; - - // Add the related confirm field - if (field.associated) { - fld = field.associated; - field = form.fields[field.associated]; - scope[fld] = ''; - html += "
\n"; - html += "\n"; - html += "Please confirm the password.\n"; - html += (field.awPassMatch) ? "This value does not match the password you entered previously. Please confirm that password.
\n" : ""; - html += "
\n"; - html += "\n"; - } - }); - // html += "\n"; - - - // $('#password-modal').empty().html(buildHtml); - // e = angular.element(document.getElementById('password-modal')); - // $compile(e)(scope); - scope.$emit(callback, html, url); - // CreateLaunchDialog({scope: scope}) - // buttons = [{ - // label: "Cancel", - // onClick: function() { - // scope.passwordCancel(); - // }, - // icon: "fa-times", - // "class": "btn btn-default", - // "id": "password-cancel-button" - // },{ - // label: "Continue", - // onClick: function() { - // scope.passwordAccept(); - // }, - // icon: "fa-check", - // "class": "btn btn-primary", - // "id": "password-accept-button" - // }]; - - - // CreateDialog({ - // id: 'password-modal', - // scope: scope, - // buttons: buttons, - // width: 600, - // height: (parent_scope.passwords.length > 1) ? 700 : 500, - // minWidth: 500, - // title: 'parent_scope.passwords Required', - // callback: 'DialogReady' - // }); - - // if (scope.removeDialogReady) { - // scope.removeDialogReady(); - // } - // scope.removeDialogReady = scope.$on('DialogReady', function() { - // $('#password-modal').dialog('open'); - // $('#password-accept-button').attr({ "disabled": "disabled" }); - // }); - // scope.keydown = function(e){ - // if(e.keyCode===13){ - // scope.passwordAccept(); - // } - // }; - - // scope.passwordAccept = function() { - // if (!scope.password_form.$invalid) { - // scope.passwords.forEach(function(password) { - // acceptedPasswords[password] = scope[password]; - // }); - // $('#password-modal').dialog('close'); - // scope.$emit(callback, acceptedPasswords); - // } - // }; - - // scope.passwordCancel = function() { - // $('#password-modal').dialog('close'); - // scope.$emit('CancelJob'); - // scope.$destroy(); - // }; - - // Password change - scope.clearPWConfirm = function (fld) { - // If password value changes, make sure password_confirm must be re-entered - scope[fld] = ''; - scope.job_launch_form[fld].$setValidity('awpassmatch', false); - scope.checkStatus(); - }; - - scope.checkStatus = function() { - if (!scope.job_launch_form.$invalid) { - $('#password-accept-button').removeAttr('disabled'); - } - else { - $('#password-accept-button').attr({ "disabled": "disabled" }); - } - }; - }; - }]) - - .factory('PromptForVars', ['$compile', 'Rest', 'GetBasePath', 'TextareaResize', 'CreateLaunchDialog', 'GenerateForm', 'JobVarsPromptForm', 'Wait', - 'ParseVariableString', 'ToJSON', 'ProcessErrors', '$routeParams' , - function($compile, Rest, GetBasePath, TextareaResize,CreateLaunchDialog, GenerateForm, JobVarsPromptForm, Wait, - ParseVariableString, ToJSON, ProcessErrors, $routeParams) { - return function(params) { - var - // parent_scope = params.scope, - scope = params.scope, - callback = params.callback, - // job = params.job, - url = params.url, - vars_url = GetBasePath('job_templates')+scope.job_template_id + '/', - html = params.html || ""; - - - function buildHtml(extra_vars){ - - html += GenerateForm.buildHTML(JobVarsPromptForm, { mode: 'edit', modal: true, scope: scope }); - html = html.replace("", ""); - scope.helpContainer = "\n"; - - scope.helpText = "

After defining any extra variables, click Continue to start the job. Otherwise, click cancel to abort.

" + - "

Extra variables are passed as command line variables to the playbook run. It is equivalent to the -e or --extra-vars " + - "command line parameter for ansible-playbook. Provide key/value pairs using either YAML or JSON.

" + - "JSON:
\n" + - "
{
\"somevar\": \"somevalue\",
\"password\": \"magic\"
}
\n" + - "YAML:
\n" + - "
---
somevar: somevalue
password: magic
\n"; - - scope.extra_vars = ParseVariableString(extra_vars); - scope.parseType = 'yaml'; - scope.$emit(callback, html, url); - } - - Rest.setUrl(vars_url); - Rest.get() - .success(function (data) { - buildHtml(data.extra_vars); - - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, { hdr: 'Error!', - msg: 'Failed to retrieve organization: ' + $routeParams.id + '. GET status: ' + status }); - }); - - }; - }]) - - .factory('PromptForSurvey', ['$compile', 'Wait', 'Alert', 'CredentialForm', 'CreateLaunchDialog', 'SurveyControllerInit' , 'GetBasePath', 'Rest' , 'Empty', - 'GenerateForm', 'ShowSurveyModal', 'ProcessErrors', '$routeParams' , - function($compile, Wait, Alert, CredentialForm, CreateLaunchDialog, SurveyControllerInit, GetBasePath, Rest, Empty, - GenerateForm, ShowSurveyModal, ProcessErrors, $routeParams) { - return function(params) { - var html = params.html || "", - id= params.id, - url = params.url, - callback=params.callback, - scope = params.scope, - i, j, - requiredAsterisk, - requiredClasses, - defaultValue, - choices, - element, - minlength, maxlength, - checked, min, max, - survey_url = GetBasePath('job_templates') + id + '/survey_spec/' ; - - function buildHtml(question, index){ - question.index = index; - question.question_name = question.question_name.replace(//g, ">"); - question.question_description = (question.question_description) ? question.question_description.replace(//g, ">") : undefined; - - - requiredAsterisk = (question.required===true) ? "prepend-asterisk" : ""; - requiredClasses = (question.required===true) ? "ng-pristine ng-invalid-required ng-invalid" : ""; - - html+='
'; - html += '\n'; - - if(!Empty(question.question_description)){ - html += '
'+question.question_description+'
\n'; - } - - // if(question.default && question.default.indexOf('<') !== -1){ - // question.default = (question.default) ? question.default.replace(/') !== -1){ - // question.default = (question.default) ? question.default.replace(/>/g, ">") : undefined; - // } - scope[question.variable] = question.default; - - if(question.type === 'text' ){ - minlength = (!Empty(question.min)) ? Number(question.min) : ""; - maxlength =(!Empty(question.max)) ? Number(question.max) : "" ; - html+=''+ - '
Please enter an answer.
'+ - '
Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.
'+ - '
'; - } - - if(question.type === "textarea"){ - scope[question.variable] = (question.default_textarea) ? question.default_textarea : (question.default) ? question.default : ""; - minlength = (!Empty(question.min)) ? Number(question.min) : ""; - maxlength =(!Empty(question.max)) ? Number(question.max) : "" ; - html+=''+ - '
Please enter an answer.
'+ - '
Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.
'+ - '
'; - } - if(question.type === 'password' ){ - minlength = (!Empty(question.min)) ? Number(question.min) : ""; - maxlength =(!Empty(question.max)) ? Number(question.max) : "" ; - html+=''+ - '
Please enter an answer.
'+ - '
Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.
'+ - '
'; - html+=''+ - '
Please enter an answer.
'+ - '
Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.
'+ - '
'; - html+= ''; - - } - if(question.type === 'multiplechoice'){ - choices = question.choices.split(/\n/); - element = (question.type==="multiselect") ? "checkbox" : 'radio'; - question.default = (question.default) ? question.default : (question.default_multiselect) ? question.default_multiselect : "" ; - html+='
'; - for( j = 0; j/g, ">"); - html+= '' + - ''+choices[j] +'
' ; - } - html+= '
Please select an answer.
'+ - '
'; - html+= '
'; //end survey_taker_input - } - - if(question.type === "multiselect"){ - //seperate the choices out into an array - choices = question.choices.split(/\n/); - question.default = (question.default) ? question.default : (question.default_multiselect) ? question.default_multiselect : "" ; - //ensure that the default answers are in an array - scope[question.variable] = question.default.split(/\n/); - //create a new object to be used by the surveyCheckboxes directive - scope[question.variable + '_object'] = { - name: question.variable, - value: (question.default.split(/\n/)[0]==="") ? [] : question.default.split(/\n/) , - required: question.required, - options:[] - }; - //load the options into the 'options' key of the new object - for(j=0; j'+ - '{{job_launch_form.'+question.variable+'_object.$error.checkbox}}'+ - '
Please select at least one answer.
'; - } - - if(question.type === 'integer'){ - min = (!Empty(question.min)) ? Number(question.min) : ""; - max = (!Empty(question.max)) ? Number(question.max) : "" ; - html+=''+ - '
Please enter an answer.
'+ - '
Please enter an answer that is a valid integer.
'+ - '
Please enter an answer between {{'+min+'}} and {{'+max+'}}.
'; - - } - - if(question.type === "float"){ - min = (!Empty(question.min)) ? question.min : ""; - max = (!Empty(question.max)) ? question.max : "" ; - defaultValue = (!Empty(question.default)) ? question.default : (!Empty(question.default_float)) ? question.default_float : "" ; - html+=''+ - '
Please enter an answer.
'+ - '
Please enter an answer that is a decimal number.
'+ - '
Please enter a decimal number between {{'+min+'}} and {{'+max+'}}.
'; - } - html+='
'; - if(question.index === scope.survey_questions.length-1){ - scope.$emit(callback, html, url); - } - } - - - - - Rest.setUrl(survey_url); - Rest.get() - .success(function (data) { - if(!Empty(data)){ - scope.survey_name = data.name; - scope.survey_description = data.description; - scope.survey_questions = data.spec; - - for(i=0; i0){ - scope.passwords_needed_to_start = passwords; - scope.$emit('PromptForPasswords', passwords, html, url); - } - else if (scope.ask_variables_on_launch){ - scope.$emit('PromptForVars', html, url); - } - else if (!Empty(scope.survey_enabled) && scope.survey_enabled===true) { - scope.$emit('PromptForSurvey', html, url); - } - else { - scope.$emit('StartPlaybookRun', url); - } - } - - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get job template details. GET returned status: ' + status }); - }); - } - - }); - - // Get the job or job_template record - Wait('start'); - Rest.setUrl(url); - Rest.get() - .success(function (data) { - new_job_id = data.id; - launch_url = url;//data.related.start; - scope.passwords_needed_to_start = data.passwords_needed_to_start; - scope.prompt_for_vars = data.ask_variables_on_launch; - scope.survey_enabled = data.survey_enabled; - scope.ask_variables_on_launch = data.ask_variables_on_launch; - scope.variables_needed_to_start = data.variables_needed_to_start; - html = '
'; - - if(data.credential_needed_to_start === true){ - scope.$emit('PromptForCredential'); - } - else if (!Empty(data.passwords_needed_to_start) && data.passwords_needed_to_start.length > 0) { - scope.$emit('PromptForPasswords', data.passwords_needed_to_start, html, url); - } - else if (data.ask_variables_on_launch) { - scope.$emit('PromptForVars', html, url); - } - else if (!Empty(data.survey_enabled) && data.survey_enabled===true) { - scope.$emit('PromptForSurvey', html, url); - } - else { - scope.$emit('StartPlaybookRun', url); - } - - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get job template details. GET returned status: ' + status }); - }); - }; - } - ]) - - // Submit SCM Update request - .factory('ProjectUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', - 'ProjectsForm', 'Wait', - function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, ProjectsForm, Wait) { - return function (params) { - var scope = params.scope, - project_id = params.project_id, - url = GetBasePath('projects') + project_id + '/update/', - project; - - if (scope.removeUpdateSubmitted) { - scope.removeUpdateSubmitted(); - } - scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function() { - // Refresh the project list after update request submitted - Wait('stop'); - if (/\d$/.test($location.path())) { - //Request submitted from projects/N page. Navigate back to the list so user can see status - $location.path('/projects'); - } - if (scope.socketStatus === 'error') { - Alert('Update Started', 'The request to start the SCM update process was submitted. ' + - 'To monitor the update status, refresh the page by clicking the button.', 'alert-info'); - if (scope.refresh) { - scope.refresh(); - } - } - }); - - if (scope.removePromptForPasswords) { - scope.removePromptForPasswords(); - } - scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() { - PromptForPasswords({ scope: scope, passwords: project.passwords_needed_to_update, callback: 'StartTheUpdate' }); - }); - - if (scope.removeStartTheUpdate) { - scope.removeStartTheUpdate(); - } - scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) { - LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' }); - }); - - // Check to see if we have permission to perform the update and if any passwords are needed - Wait('start'); - Rest.setUrl(url); - Rest.get() - .success(function (data) { - project = data; - if (project.can_update) { - if (project.passwords_needed_to_updated) { - Wait('stop'); - scope.$emit('PromptForPasswords'); - } - else { - scope.$emit('StartTheUpdate', {}); - } - } - else { - Alert('Permission Denied', 'You do not have access to update this project. Please contact your system administrator.', - 'alert-danger'); - } - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to lookup project ' + url + ' GET returned: ' + status }); - }); - }; - } - ]) - - - // Submit Inventory Update request - .factory('InventoryUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait', - function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, Wait) { - return function (params) { - - var scope = params.scope, - url = params.url, - inventory_source; - - if (scope.removeUpdateSubmitted) { - scope.removeUpdateSubmitted(); - } - scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function () { - Wait('stop'); - if (scope.socketStatus === 'error') { - Alert('Sync Started', 'The request to start the inventory sync process was submitted. ' + - 'To monitor the status refresh the page by clicking the button.', 'alert-info'); - if (scope.refreshGroups) { - // inventory detail page - scope.refreshGroups(); - } - else if (scope.refresh) { - scope.refresh(); - } - } - }); - - if (scope.removePromptForPasswords) { - scope.removePromptForPasswords(); - } - scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() { - PromptForPasswords({ scope: scope, passwords: inventory_source.passwords_needed_to_update, callback: 'StartTheUpdate' }); - }); - - if (scope.removeStartTheUpdate) { - scope.removeStartTheUpdate(); - } - scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) { - LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' }); - }); - - // Check to see if we have permission to perform the update and if any passwords are needed - Wait('start'); - Rest.setUrl(url); - Rest.get() - .success(function (data) { - inventory_source = data; - if (data.can_update) { - if (data.passwords_needed_to_update) { - Wait('stop'); - scope.$emit('PromptForPasswords'); - } - else { - scope.$emit('StartTheUpdate', {}); - } - } else { - Wait('stop'); - Alert('Permission Denied', 'You do not have access to run the inventory sync. Please contact your system administrator.', - 'alert-danger'); - } - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get inventory source ' + url + ' GET returned: ' + status }); - }); - }; - } - ]); +'LookUpHelper', 'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog', 'FormGenerator', 'JobVarsPromptFormDefinition']) + +.factory('LaunchJob', ['Rest', 'Wait', 'ProcessErrors', 'ToJSON', 'Empty', 'GetBasePath', +function(Rest, Wait, ProcessErrors, ToJSON, Empty, GetBasePath) { + return function(params) { + var scope = params.scope, + callback = params.callback || 'JobLaunched', + job_launch_data = {}, + url = params.url, + vars_url = GetBasePath('job_templates')+scope.job_template_id + '/', + // fld, + extra_vars; + + //found it easier to assume that there will be extra vars, and then check for a blank object at the end + job_launch_data.extra_vars = {}; + + //gather the extra vars from the job template if survey is enabled and prompt for vars is false + if (scope.removeGetExtraVars) { + scope.removeGetExtraVars(); + } + scope.removeGetExtraVars = scope.$on('GetExtraVars', function() { + + Rest.setUrl(vars_url); + Rest.get() + .success(function (data) { + if(!Empty(data.extra_vars)){ + data.extra_vars = ToJSON('yaml', data.extra_vars, false); + $.each(data.extra_vars, function(key,value){ + job_launch_data.extra_vars[key] = value; + }); + } + scope.$emit('BuildData'); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, { hdr: 'Error!', + msg: 'Failed to retrieve job template extra variables.' }); + }); + }); + + //build the data object to be sent to the job launch endpoint. Any variables gathered from the survey and the extra variables text editor are inserted into the extra_vars dict of the job_launch_data + if (scope.removeBuildData) { + scope.removeBuildData(); + } + scope.removeBuildData = scope.$on('BuildData', function() { + if(!Empty(scope.passwords_needed_to_start) && scope.passwords_needed_to_start.length>0){ + scope.passwords.forEach(function(password) { + job_launch_data[password] = scope[password]; + scope.passwords_needed_to_start.push(password+'_confirm'); // i'm pushing these values into this array for use during the survey taker parsing + }); + } + if(scope.prompt_for_vars===true){ + extra_vars = ToJSON(scope.parseType, scope.extra_vars, false); + if(!Empty(extra_vars)){ + $.each(extra_vars, function(key,value){ + job_launch_data.extra_vars[key] = value; + }); + } + + } + + if(scope.survey_enabled===true){ + for (var i=0; i < scope.survey_questions.length; i++){ + var fld = scope.survey_questions[i].variable; + // grab all survey questions that have answers + if(scope.survey_questions[i].required || (scope.survey_questions[i].required === false && scope[fld].toString()!=="")) { + job_launch_data.extra_vars[fld] = scope[fld]; + } + // for optional text and text-areas, submit a blank string if min length is 0 + if(scope.survey_questions[i].required === false && (scope.survey_questions[i].type === "text" || scope.survey_questions[i].type === "textarea") && scope.survey_questions[i].min === 0 && (scope[fld] === "" || scope[fld] === undefined)){ + job_launch_data.extra_vars[fld] = ""; + } + } + } + + // include the credential used if the user was prompted to choose a cred + if(!Empty(scope.credential)){ + job_launch_data.credential_id = scope.credential; + } + + // If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything. + if(jQuery.isEmptyObject(job_launch_data.extra_vars)===true && scope.prompt_for_vars===false){ + delete job_launch_data.extra_vars; + } + + Rest.setUrl(url); + Rest.post(job_launch_data) + .success(function(data) { + Wait('stop'); + if(!$('#password-modal').is(':hidden')){ + $('#password-modal').dialog('close'); + } + scope.$emit(callback, data); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed updating job ' + scope.job_template_id + ' with variables. POST returned: ' + status }); + }); + }); + + // if the user has a survey and does not have 'prompt for vars' selected, then we want to + // include the extra vars from the job template in the job launch. so first check for these conditions + // and then overlay any survey vars over those. + if(scope.prompt_for_vars===false && scope.survey_enabled===true){ + scope.$emit('GetExtraVars'); + } + else { + scope.$emit('BuildData'); + } + + + }; +}]) + +.factory('PromptForCredential', ['$location', 'Wait', 'GetBasePath', 'LookUpInit', 'JobTemplateForm', 'CredentialList', 'Rest', 'Prompt', 'ProcessErrors', +function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialList, Rest, Prompt, ProcessErrors) { + return function(params) { + + var scope = params.scope, + callback = params.callback || 'CredentialReady', + selectionMade; + + Wait('stop'); + scope.credential = ''; + + if (scope.removeShowLookupDialog) { + scope.removeShowLookupDialog(); + } + scope.removeShowLookupDialog = scope.$on('ShowLookupDialog', function() { + selectionMade = function () { + scope.$emit(callback, scope.credential); + }; + + LookUpInit({ + url: GetBasePath('credentials') + '?kind=ssh', + scope: scope, + form: JobTemplateForm(), + current_item: null, + list: CredentialList, + field: 'credential', + hdr: 'Credential Required', + instructions: "Launching this job requires a machine credential. Please select your machine credential now or Cancel to quit.", + postAction: selectionMade, + input_type: 'radio' + }); + scope.lookUpCredential(); + }); + + if (scope.removeAlertNoCredentials) { + scope.removeAlertNoCredentials(); + } + scope.removeAlertNoCredentials = scope.$on('AlertNoCredentials', function() { + var action = function () { + $('#prompt-modal').modal('hide'); + $location.url('/credentials/add'); + }; + + Prompt({ + hdr: 'Machine Credential Required', + body: "
There are no machine credentials defined in Tower. Launching this job requires a machine credential. " + + "Create one now?", + action: action + }); + }); + + Rest.setUrl(GetBasePath('credentials') + '?kind=ssh'); + Rest.get() + .success(function(data) { + if (data.results.length > 0) { + scope.$emit('ShowLookupDialog'); + } + else { + scope.$emit('AlertNoCredentials'); + } + }) + .error(function(data,status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Checking for machine credentials failed. GET returned: ' + status }); + }); + }; +}]) + + + +.factory('CreateLaunchDialog', ['$compile', 'Rest', 'GetBasePath', 'TextareaResize', 'CreateDialog', 'GenerateForm', +'JobVarsPromptForm', 'Wait', 'ParseTypeChange', +function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm, + JobVarsPromptForm, Wait, ParseTypeChange) { + return function(params) { + var buttons, + scope = params.scope, + html = params.html, + // job_launch_data = {}, + callback = params.callback || 'PlaybookLaunchFinished', + // url = params.url, + e; + + // html+='
job_launch_form.$valid = {{job_launch_form.$valid}}
'; + html+=''; + $('#password-modal').empty().html(html); + $('#password-modal').find('#job_extra_vars').before(scope.helpContainer); + e = angular.element(document.getElementById('password-modal')); + $compile(e)(scope); + + if(scope.prompt_for_vars===true){ + ParseTypeChange({ scope: scope, field_id: 'job_extra_vars' , variable: "extra_vars"}); + } + + buttons = [{ + label: "Cancel", + onClick: function() { + $('#password-modal').dialog('close'); + // scope.$emit('CancelJob'); + // scope.$destroy(); + }, + icon: "fa-times", + "class": "btn btn-default", + "id": "password-cancel-button" + },{ + label: "Launch", + onClick: function() { + scope.$emit(callback); + }, + icon: "fa-check", + "class": "btn btn-primary", + "id": "password-accept-button" + }]; + + CreateDialog({ + id: 'password-modal', + scope: scope, + buttons: buttons, + width: 620, + height: 700, //(scope.passwords.length > 1) ? 700 : 500, + minWidth: 500, + title: 'Launch Configuration', + callback: 'DialogReady', + onOpen: function(){ + Wait('stop'); + } + }); + + if (scope.removeDialogReady) { + scope.removeDialogReady(); + } + scope.removeDialogReady = scope.$on('DialogReady', function() { + $('#password-modal').dialog('open'); + $('#password-accept-button').attr('ng-disabled', 'job_launch_form.$invalid' ); + e = angular.element(document.getElementById('password-accept-button')); + $compile(e)(scope); + // if(scope.prompt_for_vars===true){ + // setTimeout(function() { + // TextareaResize({ + // scope: scope, + // textareaId: 'job_variables', + // modalId: 'password-modal', + // formId: 'job_launch_form', + // parse: true + // }); + // }, 300); + // } + + }); + }; + + }]) + + + + + .factory('PromptForPasswords', ['$compile', 'Wait', 'Alert', 'CredentialForm', + function($compile, Wait, Alert, CredentialForm) { + return function(params) { + var scope = params.scope, + callback = params.callback || 'PasswordsAccepted', + url = params.url, + form = CredentialForm, + // acceptedPasswords = {}, + fld, field, + html=params.html || ""; + + scope.passwords = params.passwords; + // Wait('stop'); + + + html += "
Launching this job requires the passwords listed below. Enter and confirm each password before continuing.
\n"; + // html += "
\n"; + + scope.passwords.forEach(function(password) { + // Prompt for password + field = form.fields[password]; + fld = password; + scope[fld] = ''; + html += "
\n"; + html += "\n"; + html += "Please enter a password.
\n"; + html += "
\n"; + html += "
\n"; + + // Add the related confirm field + if (field.associated) { + fld = field.associated; + field = form.fields[field.associated]; + scope[fld] = ''; + html += "
\n"; + html += "\n"; + html += "Please confirm the password.\n"; + html += (field.awPassMatch) ? "This value does not match the password you entered previously. Please confirm that password.
\n" : ""; + html += "
\n"; + html += "\n"; + } + }); + // html += "\n"; + + + // $('#password-modal').empty().html(buildHtml); + // e = angular.element(document.getElementById('password-modal')); + // $compile(e)(scope); + scope.$emit(callback, html, url); + // CreateLaunchDialog({scope: scope}) + // buttons = [{ + // label: "Cancel", + // onClick: function() { + // scope.passwordCancel(); + // }, + // icon: "fa-times", + // "class": "btn btn-default", + // "id": "password-cancel-button" + // },{ + // label: "Continue", + // onClick: function() { + // scope.passwordAccept(); + // }, + // icon: "fa-check", + // "class": "btn btn-primary", + // "id": "password-accept-button" + // }]; + + + // CreateDialog({ + // id: 'password-modal', + // scope: scope, + // buttons: buttons, + // width: 600, + // height: (parent_scope.passwords.length > 1) ? 700 : 500, + // minWidth: 500, + // title: 'parent_scope.passwords Required', + // callback: 'DialogReady' + // }); + + // if (scope.removeDialogReady) { + // scope.removeDialogReady(); + // } + // scope.removeDialogReady = scope.$on('DialogReady', function() { + // $('#password-modal').dialog('open'); + // $('#password-accept-button').attr({ "disabled": "disabled" }); + // }); + // scope.keydown = function(e){ + // if(e.keyCode===13){ + // scope.passwordAccept(); + // } + // }; + + // scope.passwordAccept = function() { + // if (!scope.password_form.$invalid) { + // scope.passwords.forEach(function(password) { + // acceptedPasswords[password] = scope[password]; + // }); + // $('#password-modal').dialog('close'); + // scope.$emit(callback, acceptedPasswords); + // } + // }; + + // scope.passwordCancel = function() { + // $('#password-modal').dialog('close'); + // scope.$emit('CancelJob'); + // scope.$destroy(); + // }; + + // Password change + scope.clearPWConfirm = function (fld) { + // If password value changes, make sure password_confirm must be re-entered + scope[fld] = ''; + scope.job_launch_form[fld].$setValidity('awpassmatch', false); + scope.checkStatus(); + }; + + scope.checkStatus = function() { + if (!scope.job_launch_form.$invalid) { + $('#password-accept-button').removeAttr('disabled'); + } + else { + $('#password-accept-button').attr({ "disabled": "disabled" }); + } + }; + }; + }]) + + .factory('PromptForVars', ['$compile', 'Rest', 'GetBasePath', 'TextareaResize', 'CreateLaunchDialog', 'GenerateForm', 'JobVarsPromptForm', 'Wait', + 'ParseVariableString', 'ToJSON', 'ProcessErrors', '$routeParams' , + function($compile, Rest, GetBasePath, TextareaResize,CreateLaunchDialog, GenerateForm, JobVarsPromptForm, Wait, + ParseVariableString, ToJSON, ProcessErrors, $routeParams) { + return function(params) { + var + // parent_scope = params.scope, + scope = params.scope, + callback = params.callback, + // job = params.job, + url = params.url, + vars_url = GetBasePath('job_templates')+scope.job_template_id + '/', + html = params.html || ""; + + + function buildHtml(extra_vars){ + + html += GenerateForm.buildHTML(JobVarsPromptForm, { mode: 'edit', modal: true, scope: scope }); + html = html.replace("", ""); + scope.helpContainer = "\n"; + + scope.helpText = "

After defining any extra variables, click Continue to start the job. Otherwise, click cancel to abort.

" + + "

Extra variables are passed as command line variables to the playbook run. It is equivalent to the -e or --extra-vars " + + "command line parameter for ansible-playbook. Provide key/value pairs using either YAML or JSON.

" + + "JSON:
\n" + + "
{
\"somevar\": \"somevalue\",
\"password\": \"magic\"
}
\n" + + "YAML:
\n" + + "
---
somevar: somevalue
password: magic
\n"; + + scope.extra_vars = ParseVariableString(extra_vars); + scope.parseType = 'yaml'; + scope.$emit(callback, html, url); + } + + Rest.setUrl(vars_url); + Rest.get() + .success(function (data) { + buildHtml(data.extra_vars); + + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, { hdr: 'Error!', + msg: 'Failed to retrieve organization: ' + $routeParams.id + '. GET status: ' + status }); + }); + + }; + }]) + + .factory('PromptForSurvey', ['$compile', 'Wait', 'Alert', 'CredentialForm', 'CreateLaunchDialog', 'SurveyControllerInit' , 'GetBasePath', 'Rest' , 'Empty', + 'GenerateForm', 'ShowSurveyModal', 'ProcessErrors', '$routeParams' , + function($compile, Wait, Alert, CredentialForm, CreateLaunchDialog, SurveyControllerInit, GetBasePath, Rest, Empty, + GenerateForm, ShowSurveyModal, ProcessErrors, $routeParams) { + return function(params) { + var html = params.html || "", + id= params.id, + url = params.url, + callback=params.callback, + scope = params.scope, + i, j, + requiredAsterisk, + requiredClasses, + defaultValue, + choices, + element, + minlength, maxlength, + checked, min, max, + survey_url = GetBasePath('job_templates') + id + '/survey_spec/' ; + + function buildHtml(question, index){ + question.index = index; + question.question_name = question.question_name.replace(//g, ">"); + question.question_description = (question.question_description) ? question.question_description.replace(//g, ">") : undefined; + + + requiredAsterisk = (question.required===true) ? "prepend-asterisk" : ""; + requiredClasses = (question.required===true) ? "ng-pristine ng-invalid-required ng-invalid" : ""; + + html+='
'; + html += '\n'; + + if(!Empty(question.question_description)){ + html += '
'+question.question_description+'
\n'; + } + + // if(question.default && question.default.indexOf('<') !== -1){ + // question.default = (question.default) ? question.default.replace(/') !== -1){ + // question.default = (question.default) ? question.default.replace(/>/g, ">") : undefined; + // } + scope[question.variable] = question.default; + + if(question.type === 'text' ){ + minlength = (!Empty(question.min)) ? Number(question.min) : ""; + maxlength =(!Empty(question.max)) ? Number(question.max) : "" ; + html+=''+ + '
Please enter an answer.
'+ + '
Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.
'+ + '
'; + } + + if(question.type === "textarea"){ + scope[question.variable] = (question.default_textarea) ? question.default_textarea : (question.default) ? question.default : ""; + minlength = (!Empty(question.min)) ? Number(question.min) : ""; + maxlength =(!Empty(question.max)) ? Number(question.max) : "" ; + html+=''+ + '
Please enter an answer.
'+ + '
Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.
'+ + '
'; + } + if(question.type === 'password' ){ + minlength = (!Empty(question.min)) ? Number(question.min) : ""; + maxlength =(!Empty(question.max)) ? Number(question.max) : "" ; + html+=''+ + '
Please enter an answer.
'+ + '
Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.
'+ + '
'; + html+=''+ + '
Please enter an answer.
'+ + '
Please enter an answer between {{'+minlength+'}} to {{'+maxlength+'}} characters long.
'+ + '
'; + html+= ''; + + } + if(question.type === 'multiplechoice'){ + choices = question.choices.split(/\n/); + element = (question.type==="multiselect") ? "checkbox" : 'radio'; + question.default = (question.default) ? question.default : (question.default_multiselect) ? question.default_multiselect : "" ; + html+='
'; + for( j = 0; j/g, ">"); + html+= '' + + ''+choices[j] +'
' ; + } + html+= '
Please select an answer.
'+ + '
'; + html+= '
'; //end survey_taker_input + } + + if(question.type === "multiselect"){ + //seperate the choices out into an array + choices = question.choices.split(/\n/); + question.default = (question.default) ? question.default : (question.default_multiselect) ? question.default_multiselect : "" ; + //ensure that the default answers are in an array + scope[question.variable] = question.default.split(/\n/); + //create a new object to be used by the surveyCheckboxes directive + scope[question.variable + '_object'] = { + name: question.variable, + value: (question.default.split(/\n/)[0]==="") ? [] : question.default.split(/\n/) , + required: question.required, + options:[] + }; + //load the options into the 'options' key of the new object + for(j=0; j'+ + '{{job_launch_form.'+question.variable+'_object.$error.checkbox}}'+ + '
Please select at least one answer.
'; + } + + if(question.type === 'integer'){ + min = (!Empty(question.min)) ? Number(question.min) : ""; + max = (!Empty(question.max)) ? Number(question.max) : "" ; + html+=''+ + '
Please enter an answer.
'+ + '
Please enter an answer that is a valid integer.
'+ + '
Please enter an answer between {{'+min+'}} and {{'+max+'}}.
'; + + } + + if(question.type === "float"){ + min = (!Empty(question.min)) ? question.min : ""; + max = (!Empty(question.max)) ? question.max : "" ; + defaultValue = (!Empty(question.default)) ? question.default : (!Empty(question.default_float)) ? question.default_float : "" ; + html+=''+ + '
Please enter an answer.
'+ + '
Please enter an answer that is a decimal number.
'+ + '
Please enter a decimal number between {{'+min+'}} and {{'+max+'}}.
'; + } + html+='
'; + if(question.index === scope.survey_questions.length-1){ + scope.$emit(callback, html, url); + } + } + + + + + Rest.setUrl(survey_url); + Rest.get() + .success(function (data) { + if(!Empty(data)){ + scope.survey_name = data.name; + scope.survey_description = data.description; + scope.survey_questions = data.spec; + + for(i=0; i0){ + scope.passwords_needed_to_start = passwords; + scope.$emit('PromptForPasswords', passwords, html, url); + } + else if (scope.ask_variables_on_launch){ + scope.$emit('PromptForVars', html, url); + } + else if (!Empty(scope.survey_enabled) && scope.survey_enabled===true) { + scope.$emit('PromptForSurvey', html, url); + } + else { + scope.$emit('StartPlaybookRun', url); + } + } + + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get job template details. GET returned status: ' + status }); + }); + } + + }); + + // Get the job or job_template record + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function (data) { + new_job_id = data.id; + launch_url = url;//data.related.start; + scope.passwords_needed_to_start = data.passwords_needed_to_start; + scope.prompt_for_vars = data.ask_variables_on_launch; + scope.survey_enabled = data.survey_enabled; + scope.ask_variables_on_launch = data.ask_variables_on_launch; + scope.variables_needed_to_start = data.variables_needed_to_start; + html = '
'; + + if(data.credential_needed_to_start === true){ + scope.$emit('PromptForCredential'); + } + else if (!Empty(data.passwords_needed_to_start) && data.passwords_needed_to_start.length > 0) { + scope.$emit('PromptForPasswords', data.passwords_needed_to_start, html, url); + } + else if (data.ask_variables_on_launch) { + scope.$emit('PromptForVars', html, url); + } + else if (!Empty(data.survey_enabled) && data.survey_enabled===true) { + scope.$emit('PromptForSurvey', html, url); + } + else { + scope.$emit('StartPlaybookRun', url); + } + + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get job template details. GET returned status: ' + status }); + }); + }; + } + ]) + + // Submit SCM Update request + .factory('ProjectUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', + 'ProjectsForm', 'Wait', + function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, ProjectsForm, Wait) { + return function (params) { + var scope = params.scope, + project_id = params.project_id, + url = GetBasePath('projects') + project_id + '/update/', + project; + + if (scope.removeUpdateSubmitted) { + scope.removeUpdateSubmitted(); + } + scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function() { + // Refresh the project list after update request submitted + Wait('stop'); + if (/\d$/.test($location.path())) { + //Request submitted from projects/N page. Navigate back to the list so user can see status + $location.path('/projects'); + } + if (scope.socketStatus === 'error') { + Alert('Update Started', 'The request to start the SCM update process was submitted. ' + + 'To monitor the update status, refresh the page by clicking the button.', 'alert-info'); + if (scope.refresh) { + scope.refresh(); + } + } + }); + + if (scope.removePromptForPasswords) { + scope.removePromptForPasswords(); + } + scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() { + PromptForPasswords({ scope: scope, passwords: project.passwords_needed_to_update, callback: 'StartTheUpdate' }); + }); + + if (scope.removeStartTheUpdate) { + scope.removeStartTheUpdate(); + } + scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) { + LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' }); + }); + + // Check to see if we have permission to perform the update and if any passwords are needed + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function (data) { + project = data; + if (project.can_update) { + if (project.passwords_needed_to_updated) { + Wait('stop'); + scope.$emit('PromptForPasswords'); + } + else { + scope.$emit('StartTheUpdate', {}); + } + } + else { + Alert('Permission Denied', 'You do not have access to update this project. Please contact your system administrator.', + 'alert-danger'); + } + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to lookup project ' + url + ' GET returned: ' + status }); + }); + }; + } + ]) + + + // Submit Inventory Update request + .factory('InventoryUpdate', ['PromptForPasswords', 'LaunchJob', 'Rest', '$location', 'GetBasePath', 'ProcessErrors', 'Alert', 'Wait', + function (PromptForPasswords, LaunchJob, Rest, $location, GetBasePath, ProcessErrors, Alert, Wait) { + return function (params) { + + var scope = params.scope, + url = params.url, + inventory_source; + + if (scope.removeUpdateSubmitted) { + scope.removeUpdateSubmitted(); + } + scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function () { + Wait('stop'); + if (scope.socketStatus === 'error') { + Alert('Sync Started', 'The request to start the inventory sync process was submitted. ' + + 'To monitor the status refresh the page by clicking the button.', 'alert-info'); + if (scope.refreshGroups) { + // inventory detail page + scope.refreshGroups(); + } + else if (scope.refresh) { + scope.refresh(); + } + } + }); + + if (scope.removePromptForPasswords) { + scope.removePromptForPasswords(); + } + scope.removePromptForPasswords = scope.$on('PromptForPasswords', function() { + PromptForPasswords({ scope: scope, passwords: inventory_source.passwords_needed_to_update, callback: 'StartTheUpdate' }); + }); + + if (scope.removeStartTheUpdate) { + scope.removeStartTheUpdate(); + } + scope.removeStartTheUpdate = scope.$on('StartTheUpdate', function(e, passwords) { + LaunchJob({ scope: scope, url: url, passwords: passwords, callback: 'UpdateSubmitted' }); + }); + + // Check to see if we have permission to perform the update and if any passwords are needed + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function (data) { + inventory_source = data; + if (data.can_update) { + if (data.passwords_needed_to_update) { + Wait('stop'); + scope.$emit('PromptForPasswords'); + } + else { + scope.$emit('StartTheUpdate', {}); + } + } else { + Wait('stop'); + Alert('Permission Denied', 'You do not have access to run the inventory sync. Please contact your system administrator.', + 'alert-danger'); + } + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get inventory source ' + url + ' GET returned: ' + status }); + }); + }; + } + ]); From 427f42dec374c47647aa52f983a4f5cda5d52e23 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 25 Mar 2015 14:23:04 -0400 Subject: [PATCH 07/12] changed default project/playbook scheme + actions changed the design of the 'Reset to default project/playbook' on the job template form. Also added actions on the inventory properties accordion for scan job templates for scheduling and copying --- awx/ui/static/js/controllers/Inventories.js | 148 ++++++++++++++++++- awx/ui/static/js/controllers/JobTemplates.js | 99 ++++++------- awx/ui/static/js/forms/Inventories.js | 22 +++ awx/ui/static/js/forms/JobTemplates.js | 26 ++-- awx/ui/static/js/helpers/JobTemplates.js | 1 - awx/ui/static/js/lists/JobTemplates.js | 1 - awx/ui/static/partials/inventories.html | 9 +- 7 files changed, 235 insertions(+), 71 deletions(-) diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index 830d8c1f1e..b21fdd3bf7 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -482,7 +482,8 @@ InventoriesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit, PaginateInit, - LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, ParseVariableString, Stream, RelatedSearchInit, RelatedPaginateInit, Prompt, PlaybookRun) { + LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, ParseVariableString, Stream, RelatedSearchInit, RelatedPaginateInit, + Prompt, PlaybookRun, CreateDialog) { ClearScope(); @@ -664,10 +665,152 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ PlaybookRun({ scope: $scope, id: this.scan_job_template.id }); }; + $scope.scheduleScanJob = function(){ + $location.path('/job_templates/'+this.scan_job_template.id+'/schedules'); + }; + $scope.editScanJob = function(){ $location.path($location.path()+'/job_templates/'+this.scan_job_template.id); }; + $scope.copyScanJobTemplate = function(){ + var id = this.scan_job_template.id, + name = this.scan_job_template.name, + 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 = GetBasePath('job_templates')+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 = GetBasePath('job_templates'), + 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() + '/job_templates/' + 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.deleteScanJob = function () { var id = this.scan_job_template.id , action = function () { @@ -698,7 +841,8 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ InventoriesEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'generateList', 'OrganizationList', 'SearchInit', - 'PaginateInit', 'LookUpInit', 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', 'Stream', 'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt', 'PlaybookRun' + 'PaginateInit', 'LookUpInit', 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', 'Stream', 'RelatedSearchInit', 'RelatedPaginateInit', + 'Prompt', 'PlaybookRun', 'CreateDialog' ]; diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js index 8de864f0ca..5610037087 100644 --- a/awx/ui/static/js/controllers/JobTemplates.js +++ b/awx/ui/static/js/controllers/JobTemplates.js @@ -342,37 +342,6 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $ parent_scope: $scope }); - - $scope.jobTypeChange = function(){ - if($scope.job_type){ - if($scope.job_type.value === 'scan'){ - $scope.default_scan = true; - $scope.project_name = 'Default'; - $scope.project = null; - $scope.toggleScanInfo(); - } - else{ - $scope.default_scan = false; - $scope.project_name = null; - $scope.project = null; - $scope.playbook_options = []; - $scope.playbook = 'null'; - } - } - }; - - $scope.toggleScanInfo = function() { - if($scope.default_scan){ - $scope.project_name = 'Default'; - $scope.project = null; - } - if(!$scope.default_scan){ - $scope.project_name = null; - $scope.playbook_options = []; - $scope.playbook = 'null'; - } - }; - if ($routeParams.inventory_id) { // This means that the job template form was accessed via inventory prop's // This also means the job is a scan job. @@ -393,10 +362,10 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $ // Update playbook select whenever project value changes selectPlaybook = function (oldValue, newValue) { var url; - if($scope.job_type.value === 'scan' && $scope.default_scan === true){ - $scope.playbook_options = ['Default']; - $scope.playbook = 'Default'; - Wait('stop'); + 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) { @@ -420,6 +389,32 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $ } }; + $scope.jobTypeChange = function(){ + if($scope.job_type){ + if($scope.job_type.value === 'scan'){ + // $scope.project_name = 'Default'; + // $scope.project = null; + $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)) { @@ -686,10 +681,10 @@ export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, getPlaybooks = function (project) { var url; - if($scope.job_type.value === 'scan' && $scope.default_scan === true){ - $scope.playbook_options = ['Default']; - $scope.playbook = 'Default'; - Wait('stop'); + 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/'; @@ -725,31 +720,27 @@ export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $scope.jobTypeChange = function(){ if($scope.job_type){ if($scope.job_type.value === 'scan'){ - $scope.default_scan = true; - $scope.project_name = 'Default'; - $scope.project = null; + // $scope.project_name = 'Default'; + // $scope.project = null; $scope.toggleScanInfo(); } - else{ - $scope.default_scan = false; + else if($scope.project_name === "Default"){ $scope.project_name = null; $scope.playbook_options = []; - $scope.playbook = 'null'; + // $scope.playbook = 'null'; + $scope.job_templates_form.playbook.$setPristine(); } } }; $scope.toggleScanInfo = function() { - if($scope.default_scan){ - $scope.project_name = 'Default'; - $scope.project = null; + $scope.project_name = 'Default'; + if($scope.project === null){ getPlaybooks(); - } - if(!$scope.default_scan){ - $scope.project_name = null; - $scope.playbook_options = []; - $scope.playbook = 'null'; - } + } + else { + $scope.project = null; + } }; // Detect and alert user to potential SCM status issues diff --git a/awx/ui/static/js/forms/Inventories.js b/awx/ui/static/js/forms/Inventories.js index 1b20e523f4..b745ce7b00 100644 --- a/awx/ui/static/js/forms/Inventories.js +++ b/awx/ui/static/js/forms/Inventories.js @@ -114,6 +114,14 @@ export default }, fields: { + // smart_status: { + // label: 'Status', + // // columnClass: 'col-md-2 col-sm-2 col-xs-2', + // searchable: false, + // nosort: true, + // ngInclude: "'/static/partials/job-template-smart-status.html'", + // type: 'template' + // }, name: { key: true, label: 'Name', @@ -131,6 +139,12 @@ export default awToolTip: 'Launch the scan job template', 'class': 'btn btn-default' }, + schedule: { + label: 'Schedule', + ngClick: 'scheduleScanJob()', + awToolTip: 'Schedule future job template runs', + dataPlacement: 'top', + }, edit: { label: 'Edit', ngClick: "editScanJob()", @@ -144,6 +158,14 @@ export default icon: 'icon-trash', "class": 'btn-danger', awToolTip: 'Delete the scan job template' + }, + copy: { + label: 'Copy', + ngClick: "copyScanJobTemplate()", + "class": 'btn-danger btn-xs', + awToolTip: 'Copy template', + dataPlacement: 'top', + ngHide: 'job_template.summary_fields.can_copy === false' } } } diff --git a/awx/ui/static/js/forms/JobTemplates.js b/awx/ui/static/js/forms/JobTemplates.js index 96e6db7d4f..c8fc483f53 100644 --- a/awx/ui/static/js/forms/JobTemplates.js +++ b/awx/ui/static/js/forms/JobTemplates.js @@ -103,8 +103,6 @@ export default dataTitle: 'Project', dataPlacement: 'right', dataContainer: "body", - ngDisabled: 'default_scan === true', - ngRequired: 'default_scan === false' }, playbook: { label: 'Playbook', @@ -117,19 +115,23 @@ export default dataTitle: 'Playbook', dataPlacement: 'right', dataContainer: "body", - ngDisabled: 'default_scan === true', - ngRequired: 'default_scan === false' }, + // default_scan: { + // label: "Use default scan job project and playbook", + // type: 'checkbox', + // ngChange: 'toggleScanInfo()', + // ngShow: 'job_type.value === "scan" && project_name !== "Default"', + // column: 1, + // awPopOver: "

Scan jobs templates use a default project and default playbook. Uncheck this box to override these defaults.

", + // dataTitle: 'Scan jobs', + // dataPlacement: 'right', + // dataContainer: "body" + // }, default_scan: { - label: "Use default scan job project and playbook", - type: 'checkbox', - ngChange: 'toggleScanInfo()', - ngShow: 'job_type.value === "scan"', + type: 'custom', column: 1, - awPopOver: "

Scan jobs templates use a default project and default playbook. Uncheck this box to override these defaults.

", - dataTitle: 'Scan jobs', - dataPlacement: 'right', - dataContainer: "body" + ngShow: 'job_type.value === "scan" && project_name !== "Default"', + control: 'Reset to default project and playbook' }, credential: { label: 'Machine Credential', diff --git a/awx/ui/static/js/helpers/JobTemplates.js b/awx/ui/static/js/helpers/JobTemplates.js index 7ab79fc385..bc69745570 100644 --- a/awx/ui/static/js/helpers/JobTemplates.js +++ b/awx/ui/static/js/helpers/JobTemplates.js @@ -173,7 +173,6 @@ angular.module('JobTemplatesHelper', ['Utilities']) if(scope.project === "" && scope.playbook === ""){ - scope.default_scan = true; scope.toggleScanInfo(); } diff --git a/awx/ui/static/js/lists/JobTemplates.js b/awx/ui/static/js/lists/JobTemplates.js index 6b1afb5df2..536768a692 100644 --- a/awx/ui/static/js/lists/JobTemplates.js +++ b/awx/ui/static/js/lists/JobTemplates.js @@ -95,7 +95,6 @@ export default awToolTip: 'Copy template', dataPlacement: 'top', ngHide: 'job_template.summary_fields.can_copy===false' - } } }); diff --git a/awx/ui/static/partials/inventories.html b/awx/ui/static/partials/inventories.html index 5fb9122c65..b4869d6bdf 100644 --- a/awx/ui/static/partials/inventories.html +++ b/awx/ui/static/partials/inventories.html @@ -1,6 +1,13 @@
-
+
+
From 267653e50cfd28f0bf8cd5b1c38883065691e8a1 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 25 Mar 2015 16:08:31 -0400 Subject: [PATCH 08/12] added smart status to scan job templates in the inventory properties page --- awx/ui/static/js/controllers/Inventories.js | 2 +- awx/ui/static/js/forms/Inventories.js | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index b21fdd3bf7..4e1b44c0ff 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -791,7 +791,7 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ .success(function () { $('#copy-job-modal').dialog('close'); Wait('stop'); - $location.path($location.path() + '/' + new_data.id); + $location.path($location.path() + '/job_templates/' + new_data.id); }) .error(function (data) { Wait('stop'); diff --git a/awx/ui/static/js/forms/Inventories.js b/awx/ui/static/js/forms/Inventories.js index b745ce7b00..3b628da008 100644 --- a/awx/ui/static/js/forms/Inventories.js +++ b/awx/ui/static/js/forms/Inventories.js @@ -114,14 +114,14 @@ export default }, fields: { - // smart_status: { - // label: 'Status', - // // columnClass: 'col-md-2 col-sm-2 col-xs-2', - // searchable: false, - // nosort: true, - // ngInclude: "'/static/partials/job-template-smart-status.html'", - // type: 'template' - // }, + smart_status: { + label: 'Status', + // columnClass: 'col-md-2 col-sm-2 col-xs-2', + searchable: false, + nosort: true, + ngInclude: "'/static/partials/scan-job-template-smart-status.html'", + type: 'template' + }, name: { key: true, label: 'Name', From 3c0c5b89b6607952a592689584743192ad024d09 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 25 Mar 2015 16:17:11 -0400 Subject: [PATCH 09/12] adding html file for smart status --- awx/ui/static/partials/scan-job-template-smart-status.html | 1 + 1 file changed, 1 insertion(+) create mode 100644 awx/ui/static/partials/scan-job-template-smart-status.html diff --git a/awx/ui/static/partials/scan-job-template-smart-status.html b/awx/ui/static/partials/scan-job-template-smart-status.html new file mode 100644 index 0000000000..cb5787fed1 --- /dev/null +++ b/awx/ui/static/partials/scan-job-template-smart-status.html @@ -0,0 +1 @@ + From 1afeed3588ba708a2fe99730c323863eb43ffa4a Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Thu, 26 Mar 2015 10:07:25 -0400 Subject: [PATCH 10/12] fix undefined function --- awx/ui/static/js/controllers/JobTemplates.js | 34 +++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js index 5610037087..8279d0f61e 100644 --- a/awx/ui/static/js/controllers/JobTemplates.js +++ b/awx/ui/static/js/controllers/JobTemplates.js @@ -342,22 +342,7 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $ parent_scope: $scope }); - if ($routeParams.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 = $routeParams.inventory_id; - Rest.setUrl(GetBasePath('inventory') + $routeParams.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 }); - }); - } + // Update playbook select whenever project value changes selectPlaybook = function (oldValue, newValue) { @@ -415,6 +400,23 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $ } }; + if ($routeParams.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 = $routeParams.inventory_id; + Rest.setUrl(GetBasePath('inventory') + $routeParams.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 }); + }); + } + // Detect and alert user to potential SCM status issues checkSCMStatus = function (oldValue, newValue) { if (oldValue !== newValue && !Empty($scope.project)) { From 2401865f598d95fc8cd1a117c56d9535a068f2d7 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Fri, 27 Mar 2015 14:03:16 -0400 Subject: [PATCH 11/12] adding delete jt service --- awx/ui/static/js/app.js | 3 +++ awx/ui/static/js/controllers/Inventories.js | 24 ++++++------------- .../delete-job-template.service-test.js | 24 +++++++++++++++++++ .../delete-job-template.service.js | 20 ++++++++++++++++ awx/ui/static/js/job-templates/main.js | 5 ++++ awx/ui/tests/unit/rest-stub.js | 9 +++++++ 6 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 awx/ui/static/js/job-templates/delete-job-template.service-test.js create mode 100644 awx/ui/static/js/job-templates/delete-job-template.service.js create mode 100644 awx/ui/static/js/job-templates/main.js diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 9256860cb2..12ee00cba3 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -55,6 +55,8 @@ import 'tower/shared/InventoryTree'; import 'tower/shared/Timer'; import 'tower/shared/Socket'; +import 'tower/job-templates/main'; + /*#if DEBUG#*/ import {__deferLoadIfEnabled} from 'tower/debug'; __deferLoadIfEnabled(); @@ -74,6 +76,7 @@ var tower = angular.module('Tower', [ 'UserFormDefinition', 'FormGenerator', 'OrganizationListDefinition', + 'jobTemplates', 'UserListDefinition', 'UserHelper', 'PromptDialog', diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index 4e1b44c0ff..b109ff26f9 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -13,6 +13,7 @@ * @description This controller's for the Inventory page */ +import 'tower/job-templates/main'; export function InventoriesList($scope, $rootScope, $location, $log, $routeParams, $compile, $filter, Rest, Alert, InventoryList, generateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, Wait, Stream, @@ -483,7 +484,7 @@ InventoriesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit, PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, ParseVariableString, Stream, RelatedSearchInit, RelatedPaginateInit, - Prompt, PlaybookRun, CreateDialog) { + Prompt, PlaybookRun, CreateDialog, deleteJobTemplate) { ClearScope(); @@ -491,7 +492,6 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ var defaultUrl = GetBasePath('inventory'), form = InventoryForm(), generator = GenerateForm, - jobtemplateUrl = GetBasePath('job_templates'), inventory_id = $routeParams.inventory_id, master = {}, fld, json_data, data, @@ -689,15 +689,6 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ "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", @@ -816,9 +807,7 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ action = function () { $('#prompt-modal').modal('hide'); Wait('start'); - var url = jobtemplateUrl+id; - Rest.setUrl(url); - Rest.destroy() + deleteJobTemplate(id) .success(function () { $('#prompt-modal').modal('hide'); $scope.search(form.related.scan_job_templates.iterator); @@ -826,7 +815,7 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ .error(function (data) { Wait('stop'); ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); + msg: 'DELETE returned status: ' + status }); }); }; @@ -835,14 +824,15 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $ body: '
Delete job template ' + this.scan_job_template.name + '?
', action: action }); - }; + + }; } InventoriesEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'generateList', 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit', 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', 'Stream', 'RelatedSearchInit', 'RelatedPaginateInit', - 'Prompt', 'PlaybookRun', 'CreateDialog' + 'Prompt', 'PlaybookRun', 'CreateDialog', 'deleteJobTemplate' ]; diff --git a/awx/ui/static/js/job-templates/delete-job-template.service-test.js b/awx/ui/static/js/job-templates/delete-job-template.service-test.js new file mode 100644 index 0000000000..f1fcb871fc --- /dev/null +++ b/awx/ui/static/js/job-templates/delete-job-template.service-test.js @@ -0,0 +1,24 @@ +import jobTemplates from 'tower/job-templates/main'; +import {describeModule} from '../describe-module'; + +describeModule(jobTemplates.name) + .testService('deleteJobTemplate', function(test, restStub) { + + var service; + + test.withService(function(_service) { + service = _service; + }); + + it('deletes the job template', function() { + var result = {}; + + var actual = service(); + + restStub.succeedOn('destroy', result); + restStub.flush(); + + expect(actual).to.eventually.equal(result); + + }); + }); diff --git a/awx/ui/static/js/job-templates/delete-job-template.service.js b/awx/ui/static/js/job-templates/delete-job-template.service.js new file mode 100644 index 0000000000..a61754b460 --- /dev/null +++ b/awx/ui/static/js/job-templates/delete-job-template.service.js @@ -0,0 +1,20 @@ +var rest, getBasePath; + +export default + [ 'Rest', + 'GetBasePath', + function(_rest, _getBasePath) { + rest = _rest; + getBasePath = _getBasePath; + return deleteJobTemplate; + } + ]; + +function deleteJobTemplate(id) { + var url = getBasePath('job_templates'); + + url = url + id; + + rest.setUrl(url); + return rest.destroy(); +} diff --git a/awx/ui/static/js/job-templates/main.js b/awx/ui/static/js/job-templates/main.js new file mode 100644 index 0000000000..3daabbf1d6 --- /dev/null +++ b/awx/ui/static/js/job-templates/main.js @@ -0,0 +1,5 @@ +import deleteJobTemplate from './delete-job-template.service'; + +export default + angular.module('jobTemplates', []) + .service('deleteJobTemplate', deleteJobTemplate); diff --git a/awx/ui/tests/unit/rest-stub.js b/awx/ui/tests/unit/rest-stub.js index be91f1afe7..e73c3b735b 100644 --- a/awx/ui/tests/unit/rest-stub.js +++ b/awx/ui/tests/unit/rest-stub.js @@ -35,10 +35,19 @@ RestStub.prototype = return this.deferred.promise; }, + destroy: function() { + this.deferred = this.deferred || {}; + this.deferred.destroy = this[this.currentUrl]; + + return this.deferred.destroy.promise; + }, succeedAt: function(url, value) { assertUrlDeferred(url, this); this[url].resolve(value); }, + succeedOn: function(method, value) { + this.deferred[method] = value; + }, succeed: function(value) { this.deferred.resolve(value); }, From 3512bcdc5948bdda424ca95f507aa276cb87279d Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Fri, 27 Mar 2015 15:24:36 -0400 Subject: [PATCH 12/12] moving jt test to test folder so that merge will pass jenkins --- awx/ui/static/partials/inventories.html | 8 ++++---- .../job-templates/delete-job-template.service-test.js | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename awx/ui/{static/js => tests/unit}/job-templates/delete-job-template.service-test.js (100%) diff --git a/awx/ui/static/partials/inventories.html b/awx/ui/static/partials/inventories.html index b4869d6bdf..a3b4306a19 100644 --- a/awx/ui/static/partials/inventories.html +++ b/awx/ui/static/partials/inventories.html @@ -5,9 +5,9 @@
diff --git a/awx/ui/static/js/job-templates/delete-job-template.service-test.js b/awx/ui/tests/unit/job-templates/delete-job-template.service-test.js similarity index 100% rename from awx/ui/static/js/job-templates/delete-job-template.service-test.js rename to awx/ui/tests/unit/job-templates/delete-job-template.service-test.js