diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js
index fc0964a028..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',
@@ -267,6 +270,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..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,
@@ -482,7 +483,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) {
+ LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON, ParseVariableString, Stream, RelatedSearchInit, RelatedPaginateInit,
+ Prompt, PlaybookRun, CreateDialog, deleteJobTemplate) {
ClearScope();
@@ -492,16 +494,28 @@ 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;
+ for (set in relatedSets) {
+ $scope.search(relatedSets[set].iterator);
+ }
+ });
Wait('start');
Rest.setUrl(GetBasePath('inventory') + inventory_id + '/');
@@ -530,6 +544,24 @@ 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
+ });
+
+ LoadBreadCrumbs({
+ path: $location.path(),
+ title: $scope.inventory_name
+ });
+
Wait('stop');
$scope.parseType = 'yaml';
ParseTypeChange({
@@ -546,6 +578,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 +660,179 @@ export function InventoriesEdit($scope, $rootScope, $compile, $location, $log, $
$scope.addScanJob = function(){
$location.path($location.path()+'/job_templates/add');
};
+
+ $scope.launchScanJob = function(){
+ 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();
+ },
+ "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() + '/job_templates/' + 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 () {
+ $('#prompt-modal').modal('hide');
+ Wait('start');
+ deleteJobTemplate(id)
+ .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: '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'
+ 'PaginateInit', 'LookUpInit', 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString', 'Stream', 'RelatedSearchInit', 'RelatedPaginateInit',
+ 'Prompt', 'PlaybookRun', 'CreateDialog', 'deleteJobTemplate'
];
diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js
index 063b376df9..8279d0f61e 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 = [
@@ -301,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);
@@ -340,10 +342,17 @@ export function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $
parent_scope: $scope
});
+
+
// Update playbook select whenever project value changes
selectPlaybook = function (oldValue, newValue) {
var url;
- if (oldValue !== newValue) {
+ if($scope.job_type.value === 'scan' && $scope.project_name === "Default"){
+ $scope.playbook_options = ['Default'];
+ $scope.playbook = 'Default';
+ Wait('stop');
+ }
+ else if (oldValue !== newValue) {
if ($scope.project) {
Wait('start');
url = GetBasePath('projects') + $scope.project + '/playbooks/';
@@ -365,6 +374,49 @@ 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;
+ }
+ };
+
+ 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)) {
@@ -432,7 +484,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) {
@@ -472,7 +534,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) {
@@ -591,7 +656,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 = [
@@ -617,7 +683,12 @@ export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log,
getPlaybooks = function (project) {
var url;
- if (!Empty(project)) {
+ if($scope.job_type.value === 'scan' && $scope.project_name === "Default"){
+ $scope.playbook_options = ['Default'];
+ $scope.playbook = 'Default';
+ Wait('stop');
+ }
+ else if (!Empty(project)) {
url = GetBasePath('projects') + project + '/playbooks/';
Wait('start');
Rest.setUrl(url);
@@ -648,6 +719,32 @@ export function JobTemplatesEdit($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){
+ getPlaybooks();
+ }
+ else {
+ $scope.project = null;
+ }
+ };
+
// Detect and alert user to potential SCM status issues
checkSCMStatus = function () {
if (!Empty($scope.project)) {
@@ -888,7 +985,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 5d0bd79d60..3b628da008 100644
--- a/awx/ui/static/js/forms/Inventories.js
+++ b/awx/ui/static/js/forms/Inventories.js
@@ -97,26 +97,35 @@ export default
},
related: {
- scan_jobs: {
+ scan_job_templates: {
type: 'collection',
- title: 'Scan Jobs',
- iterator: 'scan_job',
+ 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'
+ awToolTip: 'Add a scan job template'
}
},
fields: {
+ 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'
+ label: 'Name',
+ linkTo: '/#/inventories/{{inventory_id}}/job_templates/{{scan_job_template.id}}'
},
description: {
label: 'Description'
@@ -124,19 +133,39 @@ export default
},
fieldActions: {
+ submit: {
+ label: 'Launch',
+ ngClick: "launchScanJob()",
+ 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: "edit('organizations', organization.id, organization.name)",
+ ngClick: "editScanJob()",
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()",
icon: 'icon-trash',
"class": 'btn-danger',
- awToolTip: 'Delete the organization'
+ 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'
}
}
}
@@ -144,14 +173,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 f78f556474..c8fc483f53 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,
@@ -101,7 +102,7 @@ export default
awPopOver: "Select the project containing the playbook you want this job to execute.
",
dataTitle: 'Project',
dataPlacement: 'right',
- dataContainer: "body"
+ dataContainer: "body",
},
playbook: {
label: 'Playbook',
@@ -113,7 +114,24 @@ export default
awPopOver: "Select the playbook to be executed by this job.
",
dataTitle: 'Playbook',
dataPlacement: 'right',
- dataContainer: "body"
+ dataContainer: "body",
+ },
+ // 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: {
+ type: 'custom',
+ column: 1,
+ 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/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";
-
-
- // $('#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 += '
'+question.question_name+' \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+= '
Show Password ';
-
- }
- 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+='
'; //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; i
0){
- 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 = '