diff --git a/lib/ui/static/js/app.js b/lib/ui/static/js/app.js index e2ae135e51..4f84992fcb 100644 --- a/lib/ui/static/js/app.js +++ b/lib/ui/static/js/app.js @@ -43,18 +43,19 @@ angular.module('ansible', [ 'LookUpHelper', 'JobTemplatesListDefinition', 'JobTemplateFormDefinition', + 'JobTemplateHelper', 'ProjectsListDefinition' ]) .config(['$routeProvider', function($routeProvider) { $routeProvider. - when('/job_templates', { templateUrl: urlPrefix + 'partials/job_templates.html', - controller: JobTemplatesList }). + when('/job_templates', + { templateUrl: urlPrefix + 'partials/job_templates.html', controller: JobTemplatesList }). - when('/job_templates/add', { templateUrl: urlPrefix + 'partials/job_templates.html', - controller: JobTemplatesAdd }). + when('/job_templates/add', + { templateUrl: urlPrefix + 'partials/job_templates.html', controller: JobTemplatesAdd }). - // when('/job_templates/:id', { templateUrl: urlPrefix + 'partials/job_templates.html', - // controller: JobTemplatesEdit }). + when('/job_templates/:id', + { templateUrl: urlPrefix + 'partials/job_templates.html', controller: JobTemplatesEdit }). when('/projects', { templateUrl: urlPrefix + 'partials/projects.html', controller: ProjectsList }). diff --git a/lib/ui/static/js/controllers/JobTemplates.js b/lib/ui/static/js/controllers/JobTemplates.js index b3b403318e..272e9b3964 100644 --- a/lib/ui/static/js/controllers/JobTemplates.js +++ b/lib/ui/static/js/controllers/JobTemplates.js @@ -132,7 +132,7 @@ JobTemplatesList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$rout function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, - GetBasePath, InventoryList, CredentialList, ProjectList, LookUpInit) + GetBasePath, InventoryList, CredentialList, ProjectList, LookUpInit, PromptPasswords) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. @@ -145,7 +145,7 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP generator.reset(); LoadBreadCrumbs(); - LookUpInit({ + LookUpInit({ scope: scope, form: form, current_item: null, @@ -153,7 +153,7 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP field: 'inventory' }); - LookUpInit({ + LookUpInit({ scope: scope, form: form, current_item: null, @@ -161,48 +161,60 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP field: 'credential' }); - // Update playbook select whenever project value changes - var selectPlaybook = function(oldValue, newValue) { - $('#playbook-select option').each( function(index) { - if (index > 0) { - $(this).remove(); + // Update playbook select whenever project value changes + var selectPlaybook = function(oldValue, newValue) { + if (oldValue != newValue) { + if (scope.project) { + var url = GetBasePath('projects') + scope.project + '/playbooks/'; + Rest.setUrl(url); + Rest.get() + .success( function(data, status, headers, config) { + var opts = []; + for (var i=0; i < data.length; i++) { + opts.push({ label: data[i], value: data[i] }); + } + scope.playbook_options = opts; + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to get playbook list for ' + url +'. GET returned status: ' + status }); + }); } - }); - if (scope.project) { - var url = GetBasePath('projects') + scope.project + '/playbooks/'; - Rest.setUrl(url); - Rest.get() - .success( function(data, status, headers, config) { - for (var i=0; i < data.length; i++) { - $('#playbook-select').append(''); - } - }) - .error( function(data, status, headers, config) { - ProcessErrors(scope, data, status, form, - { hdr: 'Error!', msg: 'Failed to get playbook list for ' + url +'. GET returned status: ' + status }); - }); - } - }; - scope.$watch('project_name', selectPlaybook); + } + }; + + // Register a watcher on project_name + if (scope.selectPlaybookUnregister) { + scope.selectPlaybookUnregister(); + } + scope.selectPlaybookUnregister = scope.$watch('project_name', selectPlaybook); - LookUpInit({ - scope: scope, - form: form, - current_item: null, - list: ProjectList, - field: 'project' - }); + LookUpInit({ + scope: scope, + form: form, + current_item: null, + list: ProjectList, + field: 'project' + }); + + scope.job_type_options = [{ value: 'run', label: 'Run' }, { value: 'check', label: 'Check' }]; + scope.playbook_options = []; // Save scope.formSave = function() { Rest.setUrl(defaultUrl); var data = {} for (var fld in form.fields) { - data[fld] = scope[fld]; + if (form.fields[fld].type == 'select') { + data[fld] = scope[fld].value; + } + else { + data[fld] = scope[fld]; + } } Rest.post(data) .success( function(data, status, headers, config) { - // Template saved. Now post job + // Template saved. Now create a new job Rest.setUrl(data.related.jobs); Rest.post({ name: data.name, @@ -218,11 +230,13 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP verbosity: data.verbosity, extra_vars: data.extra_vars}) .success( function(data, status, headers, config) { - // <--- We'll need something to prompt for passwords and then actually start - // the job console.log('Job posted!'); - console.log(data); - // Do we need to cancel the watch on playbook select???? + console.log(data); + PromptPasswords({ + scope: scope, + passwords: data.passwords_needed_to_start, + start_url: data.related.start + }); }) .error( function(data, status, headers, config) { ProcessErrors(scope, data, status, form, @@ -245,5 +259,217 @@ function JobTemplatesAdd ($scope, $rootScope, $compile, $location, $log, $routeP JobTemplatesAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', - 'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit' ]; + 'GetBasePath', 'InventoryList', 'CredentialList', 'ProjectList', 'LookUpInit', 'PromptPasswords' ]; + +function JobTemplatesEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, JobTemplateForm, + GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, + RelatedPaginateInit, ReturnToCaller, ClearScope, InventoryList, CredentialList, + ProjectList, LookUpInit, PromptPasswords, GetBasePath) +{ + ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior + //scope. + + var defaultUrl= GetBasePath('job_templates'); + var generator = GenerateForm; + var form = JobTemplateForm; + var scope = generator.inject(form, {mode: 'edit', related: true}); + generator.reset(); + var base = $location.path().replace(/^\//,'').split('/')[0]; + var master = {}; + var id = $routeParams.id; + var relatedSets = {}; + + function getPlaybooks() { + var url = GetBasePath('projects') + scope.project + '/playbooks/'; + Rest.setUrl(url); + Rest.get() + .success( function(data, status, headers, config) { + var opts = []; + for (var i=0; i < data.length; i++) { + opts.push({ label: data[i], value: data[i] }); + } + scope.playbook_options = opts; + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to get playbook list for ' + url +'. GET returned status: ' + status }); + }); + } + + // Update playbook select whenever project value changes + var selectPlaybook = function(oldValue, newValue) { + if (oldValue != newValue && scope.project) { + getPlaybooks(); + } + }; + + // Register a watcher on project_name + if (scope.selectPlaybookUnregister) { + scope.selectPlaybookUnregister(); + } + scope.selectPlaybookUnregister = scope.$watch('project_name', selectPlaybook); + + // Retrieve each related set + if (scope.jobTemplateLoadedRemove) { + scope.jobTemplateLoadedRemove(); + } + scope.jobTemplateLoadedRemove = scope.$on('jobTemplateLoaded', function() { + for (var set in relatedSets) { + scope.search(relatedSets[set].iterator); + } + getPlaybooks(); + }); + + scope.job_type_options = [{ value: 'run', label: 'Run' }, { value: 'check', label: 'Check' }]; + + // Retrieve detail record and prepopulate the form + Rest.setUrl(defaultUrl + ':id/'); + Rest.get({ params: {id: id} }) + .success( function(data, status, headers, config) { + LoadBreadCrumbs({ path: '/job_templates/' + id, title: data.name }); + for (var fld in form.fields) { + if (data[fld] !== null && data[fld] !== undefined) { + + if (form.fields[fld].type == 'select') { + if (scope[fld + '_options']) { + for (var i=0; i < scope[fld + '_options'].length; i++) { + if (data[fld] == scope[fld + '_options'][i].value) { + scope[fld] = scope[fld + '_options'][i]; + } + } + } + else { + scope[fld] = { label: data[fld], value: data[fld] }; + } + } + else { + scope[fld] = data[fld]; + } + master[fld] = scope[fld]; + } + if (form.fields[fld].type == 'lookup' && data.summary_fields[form.fields[fld].sourceModel]) { + scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = + data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; + master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = + scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField]; + } + } + + var related = data.related; + for (var set in form.related) { + if (related[set]) { + relatedSets[set] = { url: related[set], iterator: form.related[set].iterator }; + } + } + + LookUpInit({ + scope: scope, + form: form, + current_item: data.inventory, + list: InventoryList, + field: 'inventory' + }); + + LookUpInit({ + scope: scope, + form: form, + current_item: data.credential, + list: CredentialList, + field: 'credential' + }); + + LookUpInit({ + scope: scope, + form: form, + current_item: data.project, + list: ProjectList, + field: 'project' + }); + + // 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 }); + scope.$emit('jobTemplateLoaded'); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to retrieve job template: ' + $routeParams.id + '. GET status: ' + status }); + }); + + // Save changes to the parent + scope.formSave = function() { + Rest.setUrl(defaultUrl + $routeParams.id); + var data = {} + for (var fld in form.fields) { + if (form.fields[fld].type == 'select') { + data[fld] = scope[fld].value; + } + else { + data[fld] = scope[fld]; + } + } + Rest.put(data) + .success( function(data, status, headers, config) { + var base = $location.path().replace(/^\//,'').split('/')[0]; + (base == 'job_templates') ? ReturnToCaller() : ReturnToCaller(1); + }) + .error( function(data, status, headers, config) { + ProcessErrors(scope, data, status, form, + { hdr: 'Error!', msg: 'Failed to update team: ' + $routeParams.id + '. PUT status: ' + status }); + }); + }; + + // Cancel + scope.formReset = function() { + generator.reset(); + for (var fld in master) { + scope[fld] = master[fld]; + } + }; + + // Related set: Add button + scope.add = function(set) { + $rootScope.flashMessage = null; + $location.path('/' + base + '/' + $routeParams.id + '/' + set); + }; + + // Related set: Edit button + scope.edit = function(set, id, name) { + $rootScope.flashMessage = null; + $location.path('/' + base + '/' + $routeParams.id + '/' + set + '/' + id); + }; + + // Related set: Delete button + scope.delete = function(set, itm_id, name, title) { + $rootScope.flashMessage = null; + + var action = function() { + var url = defaultUrl + id + '/' + set + '/'; + Rest.setUrl(url); + Rest.post({ id: itm_id, disassociate: 1 }) + .success( function(data, status, headers, config) { + $('#prompt-modal').modal('hide'); + scope.search(form.related[set].iterator); + }) + .error( function(data, status, headers, config) { + $('#prompt-modal').modal('hide'); + ProcessErrors(scope, data, status, null, + { hdr: 'Error!', msg: 'Call to ' + url + ' failed. POST returned status: ' + status }); + }); + }; + + Prompt({ hdr: 'Delete', + body: 'Are you sure you want to remove ' + name + ' from ' + scope.name + ' ' + title + '?', + action: action + }); + + } + +} + +JobTemplatesEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'JobTemplateForm', + 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', + 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'InventoryList', 'CredentialList', + 'ProjectList', 'LookUpInit', 'PromptPasswords', 'GetBasePath' + ]; diff --git a/lib/ui/static/js/forms/JobTemplates.js b/lib/ui/static/js/forms/JobTemplates.js index 1472e8a382..2c4f9a9576 100644 --- a/lib/ui/static/js/forms/JobTemplates.js +++ b/lib/ui/static/js/forms/JobTemplates.js @@ -31,7 +31,6 @@ angular.module('JobTemplateFormDefinition', []) job_type: { label: 'Job Type', type: 'select', - options: [{ value: 'run', label: 'Run' }, { value: 'check', label: 'Check' }], default: 'run', addRequired: true, editRequired: true diff --git a/lib/ui/static/js/helpers/JobTemplate.js b/lib/ui/static/js/helpers/JobTemplate.js new file mode 100644 index 0000000000..3444c12c20 --- /dev/null +++ b/lib/ui/static/js/helpers/JobTemplate.js @@ -0,0 +1,84 @@ +/********************************************* + * Copyright (c) 2013 AnsibleWorks, Inc. + * + * JobTemplateHelper + * + */ +angular.module('JobTemplateHelper', [ 'RestServices', 'Utilities', 'CredentialFormDefinition' ]) + .factory('PromptPasswords',['CredentialForm', '$compile', 'Rest', function(JobTemplateForm, $compile, Rest) { + return function(params) { + + var scope = params.scope; + var passwords = params.passwords; + var start_url = params.start_url; + var form = JobTemplateForm; + var html = ''; + var field, element, dialogScope, fld; + + scope.startJob = function() { + $('#password-modal').modal('hide'); + var pswd = {}; + $('.password-field').each(function(index) { + pswd[$(this).attr('name')] = $(this).val(); + }); + console.log(pswd); + Rest.setUrl(start_url); + Rest.post(pswd) + .success( function(data) { alert('success!'); }) + .error( function(data) { alert('fail!'); }); + } + + html += html += "
\n"; + console.log(html); + element = angular.element(document.getElementById('password-body')); + element.html(html); + $compile(element.contents())(scope); + $('#password-modal').modal(); + } + }]); diff --git a/lib/ui/static/js/lists/JobTemplates.js b/lib/ui/static/js/lists/JobTemplates.js index a8435d34d1..f1f451c1b4 100644 --- a/lib/ui/static/js/lists/JobTemplates.js +++ b/lib/ui/static/js/lists/JobTemplates.js @@ -41,7 +41,7 @@ angular.module('JobTemplatesListDefinition', []) edit: { ngClick: "editJobTemplate(\{\{ job_template.id \}\})", icon: 'icon-edit', - awToolTip: 'Edit user' + awToolTip: 'Edit template' }, delete: { diff --git a/lib/ui/static/lib/ansible/form-generator.js b/lib/ui/static/lib/ansible/form-generator.js index d41452b6b2..1e83589617 100644 --- a/lib/ui/static/lib/ansible/form-generator.js +++ b/lib/ui/static/lib/ansible/form-generator.js @@ -271,20 +271,15 @@ angular.module('FormGenerator', ['GeneratorHelpers']) html += "\n"; + html += ">\n"; + html += "\n"; + html += "