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 += ` { + $scope.multiCredential = { + credentialTypes: data.results, + selectedCredentials: [] + }; + }); } callback = function() { @@ -280,25 +288,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 @@ -340,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: @@ -359,12 +354,6 @@ null, true); } - MultiCredentialService - .saveExtraCredentials({ - creds: $scope.selectedCredentials.extra, - url: data.related.extra_credentials - }); - var orgDefer = $q.defer(); var associationDefer = $q.defer(); Rest.setUrl(data.related.labels); @@ -453,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 476223cd2e..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 { + const multiCredential = {}; + const credentialTypesPromise = MultiCredentialService.getCredentialTypes() + .then(({ data }) => { + multiCredential.credentialTypes = data.results; + }); + const multiCredentialPromises = [credentialTypesPromise]; - 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.extra_credentials) { - $scope.selectedCredentials.extra = jobTemplateData.summary_fields.extra_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.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; } - 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 + ":" - })); - } - - $scope.credentialsToPost = machineAndVaultCreds.concat(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.selectedCredentials.extra, - url: data.related.extra_credentials - }); + .saveRelated(jobTemplateData, $scope.multiCredential.selectedCredentials); InstanceGroupsService.editInstanceGroups(instance_group_url, $scope.instance_groups) .catch(({data, status}) => { @@ -724,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 891eb72dab..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,12 +124,11 @@ function(NotificationsList, CompletedJobsList, i18n) { `, - 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 6b9e8ba709..5e5baa5a91 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,311 +1,262 @@ -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'); - scope.credentialKind = scope.selectedCredentials.machine && scope.selectedCredentials.machine.readOnly ? (scope.selectedCredentials.vault && scope.selectedCredentials.vault.readOnly ? "" + kinds.Network : "" + kinds.Vault) : "" + kinds.Machine; + scope.showModal = () => modalElement.modal('show'); + scope.hideModal = () => modalElement.modal('hide'); - scope.showModal = function() { - $('#multi-credential-modal').modal('show'); - }; + scope.createList = () => modalBodyElement.append(compiledList); + scope.createVaultList = () => modalBodyElement.append(compiledVaultList); + scope.destroyList = () => modalBodyElement.empty(); - scope.destroyModal = function() { - scope.credentialKind = kinds.Machine; - $('#multi-credential-modal').modal('hide'); - }; - - scope.generateCredentialList = function() { - let html = GenerateList.build({ - list: scope.list, - input_type: 'radio', - mode: 'lookup' - }); - $('#multi-credential-modal-body') - .append($compile(html)(scope)); - }; - - $('#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 machine and vault dropdown options if a credential - // has already been selected for those types and the user interacting - // with the form doesn't have the ability to change them - 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 => { - if (cred.credential_type !== $scope.credentialKinds.Machine) { - cred.checked = (extraCredIds - .indexOf(cred.id) > - 1) ? 1 : 0; - } - }); - - $scope.credTags = MultiCredentialService - .updateCredentialTags($scope.selectedCredentials, - $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; - }); - - $scope.credTags = MultiCredentialService - .updateCredentialTags($scope.selectedCredentials, - $scope.allCredentialTypeOptions); - }; - - let init = function() { - $scope.originalSelectedCredentials = _.cloneDeep($scope - .selectedCredentials); - $scope.credential_dataset = []; - $scope.credentials = $scope.credentials || []; - $scope.listRendered = false; - - let credList = _.cloneDeep(CredentialList); - credList.emptyListText = i18n._('No Credentials Matching This Type Have Been Created'); - $scope.list = credList; - - $scope.credential_default_params = { - order_by: 'name', - page_size: 5 - }; - - $scope.credential_queryset = { - order_by: 'name', - 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.$watchCollection('selectedCredentials.extra', () => { - if($scope.credentials && $scope.credentials.length > 0) { - if($scope.selectedCredentials.extra && - $scope.selectedCredentials.extra.length > 0 && - parseInt($scope.credentialKind) !== $scope.credentialKinds.Machine) { - updateExtraCredentialsList(); - } 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.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(); + modalElement.on('hidden.bs.modal', () => { + modalElement.off('hidden.bs.modal'); + $(element).remove(); }); - $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 { - 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) { - $scope.selectedCredentials.extra.splice(i, 1); - } - } - if(!rowDeselected) { - $scope.selectedCredentials.extra - .push(_.cloneDeep(selectedRow)); - } - } - }; + CreateSelect2({ + placeholder: i18n._('Select a credential'), + element: '#multi-credential-kind-select', + multiple: false, + }); - $scope.selectedCredentialsDirty = function() { - if ($scope.originalSelectedCredentials) { - return !($scope.originalSelectedCredentials.machine === null && - $scope.originalSelectedCredentials.vault === null && - $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.destroyModal(); - }; - - $scope.saveForm = function() { - $scope.credentialsToPost = $scope.credTags; - $scope.destroyModal(); - }; - }] + 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 = getInitialCredentialType(); + 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 getInitialCredentialType () { + const selectedMachineCredential = scope.modalSelectedCredentials + .find(c => c.id === types.Machine); + + if (selectedMachineCredential && isReadOnly(selectedMachineCredential)) { + return `${types.Vault}`; + } + + return `${types.Machine}`; + } + + 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 f14933339e..93b6a26e67 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,75 +2,90 @@