From ef8af79700471bd6cd814b94016e882f521669e0 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Mon, 11 Dec 2017 15:25:55 -0500 Subject: [PATCH 1/7] load multiselect list when vault kind is selected --- .../multi-credential-modal.directive.js | 75 ++++++++++++------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js index 6b9e8ba709..3675c04e41 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js @@ -17,24 +17,32 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' scope.credentialKind = scope.selectedCredentials.machine && scope.selectedCredentials.machine.readOnly ? (scope.selectedCredentials.vault && scope.selectedCredentials.vault.readOnly ? "" + kinds.Network : "" + kinds.Vault) : "" + kinds.Machine; scope.showModal = function() { + scope.modalHidden = false; $('#multi-credential-modal').modal('show'); }; - scope.destroyModal = function() { + scope.hideModal = function() { + scope.modalHidden = true; scope.credentialKind = kinds.Machine; $('#multi-credential-modal').modal('hide'); }; - scope.generateCredentialList = function() { + scope.generateCredentialList = function(inputType = 'radio') { let html = GenerateList.build({ list: scope.list, - input_type: 'radio', + input_type: inputType, mode: 'lookup' }); - $('#multi-credential-modal-body') - .append($compile(html)(scope)); + + $('#multi-credential-modal-body').append($compile(html)(scope)); + scope.listRendered = true; }; + scope.destroyCredentialList = () => { + $('#multi-credential-modal-body').empty(); + scope.listRendered = false; + } + $('#multi-credential-modal').on('hidden.bs.modal', function () { $('#multi-credential-modal').off('hidden.bs.modal'); $(element).remove(); @@ -129,9 +137,37 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' $scope.allCredentialTypeOptions); }; + const onCredentialKindChanged = (newValue, oldValue) => { + const newValueIsVault = (parseInt(newValue) === _.get($scope, 'credentialKinds.Vault')); + const oldValueIsVault = (parseInt(oldValue) === _.get($scope, 'credentialKinds.Vault')); + const isClosing = ((newValue !== oldValue) && $scope.modalHidden); + + if ((oldValueIsVault || newValueIsVault) && !isClosing) { + $scope.destroyCredentialList(); + } + + $scope.credential_queryset.page = 1; + $scope.credential_default_params.credential_type = parseInt($scope.credentialKind); + $scope.credential_queryset.credential_type = parseInt($scope.credentialKind); + + qs.search(GetBasePath('credentials'), $scope.credential_default_params) + .then(res => { + $scope.credential_dataset = res.data; + $scope.credentials = $scope.credential_dataset.results; + + if(!$scope.listRendered) { + if (newValueIsVault) { + $scope.generateCredentialList('checkbox'); + } else { + $scope.generateCredentialList(); + } + $scope.showModal(); + } + }); + }; + let init = function() { - $scope.originalSelectedCredentials = _.cloneDeep($scope - .selectedCredentials); + $scope.originalSelectedCredentials = _.cloneDeep($scope.selectedCredentials); $scope.credential_dataset = []; $scope.credentials = $scope.credentials || []; $scope.listRendered = false; @@ -150,26 +186,7 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' page_size: 5 }; - $scope.$watch('credentialKind', function(){ - $scope.credential_queryset.page = 1; - $scope.credential_default_params.credential_type = $scope - .credential_queryset.credential_type = parseInt($scope - .credentialKind); - - qs.search(GetBasePath('credentials'), $scope - .credential_default_params) - .then(res => { - $scope.credential_dataset = res.data; - $scope.credentials = $scope.credential_dataset - .results; - - if(!$scope.listRendered) { - $scope.generateCredentialList(); - $scope.listRendered = true; - $scope.showModal(); - } - }); - }); + $scope.$watch('credentialKind', onCredentialKindChanged); $scope.$watchCollection('selectedCredentials.extra', () => { if($scope.credentials && $scope.credentials.length > 0) { @@ -299,12 +316,12 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' $scope.cancelForm = function() { $scope.selectedCredentials = $scope.originalSelectedCredentials; $scope.credTags = $scope.credentialsToPost; - $scope.destroyModal(); + $scope.hideModal(); }; $scope.saveForm = function() { $scope.credentialsToPost = $scope.credTags; - $scope.destroyModal(); + $scope.hideModal(); }; }] }; From 6759e60428b5034b5983c6fc8df2e2c9b935aa8a Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Tue, 12 Dec 2017 02:04:21 -0500 Subject: [PATCH 2/7] add working multivault select for templates form --- .../job-template-add.controller.js | 28 +-- .../job-template-edit.controller.js | 61 ++--- .../job_templates/job-template.form.js | 3 +- .../multi-credential-modal.directive.js | 214 +++++++++--------- .../multi-credential-modal.partial.html | 11 +- .../multi-credential.partial.html | 24 +- .../multi-credential.service.js | 106 ++++----- 7 files changed, 211 insertions(+), 236 deletions(-) diff --git a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js index b35cdfd2dd..379496fe0c 100644 --- a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js +++ b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js @@ -280,25 +280,13 @@ data.ask_credential_on_launch = $scope.ask_credential_on_launch ? $scope.ask_credential_on_launch : false; data.job_tags = (Array.isArray($scope.job_tags)) ? $scope.job_tags.join() : ""; data.skip_tags = (Array.isArray($scope.skip_tags)) ? $scope.skip_tags.join() : ""; - if ($scope.selectedCredentials && $scope.selectedCredentials - .machine && $scope.selectedCredentials - .machine) { - data.credential = $scope.selectedCredentials - .machine.id; - } else { - data.credential = null; - } - if ($scope.selectedCredentials && $scope.selectedCredentials - .vault && $scope.selectedCredentials - .vault.id) { - data.vault_credential = $scope.selectedCredentials - .vault.id; - } else { - data.vault_credential = null; - } - data.extra_vars = ToJSON($scope.parseType, - $scope.variables, true); + // drop legacy 'credential' and 'vault_credential' keys from the creation request as they will + // be provided to the related credentials endpoint by the template save success handler. + delete data.credential; + delete data.vault_credential; + + data.extra_vars = ToJSON($scope.parseType, $scope.variables, true); // We only want to set the survey_enabled flag to // true for this job template if a survey exists @@ -361,8 +349,8 @@ MultiCredentialService .saveExtraCredentials({ - creds: $scope.selectedCredentials.extra, - url: data.related.extra_credentials + creds: $scope.credentialsToPost, + url: data.related.credentials }); var orgDefer = $q.defer(); diff --git a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js index 476223cd2e..19c2964e3b 100644 --- a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js +++ b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js @@ -375,17 +375,16 @@ export default }); } else { + // if (jobTemplateData.summary_fields.credential) { + // $scope.selectedCredentials.machine = jobTemplateData.summary_fields.credential; + // } - if (jobTemplateData.summary_fields.credential) { - $scope.selectedCredentials.machine = jobTemplateData.summary_fields.credential; - } + // if (jobTemplateData.summary_fields.vault_credential) { + // $scope.selectedCredentials.vault = jobTemplateData.summary_fields.vault_credential; + // } - if (jobTemplateData.summary_fields.vault_credential) { - $scope.selectedCredentials.vault = jobTemplateData.summary_fields.vault_credential; - } - - if (jobTemplateData.summary_fields.extra_credentials) { - $scope.selectedCredentials.extra = jobTemplateData.summary_fields.extra_credentials; + if (jobTemplateData.summary_fields.credentials) { + $scope.selectedCredentials.extra = jobTemplateData.summary_fields.credentials; } MultiCredentialService.getCredentialTypes() @@ -396,12 +395,12 @@ export default let machineAndVaultCreds = [], extraCreds = []; - if($scope.selectedCredentials.machine) { - machineAndVaultCreds.push($scope.selectedCredentials.machine); - } - if($scope.selectedCredentials.vault) { - machineAndVaultCreds.push($scope.selectedCredentials.vault); - } + //if($scope.selectedCredentials.machine) { + // machineAndVaultCreds.push($scope.selectedCredentials.machine); + //} + //if($scope.selectedCredentials.vault) { + // machineAndVaultCreds.push($scope.selectedCredentials.vault); + //} machineAndVaultCreds.map(cred => ({ name: cred.name, @@ -425,7 +424,8 @@ export default })); } - $scope.credentialsToPost = machineAndVaultCreds.concat(extraCreds); + //$scope.credentialsToPost = machineAndVaultCreds.concat(extraCreds); + $scope.credentialsToPost = extraCreds; $scope.$emit('jobTemplateLoaded', master); }); @@ -523,8 +523,8 @@ export default MultiCredentialService .findChangedExtraCredentials({ - creds: $scope.selectedCredentials.extra, - url: data.related.extra_credentials + creds: $scope.credentialsToPost, + url: data.related.credentials }); InstanceGroupsService.editInstanceGroups(instance_group_url, $scope.instance_groups) @@ -668,24 +668,13 @@ export default data.ask_credential_on_launch = $scope.ask_credential_on_launch ? $scope.ask_credential_on_launch : false; data.job_tags = (Array.isArray($scope.job_tags)) ? $scope.job_tags.join() : ""; data.skip_tags = (Array.isArray($scope.skip_tags)) ? $scope.skip_tags.join() : ""; - if ($scope.selectedCredentials && $scope.selectedCredentials - .machine && $scope.selectedCredentials - .machine.id) { - data.credential = $scope.selectedCredentials - .machine.id; - } else { - data.credential = null; - } - if ($scope.selectedCredentials && $scope.selectedCredentials - .vault && $scope.selectedCredentials - .vault.id) { - data.vault_credential = $scope.selectedCredentials - .vault.id; - } else { - data.vault_credential = null; - } - data.extra_vars = ToJSON($scope.parseType, - $scope.variables, true); + + // drop legacy 'credential' and 'vault_credential' keys from the update request as they will + // be provided to the related credentials endpoint by the template save success handler. + delete data.credential; + delete data.vault_credential; + + data.extra_vars = ToJSON($scope.parseType, $scope.variables, true); // We only want to set the survey_enabled flag to // true for this job template if a survey exists diff --git a/awx/ui/client/src/templates/job_templates/job-template.form.js b/awx/ui/client/src/templates/job_templates/job-template.form.js index 891eb72dab..62921638d9 100644 --- a/awx/ui/client/src/templates/job_templates/job-template.form.js +++ b/awx/ui/client/src/templates/job_templates/job-template.form.js @@ -129,7 +129,6 @@ function(NotificationsList, CompletedJobsList, i18n) { credentials-to-post="credentialsToPost" field-is-disabled="!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)"> `, - required: true, awPopOver: i18n._('Select credentials that allow Tower to access the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking "Prompt on launch" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check "Prompt on launch", the selected credential(s) become the defaults that can be updated at run time.'), dataTitle: i18n._('Credentials'), dataPlacement: 'right', @@ -368,7 +367,7 @@ function(NotificationsList, CompletedJobsList, i18n) { }, save: { ngClick: 'formSave()', //$scope.function to call on click, optional - ngDisabled: "job_template_form.$invalid || credentialNotPresent",//true //Disable when $pristine or $invalid, optional and when can_edit = false, for permission reasons + ngDisabled: "job_template_form.$invalid",//true //Disable when $pristine or $invalid, optional and when can_edit = false, for permission reasons ngShow: '(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' } }, diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js index 3675c04e41..2077bf933e 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js @@ -14,7 +14,16 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' .then(kinds => { scope.credentialKinds = kinds; - scope.credentialKind = scope.selectedCredentials.machine && scope.selectedCredentials.machine.readOnly ? (scope.selectedCredentials.vault && scope.selectedCredentials.vault.readOnly ? "" + kinds.Network : "" + kinds.Vault) : "" + kinds.Machine; + const machineIsReadOnly = _.get(scope.selectedCredentials, 'machine.readOnly'); + const vaultIsReadOnly = _.get(scope.selectedCredentials, 'vault.readOnly'); + + if (!machineIsReadOnly) { + scope.credentialKind = `${kinds.Machine}`; + } else if (!vaultIsReadOnly) { + scope.credentialKind = `${kinds.Vault}`; + } else { + scope.credentialKind = `${kinds.Network}`; + } scope.showModal = function() { scope.modalHidden = false; @@ -83,13 +92,12 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' 'GetBasePath', function($scope, CredentialList, i18n, qs, GetBasePath) { let updateExtraCredentialsList = function() { - let extraCredIds = $scope.selectedCredentials.extra - .map(cred => cred.id); + let extraCredIds = $scope.selectedCredentials.extra.map(cred => cred.id); $scope.credentials.forEach(cred => { - if (cred.credential_type !== $scope.credentialKinds.Machine) { - cred.checked = (extraCredIds - .indexOf(cred.id) > - 1) ? 1 : 0; - } + cred.checked = (extraCredIds.indexOf(cred.id) > - 1) ? 1 : 0; + // if (cred.credential_type !== $scope.credentialKinds.Machine) { + // cred.checked = (extraCredIds.indexOf(cred.id) > - 1) ? 1 : 0; + //} }); $scope.credTags = MultiCredentialService @@ -97,35 +105,35 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' $scope.allCredentialTypeOptions); }; - let updateMachineCredentialList = function() { - $scope.credentials.forEach(cred => { - if (cred.credential_type === $scope.credentialKinds.Machine) { - cred.checked = ($scope.selectedCredentials - .machine !== null && - cred.id === $scope.selectedCredentials - .machine.id) ? 1 : 0; - } - }); + // let updateMachineCredentialList = function() { + // $scope.credentials.forEach(cred => { + // if (cred.credential_type === $scope.credentialKinds.Machine) { + // cred.checked = ($scope.selectedCredentials + // .machine !== null && + // cred.id === $scope.selectedCredentials + // .machine.id) ? 1 : 0; + // } + // }); - $scope.credTags = MultiCredentialService - .updateCredentialTags($scope.selectedCredentials, - $scope.allCredentialTypeOptions); - }; + // $scope.credTags = MultiCredentialService + // .updateCredentialTags($scope.selectedCredentials, + // $scope.allCredentialTypeOptions); + // }; - let updateVaultCredentialList = function() { - $scope.credentials.forEach(cred => { - if (cred.credential_type === $scope.credentialKinds.Vault) { - cred.checked = ($scope.selectedCredentials - .vault !== null && - cred.id === $scope.selectedCredentials - .vault.id) ? 1 : 0; - } - }); + // let updateVaultCredentialList = function() { + // $scope.credentials.forEach(cred => { + // if (cred.credential_type === $scope.credentialKinds.Vault) { + // cred.checked = ($scope.selectedCredentials + // .vault !== null && + // cred.id === $scope.selectedCredentials + // .vault.id) ? 1 : 0; + // } + // }); - $scope.credTags = MultiCredentialService - .updateCredentialTags($scope.selectedCredentials, - $scope.allCredentialTypeOptions); - }; + // $scope.credTags = MultiCredentialService + // .updateCredentialTags($scope.selectedCredentials, + // $scope.allCredentialTypeOptions); + // }; let uncheckAllCredentials = function() { $scope.credentials.forEach(cred => { @@ -161,6 +169,7 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' } else { $scope.generateCredentialList(); } + updateExtraCredentialsList(); $scope.showModal(); } }); @@ -188,61 +197,64 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' $scope.$watch('credentialKind', onCredentialKindChanged); + $scope.$watchCollection('credentials', updateExtraCredentialsList); + $scope.$watchCollection('selectedCredentials.extra', () => { + //$scope.$watchGroup(['credentials', 'selectedCredentials.extra'], () => { if($scope.credentials && $scope.credentials.length > 0) { if($scope.selectedCredentials.extra && - $scope.selectedCredentials.extra.length > 0 && - parseInt($scope.credentialKind) !== $scope.credentialKinds.Machine) { + $scope.selectedCredentials.extra.length > 0){ //&& + // parseInt($scope.credentialKind) !== $scope.credentialKinds.Machine) { updateExtraCredentialsList(); - } else if (parseInt($scope.credentialKind) !== $scope.credentialKinds.Machine) { + } else { //if (parseInt($scope.credentialKind) !== $scope.credentialKinds.Machine) { uncheckAllCredentials(); } } }); - $scope.$watch('selectedCredentials.machine', () => { - if($scope.selectedCredentials && - $scope.selectedCredentials.machine && - parseInt($scope.credentialKind) === $scope.credentialKinds.Machine) { - updateMachineCredentialList(); - } else { - uncheckAllCredentials(); - } - }); + // $scope.$watch('selectedCredentials.machine', () => { + // if($scope.selectedCredentials && + // $scope.selectedCredentials.machine && + // parseInt($scope.credentialKind) === $scope.credentialKinds.Machine) { + // updateMachineCredentialList(); + // } else { + // uncheckAllCredentials(); + // } + // }); - $scope.$watch('selectedCredentials.vault', () => { - if($scope.selectedCredentials && - $scope.selectedCredentials.vault && - parseInt($scope.credentialKind) === $scope.credentialKinds.Vault) { - updateVaultCredentialList(); - } else { - uncheckAllCredentials(); - } - }); + // $scope.$watch('selectedCredentials.vault', () => { + // if($scope.selectedCredentials && + // $scope.selectedCredentials.vault && + // parseInt($scope.credentialKind) === $scope.credentialKinds.Vault) { + // updateVaultCredentialList(); + // } else { + // uncheckAllCredentials(); + // } + // }); - $scope.$watchGroup(['credentials', - 'selectedCredentials.machine', - 'selectedCredentials.vault'], () => { - if($scope.credentials && - $scope.credentials.length > 0) { - if($scope.selectedCredentials && - $scope.selectedCredentials.machine && - parseInt($scope.credentialKind) === $scope.credentialKinds.Machine) { - updateMachineCredentialList(); - } else if($scope.selectedCredentials && - $scope.selectedCredentials.vault && - parseInt($scope.credentialKind) === $scope.credentialKinds.Vault) { - updateVaultCredentialList(); - } else if($scope.selectedCredentials && - $scope.selectedCredentials.extra && - $scope.selectedCredentials.extra.length > 0 && - parseInt($scope.credentialKind) !== $scope.credentialKinds.Machine) { - updateExtraCredentialsList(); - } else { - uncheckAllCredentials(); - } - } - }); + // $scope.$watchGroup(['credentials', + // 'selectedCredentials.machine', + // 'selectedCredentials.vault'], () => { + // if($scope.credentials && + // $scope.credentials.length > 0) { + // if($scope.selectedCredentials && + // $scope.selectedCredentials.machine && + // parseInt($scope.credentialKind) === $scope.credentialKinds.Machine) { + // updateMachineCredentialList(); + // } else if($scope.selectedCredentials && + // $scope.selectedCredentials.vault && + // parseInt($scope.credentialKind) === $scope.credentialKinds.Vault) { + // updateVaultCredentialList(); + // } else if($scope.selectedCredentials && + // $scope.selectedCredentials.extra && + // $scope.selectedCredentials.extra.length > 0 && + // parseInt($scope.credentialKind) !== $scope.credentialKinds.Machine) { + // updateExtraCredentialsList(); + // } else { + // uncheckAllCredentials(); + // } + // } + // }); }; $scope.$on('multiCredentialModalLinked', function() { @@ -250,39 +262,39 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' }); $scope.toggle_row = function(selectedRow) { - if(parseInt($scope.credentialKind) === $scope.credentialKinds.Machine) { - if($scope.selectedCredentials && - $scope.selectedCredentials.machine && - $scope.selectedCredentials.machine.id === selectedRow.id) { - $scope.selectedCredentials.machine = null; - } else { - $scope.selectedCredentials.machine = _.cloneDeep(selectedRow); - } - }else if(parseInt($scope.credentialKind) === $scope.credentialKinds.Vault) { - if($scope.selectedCredentials && - $scope.selectedCredentials.vault && - $scope.selectedCredentials.vault.id === selectedRow.id) { - $scope.selectedCredentials.vault = null; - } else { - $scope.selectedCredentials.vault = _.cloneDeep(selectedRow); - } - } else { + // if(false) { //if(parseInt($scope.credentialKind) === $scope.credentialKinds.Machine) { + // if($scope.selectedCredentials && + // $scope.selectedCredentials.machine && + // $scope.selectedCredentials.machine.id === selectedRow.id) { + // $scope.selectedCredentials.machine = null; + // } else { + // $scope.selectedCredentials.machine = _.cloneDeep(selectedRow); + // } + // }else if (false) { //if(parseInt($scope.credentialKind) === $scope.credentialKinds.Vault) { + // if($scope.selectedCredentials && + // $scope.selectedCredentials.vault && + // $scope.selectedCredentials.vault.id === selectedRow.id) { + // $scope.selectedCredentials.vault = null; + // } else { + // $scope.selectedCredentials.vault = _.cloneDeep(selectedRow); + // } + // } else { let rowDeselected = false; for (let i = $scope.selectedCredentials.extra.length - 1; i >= 0; i--) { - if($scope.selectedCredentials.extra[i].id === selectedRow - .id) { - $scope.selectedCredentials.extra.splice(i, 1); - rowDeselected = true; - } else if(selectedRow.credential_type === $scope - .selectedCredentials.extra[i].credential_type) { + if($scope.selectedCredentials.extra[i].id === selectedRow.id) { + $scope.selectedCredentials.extra.splice(i, 1); + rowDeselected = true; + } else if(selectedRow.credential_type === $scope.selectedCredentials.extra[i].credential_type) { + if (selectedRow.credential_type !== $scope.credentialKinds.Vault) { $scope.selectedCredentials.extra.splice(i, 1); + } } } if(!rowDeselected) { $scope.selectedCredentials.extra .push(_.cloneDeep(selectedRow)); } - } + // } }; $scope.selectedCredentialsDirty = function() { diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html index f14933339e..45f2ee15f7 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html @@ -34,14 +34,11 @@
- - {{ tag.kind }} + + {{ tag.kind }} - - {{ tag.name }} + + {{ tag.name }}
diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html index 912dc078a5..3bfb1c6688 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html @@ -8,14 +8,10 @@ - +
@@ -24,14 +20,12 @@
- - +
- - {{ tag.kind }} + + {{ tag.kind }} {{ tag.name }} @@ -43,7 +37,3 @@
-
- Please select a machine (SSH) credential or check the "Prompt on launch" option. -
diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js index a22937fb61..58d80605db 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js @@ -182,61 +182,61 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro let credentialGetPermissionDenied = false; // get machine credential - if (data.related.credential) { - Rest.setUrl(data.related.credential); - credDefers.push(Rest.get() - .then(({data}) => { - selectedCredentials.machine = data; - }) - .catch(({data, status}) => { - if (status === 403) { - /* User doesn't have read access to the machine credential, so use summary_fields */ - credentialGetPermissionDenied = true; - selectedCredentials.machine = job_template_obj.summary_fields.credential; - selectedCredentials.machine.credential_type = job_template_obj.summary_fields.credential.credential_type_id; - selectedCredentials.machine.readOnly = true; - } else { - ProcessErrors( - null, data, status, null, - { - hdr: 'Error!', - msg: 'Failed to get machine credential. ' + - 'Get returned status: ' + - status - }); - } - })); - } + // if (data.related.credential) { + // Rest.setUrl(data.related.credential); + // credDefers.push(Rest.get() + // .then(({data}) => { + // selectedCredentials.machine = data; + // }) + // .catch(({data, status}) => { + // if (status === 403) { + // /* User doesn't have read access to the machine credential, so use summary_fields */ + // credentialGetPermissionDenied = true; + // selectedCredentials.machine = job_template_obj.summary_fields.credential; + // selectedCredentials.machine.credential_type = job_template_obj.summary_fields.credential.credential_type_id; + // selectedCredentials.machine.readOnly = true; + // } else { + // ProcessErrors( + // null, data, status, null, + // { + // hdr: 'Error!', + // msg: 'Failed to get machine credential. ' + + // 'Get returned status: ' + + // status + // }); + // } + // })); + // } - if (data.related.vault_credential) { - Rest.setUrl(data.related.vault_credential); - credDefers.push(Rest.get() - .then(({data}) => { - selectedCredentials.vault = data; - }) - .catch(({data, status}) => { - if (status === 403) { - /* User doesn't have read access to the vault credential, so use summary_fields */ - credentialGetPermissionDenied = true; - selectedCredentials.vault = job_template_obj.summary_fields.vault_credential; - selectedCredentials.vault.credential_type = job_template_obj.summary_fields.vault_credential.credential_type_id; - selectedCredentials.vault.readOnly = true; - } else { - ProcessErrors( - null, data, status, null, - { - hdr: 'Error!', - msg: 'Failed to get machine credential. ' + - 'Get returned status: ' + - status - }); - } - })); - } + // if (data.related.vault_credential) { + // Rest.setUrl(data.related.vault_credential); + // credDefers.push(Rest.get() + // .then(({data}) => { + // selectedCredentials.vault = data; + // }) + // .catch(({data, status}) => { + // if (status === 403) { + // /* User doesn't have read access to the vault credential, so use summary_fields */ + // credentialGetPermissionDenied = true; + // selectedCredentials.vault = job_template_obj.summary_fields.vault_credential; + // selectedCredentials.vault.credential_type = job_template_obj.summary_fields.vault_credential.credential_type_id; + // selectedCredentials.vault.readOnly = true; + // } else { + // ProcessErrors( + // null, data, status, null, + // { + // hdr: 'Error!', + // msg: 'Failed to get machine credential. ' + + // 'Get returned status: ' + + // status + // }); + // } + // })); + // } // get extra credentials - if (data.related.extra_credentials) { - Rest.setUrl(data.related.extra_credentials); + if (data.related.credentials) { + Rest.setUrl(data.related.credentials); credDefers.push(Rest.get() .then(({data}) => { selectedCredentials.extra = data.results; @@ -245,7 +245,7 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro if (status === 403) { /* User doesn't have read access to the extra credentials, so use summary_fields */ credentialGetPermissionDenied = true; - selectedCredentials.extra = job_template_obj.summary_fields.extra_credentials; + selectedCredentials.extra = job_template_obj.summary_fields.credentials; _.map(selectedCredentials.extra, (cred) => { cred.credential_type = cred.credential_type_id; cred.readOnly = true; From 5939116b0afdfcdc527e7607204690ee3539ebba Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Thu, 14 Dec 2017 00:47:37 -0500 Subject: [PATCH 3/7] update e2e and smoke tests for multivault select --- .../multi-credential-modal.directive.js | 138 +++--------------- awx/ui/test/e2e/objects/credentials.js | 1 + awx/ui/test/e2e/objects/templates.js | 10 +- awx/ui/test/e2e/tests/smoke.js | 51 +++++-- awx/ui/test/e2e/tests/smoke.yml | 31 +++- 5 files changed, 92 insertions(+), 139 deletions(-) diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js index 2077bf933e..5603f7f29e 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js @@ -95,9 +95,6 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' let extraCredIds = $scope.selectedCredentials.extra.map(cred => cred.id); $scope.credentials.forEach(cred => { cred.checked = (extraCredIds.indexOf(cred.id) > - 1) ? 1 : 0; - // if (cred.credential_type !== $scope.credentialKinds.Machine) { - // cred.checked = (extraCredIds.indexOf(cred.id) > - 1) ? 1 : 0; - //} }); $scope.credTags = MultiCredentialService @@ -105,36 +102,6 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' $scope.allCredentialTypeOptions); }; - // let updateMachineCredentialList = function() { - // $scope.credentials.forEach(cred => { - // if (cred.credential_type === $scope.credentialKinds.Machine) { - // cred.checked = ($scope.selectedCredentials - // .machine !== null && - // cred.id === $scope.selectedCredentials - // .machine.id) ? 1 : 0; - // } - // }); - - // $scope.credTags = MultiCredentialService - // .updateCredentialTags($scope.selectedCredentials, - // $scope.allCredentialTypeOptions); - // }; - - // let updateVaultCredentialList = function() { - // $scope.credentials.forEach(cred => { - // if (cred.credential_type === $scope.credentialKinds.Vault) { - // cred.checked = ($scope.selectedCredentials - // .vault !== null && - // cred.id === $scope.selectedCredentials - // .vault.id) ? 1 : 0; - // } - // }); - - // $scope.credTags = MultiCredentialService - // .updateCredentialTags($scope.selectedCredentials, - // $scope.allCredentialTypeOptions); - // }; - let uncheckAllCredentials = function() { $scope.credentials.forEach(cred => { cred.checked = 0; @@ -200,108 +167,51 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' $scope.$watchCollection('credentials', updateExtraCredentialsList); $scope.$watchCollection('selectedCredentials.extra', () => { - //$scope.$watchGroup(['credentials', 'selectedCredentials.extra'], () => { if($scope.credentials && $scope.credentials.length > 0) { if($scope.selectedCredentials.extra && - $scope.selectedCredentials.extra.length > 0){ //&& - // parseInt($scope.credentialKind) !== $scope.credentialKinds.Machine) { + $scope.selectedCredentials.extra.length > 0) { updateExtraCredentialsList(); - } else { //if (parseInt($scope.credentialKind) !== $scope.credentialKinds.Machine) { + } else { uncheckAllCredentials(); } } }); - - // $scope.$watch('selectedCredentials.machine', () => { - // if($scope.selectedCredentials && - // $scope.selectedCredentials.machine && - // parseInt($scope.credentialKind) === $scope.credentialKinds.Machine) { - // updateMachineCredentialList(); - // } else { - // uncheckAllCredentials(); - // } - // }); - - // $scope.$watch('selectedCredentials.vault', () => { - // if($scope.selectedCredentials && - // $scope.selectedCredentials.vault && - // parseInt($scope.credentialKind) === $scope.credentialKinds.Vault) { - // updateVaultCredentialList(); - // } else { - // uncheckAllCredentials(); - // } - // }); - - // $scope.$watchGroup(['credentials', - // 'selectedCredentials.machine', - // 'selectedCredentials.vault'], () => { - // if($scope.credentials && - // $scope.credentials.length > 0) { - // if($scope.selectedCredentials && - // $scope.selectedCredentials.machine && - // parseInt($scope.credentialKind) === $scope.credentialKinds.Machine) { - // updateMachineCredentialList(); - // } else if($scope.selectedCredentials && - // $scope.selectedCredentials.vault && - // parseInt($scope.credentialKind) === $scope.credentialKinds.Vault) { - // updateVaultCredentialList(); - // } else if($scope.selectedCredentials && - // $scope.selectedCredentials.extra && - // $scope.selectedCredentials.extra.length > 0 && - // parseInt($scope.credentialKind) !== $scope.credentialKinds.Machine) { - // updateExtraCredentialsList(); - // } else { - // uncheckAllCredentials(); - // } - // } - // }); }; $scope.$on('multiCredentialModalLinked', function() { init(); }); + $scope.toggle_credential = id => { + // This is called only when a checkbox input is clicked directly. For clicks anywhere + // else on the row or direct radio button clicks, the toggle_row handler is called + // instead with a slightly different set of arguments. We normalize those arguments + // here and pass them through to the other handler so that the behavior is consistent. + const [credential] = $scope.credentials.filter(credential => credential.id === id); + return $scope.toggle_row(credential); + }; + $scope.toggle_row = function(selectedRow) { - // if(false) { //if(parseInt($scope.credentialKind) === $scope.credentialKinds.Machine) { - // if($scope.selectedCredentials && - // $scope.selectedCredentials.machine && - // $scope.selectedCredentials.machine.id === selectedRow.id) { - // $scope.selectedCredentials.machine = null; - // } else { - // $scope.selectedCredentials.machine = _.cloneDeep(selectedRow); - // } - // }else if (false) { //if(parseInt($scope.credentialKind) === $scope.credentialKinds.Vault) { - // if($scope.selectedCredentials && - // $scope.selectedCredentials.vault && - // $scope.selectedCredentials.vault.id === selectedRow.id) { - // $scope.selectedCredentials.vault = null; - // } else { - // $scope.selectedCredentials.vault = _.cloneDeep(selectedRow); - // } - // } else { - let rowDeselected = false; - for (let i = $scope.selectedCredentials.extra.length - 1; i >= 0; i--) { - if($scope.selectedCredentials.extra[i].id === selectedRow.id) { + let rowDeselected = false; + for (let i = $scope.selectedCredentials.extra.length - 1; i >= 0; i--) { + if($scope.selectedCredentials.extra[i].id === selectedRow.id) { + $scope.selectedCredentials.extra.splice(i, 1); + rowDeselected = true; + } else if(selectedRow.credential_type === $scope.selectedCredentials.extra[i].credential_type) { + if (selectedRow.credential_type !== $scope.credentialKinds.Vault) { $scope.selectedCredentials.extra.splice(i, 1); - rowDeselected = true; - } else if(selectedRow.credential_type === $scope.selectedCredentials.extra[i].credential_type) { - if (selectedRow.credential_type !== $scope.credentialKinds.Vault) { - $scope.selectedCredentials.extra.splice(i, 1); - } } } - if(!rowDeselected) { - $scope.selectedCredentials.extra - .push(_.cloneDeep(selectedRow)); - } - // } + } + if(!rowDeselected) { + $scope.selectedCredentials.extra + .push(_.cloneDeep(selectedRow)); + } }; $scope.selectedCredentialsDirty = function() { if ($scope.originalSelectedCredentials) { - return !($scope.originalSelectedCredentials.machine === null && - $scope.originalSelectedCredentials.vault === null && - $scope.originalSelectedCredentials.extra.length === 0) && + return !($scope.originalSelectedCredentials.extra.length === 0) && !_.isEqual($scope.selectedCredentials, $scope.originalSelectedCredentials); } else { diff --git a/awx/ui/test/e2e/objects/credentials.js b/awx/ui/test/e2e/objects/credentials.js index ad3cd3bb91..d271eb953a 100644 --- a/awx/ui/test/e2e/objects/credentials.js +++ b/awx/ui/test/e2e/objects/credentials.js @@ -39,6 +39,7 @@ const vault = createFormSection({ selector: '.at-InputGroup-inset', labels: { vaultPassword: 'Vault Password', + vaultIdentifier: 'Vault Identifier' } }); diff --git a/awx/ui/test/e2e/objects/templates.js b/awx/ui/test/e2e/objects/templates.js index 073086e8ed..86a5478d3e 100644 --- a/awx/ui/test/e2e/objects/templates.js +++ b/awx/ui/test/e2e/objects/templates.js @@ -226,11 +226,10 @@ module.exports = { .waitForElementVisible('div.spinny') .waitForElementNotVisible('div.spinny') .waitForElementNotPresent('multi-credential-modal tbody tr:nth-child(2)') - .waitForElementVisible('multi-credential-modal tbody tr:nth-child(1) input[type="radio"]') - .click('multi-credential-modal tbody tr:nth-child(1) input[type="radio"]') + .waitForElementVisible('multi-credential-modal tbody tr:nth-child(1) input[type="checkbox"]') + .click('multi-credential-modal tbody tr:nth-child(1) input[type="checkbox"]') .click('multi-credential-modal button[class*="save"]') - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); + .pause(1000); return this; }, @@ -256,8 +255,7 @@ module.exports = { .waitForElementVisible('multi-credential-modal tbody tr:nth-child(1) input[type="radio"]') .click('multi-credential-modal tbody tr:nth-child(1) input[type="radio"]') .click('multi-credential-modal button[class*="save"]') - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); + .pause(1000); return this; } diff --git a/awx/ui/test/e2e/tests/smoke.js b/awx/ui/test/e2e/tests/smoke.js index b65e1d5f8a..6d855794ff 100644 --- a/awx/ui/test/e2e/tests/smoke.js +++ b/awx/ui/test/e2e/tests/smoke.js @@ -6,12 +6,12 @@ const INVENTORY_NAME = `inventory-${id}`; const MACHINE_CREDENTIAL_NAME = `credential-machine-${id}`; const ORGANIZATION_NAME = `organization-${id}`; const PROJECT_NAME = `project-${id}`; -const PROJECT_URL = 'https://github.com/ansible/awx'; -const PROJECT_BRANCH = 'devel'; -const PLAYBOOK_NAME = 'awx/ui/test/e2e/tests/smoke.yml'; +const PROJECT_URL = 'https://github.com/jlaska/ansible-playbooks'; +const PROJECT_BRANCH = 'master'; +const PLAYBOOK_NAME = 'multivault.yml'; const TEMPLATE_NAME = `template-${id}`; -const VAULT_CREDENTIAL_NAME = `credential-vault-${id}`; -const VAULT_CREDENTIAL_PASSWORD = 'VAULT_CREDENTIAL_PASSWORD'; +const VAULT_CREDENTIAL_NAME_1 = `credential-vault-${id}-1`; +const VAULT_CREDENTIAL_NAME_2 = `credential-vault-${id}-2`; module.exports = { 'login to awx': client => { @@ -153,7 +153,7 @@ module.exports = { client.waitForElementVisible('div.spinny'); client.waitForElementNotVisible('div.spinny'); }, - 'create vault credential': client => { + 'create vault credentials': client => { const credentials = client.page.credentials(); const { details } = credentials.section.add.section; @@ -171,9 +171,35 @@ module.exports = { details.waitForElementVisible('@save'); details.clearAndSelectType('Vault'); details.setValue('@organization', ORGANIZATION_NAME); - details.setValue('@name', VAULT_CREDENTIAL_NAME); + details.setValue('@name', VAULT_CREDENTIAL_NAME_1); - details.section.vault.setValue('@vaultPassword', VAULT_CREDENTIAL_PASSWORD); + details.section.vault.setValue('@vaultPassword', 'secret1'); + details.section.vault.setValue('@vaultIdentifier', 'first'); + + details.expect.element('@save').enabled; + details.click('@save'); + + credentials.waitForElementVisible('div.spinny'); + credentials.waitForElementNotVisible('div.spinny'); + + credentials.section.navigation.waitForElementVisible('@credentials'); + credentials.section.navigation.expect.element('@credentials').enabled; + credentials.section.navigation.click('@credentials'); + + credentials.waitForElementVisible('div.spinny'); + credentials.waitForElementNotVisible('div.spinny'); + + credentials.section.list.waitForElementVisible('@add'); + credentials.section.list.expect.element('@add').enabled; + credentials.section.list.click('@add'); + + details.waitForElementVisible('@save'); + details.clearAndSelectType('Vault'); + details.setValue('@organization', ORGANIZATION_NAME); + details.setValue('@name', VAULT_CREDENTIAL_NAME_2); + + details.section.vault.setValue('@vaultPassword', 'secret2'); + details.section.vault.setValue('@vaultIdentifier', 'second'); details.expect.element('@save').enabled; details.click('@save'); @@ -217,7 +243,8 @@ module.exports = { templates.selectAdd('Job Template'); templates.selectInventory(INVENTORY_NAME); templates.selectProject(PROJECT_NAME); - templates.selectVaultCredential(VAULT_CREDENTIAL_NAME); + templates.selectVaultCredential(VAULT_CREDENTIAL_NAME_1); + templates.selectVaultCredential(VAULT_CREDENTIAL_NAME_2); templates.selectMachineCredential(MACHINE_CREDENTIAL_NAME); templates.selectPlaybook(PLAYBOOK_NAME); templates.sendKeys('label[for="name"] + div input', TEMPLATE_NAME); @@ -253,7 +280,8 @@ module.exports = { templates.click('i[class$="launch"]'); }, 'verify expected job results': client => { - const output = './/span[normalize-space(text())=\'"msg": "Hello World!"\']'; + const output1 = './/span[normalize-space(text())=\'"first": "First!"\']'; + const output2 = './/span[normalize-space(text())=\'"second": "Second!"\']'; const running = 'i[class$="icon-job-running"]'; const success = 'i[class$="icon-job-successful"]'; @@ -265,7 +293,8 @@ module.exports = { client.waitForElementVisible(success, 60000); client.useXpath(); - client.waitForElementVisible(output, 60000); + client.waitForElementVisible(output1, 60000); + client.waitForElementVisible(output2, 60000); client.useCss(); client.end(); diff --git a/awx/ui/test/e2e/tests/smoke.yml b/awx/ui/test/e2e/tests/smoke.yml index f85e892dff..10edd0ab0f 100644 --- a/awx/ui/test/e2e/tests/smoke.yml +++ b/awx/ui/test/e2e/tests/smoke.yml @@ -1,10 +1,25 @@ --- -- hosts: all - tasks: - - name: Import Vault Variables - include_vars: smoke-vars.yml - no_log: true +# ansible-playbook multivault.yml --vault-id var1@prompt --vault-id var2@prompt +# Vault password (var1): secret1 +# Vault password (var2): secret2 - - name: Display Vault Message - debug: - msg: '{{ vault_message }}' +- hosts: all + gather_facts: false + vars: + first: !vault | + $ANSIBLE_VAULT;1.2;AES256;first + 30326539376633656433636231653132623266336338316462356132366361653566303364353335 + 6665626463633737666336643334353262373836613332650a353531666262636531383430363935 + 33633465306165393538323336323135393730383563653738666163633835383262396135353765 + 6238333837306332630a336538623333313636353363326666613564353666623635373432386162 + 3562 + second: !vault | + $ANSIBLE_VAULT;1.2;AES256;second + 34653738643565633930336534363230343562343362643432616165373034376565353833366361 + 6264346330376564643262643166623164323433336631360a396336353866323663613935383534 + 33643034373439326435373539323433313832366437303764353562653834623966663533613464 + 3961663934613264360a613763346638636566386461333235366335336564353935356232316265 + 3164 + tasks: + - debug: var=first + - debug: var=second From 79bd8b2c728772778d110d856e1f7d778aa4fafc Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Mon, 18 Dec 2017 16:15:24 -0500 Subject: [PATCH 4/7] show vault id --- .../list-generator/list-generator.factory.js | 3 +- .../multi-credential-modal.directive.js | 50 +++++++++++++------ .../multi-credential-modal.partial.html | 11 ++-- .../multi-credential.partial.html | 7 ++- .../multi-credential.service.js | 32 +++++++----- 5 files changed, 70 insertions(+), 33 deletions(-) diff --git a/awx/ui/client/src/shared/list-generator/list-generator.factory.js b/awx/ui/client/src/shared/list-generator/list-generator.factory.js index 3b6b762d9f..a94513449e 100644 --- a/awx/ui/client/src/shared/list-generator/list-generator.factory.js +++ b/awx/ui/client/src/shared/list-generator/list-generator.factory.js @@ -552,8 +552,9 @@ export default ['$compile', 'Attr', 'Icon', if(list.fields.info) { customClass = list.fields.name.modalColumnClass || ''; + const infoHeaderClass = _.get(list.fields.info, 'infoHeaderClass', 'List-tableHeader--info'); html += `=0; i--) { if((scope.selectedCredentials.machine && scope.selectedCredentials.machine.credential_type_id === scope.credentialTypeOptions[i].value && @@ -126,13 +127,13 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' $scope.credential_queryset.credential_type = parseInt($scope.credentialKind); qs.search(GetBasePath('credentials'), $scope.credential_default_params) - .then(res => { - $scope.credential_dataset = res.data; + .then(({ data }) => { + $scope.credential_dataset = data; $scope.credentials = $scope.credential_dataset.results; if(!$scope.listRendered) { if (newValueIsVault) { - $scope.generateCredentialList('checkbox'); + $scope.generateCredentialList('checkbox', $scope.vaultList); } else { $scope.generateCredentialList(); } @@ -148,9 +149,26 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' $scope.credentials = $scope.credentials || []; $scope.listRendered = false; - let credList = _.cloneDeep(CredentialList); + const credList = _.cloneDeep(CredentialList); + credList.emptyListText = i18n._('No Credentials Matching This Type Have Been Created'); + + const vaultCredList = _.cloneDeep(credList); + + vaultCredList.fields.name.modalColumnClass = 'col-md-6'; + + vaultCredList.fields.info = { + label: i18n._('Vault ID'), + ngBind: 'credential.inputs.vault_id', + key: false, + nosort: true, + modalColumnClass: 'col-md-6', + infoHeaderClass: '', + dataPlacement: 'top' + }; + $scope.list = credList; + $scope.vaultList = vaultCredList; $scope.credential_default_params = { order_by: 'name', @@ -191,21 +209,25 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' return $scope.toggle_row(credential); }; - $scope.toggle_row = function(selectedRow) { + $scope.toggle_row = function(credential) { let rowDeselected = false; for (let i = $scope.selectedCredentials.extra.length - 1; i >= 0; i--) { - if($scope.selectedCredentials.extra[i].id === selectedRow.id) { + if($scope.selectedCredentials.extra[i].id === credential.id) { $scope.selectedCredentials.extra.splice(i, 1); rowDeselected = true; - } else if(selectedRow.credential_type === $scope.selectedCredentials.extra[i].credential_type) { - if (selectedRow.credential_type !== $scope.credentialKinds.Vault) { + } else if(credential.credential_type === $scope.selectedCredentials.extra[i].credential_type) { + if (credential.credential_type !== $scope.credentialKinds.Vault) { + $scope.selectedCredentials.extra.splice(i, 1); + } else if($scope.selectedCredentials.extra[i].inputs.vault_id === credential.inputs.vault_id) { + // remove existing vault credentials if they have the same vault_id as a recently + // toggled vault credential $scope.selectedCredentials.extra.splice(i, 1); } } } if(!rowDeselected) { $scope.selectedCredentials.extra - .push(_.cloneDeep(selectedRow)); + .push(_.cloneDeep(credential)); } }; diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html index 45f2ee15f7..95d28195af 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html @@ -34,10 +34,13 @@
- - {{ tag.kind }} - - + + {{ tag.kind }}: + + + {{ tag.kind }} ({{ tag.vault_id }}): + + {{ tag.name }}
diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html index 3bfb1c6688..95e8a1b670 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html @@ -24,8 +24,11 @@
- - {{ tag.kind }} + + {{ tag.kind }}: + + + {{ tag.kind }} ({{ tag.vault_id }}): {{ tag.name }} diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js index 58d80605db..7cf971b33d 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js @@ -134,16 +134,24 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro }); } - return machineCred.concat(extraCreds).concat(vaultCred).map(cred => ({ - name: cred.name, - id: cred.id, - postType: cred.postType, - readOnly: cred.readOnly ? true : false, - kind: typeOpts - .filter(type => { - return parseInt(cred.credential_type) === type.value; - })[0].name + ":" - })); + return machineCred.concat(extraCreds).concat(vaultCred).map(cred => { + const { name, id, postType, readOnly } = cred; + const [type] = typeOpts.filter(type => parseInt(cred.credential_type) === type.value) + + const tagData = { + name: cred.name, + id: cred.id, + postType: cred.postType, + readOnly: cred.readOnly ? true : false, + kind: `${type.name}` + }; + + if (type.name === 'Vault') { + tagData.vault_id = _.get(cred, 'inputs.vault_id'); + } + + return tagData; + }) }; // remove credential from structured selected credential data and tag-view @@ -234,7 +242,7 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro // })); // } - // get extra credentials + // get credentials if (data.related.credentials) { Rest.setUrl(data.related.credentials); credDefers.push(Rest.get() @@ -243,7 +251,7 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro }) .catch(({data, status}) => { if (status === 403) { - /* User doesn't have read access to the extra credentials, so use summary_fields */ + /* User doesn't have read access to the credentials, so use summary_fields */ credentialGetPermissionDenied = true; selectedCredentials.extra = job_template_obj.summary_fields.credentials; _.map(selectedCredentials.extra, (cred) => { From 916d91cbc7e2ba23963ff706bc895791b0ab5cc7 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Sun, 7 Jan 2018 16:49:14 -0500 Subject: [PATCH 5/7] use updated credentials endpoint --- .../job-template-add.controller.js | 19 +- .../job-template-edit.controller.js | 125 ++--- .../job_templates/job-template.form.js | 4 +- .../multi-credential-modal.directive.js | 499 +++++++++--------- .../multi-credential-modal.partial.html | 51 +- .../multi-credential.directive.js | 118 ++--- .../multi-credential.partial.html | 14 +- .../multi-credential.service.js | 351 +++--------- .../multi-credential.service-test.js | 416 --------------- 9 files changed, 475 insertions(+), 1122 deletions(-) delete mode 100644 awx/ui/test/spec/multi-credential/multi-credential.service-test.js diff --git a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js index 379496fe0c..b213d3d498 100644 --- a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js +++ b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js @@ -48,6 +48,14 @@ CallbackHelpInit({ scope: $scope }); $scope.surveyTooltip = i18n._('Please save before adding a survey to this job template.'); + + MultiCredentialService.getCredentialTypes() + .then(({ data }) => { + $scope.multiCredential = { + credentialTypes: data.results, + selectedCredentials: [] + }; + }); } callback = function() { @@ -328,7 +336,6 @@ Rest.setUrl(defaultUrl); Rest.post(data) .then(({data}) => { - if (data.related && data.related.callback) { Alert('Callback URL', `Host callbacks are enabled for this template. The callback URL is: @@ -347,12 +354,6 @@ null, true); } - MultiCredentialService - .saveExtraCredentials({ - creds: $scope.credentialsToPost, - url: data.related.credentials - }); - var orgDefer = $q.defer(); var associationDefer = $q.defer(); Rest.setUrl(data.related.labels); @@ -441,7 +442,9 @@ }); } - saveCompleted(data.id); + MultiCredentialService + .saveRelated(data, $scope.multiCredential.selectedCredentials) + .then(() => saveCompleted(data.id)); }); }); }); diff --git a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js index 19c2964e3b..c7cc71ce4b 100644 --- a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js +++ b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js @@ -362,74 +362,37 @@ export default $scope.can_edit = jobTemplateData.summary_fields.user_capabilities.edit; - if($scope.job_template_obj.summary_fields.user_capabilities.edit) { - MultiCredentialService.loadCredentials(jobTemplateData) - .then(([selectedCredentials, credTypes, credTypeOptions, - credTags, credentialGetPermissionDenied]) => { - $scope.canGetAllRelatedResources = !projectGetPermissionDenied && !inventoryGetPermissionDenied && !credentialGetPermissionDenied ? true : false; - $scope.selectedCredentials = selectedCredentials; - $scope.credential_types = credTypes; - $scope.credentialTypeOptions = credTypeOptions; - $scope.credentialsToPost = credTags; - $scope.$emit('jobTemplateLoaded', master); - }); - } - else { - // if (jobTemplateData.summary_fields.credential) { - // $scope.selectedCredentials.machine = jobTemplateData.summary_fields.credential; - // } + const multiCredential = {}; + const credentialTypesPromise = MultiCredentialService.getCredentialTypes() + .then(({ data }) => { + multiCredential.credentialTypes = data.results; + }); + const multiCredentialPromises = [credentialTypesPromise]; - // if (jobTemplateData.summary_fields.vault_credential) { - // $scope.selectedCredentials.vault = jobTemplateData.summary_fields.vault_credential; - // } - - if (jobTemplateData.summary_fields.credentials) { - $scope.selectedCredentials.extra = jobTemplateData.summary_fields.credentials; - } - - MultiCredentialService.getCredentialTypes() - .then(({credential_types, credentialTypeOptions}) => { - let typesArray = Object.keys(credential_types).map(key => credential_types[key]); - let credTypeOptions = credentialTypeOptions; - - let machineAndVaultCreds = [], - extraCreds = []; - - //if($scope.selectedCredentials.machine) { - // machineAndVaultCreds.push($scope.selectedCredentials.machine); - //} - //if($scope.selectedCredentials.vault) { - // machineAndVaultCreds.push($scope.selectedCredentials.vault); - //} - - machineAndVaultCreds.map(cred => ({ - name: cred.name, - id: cred.id, - postType: cred.postType, - kind: typesArray - .filter(type => { - return cred.kind === type.kind || parseInt(cred.credential_type) === type.value; - })[0].name + ":" - })); - - if($scope.selectedCredentials.extra && $scope.selectedCredentials.extra.length > 0) { - extraCreds = extraCreds.concat($scope.selectedCredentials.extra).map(cred => ({ - name: cred.name, - id: cred.id, - postType: cred.postType, - kind: credTypeOptions - .filter(type => { - return parseInt(cred.credential_type_id) === type.value; - })[0].name + ":" - })); + if ($scope.can_edit) { + const selectedCredentialsPromise = MultiCredentialService + .getRelated(jobTemplateData, { permitted: [403] }) + .then(({ data, status }) => { + if (status === 403) { + $scope.canGetAllRelatedResources = false; + multiCredential.selectedCredentials = _.get(jobTemplateData, 'summary_fields.credentials'); + } else { + $scope.canGetAllRelatedResources = !projectGetPermissionDenied && !inventoryGetPermissionDenied; + multiCredential.selectedCredentials = data.results; } - - //$scope.credentialsToPost = machineAndVaultCreds.concat(extraCreds); - $scope.credentialsToPost = extraCreds; - - $scope.$emit('jobTemplateLoaded', master); }); + + multiCredentialPromises.push(selectedCredentialsPromise); + } else { + $scope.canGetAllRelatedResources = false; + multiCredential.selectedCredentials = _.get(jobTemplateData, 'summary_fields.credentials'); } + + $q.all(multiCredentialPromises) + .then(() => { + $scope.multiCredential = multiCredential; + $scope.$emit('jobTemplateLoaded', master); + }); }); if ($scope.removeChoicesReady) { @@ -522,10 +485,7 @@ export default } MultiCredentialService - .findChangedExtraCredentials({ - creds: $scope.credentialsToPost, - url: data.related.credentials - }); + .saveRelated(jobTemplateData, $scope.multiCredential.selectedCredentials); InstanceGroupsService.editInstanceGroups(instance_group_url, $scope.instance_groups) .catch(({data, status}) => { @@ -668,13 +628,24 @@ export default data.ask_credential_on_launch = $scope.ask_credential_on_launch ? $scope.ask_credential_on_launch : false; data.job_tags = (Array.isArray($scope.job_tags)) ? $scope.job_tags.join() : ""; data.skip_tags = (Array.isArray($scope.skip_tags)) ? $scope.skip_tags.join() : ""; - - // drop legacy 'credential' and 'vault_credential' keys from the update request as they will - // be provided to the related credentials endpoint by the template save success handler. - delete data.credential; - delete data.vault_credential; - - data.extra_vars = ToJSON($scope.parseType, $scope.variables, true); + if ($scope.selectedCredentials && $scope.selectedCredentials + .machine && $scope.selectedCredentials + .machine.id) { + data.credential = $scope.selectedCredentials + .machine.id; + } else { + data.credential = null; + } + if ($scope.selectedCredentials && $scope.selectedCredentials + .vault && $scope.selectedCredentials + .vault.id) { + data.vault_credential = $scope.selectedCredentials + .vault.id; + } else { + data.vault_credential = null; + } + data.extra_vars = ToJSON($scope.parseType, + $scope.variables, true); // We only want to set the survey_enabled flag to // true for this job template if a survey exists @@ -713,6 +684,10 @@ export default data.job_tags = (Array.isArray($scope.job_tags)) ? _.uniq($scope.job_tags).join() : ""; data.skip_tags = (Array.isArray($scope.skip_tags)) ? _.uniq($scope.skip_tags).join() : ""; + // drop legacy 'credential' and 'vault_credential' keys from the update request as they will + // be provided to the related credentials endpoint by the template save success handler. + delete data.credential; + delete data.vault_credential; Rest.setUrl(defaultUrl + $state.params.job_template_id); Rest.put(data) diff --git a/awx/ui/client/src/templates/job_templates/job-template.form.js b/awx/ui/client/src/templates/job_templates/job-template.form.js index 62921638d9..02eaeff943 100644 --- a/awx/ui/client/src/templates/job_templates/job-template.form.js +++ b/awx/ui/client/src/templates/job_templates/job-template.form.js @@ -124,9 +124,9 @@ function(NotificationsList, CompletedJobsList, i18n) { `, awPopOver: i18n._('Select credentials that allow Tower to access the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking "Prompt on launch" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check "Prompt on launch", the selected credential(s) become the defaults that can be updated at run time.'), diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js index b66448c336..511381ef77 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js @@ -1,272 +1,251 @@ -export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile', 'CreateSelect2', 'i18n', 'MultiCredentialService', 'credentialTypesLookup', - function(templateUrl, Rest, GetBasePath, GenerateList, $compile, CreateSelect2, i18n, MultiCredentialService, credentialTypesLookup) { +/************************************************* + * Copyright (c) 2018 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ +function MultiCredentialModal( + templateUrl, + generateList, + $compile, + CreateSelect2, + i18n, + CredentialList +) { + const templatePath = 'templates/job_templates/multi-credential/multi-credential-modal'; + const emptyListText = i18n._('No Credentials Matching This Type Have Been Created'); + + const list = _.cloneDeep(CredentialList); + const vaultList = _.cloneDeep(CredentialList); + + list.emptyListText = emptyListText; + + vaultList.emptyListText = emptyListText; + vaultList.fields.name.modalColumnClass = 'col-md-6'; + vaultList.fields.info = { + label: i18n._('Vault ID'), + ngBind: 'credential.inputs.vault_id', + key: false, + nosort: true, + modalColumnClass: 'col-md-6', + infoHeaderClass: '', + dataPlacement: 'top', + }; + + const listHtml = generateList.build({ mode: 'lookup', input_type: 'radio', list }); + const vaultHtml = generateList.build({ mode: 'lookup', input_type: 'checkbox', list: vaultList }); + return { + templateUrl: templateUrl(templatePath), restrict: 'E', + controllerAs: 'vm', + require: ['multiCredentialModal'], scope: { - credentialsToPost: '=', - credentials: '=', - selectedCredentials: '=' + credentialTypes: '=', + selectedCredentials: '=', }, - templateUrl: templateUrl('templates/job_templates/multi-credential/multi-credential-modal'), + link: (scope, element, attrs, controllers) => { + const compiledList = $compile(listHtml)(scope); + const compiledVaultList = $compile(vaultHtml)(scope); - link: function(scope, element) { - credentialTypesLookup() - .then(kinds => { - scope.credentialKinds = kinds; + const modalBodyElement = $('#multi-credential-modal-body'); + const modalElement = $('#multi-credential-modal'); - const machineIsReadOnly = _.get(scope.selectedCredentials, 'machine.readOnly'); - const vaultIsReadOnly = _.get(scope.selectedCredentials, 'vault.readOnly'); + scope.showModal = () => modalElement.modal('show'); + scope.hideModal = () => modalElement.modal('hide'); - if (!machineIsReadOnly) { - scope.credentialKind = `${kinds.Machine}`; - } else if (!vaultIsReadOnly) { - scope.credentialKind = `${kinds.Vault}`; - } else { - scope.credentialKind = `${kinds.Network}`; - } + scope.createList = () => modalBodyElement.append(compiledList); + scope.createVaultList = () => modalBodyElement.append(compiledVaultList); + scope.destroyList = () => modalBodyElement.empty(); - scope.showModal = function() { - scope.modalHidden = false; - $('#multi-credential-modal').modal('show'); - }; - - scope.hideModal = function() { - scope.modalHidden = true; - scope.credentialKind = kinds.Machine; - $('#multi-credential-modal').modal('hide'); - }; - - scope.generateCredentialList = function(inputType = 'radio', list = scope.list) { - console.log(inputType); - let html = GenerateList.build({ - list, - input_type: inputType, - mode: 'lookup' - }); - - $('#multi-credential-modal-body').append($compile(html)(scope)); - scope.listRendered = true; - }; - - scope.destroyCredentialList = () => { - $('#multi-credential-modal-body').empty(); - scope.listRendered = false; - } - - $('#multi-credential-modal').on('hidden.bs.modal', function () { - $('#multi-credential-modal').off('hidden.bs.modal'); - $(element).remove(); - }); - - CreateSelect2({ - element: `#multi-credential-kind-select`, - multiple: false, - placeholder: i18n._('Select a credential') - }); - - MultiCredentialService.getCredentialTypes() - .then(({credential_types, credentialTypeOptions}) => { - scope.credential_types = credential_types; - scope.credentialTypeOptions = credentialTypeOptions; - scope.allCredentialTypeOptions = _.cloneDeep(credentialTypeOptions); - - // We want to hide the machine dropdown option if a machine credential - // has already been selected and the user interacting with the form doesn't - // have the ability to change it. - for(let i=scope.credentialTypeOptions.length - 1; i >=0; i--) { - if((scope.selectedCredentials.machine && - scope.selectedCredentials.machine.credential_type_id === scope.credentialTypeOptions[i].value && - scope.selectedCredentials.machine.readOnly) || - (scope.selectedCredentials.vault && - scope.selectedCredentials.vault.credential_type_id === scope.credentialTypeOptions[i].value && - scope.selectedCredentials.vault.readOnly)) { - scope.credentialTypeOptions.splice(i, 1); - } - } - - scope.$emit('multiCredentialModalLinked'); - }); - }); - }, - - controller: ['$scope', 'CredentialList', 'i18n', 'QuerySet', - 'GetBasePath', function($scope, CredentialList, i18n, qs, - GetBasePath) { - let updateExtraCredentialsList = function() { - let extraCredIds = $scope.selectedCredentials.extra.map(cred => cred.id); - $scope.credentials.forEach(cred => { - cred.checked = (extraCredIds.indexOf(cred.id) > - 1) ? 1 : 0; - }); - - $scope.credTags = MultiCredentialService - .updateCredentialTags($scope.selectedCredentials, - $scope.allCredentialTypeOptions); - }; - - let uncheckAllCredentials = function() { - $scope.credentials.forEach(cred => { - cred.checked = 0; - }); - - $scope.credTags = MultiCredentialService - .updateCredentialTags($scope.selectedCredentials, - $scope.allCredentialTypeOptions); - }; - - const onCredentialKindChanged = (newValue, oldValue) => { - const newValueIsVault = (parseInt(newValue) === _.get($scope, 'credentialKinds.Vault')); - const oldValueIsVault = (parseInt(oldValue) === _.get($scope, 'credentialKinds.Vault')); - const isClosing = ((newValue !== oldValue) && $scope.modalHidden); - - if ((oldValueIsVault || newValueIsVault) && !isClosing) { - $scope.destroyCredentialList(); - } - - $scope.credential_queryset.page = 1; - $scope.credential_default_params.credential_type = parseInt($scope.credentialKind); - $scope.credential_queryset.credential_type = parseInt($scope.credentialKind); - - qs.search(GetBasePath('credentials'), $scope.credential_default_params) - .then(({ data }) => { - $scope.credential_dataset = data; - $scope.credentials = $scope.credential_dataset.results; - - if(!$scope.listRendered) { - if (newValueIsVault) { - $scope.generateCredentialList('checkbox', $scope.vaultList); - } else { - $scope.generateCredentialList(); - } - updateExtraCredentialsList(); - $scope.showModal(); - } - }); - }; - - let init = function() { - $scope.originalSelectedCredentials = _.cloneDeep($scope.selectedCredentials); - $scope.credential_dataset = []; - $scope.credentials = $scope.credentials || []; - $scope.listRendered = false; - - const credList = _.cloneDeep(CredentialList); - - credList.emptyListText = i18n._('No Credentials Matching This Type Have Been Created'); - - const vaultCredList = _.cloneDeep(credList); - - vaultCredList.fields.name.modalColumnClass = 'col-md-6'; - - vaultCredList.fields.info = { - label: i18n._('Vault ID'), - ngBind: 'credential.inputs.vault_id', - key: false, - nosort: true, - modalColumnClass: 'col-md-6', - infoHeaderClass: '', - dataPlacement: 'top' - }; - - $scope.list = credList; - $scope.vaultList = vaultCredList; - - $scope.credential_default_params = { - order_by: 'name', - page_size: 5 - }; - - $scope.credential_queryset = { - order_by: 'name', - page_size: 5 - }; - - $scope.$watch('credentialKind', onCredentialKindChanged); - - $scope.$watchCollection('credentials', updateExtraCredentialsList); - - $scope.$watchCollection('selectedCredentials.extra', () => { - if($scope.credentials && $scope.credentials.length > 0) { - if($scope.selectedCredentials.extra && - $scope.selectedCredentials.extra.length > 0) { - updateExtraCredentialsList(); - } else { - uncheckAllCredentials(); - } - } - }); - }; - - $scope.$on('multiCredentialModalLinked', function() { - init(); + modalElement.on('hidden.bs.modal', () => { + modalElement.off('hidden.bs.modal'); + $(element).remove(); }); - $scope.toggle_credential = id => { - // This is called only when a checkbox input is clicked directly. For clicks anywhere - // else on the row or direct radio button clicks, the toggle_row handler is called - // instead with a slightly different set of arguments. We normalize those arguments - // here and pass them through to the other handler so that the behavior is consistent. - const [credential] = $scope.credentials.filter(credential => credential.id === id); - return $scope.toggle_row(credential); - }; + CreateSelect2({ + placeholder: i18n._('Select a credential'), + element: '#multi-credential-kind-select', + multiple: false, + }); - $scope.toggle_row = function(credential) { - let rowDeselected = false; - for (let i = $scope.selectedCredentials.extra.length - 1; i >= 0; i--) { - if($scope.selectedCredentials.extra[i].id === credential.id) { - $scope.selectedCredentials.extra.splice(i, 1); - rowDeselected = true; - } else if(credential.credential_type === $scope.selectedCredentials.extra[i].credential_type) { - if (credential.credential_type !== $scope.credentialKinds.Vault) { - $scope.selectedCredentials.extra.splice(i, 1); - } else if($scope.selectedCredentials.extra[i].inputs.vault_id === credential.inputs.vault_id) { - // remove existing vault credentials if they have the same vault_id as a recently - // toggled vault credential - $scope.selectedCredentials.extra.splice(i, 1); - } - } - } - if(!rowDeselected) { - $scope.selectedCredentials.extra - .push(_.cloneDeep(credential)); - } - }; - - $scope.selectedCredentialsDirty = function() { - if ($scope.originalSelectedCredentials) { - return !($scope.originalSelectedCredentials.extra.length === 0) && - !_.isEqual($scope.selectedCredentials, - $scope.originalSelectedCredentials); - } else { - return false; - } - }; - - $scope.revertToDefaultCredentials = function() { - $scope.selectedCredentials = _.cloneDeep($scope.originalSelectedCredentials); - }; - - $scope.removeCredential = function(credToRemove) { - [$scope.selectedCredentials, - $scope.credTags] = MultiCredentialService - .removeCredential(credToRemove, - $scope.selectedCredentials, $scope.credTags); - - if ($scope.credentials - .filter(cred => cred.id === credToRemove).length) { - uncheckAllCredentials(); - } - }; - - $scope.cancelForm = function() { - $scope.selectedCredentials = $scope.originalSelectedCredentials; - $scope.credTags = $scope.credentialsToPost; - $scope.hideModal(); - }; - - $scope.saveForm = function() { - $scope.credentialsToPost = $scope.credTags; - $scope.hideModal(); - }; - }] + scope.list = list; + controllers[0].init(scope); + }, + controller: ['GetBasePath', 'QuerySet', 'MultiCredentialService', + multiCredentialModalController], }; -}]; +} + +function multiCredentialModalController(GetBasePath, qs, MultiCredentialService) { + const vm = this; + const { createTag, isReadOnly } = MultiCredentialService; + + const types = {}; + const unwatch = []; + + let scope; + + vm.init = _scope_ => { + scope = _scope_; + scope.modalSelectedCredentials = _.cloneDeep(scope.selectedCredentials); + + scope.credentialTypes.forEach(({ name, id }) => types[name] = id); + + scope.toggle_row = vm.toggle_row; + scope.toggle_credential = vm.toggle_credential; + + scope.credential_default_params = { order_by: 'name', page_size: 5 }; + scope.credential_queryset = { order_by: 'name', page_size: 5 }; + scope.credential_dataset = { results: [], count: 0 }; + scope.credentials = scope.credential_dataset.results; + + scope.credentialType = `${types.Vault}`; + scope.displayedCredentialTypes = scope.credentialTypes; + + const watchType = scope.$watch('credentialType', (newValue, oldValue) => { + if (newValue !== oldValue) { + fetchCredentials(parseInt(newValue)); + } + }); + scope.$watchCollection('modalSelectedCredentials', updateListView); + scope.$watchCollection('modalSelectedCredentials', updateTagView); + scope.$watchCollection('modalSelectedCredentials', updateDisplayedCredentialTypes); + scope.$watchCollection('credentials', updateListView); + + unwatch.push(watchType); + + fetchCredentials(parseInt(scope.credentialType)) + .then(scope.showModal); + }; + + function updateTagView () { + scope.tags = scope.modalSelectedCredentials + .map(c => createTag(c, scope.credentialTypes)); + } + + function updateListView () { + scope.credentials.forEach(credential => { + const index = scope.modalSelectedCredentials + .map(({ id }) => id) + .indexOf(credential.id); + + if (index > -1) { + credential.checked = 1; + } else { + credential.checked = 0; + } + }); + } + + function updateDisplayedCredentialTypes() { + const displayedCredentialTypes = _.cloneDeep(scope.credentialTypes); + + scope.modalSelectedCredentials.forEach(credential => { + const credentialTypeId = credential.credential_type || credential.credential_type_id; + + if(isReadOnly(credential) && credentialTypeId !== types.Vault) { + const index = displayedCredentialTypes + .map(t => t.id).indexOf(credential.credential_type); + + if (index > -1) { + displayedCredentialTypes.splice(index, 1); + } + } + }); + + scope.displayedCredentialTypes = displayedCredentialTypes; + } + + function fetchCredentials (credentialType) { + const endpoint = GetBasePath('credentials'); + + scope.credential_queryset.page = 1; + scope.credential_default_params.credential_type = credentialType; + scope.credential_queryset.credential_type = credentialType; + + return qs.search(endpoint, scope.credential_default_params) + .then(({ data }) => { + const results = data.results.filter(c => !isReadOnly(c)); + const readOnlyCount = data.results.length - results.length; + + data.results = results; + data.count = data.count - readOnlyCount; + + scope.credential_dataset = data; + scope.credentials = data.results; + + scope.destroyList(); + + if (credentialType === types.Vault) { + scope.createVaultList(); + } else { + scope.createList(); + } + }); + } + + vm.revertSelectedCredentials = () => { + scope.modalSelectedCredentials = _.cloneDeep(scope.selectedCredentials); + }; + + vm.removeCredential = id => { + const index = scope.modalSelectedCredentials.map(c => c.id).indexOf(id); + const isSelected = index > -1; + + if (isSelected) { + scope.modalSelectedCredentials.splice(index, 1); + return; + } + }; + + vm.toggle_credential = id => { + // This is called only when a checkbox input is clicked directly. Clicks anywhere else on + // the row or direct radio button clicks invoke the toggle_row handler instead with a + // different set of arguments. We normalize those arguments here and pass them through to + // the other function so that the behavior is consistent. + const credential = scope.credentials.find(c => c.id === id); + scope.toggle_row(credential); + }; + + vm.toggle_row = credential => { + const index = scope.modalSelectedCredentials.map(c => c.id).indexOf(credential.id); + const isSelected = index > -1; + + if (isSelected) { + scope.modalSelectedCredentials.splice(index, 1); + return; + } + + if (credential.credential_type === types.Vault) { + scope.modalSelectedCredentials = scope.modalSelectedCredentials + .filter(({ inputs }) => inputs.vault_id !== credential.inputs.vault_id) + .concat([credential]); + } else { + scope.modalSelectedCredentials = scope.modalSelectedCredentials + .filter(({ credential_type }) => credential_type !== credential.credential_type) + .concat([credential]); + } + }; + + vm.cancelForm = () => { + unwatch.forEach(cb => cb()); + scope.hideModal(); + }; + + vm.saveForm = () => { + scope.selectedCredentials = _.cloneDeep(scope.modalSelectedCredentials); + unwatch.forEach(cb => cb()); + scope.hideModal(); + }; +} + +MultiCredentialModal.$inject = [ + 'templateUrl', + 'generateList', + '$compile', + 'CreateSelect2', + 'i18n', + 'CredentialList', +]; + +export default MultiCredentialModal; diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html index 95d28195af..1e52f574fc 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html @@ -2,21 +2,19 @@