From 4f5ecb70546d3166d011073e381976169bf1e7db Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 6 Apr 2015 10:14:19 -0400 Subject: [PATCH 1/7] Remove Ad Hoc from command job type --- awx/api/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index b34a38617f..4f7220e7d5 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', From 584abcc05bee281a0483fcab268aba28262b61b7 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 6 Apr 2015 10:30:36 -0400 Subject: [PATCH 2/7] random syntax and style fixes to tower js may include some new dependencies needed for adhoc commands --- awx/ui/static/js/app.js | 2 +- awx/ui/static/js/controllers/Inventories.js | 59 ++++++++++++------- awx/ui/static/js/controllers/JobDetail.js | 1 + awx/ui/static/js/controllers/JobTemplates.js | 2 +- awx/ui/static/js/controllers/Permissions.js | 9 +-- awx/ui/static/js/helpers/Credentials.js | 1 - awx/ui/static/js/helpers/JobSubmission.js | 1 - awx/ui/static/js/helpers/inventory.js | 3 - awx/ui/static/js/lists/InventoryGroups.js | 6 +- .../list-generator/list-generator.factory.js | 3 +- 10 files changed, 51 insertions(+), 36 deletions(-) diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 12ee00cba3..235539f398 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -76,7 +76,7 @@ var tower = angular.module('Tower', [ 'UserFormDefinition', 'FormGenerator', 'OrganizationListDefinition', - 'jobTemplates', + 'jobTemplates', 'UserListDefinition', 'UserHelper', 'PromptDialog', diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index b109ff26f9..00af03e462 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, @@ -1365,7 +1372,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 +1382,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 d4c3942a4f..3d8567cd37 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -1302,6 +1302,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/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..a0880dd980 100644 --- a/awx/ui/static/js/controllers/Permissions.js +++ b/awx/ui/static/js/controllers/Permissions.js @@ -332,7 +332,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/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 += "
Launching this job requires the passwords listed below. Enter and confirm each password before continuing.
\n"; // html += "
\n"; - scope.passwords.forEach(function(password) { // Prompt for password field = form.fields[password]; diff --git a/awx/ui/static/js/helpers/inventory.js b/awx/ui/static/js/helpers/inventory.js index 3860c93929..18dd2b4afa 100644 --- a/awx/ui/static/js/helpers/inventory.js +++ b/awx/ui/static/js/helpers/inventory.js @@ -18,7 +18,6 @@ export default 'InventoryHelper', 'InventoryFormDefinition', 'ParseHelper', 'SearchHelper', 'VariablesHelper', ]) - .factory('GetGroupContainerHeight', [ function() { return function() { @@ -101,7 +100,6 @@ export default }; }]) - .factory('WatchInventoryWindowResize', ['ApplyEllipsis', 'SetContainerHeights', function (ApplyEllipsis, SetContainerHeights) { return function (params) { @@ -185,7 +183,6 @@ export default } ]) - .factory('EditInventoryProperties', ['InventoryForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LookUpInit', 'OrganizationList', 'GetBasePath', 'ParseTypeChange', 'SaveInventory', 'Wait', 'Store', 'SearchInit', 'ParseVariableString', 'CreateDialog', 'TextareaResize', function (InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, LookUpInit, OrganizationList, GetBasePath, ParseTypeChange, SaveInventory, diff --git a/awx/ui/static/js/lists/InventoryGroups.js b/awx/ui/static/js/lists/InventoryGroups.js index ef88713e4d..c55c89b4db 100644 --- a/awx/ui/static/js/lists/InventoryGroups.js +++ b/awx/ui/static/js/lists/InventoryGroups.js @@ -134,7 +134,8 @@ export default ngClick: 'updateGroup(group.id)', awToolTip: "{{ group.launch_tooltip }}", dataTipWatch: "group.launch_tooltip", - ngShow: "group.status !== 'running' && group.status !== 'pending' && group.status !== 'updating'", + ngShow: "group.status !== 'running' && group.status " + + "!== 'pending' && group.status !== 'updating'", ngClass: "group.launch_class", dataPlacement: "top" }, @@ -144,7 +145,8 @@ export default ngClick: "cancelUpdate(group.id)", awToolTip: "Cancel sync process", 'class': 'red-txt', - ngShow: "group.status == 'running' || group.status == 'pending' || group.status == 'updating'", + ngShow: "group.status == 'running' || group.status == 'pending' " + + "|| group.status == 'updating'", dataPlacement: "top" }, edit: { diff --git a/awx/ui/static/js/shared/list-generator/list-generator.factory.js b/awx/ui/static/js/shared/list-generator/list-generator.factory.js index ed320a23ba..8dabd91a4e 100644 --- a/awx/ui/static/js/shared/list-generator/list-generator.factory.js +++ b/awx/ui/static/js/shared/list-generator/list-generator.factory.js @@ -229,7 +229,6 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate selection.deselectedItems.forEach(function(item) { item.isSelected = false; }); - }.bind(this)); this.scope.$on('$destroy', function() { @@ -396,7 +395,7 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate }); } - html += "\n"; + html += "\n"; html += "\n"; } else { From ce31b9863517f8de1ef234e81c2267e226c3f7cb Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 6 Apr 2015 10:31:11 -0400 Subject: [PATCH 3/7] UI support for adhoc commands --- awx/ui/static/js/app.js | 15 +- awx/ui/static/js/controllers/Adhoc.js | 171 ++++++++++++++++++ awx/ui/static/js/controllers/Inventories.js | 47 +++++ awx/ui/static/js/controllers/JobStdout.js | 9 +- awx/ui/static/js/controllers/Permissions.js | 19 ++ awx/ui/static/js/controllers/Users.js | 9 + awx/ui/static/js/forms.js | 2 + awx/ui/static/js/forms/Adhoc.js | 98 ++++++++++ awx/ui/static/js/forms/Permissions.js | 20 +- awx/ui/static/js/forms/Users.js | 4 +- awx/ui/static/js/helpers.js | 4 +- awx/ui/static/js/helpers/Adhoc.js | 147 +++++++++++++++ awx/ui/static/js/helpers/JobTemplates.js | 2 + awx/ui/static/js/helpers/Jobs.js | 19 +- awx/ui/static/js/helpers/Permissions.js | 42 ++++- awx/ui/static/js/lists/InventoryGroups.js | 12 ++ awx/ui/static/js/lists/Permissions.js | 3 +- awx/ui/static/js/shared/form-generator.js | 15 +- awx/ui/static/js/shared/generator-helpers.js | 7 +- .../list-generator/list-actions.partial.html | 3 + awx/ui/static/less/ansible-ui.less | 6 +- awx/ui/static/partials/adhoc.html | 4 + awx/ui/static/partials/job_stdout_adhoc.html | 50 +++++ 23 files changed, 678 insertions(+), 30 deletions(-) create mode 100644 awx/ui/static/js/controllers/Adhoc.js create mode 100644 awx/ui/static/js/forms/Adhoc.js create mode 100644 awx/ui/static/js/helpers/Adhoc.js create mode 100644 awx/ui/static/partials/adhoc.html create mode 100644 awx/ui/static/partials/job_stdout_adhoc.html 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 = "
\n" + "
Read
\n" + - "
Only allow the user or team to view the inventory.
\n" + + "
Only allow the user or team to view the inventory." + + "
\n" + "
Write
\n" + - "
Allow the user or team to modify hosts and groups contained in the inventory, add new hosts and groups, and perform inventory sync operations.\n" + + "
Allow the user or team to modify hosts and groups " + + "contained in the inventory, add new hosts and groups" + + ", and perform inventory sync operations.\n" + "
Admin
\n" + - "
Allow the user or team full access to the inventory. This includes reading, writing, deletion of the inventory and inventory sync operations.
\n" + + "
Allow the user or team full access to the " + + "inventory. This includes reading, writing, deletion " + + "of the inventory, inventory sync operations, and " + + "the ability to execute commands on the inventory." + + "
\n" + + "
Execute commands
\n" + + "
Allow the user to execute commands on the " + + "inventory.
\n" + "
\n"; scope.permissionTypeHelp = $sce.trustAsHtml(html); } else { scope.projectrequired = true; html = "
\n" + - "
Create
\n" + - "
Allow the user or team to create job templates. This implies that they have the Run and Check permissions.
\n" + + "
Create
\n" + + "
Allow the user or team to create job templates. " + + "This implies that they have the Run and Check " + + "permissions.
\n" + "
Run
\n" + - "
Allow the user or team to run a job template from the project against the inventory. In Run mode modules will " + - "be executed, and changes to the inventory will occur.
\n" + + "
Allow the user or team to run a job template from " + + "the project against the inventory. In Run mode " + + "modules will " + + "be executed, and changes to the inventory will occur." + + "
\n" + "
Check
\n" + - "
Only allow the user or team to run the project against the inventory as a dry-run operation. In Check mode, module operations " + - "will only be simulated. No changes will occur.
\n" + + "
Only allow the user or team to run the project " + + "against the inventory as a dry-run operation. In " + + "Check mode, module operations " + + "will only be simulated. No changes will occur." + + "
\n" + "
\n"; scope.permissionTypeHelp = $sce.trustAsHtml(html); } if (reset) { - scope.permission_type = (scope.category === 'Inventory') ? 'read' : 'run'; //default to the first option + if (scope.category === "Inventory") { + scope.permission_type = "read"; + } else { + scope.permission_type = "run"; + } } }; } diff --git a/awx/ui/static/js/lists/InventoryGroups.js b/awx/ui/static/js/lists/InventoryGroups.js index c55c89b4db..3f5351bc58 100644 --- a/awx/ui/static/js/lists/InventoryGroups.js +++ b/awx/ui/static/js/lists/InventoryGroups.js @@ -16,6 +16,7 @@ export default index: false, hover: true, 'class': 'table-no-border', + multiSelect: true, fields: { name: { @@ -78,6 +79,17 @@ export default }, actions: { + launch: { + mode: 'all', + // TODO: ngShow permissions + ngClick: 'populateAdhocForm()', + awToolTip: "Run a command on this inventory" + // TODO: set up a tip watcher and change text based on when + // things are selected/not selected. This is started and + // commented out in the inventory controller within the watchers. + // awToolTip: "{{ adhocButtonTipContents }}", + // dataTipWatch: "adhocButtonTipContents" + }, create: { mode: 'all', ngClick: "createGroup()", diff --git a/awx/ui/static/js/lists/Permissions.js b/awx/ui/static/js/lists/Permissions.js index c9bf5c5689..0188366ee2 100644 --- a/awx/ui/static/js/lists/Permissions.js +++ b/awx/ui/static/js/lists/Permissions.js @@ -41,7 +41,8 @@ export default ngBind: 'permission.summary_fields.project.name' }, permission_type: { - label: 'Permission' + label: 'Permission', + ngBind: 'getPermissionText()' } }, diff --git a/awx/ui/static/js/shared/form-generator.js b/awx/ui/static/js/shared/form-generator.js index ce17e929ed..b144c33714 100644 --- a/awx/ui/static/js/shared/form-generator.js +++ b/awx/ui/static/js/shared/form-generator.js @@ -609,6 +609,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat params.content = collapse_array[i].content; params.idx = this.accordion_count++; params.show = (collapse_array[i].show) ? collapse_array[i].show : null; + params.ngHide = (collapse_array[i].ngHide) ? collapse_array[i].ngHide : null; params.bind = (collapse_array[i].ngBind) ? collapse_array[i].ngBind : null; html += HelpCollapse(params); } @@ -676,6 +677,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += Attr(field, 'type'); html += "ng-model=\"" + fld + '" '; html += "name=\"" + fld + '" '; + if (form.name === "permission") { + html += "ng-disabled='permission_type === \"admin\"'"; + html += "ng-checked='permission_type === \"admin\"'"; + } html += (field.ngChange) ? Attr(field, 'ngChange') : ""; html += "id=\"" + form.name + "_" + fld + "_chbox\" "; html += (idx !== undefined) ? "_" + idx : ""; @@ -752,7 +757,9 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat } if ((!field.readonly) || (field.readonly && options.mode === 'edit')) { - html += "
\n"; html += "
\n"; diff --git a/awx/ui/static/js/shared/list-generator/list-actions.partial.html b/awx/ui/static/js/shared/list-generator/list-actions.partial.html index 498d1dd316..01749fdd01 100644 --- a/awx/ui/static/js/shared/list-generator/list-actions.partial.html +++ b/awx/ui/static/js/shared/list-generator/list-actions.partial.html @@ -1,9 +1,12 @@ +
diff --git a/awx/ui/static/partials/job_stdout_adhoc.html b/awx/ui/static/partials/job_stdout_adhoc.html new file mode 100644 index 0000000000..201041374a --- /dev/null +++ b/awx/ui/static/partials/job_stdout_adhoc.html @@ -0,0 +1,50 @@ +
+
+ + + +
+
+
+ + {{ job.status }} +
+
+ +
+
+
+
+
+
+ +
+
+ +
+
From 47e045e9fe26b24475580b06f538cb761ca485b2 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 6 Apr 2015 11:44:15 -0400 Subject: [PATCH 4/7] update permission list command language --- awx/ui/static/js/controllers/Permissions.js | 2 +- awx/ui/static/js/controllers/Users.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/ui/static/js/controllers/Permissions.js b/awx/ui/static/js/controllers/Permissions.js index d5e149a1a2..edfd176a2c 100644 --- a/awx/ui/static/js/controllers/Permissions.js +++ b/awx/ui/static/js/controllers/Permissions.js @@ -68,7 +68,7 @@ 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"; + return this.permission.permission_type + " + run commands"; } else { return this.permission.permission_type; } diff --git a/awx/ui/static/js/controllers/Users.js b/awx/ui/static/js/controllers/Users.js index 65e9034de6..e26e05567c 100644 --- a/awx/ui/static/js/controllers/Users.js +++ b/awx/ui/static/js/controllers/Users.js @@ -297,7 +297,7 @@ export function UsersEdit($scope, $rootScope, $compile, $location, $log, $routeP // 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"; + return this.permission.permission_type + " + run commands"; } else { return this.permission.permission_type; } From b618d9223ff482d9171f5677b3f1cdf7da76ef4e Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 6 Apr 2015 11:51:07 -0400 Subject: [PATCH 5/7] fixed reset from deleting the provided host pattern from the inventory page. --- awx/ui/static/js/controllers/Adhoc.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/awx/ui/static/js/controllers/Adhoc.js b/awx/ui/static/js/controllers/Adhoc.js index 9aa94a2e85..ef6954bfe8 100644 --- a/awx/ui/static/js/controllers/Adhoc.js +++ b/awx/ui/static/js/controllers/Adhoc.js @@ -60,6 +60,7 @@ export function AdhocForm($scope, $rootScope, $location, $routeParams, // 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) { @@ -159,6 +160,7 @@ export function AdhocForm($scope, $rootScope, $location, $routeParams, 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 }); From 3c878018be18fe9172b5c4293c6ca11e4b2b18d3 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 6 Apr 2015 14:31:47 -0400 Subject: [PATCH 6/7] fixing grunt errors --- awx/ui/static/js/app.js | 4 +- awx/ui/static/js/controllers/Adhoc.js | 38 ++++++++-------- awx/ui/static/js/controllers/Inventories.js | 4 +- awx/ui/static/js/forms/Adhoc.js | 32 ++++++------- awx/ui/static/js/helpers/Adhoc.js | 10 ++--- awx/ui/static/js/helpers/Permissions.js | 50 ++++++++++----------- awx/ui/static/js/lists/InventoryGroups.js | 8 ++-- 7 files changed, 72 insertions(+), 74 deletions(-) diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 86f807048a..e1ca78014d 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -40,7 +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 {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'; @@ -290,7 +290,7 @@ var tower = angular.module('Tower', [ when('/inventories/:inventory_id/adhoc', { templateUrl: urlPrefix + 'partials/adhoc.html', - controller: AdhocForm + controller: AdhocCtrl }). when('/organizations', { diff --git a/awx/ui/static/js/controllers/Adhoc.js b/awx/ui/static/js/controllers/Adhoc.js index ef6954bfe8..594f93ab3e 100644 --- a/awx/ui/static/js/controllers/Adhoc.js +++ b/awx/ui/static/js/controllers/Adhoc.js @@ -11,15 +11,15 @@ * @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, +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/', + var url = GetBasePath('inventory') + $routeParams.inventory_id + + '/ad_hoc_commands/', generator = GenerateForm, form = AdhocForm, master = {}, @@ -33,8 +33,8 @@ export function AdhocForm($scope, $rootScope, $location, $routeParams, // display Wait('start'); $scope.id = id; - $scope.argsPopOver = "

These arguments are used with the" - + " specified module.

"; + $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 - @@ -42,18 +42,18 @@ export function AdhocForm($scope, $rootScope, $location, $routeParams, // 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.

"; + $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.

"; + $scope.argsPopOver = "

These arguments are used with the" + + " specified module.

"; } }; @@ -127,7 +127,7 @@ export function AdhocForm($scope, $rootScope, $location, $routeParams, // populate data with the relevant form values for (fld in form.fields) { if (form.fields[fld].type === 'select') { - data[fld] = $scope[fld].value + data[fld] = $scope[fld].value; } else { data[fld] = $scope[fld]; } @@ -144,8 +144,8 @@ export function AdhocForm($scope, $rootScope, $location, $routeParams, }) .error(function (data, status) { ProcessErrors($scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to launch adhoc command. POST returned status: ' - + status }); + 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 @@ -167,7 +167,7 @@ export function AdhocForm($scope, $rootScope, $location, $routeParams, }; } -AdhocForm.$inject = ['$scope', '$rootScope', '$location', '$routeParams', +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 8e6a87c25a..eb6ee30072 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -910,12 +910,12 @@ export function InventoriesManage ($log, $scope, $rootScope, $location, $scope.populateAdhocForm = function() { var host_patterns = "all"; if ($scope.hostsSelected || $scope.groupsSelected) { - var allSelectedItems = $scope.groupsSelectedItems.concat($scope.hostsSelectedItems) + 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/forms/Adhoc.js b/awx/ui/static/js/forms/Adhoc.js index 6ab3bfb065..fcfa26590b 100644 --- a/awx/ui/static/js/forms/Adhoc.js +++ b/awx/ui/static/js/forms/Adhoc.js @@ -24,12 +24,12 @@ export default label: 'Module', excludeModal: true, type: 'select', - ngOptions: 'module.label for module in adhoc_module_options' - + ' track by module.value', + 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.', + awPopOver:'

These are the modules that Tower supports ' + + 'running commands against.', dataTitle: 'Module', dataPlacement: 'right', dataContainer: 'body' @@ -50,13 +50,13 @@ export default 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.

', + 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' @@ -67,11 +67,11 @@ export default 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.

', + 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', diff --git a/awx/ui/static/js/helpers/Adhoc.js b/awx/ui/static/js/helpers/Adhoc.js index 371dd0c497..3afe7d4584 100644 --- a/awx/ui/static/js/helpers/Adhoc.js +++ b/awx/ui/static/js/helpers/Adhoc.js @@ -43,7 +43,6 @@ export default var id = params.project_id, scope = params.scope.$new(), new_job_id, - launch_url, html, url; @@ -63,9 +62,9 @@ export default .error(function (data, status) { ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url - + ' failed. DELETE returned status: ' - + status }); + msg: 'Call to ' + url + + ' failed. DELETE returned status: ' + + status }); }); }); @@ -124,8 +123,7 @@ export default Rest.setUrl(url); Rest.get() .success(function (data) { - var new_job_id = data.id, - launch_url = url; + new_job_id = data.id; scope.passwords_needed_to_start = data.passwords_needed_to_start; if (!Empty(data.passwords_needed_to_start) && diff --git a/awx/ui/static/js/helpers/Permissions.js b/awx/ui/static/js/helpers/Permissions.js index 9ef24a086d..056f11bb10 100644 --- a/awx/ui/static/js/helpers/Permissions.js +++ b/awx/ui/static/js/helpers/Permissions.js @@ -26,42 +26,42 @@ export default scope.projectrequired = false; html = "
\n" + "
Read
\n" + - "
Only allow the user or team to view the inventory." - + "
\n" + + "
Only allow the user or team to view the inventory." + + "
\n" + "
Write
\n" + - "
Allow the user or team to modify hosts and groups " - + "contained in the inventory, add new hosts and groups" - + ", and perform inventory sync operations.\n" + + "
Allow the user or team to modify hosts and groups " + + "contained in the inventory, add new hosts and groups" + + ", and perform inventory sync operations.\n" + "
Admin
\n" + - "
Allow the user or team full access to the " - + "inventory. This includes reading, writing, deletion " - + "of the inventory, inventory sync operations, and " - + "the ability to execute commands on the inventory." - + "
\n" + + "
Allow the user or team full access to the " + + "inventory. This includes reading, writing, deletion " + + "of the inventory, inventory sync operations, and " + + "the ability to execute commands on the inventory." + + "
\n" + "
Execute commands
\n" + - "
Allow the user to execute commands on the " - + "inventory.
\n" + + "
Allow the user to execute commands on the " + + "inventory.
\n" + "
\n"; scope.permissionTypeHelp = $sce.trustAsHtml(html); } else { scope.projectrequired = true; html = "
\n" + "
Create
\n" + - "
Allow the user or team to create job templates. " - + "This implies that they have the Run and Check " - + "permissions.
\n" + + "
Allow the user or team to create job templates. " + + "This implies that they have the Run and Check " + + "permissions.
\n" + "
Run
\n" + - "
Allow the user or team to run a job template from " - + "the project against the inventory. In Run mode " - + "modules will " + - "be executed, and changes to the inventory will occur." - + "
\n" + + "
Allow the user or team to run a job template from " + + "the project against the inventory. In Run mode " + + "modules will " + + "be executed, and changes to the inventory will occur." + + "
\n" + "
Check
\n" + - "
Only allow the user or team to run the project " - + "against the inventory as a dry-run operation. In " - + "Check mode, module operations " + - "will only be simulated. No changes will occur." - + "
\n" + + "
Only allow the user or team to run the project " + + "against the inventory as a dry-run operation. In " + + "Check mode, module operations " + + "will only be simulated. No changes will occur." + + "
\n" + "
\n"; scope.permissionTypeHelp = $sce.trustAsHtml(html); } diff --git a/awx/ui/static/js/lists/InventoryGroups.js b/awx/ui/static/js/lists/InventoryGroups.js index 3f5351bc58..f5ab0b4387 100644 --- a/awx/ui/static/js/lists/InventoryGroups.js +++ b/awx/ui/static/js/lists/InventoryGroups.js @@ -146,8 +146,8 @@ export default ngClick: 'updateGroup(group.id)', awToolTip: "{{ group.launch_tooltip }}", dataTipWatch: "group.launch_tooltip", - ngShow: "group.status !== 'running' && group.status " - + "!== 'pending' && group.status !== 'updating'", + ngShow: "group.status !== 'running' && group.status " + + "!== 'pending' && group.status !== 'updating'", ngClass: "group.launch_class", dataPlacement: "top" }, @@ -157,8 +157,8 @@ export default ngClick: "cancelUpdate(group.id)", awToolTip: "Cancel sync process", 'class': 'red-txt', - ngShow: "group.status == 'running' || group.status == 'pending' " - + "|| group.status == 'updating'", + ngShow: "group.status == 'running' || group.status == 'pending' " + + "|| group.status == 'updating'", dataPlacement: "top" }, edit: { From fd5ce3d3af54a60ddd0702074c81dbd320a55e1f Mon Sep 17 00:00:00 2001 From: jlmitch5 Date: Mon, 6 Apr 2015 18:49:53 -0400 Subject: [PATCH 7/7] Fixed failed resp bug when args are necessary some modules require args...if the one selected does and the user hasn't input any, we can't reset the form...we need the api form error response to display. --- awx/ui/static/js/controllers/Adhoc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/awx/ui/static/js/controllers/Adhoc.js b/awx/ui/static/js/controllers/Adhoc.js index 594f93ab3e..44409af5f4 100644 --- a/awx/ui/static/js/controllers/Adhoc.js +++ b/awx/ui/static/js/controllers/Adhoc.js @@ -150,7 +150,6 @@ export function AdhocCtrl($scope, $rootScope, $location, $routeParams, // 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(); }); };