diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 01b2476fa7..6ec30d7921 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -227,7 +227,7 @@ class BaseSerializer(serializers.ModelSerializer): def get_type_choices(self): type_name_map = { 'job': 'Playbook Run', - 'ad_hoc_command': 'Ad Hoc Command', + 'ad_hoc_command': 'Command', 'project_update': 'SCM Update', 'inventory_update': 'Inventory Sync', 'system_job': 'Management Job', diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 10e184080c..cc4ed8b2f4 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -41,6 +41,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 {AdhocCtrl} 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'; @@ -89,6 +90,7 @@ var tower = angular.module('Tower', [ 'AdminListDefinition', 'CustomInventoryListDefinition', 'AWDirectives', + 'AdhocFormDefinition', 'InventoriesListDefinition', 'InventoryFormDefinition', 'InventoryHelper', @@ -169,7 +171,8 @@ var tower = angular.module('Tower', [ 'ConfigureTowerHelper', 'ConfigureTowerJobsListDefinition', 'CreateCustomInventoryHelper', - 'CustomInventoryListDefinition' + 'CustomInventoryListDefinition', + 'AdhocHelper' ]) .constant('AngularScheduler.partials', urlPrefix + 'lib/angular-scheduler/lib/') @@ -201,6 +204,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 @@ -281,6 +289,11 @@ var tower = angular.module('Tower', [ controller: InventoriesManage }). + when('/inventories/:inventory_id/adhoc', { + templateUrl: urlPrefix + 'partials/adhoc.html', + controller: AdhocCtrl + }). + 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..44409af5f4 --- /dev/null +++ b/awx/ui/static/js/controllers/Adhoc.js @@ -0,0 +1,172 @@ +/************************************************* + * 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 AdhocCtrl($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"; + $scope.providedHostPatterns = $scope.limit; + 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. + }); + }; + + // Remove all data input into the form + $scope.formReset = function () { + generator.reset(); + for (var fld in master) { + $scope[fld] = master[fld]; + } + $scope.limit = $scope.providedHostPatterns; + KindChange({ scope: $scope, form: form, reset: false }); + OwnerChange({ scope: $scope }); + LoginMethodChange({ scope: $scope }); + }; +} + +AdhocCtrl.$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 b109ff26f9..eb6ee30072 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -15,12 +15,12 @@ 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, +export function InventoriesList($scope, $rootScope, $location, $log, + $routeParams, $compile, $filter, Rest, Alert, InventoryList, generateList, + LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, + ClearScope, ProcessErrors, GetBasePath, Wait, Stream, EditInventoryProperties, Find, Empty, LogViewer) { - //ClearScope(); - var list = InventoryList, defaultUrl = GetBasePath('inventory'), view = generateList, @@ -51,7 +51,10 @@ export function InventoriesList($scope, $rootScope, $location, $log, $routeParam // close any lingering tool tipss $(this).hide(); }); - elem.attr({ "aw-pop-over": html, "data-popover-title": title, "data-placement": "right" }); + elem.attr({ + "aw-pop-over": html, + "data-popover-title": title, + "data-placement": "right" }); $compile(elem)($scope); elem.on('shown.bs.popover', function() { $('.popover').each(function() { @@ -837,12 +840,16 @@ InventoriesEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$lo -export function InventoriesManage ($log, $scope, $location, $routeParams, $compile, generateList, ClearScope, Empty, Wait, Rest, Alert, LoadBreadCrumbs, GetBasePath, ProcessErrors, - Breadcrumbs, InventoryGroups, InjectHosts, Find, HostsReload, SearchInit, PaginateInit, GetSyncStatusMsg, GetHostsStatusMsg, GroupsEdit, InventoryUpdate, - GroupsCancelUpdate, ViewUpdateStatus, GroupsDelete, Store, HostsEdit, HostsDelete, EditInventoryProperties, ToggleHostEnabled, Stream, ShowJobSummary, - InventoryGroupsHelp, HelpDialog, ViewJob, WatchInventoryWindowResize, GetHostContainerRows, GetGroupContainerRows, GetGroupContainerHeight, - GroupsCopy, HostsCopy, Socket) -{ +export function InventoriesManage ($log, $scope, $rootScope, $location, + $routeParams, $compile, generateList, ClearScope, Empty, Wait, Rest, Alert, + LoadBreadCrumbs, GetBasePath, ProcessErrors, Breadcrumbs, InventoryGroups, + InjectHosts, Find, HostsReload, SearchInit, PaginateInit, GetSyncStatusMsg, + GetHostsStatusMsg, GroupsEdit, InventoryUpdate, GroupsCancelUpdate, + ViewUpdateStatus, GroupsDelete, Store, HostsEdit, HostsDelete, + EditInventoryProperties, ToggleHostEnabled, Stream, ShowJobSummary, + InventoryGroupsHelp, HelpDialog, ViewJob, WatchInventoryWindowResize, + GetHostContainerRows, GetGroupContainerRows, GetGroupContainerHeight, + GroupsCopy, HostsCopy, Socket) { var PreviousSearchParams, url, @@ -863,6 +870,53 @@ export function InventoriesManage ($log, $scope, $location, $routeParams, $compi 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; @@ -1365,7 +1419,8 @@ export function InventoriesManage ($log, $scope, $location, $routeParams, $compi hostScope.show_failures = show_failures; $scope.groupSelect(group_id); hostScope.hosts = []; - $scope.show_failures = show_failures; // turn on failed hosts filter in hosts view + $scope.show_failures = show_failures; // turn on failed hosts + // filter in hosts view } else { Wait('stop'); } @@ -1374,15 +1429,24 @@ export function InventoriesManage ($log, $scope, $location, $routeParams, $compi if ($scope.removeGroupDeleteCompleted) { $scope.removeGroupDeleteCompleted(); } - $scope.removeGroupDeleteCompleted = $scope.$on('GroupDeleteCompleted', function() { - $scope.refreshGroups(); - }); + $scope.removeGroupDeleteCompleted = $scope.$on('GroupDeleteCompleted', + function() { + $scope.refreshGroups(); + } + ); } -InventoriesManage.$inject = ['$log', '$scope', '$location', '$routeParams', '$compile', 'generateList', 'ClearScope', 'Empty', 'Wait', 'Rest', 'Alert', 'LoadBreadCrumbs', - 'GetBasePath', 'ProcessErrors', 'Breadcrumbs', 'InventoryGroups', 'InjectHosts', 'Find', 'HostsReload', 'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', - 'GetHostsStatusMsg', 'GroupsEdit', 'InventoryUpdate', 'GroupsCancelUpdate', 'ViewUpdateStatus', 'GroupsDelete', 'Store', 'HostsEdit', 'HostsDelete', - 'EditInventoryProperties', 'ToggleHostEnabled', 'Stream', 'ShowJobSummary', 'InventoryGroupsHelp', 'HelpDialog', 'ViewJob', 'WatchInventoryWindowResize', - 'GetHostContainerRows', 'GetGroupContainerRows', 'GetGroupContainerHeight', 'GroupsCopy', 'HostsCopy', 'Socket' - ]; +InventoriesManage.$inject = ['$log', '$scope', '$rootScope', '$location', + '$routeParams', '$compile', 'generateList', 'ClearScope', 'Empty', 'Wait', + 'Rest', 'Alert', 'LoadBreadCrumbs', 'GetBasePath', 'ProcessErrors', + 'Breadcrumbs', 'InventoryGroups', 'InjectHosts', 'Find', 'HostsReload', + 'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', 'GetHostsStatusMsg', + 'GroupsEdit', 'InventoryUpdate', 'GroupsCancelUpdate', 'ViewUpdateStatus', + 'GroupsDelete', 'Store', 'HostsEdit', 'HostsDelete', + 'EditInventoryProperties', 'ToggleHostEnabled', 'Stream', 'ShowJobSummary', + 'InventoryGroupsHelp', 'HelpDialog', 'ViewJob', + 'WatchInventoryWindowResize', 'GetHostContainerRows', + 'GetGroupContainerRows', 'GetGroupContainerHeight', 'GroupsCopy', + 'HostsCopy', 'Socket' +]; diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 4a66b09f72..c7f2f06951 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -1285,6 +1285,7 @@ export function JobDetailController ($location, $rootScope, $scope, $compile, $r status: status }); }; + scope.refresh = function(){ $scope.$emit('LoadJob'); }; diff --git a/awx/ui/static/js/controllers/JobStdout.js b/awx/ui/static/js/controllers/JobStdout.js index b22eef2a77..b20537c2dd 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(); @@ -152,7 +152,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; @@ -252,5 +254,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/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js index 8279d0f61e..95ebbbfb76 100644 --- a/awx/ui/static/js/controllers/JobTemplates.js +++ b/awx/ui/static/js/controllers/JobTemplates.js @@ -1145,11 +1145,11 @@ export function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, action: action }); }); - if($scope.survey_enabled === true && $scope.survey_exists!==true){ $scope.$emit("PromptForSurvey"); } else { + PlaybookRun({ scope: $scope, id: id diff --git a/awx/ui/static/js/controllers/Permissions.js b/awx/ui/static/js/controllers/Permissions.js index f0bb619c26..edfd176a2c 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 + " + run commands"; + } 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; @@ -332,7 +351,8 @@ export function PermissionsEdit($scope, $rootScope, $compile, $location, $log, $ } -PermissionsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'PermissionsForm', - 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'Prompt', 'GetBasePath', - 'InventoryList', 'ProjectList', 'LookUpInit', 'CheckAccess', 'Wait', 'PermissionCategoryChange' -]; \ No newline at end of file +PermissionsEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', + '$log', '$routeParams', 'PermissionsForm', 'GenerateForm', 'Rest', 'Alert', + 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'Prompt', + 'GetBasePath', 'InventoryList', 'ProjectList', 'LookUpInit', 'CheckAccess', + 'Wait', 'PermissionCategoryChange']; diff --git a/awx/ui/static/js/controllers/Users.js b/awx/ui/static/js/controllers/Users.js index bea627584e..e26e05567c 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 + " + run commands"; + } 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..fcfa26590b --- /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..3afe7d4584 --- /dev/null +++ b/awx/ui/static/js/helpers/Adhoc.js @@ -0,0 +1,145 @@ +/********************************************* + * 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, + 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) { + new_job_id = data.id; + + 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/Credentials.js b/awx/ui/static/js/helpers/Credentials.js index 9d4c6eaec7..36abc98ba8 100644 --- a/awx/ui/static/js/helpers/Credentials.js +++ b/awx/ui/static/js/helpers/Credentials.js @@ -17,7 +17,6 @@ angular.module('CredentialsHelper', ['Utilities']) .factory('KindChange', ['Empty', function (Empty) { return function (params) { - var scope = params.scope, reset = params.reset, collapse, id; diff --git a/awx/ui/static/js/helpers/JobSubmission.js b/awx/ui/static/js/helpers/JobSubmission.js index 41626056ad..373814bbf8 100644 --- a/awx/ui/static/js/helpers/JobSubmission.js +++ b/awx/ui/static/js/helpers/JobSubmission.js @@ -299,7 +299,6 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm, html += "