From 068473bdac15df12a1c168a9405dd9f576e24bab Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Mon, 5 Jun 2017 15:20:18 -0400 Subject: [PATCH 1/6] Added the ability to select extra credentials when launching a job template with ask_extra_credentials_on_launch is true. Added extra credentials to job results panel --- awx/ui/client/legacy-styles/ansible-ui.less | 2 +- .../src/credentials/credentials.list.js | 2 +- .../copy-move/copy-move-groups.controller.js | 8 +- .../copy-move/copy-move-hosts.controller.js | 6 +- .../client/src/inventories/inventory.list.js | 2 +- .../src/job-results/job-results.controller.js | 13 +- .../src/job-results/job-results.partial.html | 16 ++ .../src/job-results/job-results.route.js | 11 ++ .../launchjob.factory.js | 11 +- .../job-submission/job-submission.block.less | 17 +- .../job-submission.controller.js | 149 ++++++++++++------ .../job-submission.directive.js | 21 ++- .../job-submission.partial.html | 48 ++++-- .../job-sub-cred-list.controller.js | 141 ++++++++++++++--- .../credential/job-sub-cred-list.directive.js | 61 ++----- .../inventory/job-sub-inv-list.controller.js | 4 +- awx/ui/client/src/shared/generator-helpers.js | 2 +- .../list-generator/list-generator.factory.js | 2 +- .../shared/lookup/lookup-modal.directive.js | 4 +- awx/ui/client/src/templates/main.js | 12 +- 20 files changed, 375 insertions(+), 157 deletions(-) diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index a3679b2142..30911ba194 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -2334,7 +2334,7 @@ html input[disabled] { } .btn.disabled,.btn[disabled],fieldset[disabled] .bt { - opacity: 1; + opacity: 0.65; } .ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled { diff --git a/awx/ui/client/src/credentials/credentials.list.js b/awx/ui/client/src/credentials/credentials.list.js index 01842554ad..74528404bd 100644 --- a/awx/ui/client/src/credentials/credentials.list.js +++ b/awx/ui/client/src/credentials/credentials.list.js @@ -26,7 +26,7 @@ export default ['i18n', function(i18n) { key: true, label: i18n._('Name'), columnClass: 'col-md-3 col-sm-9 col-xs-9', - modalColumnClass: 'col-md-11', + modalColumnClass: 'col-md-12', awToolTip: '{{credential.description}}', dataPlacement: 'top' }, diff --git a/awx/ui/client/src/inventories/copy-move/copy-move-groups.controller.js b/awx/ui/client/src/inventories/copy-move/copy-move-groups.controller.js index 3dc0bc0a27..d600e78089 100644 --- a/awx/ui/client/src/inventories/copy-move/copy-move-groups.controller.js +++ b/awx/ui/client/src/inventories/copy-move/copy-move-groups.controller.js @@ -24,11 +24,11 @@ init(); - $scope.toggle_row = function(id){ + $scope.toggle_row = function(selectedRow){ // toggle off anything else currently selected - _.forEach($scope.groups, (item) => {return item.id === id ? item.checked = 1 : item.checked = null;}); + _.forEach($scope.groups, (item) => {return item.id === selectedRow.id ? item.checked = 1 : item.checked = null;}); // yoink the currently selected thing - $scope.selected = _.find($scope.groups, (item) => {return item.id === id;}); + $scope.selected = _.find($scope.groups, (item) => {return item.id === selectedRow.id;}); }; $scope.formCancel = function(){ @@ -62,7 +62,7 @@ } } }; - + $scope.toggleTargetRootGroup = function(){ $scope.selected = !$scope.selected; // cannot perform copy operations to root group level diff --git a/awx/ui/client/src/inventories/copy-move/copy-move-hosts.controller.js b/awx/ui/client/src/inventories/copy-move/copy-move-hosts.controller.js index a31f51676a..168196a529 100644 --- a/awx/ui/client/src/inventories/copy-move/copy-move-hosts.controller.js +++ b/awx/ui/client/src/inventories/copy-move/copy-move-hosts.controller.js @@ -22,11 +22,11 @@ init(); - $scope.toggle_row = function(id){ + $scope.toggle_row = function(selectedRow){ // toggle off anything else currently selected - _.forEach($scope.groups, (item) => {return item.id === id ? item.checked = 1 : item.checked = null;}); + _.forEach($scope.groups, (item) => {return item.id === selectedRow.id ? item.checked = 1 : item.checked = null;}); // yoink the currently selected thing - $scope.selected = _.find($scope.groups, (item) => {return item.id === id;}); + $scope.selected = _.find($scope.groups, (item) => {return item.id === selectedRow.id;}); }; $scope.formCancel = function(){ $state.go('^'); diff --git a/awx/ui/client/src/inventories/inventory.list.js b/awx/ui/client/src/inventories/inventory.list.js index c729768188..1969a1920d 100644 --- a/awx/ui/client/src/inventories/inventory.list.js +++ b/awx/ui/client/src/inventories/inventory.list.js @@ -45,7 +45,7 @@ export default ['i18n', function(i18n) { key: true, label: i18n._('Name'), columnClass: 'col-md-4 col-sm-3 col-xs-6 List-staticColumnAdjacent', - modalColumnClass: 'col-md-11', + modalColumnClass: 'col-md-12', awToolTip: "{{ inventory.description }}", awTipPlacement: "top", ngClick: 'editInventory(inventory)' diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js index a74550393f..881c04037b 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -1,5 +1,12 @@ -export default ['jobData', 'jobDataOptions', 'jobLabels', 'jobFinished', 'count', '$scope', 'ParseTypeChange', 'ParseVariableString', 'jobResultsService', 'eventQueue', '$compile', '$log', 'Dataset', '$q', 'QuerySet', '$rootScope', 'moment', '$stateParams', 'i18n', 'fieldChoices', 'fieldLabels', 'workflowResultsService', 'statusSocket', 'GetBasePath', -function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTypeChange, ParseVariableString, jobResultsService, eventQueue, $compile, $log, Dataset, $q, QuerySet, $rootScope, moment, $stateParams, i18n, fieldChoices, fieldLabels, workflowResultsService, statusSocket, GetBasePath) { +export default ['jobData', 'jobDataOptions', 'jobLabels', 'jobFinished', 'count', '$scope', 'ParseTypeChange', + 'ParseVariableString', 'jobResultsService', 'eventQueue', '$compile', '$log', 'Dataset', '$q', + 'QuerySet', '$rootScope', 'moment', '$stateParams', 'i18n', 'fieldChoices', 'fieldLabels', + 'workflowResultsService', 'statusSocket', 'GetBasePath', '$state', 'jobExtraCredentials', +function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTypeChange, + ParseVariableString, jobResultsService, eventQueue, $compile, $log, Dataset, $q, + QuerySet, $rootScope, moment, $stateParams, i18n, fieldChoices, fieldLabels, + workflowResultsService, statusSocket, GetBasePath, $state, jobExtraCredentials) { + var toDestroy = []; var cancelRequests = false; var runTimeElapsedTimer = null; @@ -45,6 +52,8 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // used for tag search $scope.job_events = $scope.job_event_dataset.results; + $scope.jobExtraCredentials = jobExtraCredentials; + var getTowerLinks = function() { var getTowerLink = function(key) { if(key === 'schedule') { diff --git a/awx/ui/client/src/job-results/job-results.partial.html b/awx/ui/client/src/job-results/job-results.partial.html index 8d497ca36c..8df5da0a6f 100644 --- a/awx/ui/client/src/job-results/job-results.partial.html +++ b/awx/ui/client/src/job-results/job-results.partial.html @@ -278,6 +278,22 @@ + +
+ +
+ + + {{ extraCredential.name }} + + {{$last ? '' : ', '}} + +
+
+
diff --git a/awx/ui/client/src/job-results/job-results.route.js b/awx/ui/client/src/job-results/job-results.route.js index 493ab06f4c..c648558779 100644 --- a/awx/ui/client/src/job-results/job-results.route.js +++ b/awx/ui/client/src/job-results/job-results.route.js @@ -170,6 +170,17 @@ export default { }); return val.promise; }], + jobExtraCredentials: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) { + Rest.setUrl(GetBasePath('jobs') + $stateParams.id + '/extra_credentials'); + var val = $q.defer(); + Rest.get() + .then(function(res) { + val.resolve(res.data.results); + }, function(res) { + val.reject(res); + }); + return val.promise; + }] }, templateUrl: templateUrl('job-results/job-results'), controller: 'jobResultsController' diff --git a/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js b/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js index c08c8f2838..6179ec1b1d 100644 --- a/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js +++ b/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js @@ -107,8 +107,15 @@ export default } // include the credential used if the user was prompted to choose a cred - if(scope.ask_credential_on_launch && !Empty(scope.selected_credential)){ - job_launch_data.credential_id = scope.selected_credential.id; + if(scope.ask_credential_on_launch && !Empty(scope.selected_credentials.machine)){ + job_launch_data.credential_id = scope.selected_credentials.machine.id; + } + + if(scope.ask_extra_credentials_on_launch){ + job_launch_data.extra_credentials = []; + scope.selected_credentials.extra.forEach((extraCredential) => { + job_launch_data.extra_credentials.push(extraCredential.id); + }); } // If the extra_vars dict is empty, we don't want to include it if we didn't prompt for anything. diff --git a/awx/ui/client/src/job-submission/job-submission.block.less b/awx/ui/client/src/job-submission/job-submission.block.less index b30eabb213..66c0df012c 100644 --- a/awx/ui/client/src/job-submission/job-submission.block.less +++ b/awx/ui/client/src/job-submission/job-submission.block.less @@ -140,7 +140,7 @@ font-weight: normal; font-size: small; } -.JobSubmission-previewItemTitle { +.JobSubmission-previewItemTitle, .JobSubmission-previewItemSubTitle, .JobSubmission-selectedItemInfoSubTitle { color: @default-interface-txt; } .JobSubmission-previewItemNone { @@ -189,13 +189,18 @@ } .JobSubmission-selectedItemInfo { display: flex; - flex: 0 0 auto; + flex: 0 0 100%; } .JobSubmission-selectedItemRevert { display: flex; flex: 0 0 auto; } -.JobSubmission-selectedItemLabel { +.JobSubmission-credentialSubSection { + display: flex; + justify-content: flex-end; + margin-bottom: 15px; +} +.JobSubmission-label { color: @default-interface-txt; margin-right: 10px; } @@ -214,3 +219,9 @@ .JobSubmission-passwordButton { padding: 5px 13px!important; } +.JobSubmission .List-noItems { + margin-top: auto; +} +.JobSubmission-selectedItemLabel { + flex: 0 0 165px; +} diff --git a/awx/ui/client/src/job-submission/job-submission.controller.js b/awx/ui/client/src/job-submission/job-submission.controller.js index 963a338b6c..11655cabbc 100644 --- a/awx/ui/client/src/job-submission/job-submission.controller.js +++ b/awx/ui/client/src/job-submission/job-submission.controller.js @@ -64,10 +64,8 @@ export default [ '$scope', 'GetBasePath', 'Wait', 'Rest', 'ProcessErrors', 'LaunchJob', '$state', 'InventoryList', 'CredentialList', 'ParseTypeChange', - 'GetSurveyQuestions', function($scope, GetBasePath, Wait, Rest, ProcessErrors, - LaunchJob, $state, InventoryList, CredentialList, ParseTypeChange, - GetSurveyQuestions) { + LaunchJob, $state, InventoryList, CredentialList, ParseTypeChange) { var launch_url; @@ -84,8 +82,8 @@ export default }; var updateRequiredPasswords = function() { - if($scope.selected_credential) { - if($scope.selected_credential.id === $scope.defaults.credential.id) { + if($scope.selected_credentials.machine) { + if($scope.selected_credentials.machine.id === $scope.defaults.credential.id) { clearRequiredPasswords(); for(var i=0; i<$scope.passwords_needed_to_start.length; i++) { var password = $scope.passwords_needed_to_start[i]; @@ -106,11 +104,11 @@ export default } } else { - if($scope.selected_credential.kind === "ssh"){ - $scope.ssh_password_required = ($scope.selected_credential.password === "ASK") ? true : false; - $scope.ssh_key_unlock_required = ($scope.selected_credential.ssh_key_unlock === "ASK") ? true : false; - $scope.become_password_required = ($scope.selected_credential.become_password === "ASK") ? true : false; - $scope.vault_password_required = ($scope.selected_credential.vault_password === "ASK") ? true : false; + if($scope.selected_credentials.machine.kind === "ssh"){ + $scope.ssh_password_required = ($scope.selected_credentials.machine.password === "ASK") ? true : false; + $scope.ssh_key_unlock_required = ($scope.selected_credentials.machine.ssh_key_unlock === "ASK") ? true : false; + $scope.become_password_required = ($scope.selected_credentials.machine.become_password === "ASK") ? true : false; + $scope.vault_password_required = ($scope.selected_credentials.machine.vault_password === "ASK") ? true : false; } else { clearRequiredPasswords(); @@ -133,6 +131,10 @@ export default $scope.init = function() { $scope.forms = {}; $scope.passwords = {}; + $scope.selected_credentials = { + machine: null, + extra: [] + }; // As of 3.0, the only place the user can relaunch a // playbook is on jobTemplates.edit (completed_jobs tab), @@ -155,6 +157,10 @@ export default } } + $scope.$watch('selected_credentials.machine', function(){ + updateRequiredPasswords(); + }); + // Get the job or job_template record Wait('start'); Rest.setUrl(launch_url); @@ -170,6 +176,7 @@ export default $scope.password_needed = data.passwords_needed_to_start && data.passwords_needed_to_start.length > 0; $scope.has_default_inventory = data.defaults && data.defaults.inventory && data.defaults.inventory.id; $scope.has_default_credential = data.defaults && data.defaults.credential && data.defaults.credential.id; + $scope.has_default_extra_credentials = data.defaults && data.defaults.extra_credentials && data.defaults.extra_credentials.length > 0; $scope.other_prompt_data = {}; @@ -200,11 +207,29 @@ export default } if($scope.has_default_credential) { - $scope.selected_credential = angular.copy($scope.defaults.credential); - updateRequiredPasswords(); + $scope.selected_credentials.machine = angular.copy($scope.defaults.credential); } - if( ($scope.submitJobType === 'workflow_job_template' && !$scope.survey_enabled) || ($scope.submitJobRelaunch && !$scope.password_needed) || (!$scope.submitJobRelaunch && $scope.can_start_without_user_input && !$scope.ask_inventory_on_launch && !$scope.ask_credential_on_launch && !$scope.has_other_prompts && !$scope.survey_enabled)) { + if($scope.has_default_extra_credentials) { + // Go out and get the credential types + Rest.setUrl(GetBasePath('credential_types')); + Rest.get() + .success(function (credentialTypeData) { + let credential_types = {}; + $scope.credentialKindOptions = []; + credentialTypeData.results.forEach((credentialType => { + credential_types[credentialType.id] = credentialType; + $scope.credentialKindOptions.push({ + name: credentialType.name, + value: credentialType.id + }); + })); + $scope.credential_types = credential_types; + $scope.selected_credentials.extra = angular.copy($scope.defaults.extra_credentials); + }); + } + + if( ($scope.submitJobType === 'workflow_job_template' && !$scope.survey_enabled) || ($scope.submitJobRelaunch && !$scope.password_needed) || (!$scope.submitJobRelaunch && $scope.can_start_without_user_input && !$scope.ask_inventory_on_launch && !$scope.ask_credential_on_launch && !$scope.ask_extra_credentials_on_launch && !$scope.has_other_prompts && !$scope.survey_enabled)) { // The job can be launched if // a) It's a relaunch and no passwords are needed // or @@ -219,7 +244,7 @@ export default if($scope.ask_inventory_on_launch) { $scope.setStep("inventory", true); } - else if($scope.ask_credential_on_launch || $scope.password_needed) { + else if($scope.ask_credential_on_launch || $scope.ask_extra_credentials_on_launch || $scope.password_needed) { $scope.setStep("credential", true); } else if($scope.has_other_prompts) { @@ -247,8 +272,7 @@ export default } if(jobResultData.summary_fields.credential) { $scope.defaults.credential = angular.copy(jobResultData.summary_fields.credential); - $scope.selected_credential = angular.copy(jobResultData.summary_fields.credential); - updateRequiredPasswords(); + $scope.selected_credentials.machine = angular.copy(jobResultData.summary_fields.credential); } initiateModal(); }) @@ -296,33 +320,24 @@ export default }; - $scope.getListsAndSurvey = function() { - if($scope.ask_inventory_on_launch) { - $scope.includeInventoryList = true; - } - if($scope.ask_credential_on_launch) { - $scope.includeCredentialList = true; - } - if($scope.survey_enabled) { - GetSurveyQuestions({ - scope: $scope, - id: $scope.submitJobId, - submitJobType: $scope.submitJobType - }); - - } - }; - $scope.revertToDefaultInventory = function() { if($scope.has_default_inventory) { $scope.selected_inventory = angular.copy($scope.defaults.inventory); } }; - $scope.revertToDefaultCredential = function() { + $scope.revertToDefaultCredentials = function() { if($scope.has_default_credential) { - $scope.selected_credential = angular.copy($scope.defaults.credential); - updateRequiredPasswords(); + $scope.selected_credentials.machine = angular.copy($scope.defaults.credential); + } + else { + $scope.selected_credentials.machine = null; + } + if($scope.has_default_extra_credentials) { + $scope.selected_credentials.extra = angular.copy($scope.defaults.extra_credentials); + } + else { + $scope.selected_credentials.extra = []; } }; @@ -340,8 +355,7 @@ export default $scope.toggle_credential = function(id) { $scope.credentials.forEach(function(row, i) { if (row.id === id) { - $scope.selected_credential = angular.copy(row); - updateRequiredPasswords(); + $scope.selected_credentials.machine = angular.copy(row); $scope.credentials[i].checked = 1; } else { $scope.credentials[i].checked = 0; @@ -351,7 +365,7 @@ export default $scope.getActionButtonText = function() { if($scope.step === "inventory") { - return ($scope.ask_credential_on_launch || $scope.password_needed || $scope.has_other_prompts || $scope.survey_enabled) ? "NEXT" : "LAUNCH"; + return ($scope.ask_credential_on_launch || $scope.ask_extra_credentials_on_launch || $scope.password_needed || $scope.has_other_prompts || $scope.survey_enabled) ? "NEXT" : "LAUNCH"; } else if($scope.step === "credential") { return ($scope.has_other_prompts || $scope.survey_enabled) ? "NEXT" : "LAUNCH"; @@ -377,7 +391,7 @@ export default } } else if($scope.step === "credential") { - if($scope.selected_credential && $scope.forms.credentialpasswords && $scope.forms.credentialpasswords.$valid) { + if($scope.selected_credentials.machine && $scope.forms.credentialpasswords && $scope.forms.credentialpasswords.$valid) { return false; } else { @@ -409,7 +423,7 @@ export default $scope.takeAction = function() { if($scope.step === "inventory") { // Check to see if there's another step after this one - if($scope.ask_credential_on_launch || $scope.password_needed) { + if($scope.ask_credential_on_launch || $scope.ask_extra_credentials_on_launch || $scope.password_needed) { $scope.setStep("credential"); } else if($scope.has_other_prompts) { @@ -456,14 +470,57 @@ export default $scope.parseTypeChange('parseType', 'jobLaunchVariables'); }; + $scope.showRevertCredentials = function(){ + let machineCredentialMatches = true; + let extraCredentialsMatch = true; + + if($scope.defaults.credential) { + if(!$scope.selected_credentials.machine || ($scope.selected_credentials.machine && $scope.selected_credentials.machine.id !== $scope.defaults.credential.id)) { + machineCredentialMatches = false; + } + } + else { + if($scope.selected_credentials.machine && $scope.selected_credentials.machine.id) { + machineCredentialMatches = false; + } + } + + if($scope.defaults.extra_credentials && $scope.defaults.extra_credentials.length > 0) { + if($scope.selected_credentials.extra && $scope.selected_credentials.extra.length > 0) { + if($scope.defaults.extra_credentials.length !== $scope.selected_credentials.extra.length) { + extraCredentialsMatch = false; + } + else { + $scope.defaults.extra_credentials.forEach((defaultExtraCredential) =>{ + let matchesSelected = false; + $scope.selected_credentials.extra.forEach((selectedExtraCredential) =>{ + if(defaultExtraCredential.id === selectedExtraCredential.id) { + matchesSelected = true; + } + }); + if(!matchesSelected) { + extraCredentialsMatch = false; + } + }); + } + + } + else { + extraCredentialsMatch = false; + } + } + else { + if($scope.selected_credentials.extra && $scope.selected_credentials.extra.length > 0) { + extraCredentialsMatch = false; + } + } + + return machineCredentialMatches && extraCredentialsMatch ? false : true; + }; + $scope.$on('inventorySelected', function(evt, selectedRow){ $scope.selected_inventory = _.cloneDeep(selectedRow); }); - $scope.$on('credentialSelected', function(evt, selectedRow){ - $scope.selected_credential = _.cloneDeep(selectedRow); - updateRequiredPasswords(); - }); - } ]; diff --git a/awx/ui/client/src/job-submission/job-submission.directive.js b/awx/ui/client/src/job-submission/job-submission.directive.js index 92b9bab7ff..b0bbb180cf 100644 --- a/awx/ui/client/src/job-submission/job-submission.directive.js +++ b/awx/ui/client/src/job-submission/job-submission.directive.js @@ -6,8 +6,8 @@ import jobSubmissionController from './job-submission.controller'; -export default [ 'templateUrl', 'CreateDialog', 'Wait', 'CreateSelect2', 'ParseTypeChange', - function(templateUrl, CreateDialog, Wait, CreateSelect2, ParseTypeChange) { +export default [ 'templateUrl', 'CreateDialog', 'Wait', 'CreateSelect2', 'ParseTypeChange', 'GetSurveyQuestions', + function(templateUrl, CreateDialog, Wait, CreateSelect2, ParseTypeChange, GetSurveyQuestions) { return { scope: { submitJobId: '=', @@ -25,7 +25,22 @@ export default [ 'templateUrl', 'CreateDialog', 'Wait', 'CreateSelect2', 'ParseT } scope.removeLaunchJobModalReady = scope.$on('LaunchJobModalReady', function() { // Go get the list/survey data that we need from the server - scope.getListsAndSurvey(); + if(scope.ask_inventory_on_launch) { + scope.includeInventoryList = true; + } + if(scope.ask_credential_on_launch || scope.ask_extra_credentials_on_launch) { + scope.credentialKind = (scope.ask_credential_on_launch) ? "1" : "1"; + + scope.includeCredentialList = true; + } + if(scope.survey_enabled) { + GetSurveyQuestions({ + scope: scope, + id: scope.submitJobId, + submitJobType: scope.submitJobType + }); + + } $('#job-launch-modal').dialog('open'); diff --git a/awx/ui/client/src/job-submission/job-submission.partial.html b/awx/ui/client/src/job-submission/job-submission.partial.html index 3f473a71d7..bda6689243 100644 --- a/awx/ui/client/src/job-submission/job-submission.partial.html +++ b/awx/ui/client/src/job-submission/job-submission.partial.html @@ -1,4 +1,4 @@ -
+