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 @@
+
+
+
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..6f6e5ffd46 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,33 @@ 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.ask_extra_credentials_on_launch) {
+ // 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;
+ if(($scope.ask_credential_on_launch || (!$scope.ask_credential_on_launch && credentialType.id !== 1)) && credentialType.kind.match(/^(cloud|network|ssh)$/)) {
+ $scope.credentialKindOptions.push({
+ name: credentialType.name,
+ value: credentialType.id
+ });
+ }
+ }));
+ $scope.credential_types = credential_types;
+ if($scope.has_default_extra_credentials) {
+ $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 +248,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 +276,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 +324,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 +359,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 +369,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 +395,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 +427,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 +474,57 @@ export default
$scope.parseTypeChange('parseType', 'jobLaunchVariables');
};
+ $scope.showRevertCredentials = function(){
+ let machineCredentialMatches = true;
+ let extraCredentialsMatch = true;
+
+ if($scope.defaults.credential && $scope.defaults.credential.id) {
+ 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..639afd7790 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" : "5";
+
+ 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..0f32e35d3d 100644
--- a/awx/ui/client/src/job-submission/job-submission.partial.html
+++ b/awx/ui/client/src/job-submission/job-submission.partial.html
@@ -1,8 +1,12 @@
-