diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 235539f398..86f807048a 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -40,6 +40,7 @@ import {ScheduleEditController} from 'tower/controllers/Schedules'; import {ProjectsList, ProjectsAdd, ProjectsEdit} from 'tower/controllers/Projects'; import {OrganizationsList, OrganizationsAdd, OrganizationsEdit} from 'tower/controllers/Organizations'; import {InventoriesList, InventoriesAdd, InventoriesEdit, InventoriesManage} from 'tower/controllers/Inventories'; +import {AdhocForm} from 'tower/controllers/Adhoc'; import {AdminsList} from 'tower/controllers/Admins'; import {UsersList, UsersAdd, UsersEdit} from 'tower/controllers/Users'; import {TeamsList, TeamsAdd, TeamsEdit} from 'tower/controllers/Teams'; @@ -88,6 +89,7 @@ var tower = angular.module('Tower', [ 'AdminListDefinition', 'CustomInventoryListDefinition', 'AWDirectives', + 'AdhocFormDefinition', 'InventoriesListDefinition', 'InventoryFormDefinition', 'InventoryHelper', @@ -168,7 +170,8 @@ var tower = angular.module('Tower', [ 'ConfigureTowerHelper', 'ConfigureTowerJobsListDefinition', 'CreateCustomInventoryHelper', - 'CustomInventoryListDefinition' + 'CustomInventoryListDefinition', + 'AdhocHelper' ]) .constant('AngularScheduler.partials', urlPrefix + 'lib/angular-scheduler/lib/') @@ -200,6 +203,11 @@ var tower = angular.module('Tower', [ controller: JobStdoutController }). + when('/ad_hoc_commands/:id', { + templateUrl: urlPrefix + 'partials/job_stdout_adhoc.html', + controller: JobStdoutController + }). + when('/job_templates', { templateUrl: urlPrefix + 'partials/job_templates.html', controller: JobTemplatesList @@ -280,6 +288,11 @@ var tower = angular.module('Tower', [ controller: InventoriesManage }). + when('/inventories/:inventory_id/adhoc', { + templateUrl: urlPrefix + 'partials/adhoc.html', + controller: AdhocForm + }). + when('/organizations', { templateUrl: urlPrefix + 'partials/organizations.html', controller: OrganizationsList diff --git a/awx/ui/static/js/controllers/Adhoc.js b/awx/ui/static/js/controllers/Adhoc.js new file mode 100644 index 0000000000..9aa94a2e85 --- /dev/null +++ b/awx/ui/static/js/controllers/Adhoc.js @@ -0,0 +1,171 @@ +/************************************************* + * Copyright (c) 2015 AnsibleWorks, Inc. + * + * Adhoc.js + * + * Controller functions for the Adhoc model. + * + */ +/** + * @ngdoc function + * @name controllers.function:Adhoc + * @description This controller controls the adhoc form creation, command launching and navigating to standard out after command has been succesfully ran. +*/ +export function AdhocForm($scope, $rootScope, $location, $routeParams, + AdhocForm, GenerateForm, Rest, ProcessErrors, ClearScope, GetBasePath, + GetChoices, KindChange, LookUpInit, CredentialList, Empty, OwnerChange, + LoginMethodChange, Wait) { + + ClearScope(); + + var url = GetBasePath('inventory') + $routeParams.inventory_id + + '/ad_hoc_commands/', + generator = GenerateForm, + form = AdhocForm, + master = {}, + id = $routeParams.inventory_id; + + // inject the adhoc command form + generator.inject(form, { mode: 'edit', related: true, scope: $scope }); + generator.reset(); + + // BEGIN: populate scope with the things needed to make the adhoc form + // display + Wait('start'); + $scope.id = id; + $scope.argsPopOver = "
These arguments are used with the" + + " specified module.
"; + // fix arguments help popover based on the module selected + $scope.moduleChange = function () { + // NOTE: for selenium testing link - + // link will be displayed with id adhoc_module_arguments_docs_link + // only when a module is selected + if ($scope.module_name) { + // give the docs for the selected module + $scope.argsPopOver = "These arguments are used with the" + + " specified module. You can find information about the " + + $scope.module_name.value + + " here.
"; + } else { + // no module selected + $scope.argsPopOver = "These arguments are used with the" + + " specified module.
"; + } + }; + + // pre-populate hostPatterns from the inventory page and + // delete the value off of rootScope + $scope.limit = $rootScope.hostPatterns || "all"; + delete $rootScope.hostPatterns; + + if ($scope.removeChoicesReady) { + $scope.removeChoicesReady(); + } + $scope.removeChoicesReady = $scope.$on('choicesReadyAdhoc', function () { + LookUpInit({ + scope: $scope, + form: form, + current_item: (!Empty($scope.credential_id)) ? $scope.credential_id : null, + list: CredentialList, + field: 'credential', + input_type: 'radio' + }); + + OwnerChange({ scope: $scope }); + LoginMethodChange({ scope: $scope }); + + Wait('stop'); // END: form population + }); + + // setup Machine Credential lookup + GetChoices({ + scope: $scope, + url: url, + field: 'module_name', + variable: 'adhoc_module_options', + callback: 'choicesReadyAdhoc' + }); + + // Handle Owner change + $scope.ownerChange = function () { + OwnerChange({ scope: $scope }); + }; + + // Handle Login Method change + $scope.loginMethodChange = function () { + LoginMethodChange({ scope: $scope }); + }; + + // Handle Kind change + $scope.kindChange = function () { + KindChange({ scope: $scope, form: form, reset: true }); + }; + + // launch the job with the provided form data + $scope.launchJob = function () { + var fld, data={}; + + // stub the payload with defaults from DRF + data = { + "job_type": "run", + "limit": "", + "credential": null, + "module_name": "command", + "module_args": "", + "forks": 0, + "verbosity": 0, + "privilege_escalation": "" + }; + + generator.clearApiErrors(); + + // populate data with the relevant form values + for (fld in form.fields) { + if (form.fields[fld].type === 'select') { + data[fld] = $scope[fld].value + } else { + data[fld] = $scope[fld]; + } + } + + Wait('start'); + + // Launch the adhoc job + Rest.setUrl(url); + Rest.post(data) + .success(function (data) { + Wait('stop'); + $location.path("/ad_hoc_commands/" + data.id); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, form, { hdr: 'Error!', + msg: 'Failed to launch adhoc command. POST returned status: ' + + status }); + // TODO: still need to implement popping up a password prompt + // if the credential requires it. The way that the current end- + // point works is that I find out if I need to ask for a + // password from POST, thus I get an error response. + $scope.formReset(); + }); + }; + + // Remove all data input into the form + $scope.formReset = function () { + generator.reset(); + for (var fld in master) { + $scope[fld] = master[fld]; + } + KindChange({ scope: $scope, form: form, reset: false }); + OwnerChange({ scope: $scope }); + LoginMethodChange({ scope: $scope }); + }; +} + +AdhocForm.$inject = ['$scope', '$rootScope', '$location', '$routeParams', + 'AdhocForm', 'GenerateForm', 'Rest', 'ProcessErrors', 'ClearScope', + 'GetBasePath', 'GetChoices', 'KindChange', 'LookUpInit', 'CredentialList', + 'Empty', 'OwnerChange', 'LoginMethodChange', 'Wait']; diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index 00af03e462..8e6a87c25a 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -870,6 +870,53 @@ export function InventoriesManage ($log, $scope, $rootScope, $location, show_failures: false }]; + // TODO: only display adhoc button if the user has permission to use it. + // TODO: figure out how to get the action-list partial to update so that + // the tooltip can be changed based off things being selected or not. + $scope.adhocButtonTipContents = "Launch adhoc command for the inventory"; + + // watcher for the group list checkbox changes + $scope.$on('multiSelectList.selectionChanged', function(e, selection) { + if (selection.length > 0) { + $scope.groupsSelected = true; + // $scope.adhocButtonTipContents = "Launch adhoc command for the " + // + "selected groups and hosts."; + } else { + $scope.groupsSelected = false; + // $scope.adhocButtonTipContents = "Launch adhoc command for the " + // + "inventory."; + } + $scope.groupsSelectedItems = selection.selectedItems; + }); + + // watcher for the host list checkbox changes + hostScope.$on('multiSelectList.selectionChanged', function(e, selection) { + // you need this so that the event doesn't bubble to the watcher above + // for the host list + e.stopPropagation(); + if (selection.length > 0) { + $scope.hostsSelected = true; + // $scope.adhocButtonTipContents = "Launch adhoc command for the " + // + "selected groups and hosts."; + } else { + $scope.hostsSelected = false; + // $scope.adhocButtonTipContents = "Launch adhoc command for the " + // + "inventory."; + } + $scope.hostsSelectedItems = selection.selectedItems; + }); + + // populates host patterns based on selected hosts/groups + $scope.populateAdhocForm = function() { + var host_patterns = "all"; + if ($scope.hostsSelected || $scope.groupsSelected) { + var allSelectedItems = $scope.groupsSelectedItems.concat($scope.hostsSelectedItems) + host_patterns = _.pluck(allSelectedItems, "name").join(":"); + } + $rootScope.hostPatterns = host_patterns; + $location.path('/inventories/' + $scope.inventory.id + '/adhoc'); + } + $scope.refreshHostsOnGroupRefresh = false; $scope.selected_group_id = null; diff --git a/awx/ui/static/js/controllers/JobStdout.js b/awx/ui/static/js/controllers/JobStdout.js index 8035ec7326..89ca609fff 100644 --- a/awx/ui/static/js/controllers/JobStdout.js +++ b/awx/ui/static/js/controllers/JobStdout.js @@ -11,7 +11,7 @@ */ -export function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams, ClearScope, GetBasePath, Wait, Rest, ProcessErrors, Socket) { +export function JobStdoutController ($location, $log, $rootScope, $scope, $compile, $routeParams, ClearScope, GetBasePath, Wait, Rest, ProcessErrors, Socket) { ClearScope(); @@ -170,7 +170,9 @@ export function JobStdoutController ($log, $rootScope, $scope, $compile, $routeP } }); - Rest.setUrl(GetBasePath('jobs') + job_id + '/'); + // Note: could be ad_hoc_commands or jobs + var jobType = $location.path().replace(/^\//, '').split('/')[0]; + Rest.setUrl(GetBasePath(jobType) + job_id + '/'); Rest.get() .success(function(data) { $scope.job = data; @@ -270,6 +272,5 @@ export function JobStdoutController ($log, $rootScope, $scope, $compile, $routeP } -JobStdoutController.$inject = [ '$log', '$rootScope', '$scope', '$compile', '$routeParams', 'ClearScope', 'GetBasePath', 'Wait', 'Rest', 'ProcessErrors', +JobStdoutController.$inject = [ '$location', '$log', '$rootScope', '$scope', '$compile', '$routeParams', 'ClearScope', 'GetBasePath', 'Wait', 'Rest', 'ProcessErrors', 'Socket' ]; - diff --git a/awx/ui/static/js/controllers/Permissions.js b/awx/ui/static/js/controllers/Permissions.js index a0880dd980..d5e149a1a2 100644 --- a/awx/ui/static/js/controllers/Permissions.js +++ b/awx/ui/static/js/controllers/Permissions.js @@ -65,6 +65,15 @@ export function PermissionsList($scope, $rootScope, $location, $log, $routeParam } }; + // if the permission includes adhoc (and is not admin), display that + $scope.getPermissionText = function () { + if (this.permission.permission_type !== "admin" && this.permission.run_ad_hoc_commands) { + return this.permission.permission_type + " + ad hoc"; + } else { + return this.permission.permission_type; + } + }; + $scope.editPermission = function (id) { $location.path($location.path() + '/' + id); }; @@ -156,6 +165,11 @@ export function PermissionsAdd($scope, $rootScope, $compile, $location, $log, $r for (fld in form.fields) { data[fld] = $scope[fld]; } + // job template (or deploy) based permissions do not have the run + // ad hoc commands parameter + if (data.category === "Deploy") { + data.run_ad_hoc_commands = false; + } url = (base === 'teams') ? GetBasePath('teams') + id + '/permissions/' : GetBasePath('users') + id + '/permissions/'; Rest.setUrl(url); Rest.post(data) @@ -305,6 +319,11 @@ export function PermissionsEdit($scope, $rootScope, $compile, $location, $log, $ for (fld in form.fields) { data[fld] = $scope[fld]; } + // job template (or deploy) based permissions do not have the run + // ad hoc commands parameter + if (data.category === "Deploy") { + data.run_ad_hoc_commands = false; + } Rest.setUrl(defaultUrl); if($scope.category === "Inventory"){ delete data.project; diff --git a/awx/ui/static/js/controllers/Users.js b/awx/ui/static/js/controllers/Users.js index bea627584e..65e9034de6 100644 --- a/awx/ui/static/js/controllers/Users.js +++ b/awx/ui/static/js/controllers/Users.js @@ -294,6 +294,15 @@ export function UsersEdit($scope, $rootScope, $compile, $location, $log, $routeP $routeParams.id + '. GET status: ' + status }); }); + // if the permission includes adhoc (and is not admin), display that + $scope.getPermissionText = function () { + if (this.permission.permission_type !== "admin" && this.permission.run_ad_hoc_commands) { + return this.permission.permission_type + " + ad hoc"; + } else { + return this.permission.permission_type; + } + }; + // Save changes to the parent $scope.formSave = function () { var data = {}, fld; diff --git a/awx/ui/static/js/forms.js b/awx/ui/static/js/forms.js index 1bf67462b7..7caad49713 100644 --- a/awx/ui/static/js/forms.js +++ b/awx/ui/static/js/forms.js @@ -1,5 +1,6 @@ import ActivityDetail from "tower/forms/ActivityDetail"; import Credentials from "tower/forms/Credentials"; +import Adhoc from "tower/forms/Adhoc"; import CustomInventory from "tower/forms/CustomInventory"; import EventsViewer from "tower/forms/EventsViewer"; import Groups from "tower/forms/Groups"; @@ -30,6 +31,7 @@ import Users from "tower/forms/Users"; export { ActivityDetail, Credentials, + Adhoc, CustomInventory, EventsViewer, Groups, diff --git a/awx/ui/static/js/forms/Adhoc.js b/awx/ui/static/js/forms/Adhoc.js new file mode 100644 index 0000000000..6ab3bfb065 --- /dev/null +++ b/awx/ui/static/js/forms/Adhoc.js @@ -0,0 +1,98 @@ +/********************************************* + * Copyright (c) 2015 AnsibleWorks, Inc. + * + * Adhoc.js + * Form definition for the Adhoc model. + * + */ + /** + * @ngdoc function + * @name forms.function:Adhoc + * @description This form is for executing an adhoc command +*/ + +export default + angular.module('AdhocFormDefinition', []) + .value('AdhocForm', { + editTitle: 'Execute Command', + name: 'adhoc', + well: true, + forceListeners: true, + + fields: { + module_name: { + label: 'Module', + excludeModal: true, + type: 'select', + ngOptions: 'module.label for module in adhoc_module_options' + + ' track by module.value', + ngChange: 'moduleChange()', + editRequired: true, + awPopOver:'These are the modules that Tower supports ' + + 'running commands against.', + dataTitle: 'Module', + dataPlacement: 'right', + dataContainer: 'body' + }, + module_args: { + label: 'Arguments', + type: 'text', + awPopOverWatch: 'argsPopOver', + awPopOver: 'See adhoc controller...set as argsPopOver', + dataTitle: 'Arguments', + dataPlacement: 'right', + dataContainer: 'body', + editRequired: false, + autocomplete: false + }, + limit: { + label: 'Host Pattern', + type: 'text', + addRequired: false, + editRequired: false, + awPopOver: '
The pattern used to target hosts in the ' + + 'inventory. Leaving the field blank, all, and * will ' + + 'all target all hosts in the inventory. You can find ' + + 'more information about Ansible\'s host patterns ' + + 'here.
', + dataTitle: 'Host Pattern', + dataPlacement: 'right', + dataContainer: 'body' + }, + credential: { + label: 'Machine Credential', + type: 'lookup', + sourceModel: 'credential', + sourceField: 'name', + ngClick: 'lookUpCredential()', + awPopOver: 'Select the credential you want to use when ' + + 'accessing the remote hosts to run the command. ' + + 'Choose the credential containing ' + + 'the username and SSH key or password that Ansbile ' + + 'will need to log into the remote hosts.
', + dataTitle: 'Credential', + dataPlacement: 'right', + dataContainer: 'body', + awRequiredWhen: { + variable: 'credRequired', + init: 'false' + } + } + }, + + buttons: { + launch: { + label: 'Launch', + ngClick: 'launchJob()', + ngDisabled: true + }, + reset: { + ngClick: 'formReset()', + ngDisabled: true + } + }, + + related: {} + }); diff --git a/awx/ui/static/js/forms/Permissions.js b/awx/ui/static/js/forms/Permissions.js index d1d4c03c1c..2974b05353 100644 --- a/awx/ui/static/js/forms/Permissions.js +++ b/awx/ui/static/js/forms/Permissions.js @@ -96,6 +96,7 @@ export default label: 'Permission', labelClass: 'prepend-asterisk', type: 'radio_group', + class: 'squeeze', options: [{ label: 'Read', value: 'read', @@ -121,11 +122,26 @@ export default value: 'check', ngShow: "category == 'Deploy'" }], + // hack: attach helpCollapse here if the permissions + // category is deploy + helpCollapse: [{ + hdr: 'Permission', + ngBind: 'permissionTypeHelp', + ngHide: "category == 'Inventory'" + }] + }, + run_ad_hoc_commands: { + label: 'Execute commands', + type: 'checkbox', + // hack: attach helpCollapse here if the permissions + // category is inventory helpCollapse: [{ hdr: 'Permission', ngBind: 'permissionTypeHelp' - }] - } + }], + ngShow: "category == 'Inventory'", + associated: 'permission_type' + }, }, buttons: { diff --git a/awx/ui/static/js/forms/Users.js b/awx/ui/static/js/forms/Users.js index 7d2d8cbecc..3d882ee784 100644 --- a/awx/ui/static/js/forms/Users.js +++ b/awx/ui/static/js/forms/Users.js @@ -211,9 +211,9 @@ export default ngBind: 'permission.summary_fields.project.name' }, permission_type: { - label: 'Permission' + label: 'Permission', + ngBind: 'getPermissionText()' } - }, fieldActions: { diff --git a/awx/ui/static/js/helpers.js b/awx/ui/static/js/helpers.js index bd04c59e31..7cf2590449 100644 --- a/awx/ui/static/js/helpers.js +++ b/awx/ui/static/js/helpers.js @@ -39,6 +39,7 @@ import Refresh from "tower/helpers/refresh"; import RelatedSearch from "tower/helpers/related-search"; import Search from "tower/helpers/search"; import Teams from "tower/helpers/teams"; +import AdhocHelper from "tower/helpers/Adhoc"; export { AboutAnsible, @@ -78,5 +79,6 @@ export Refresh, RelatedSearch, Search, - Teams + Teams, + AdhocHelper }; diff --git a/awx/ui/static/js/helpers/Adhoc.js b/awx/ui/static/js/helpers/Adhoc.js new file mode 100644 index 0000000000..371dd0c497 --- /dev/null +++ b/awx/ui/static/js/helpers/Adhoc.js @@ -0,0 +1,147 @@ +/********************************************* + * Copyright (c) 2015 AnsibleWorks, Inc. + * + * AdhocHelper + * + * Routines shared by adhoc related controllers: + */ +/** + * @ngdoc function + * @name helpers.function:Adhoc + * @description routines shared by adhoc related controllers + * AdhocRun is currently only used for _relaunching_ an adhoc command + * from the Jobs page. + * TODO: once the API endpoint is figured out for running an adhoc command + * from the form is figured out, the rest work should probably be excised from + * the controller and moved into here. See the todo statements in the + * controller for more information about this. + */ + +export default + angular.module('AdhocHelper', ['RestServices', 'Utilities', + 'CredentialFormDefinition', 'CredentialsListDefinition', 'LookUpHelper', + 'JobSubmissionHelper', 'JobTemplateFormDefinition', 'ModalDialog', + 'FormGenerator', 'JobVarsPromptFormDefinition']) + + /** + * @ngdoc method + * @name helpers.function:JobSubmission#AdhocRun + * @methodOf helpers.function:JobSubmission + * @description The adhoc Run function is run when the user clicks the relaunch button + * + */ + // Submit request to run an adhoc comamand + .factory('AdhocRun', ['$location','$routeParams', 'LaunchJob', + 'PromptForPasswords', 'Rest', 'GetBasePath', 'Alert', 'ProcessErrors', + 'Wait', 'Empty', 'PromptForCredential', 'PromptForVars', + 'PromptForSurvey' , 'CreateLaunchDialog', + function ($location, $routeParams, LaunchJob, PromptForPasswords, + Rest, GetBasePath, Alert, ProcessErrors, Wait, Empty, + PromptForCredential, PromptForVars, PromptForSurvey, + CreateLaunchDialog) { + return function (params) { + var id = params.project_id, + scope = params.scope.$new(), + new_job_id, + launch_url, + html, + url; + + // this is used to cancel a running adhoc command from + // the jobs page + if (scope.removeCancelJob) { + scope.removeCancelJob(); + } + scope.removeCancelJob = scope.$on('CancelJob', function() { + // Delete the job + Wait('start'); + Rest.setUrl(GetBasePath('ad_hoc_commands') + new_job_id + '/'); + Rest.destroy() + .success(function() { + Wait('stop'); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, + null, { hdr: 'Error!', + msg: 'Call to ' + url + + ' failed. DELETE returned status: ' + + status }); + }); + }); + + if (scope.removeAdhocLaunchFinished) { + scope.removeAdhocLaunchFinished(); + } + scope.removeAdhocLaunchFinished = scope.$on('AdhocLaunchFinished', + function(e, data) { + $location.path('/ad_hoc_commands/' + data.id); + }); + + if (scope.removeStartAdhocRun) { + scope.removeStartAdhocRun(); + } + + scope.removeStartAdhocRun = scope.$on('StartAdhocRun', function() { + LaunchJob({ + scope: scope, + url: url, + callback: 'AdhocLaunchFinished' // send to the adhoc + // standard out page + }); + }); + + // start routine only if passwords need to be prompted + if (scope.removeCreateLaunchDialog) { + scope.removeCreateLaunchDialog(); + } + scope.removeCreateLaunchDialog = scope.$on('CreateLaunchDialog', + function(e, html, url) { + CreateLaunchDialog({ + scope: scope, + html: html, + url: url, + callback: 'StartAdhocRun' + }); + }); + + if (scope.removePromptForPasswords) { + scope.removePromptForPasswords(); + } + scope.removePromptForPasswords = scope.$on('PromptForPasswords', + function(e, passwords_needed_to_start,html, url) { + PromptForPasswords({ + scope: scope, + passwords: passwords_needed_to_start, + callback: 'CreateLaunchDialog', + html: html, + url: url + }); + }); // end password prompting routine + + // start the adhoc relaunch routine + Wait('start'); + url = GetBasePath('ad_hoc_commands') + id + '/relaunch/'; + Rest.setUrl(url); + Rest.get() + .success(function (data) { + var new_job_id = data.id, + launch_url = url; + + scope.passwords_needed_to_start = data.passwords_needed_to_start; + if (!Empty(data.passwords_needed_to_start) && + data.passwords_needed_to_start.length > 0) { + // go through the password prompt routine before + // starting the adhoc run + scope.$emit('PromptForPasswords', data.passwords_needed_to_start, html, url); + } + else { + // no prompting of passwords needed + scope.$emit('StartAdhocRun'); + } + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get job template details. GET returned status: ' + status }); + }); + }; + }]); diff --git a/awx/ui/static/js/helpers/JobTemplates.js b/awx/ui/static/js/helpers/JobTemplates.js index bc69745570..0e983e6fd2 100644 --- a/awx/ui/static/js/helpers/JobTemplates.js +++ b/awx/ui/static/js/helpers/JobTemplates.js @@ -70,6 +70,8 @@ angular.module('JobTemplatesHelper', ['Utilities']) scope.example_template_id = 'N'; scope.setCallbackHelp(); + // this fills the job template form both on copy of the job template + // and on edit scope.fillJobTemplate = function(){ // id = id || $rootScope.copy.id; // Retrieve detail record and prepopulate the form diff --git a/awx/ui/static/js/helpers/Jobs.js b/awx/ui/static/js/helpers/Jobs.js index b7bd68c1ea..8f5c94a705 100644 --- a/awx/ui/static/js/helpers/Jobs.js +++ b/awx/ui/static/js/helpers/Jobs.js @@ -16,7 +16,7 @@ import listGenerator from 'tower/shared/list-generator/main'; export default angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'JobSummaryDefinition', 'InventoryHelper', 'GeneratorHelpers', - 'JobSubmissionHelper', 'LogViewerHelper', 'SearchHelper', 'PaginationHelpers', listGenerator.name]) + 'JobSubmissionHelper', 'LogViewerHelper', 'SearchHelper', 'PaginationHelpers', 'AdhocHelper', listGenerator.name]) /** * JobsControllerInit({ scope: $scope }); @@ -62,7 +62,7 @@ export default else if (job.type === 'project_update') { typeId = job.project; } - else if (job.type === 'job' || job.type === "system_job") { + else if (job.type === 'job' || job.type === "system_job" || job.type === 'ad_hoc_command') { typeId = job.id; } RelaunchJob({ scope: scope, id: typeId, type: job.type, name: job.name }); @@ -112,8 +112,8 @@ export default } ]) - .factory('RelaunchJob', ['RelaunchInventory', 'RelaunchPlaybook', 'RelaunchSCM', - function(RelaunchInventory, RelaunchPlaybook, RelaunchSCM) { + .factory('RelaunchJob', ['RelaunchInventory', 'RelaunchPlaybook', 'RelaunchSCM', 'RelaunchAdhoc', + function(RelaunchInventory, RelaunchPlaybook, RelaunchSCM, RelaunchAdhoc) { return function(params) { var scope = params.scope, id = params.id, @@ -122,6 +122,9 @@ export default if (type === 'inventory_update') { RelaunchInventory({ scope: scope, id: id}); } + else if (type === 'ad_hoc_command') { + RelaunchAdhoc({ scope: scope, id: id, name: name }); + } else if (type === 'job' || type === 'system_job') { RelaunchPlaybook({ scope: scope, id: id, name: name }); } @@ -595,4 +598,12 @@ export default id = params.id; ProjectUpdate({ scope: scope, project_id: id }); }; + }]) + + .factory('RelaunchAdhoc', ['AdhocRun', function(AdhocRun) { + return function(params) { + var scope = params.scope, + id = params.id; + AdhocRun({ scope: scope, project_id: id, relaunch: true }); + }; }]); diff --git a/awx/ui/static/js/helpers/Permissions.js b/awx/ui/static/js/helpers/Permissions.js index dfe4be2867..9ef24a086d 100644 --- a/awx/ui/static/js/helpers/Permissions.js +++ b/awx/ui/static/js/helpers/Permissions.js @@ -26,30 +26,52 @@ export default scope.projectrequired = false; html = "