feat: Ado 1296 spike credential setup in templates (#7786)
- Add a 'Setup template credentials' view to setup the credentials of a template before it is created
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useUIStore } from '@/stores';
|
||||
import { listenForCredentialChanges, useCredentialsStore } from '@/stores/credentials.store';
|
||||
import { assert } from '@/utils/assert';
|
||||
import CredentialsDropdown from './CredentialsDropdown.vue';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
|
||||
const props = defineProps({
|
||||
appName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
credentialType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
selectedCredentialId: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const $emit = defineEmits({
|
||||
credentialSelected: (_credentialId: string) => true,
|
||||
credentialDeselected: () => true,
|
||||
});
|
||||
|
||||
const uiStore = useUIStore();
|
||||
const credentialsStore = useCredentialsStore();
|
||||
const i18n = useI18n();
|
||||
|
||||
const availableCredentials = computed(() => {
|
||||
return credentialsStore.getCredentialsByType(props.credentialType);
|
||||
});
|
||||
|
||||
const credentialOptions = computed(() => {
|
||||
return availableCredentials.value.map((credential) => ({
|
||||
id: credential.id,
|
||||
name: credential.name,
|
||||
typeDisplayName: credentialsStore.getCredentialTypeByName(credential.type)?.displayName,
|
||||
}));
|
||||
});
|
||||
|
||||
const onCredentialSelected = (credentialId: string) => {
|
||||
$emit('credentialSelected', credentialId);
|
||||
};
|
||||
const createNewCredential = () => {
|
||||
uiStore.openNewCredential(props.credentialType, true);
|
||||
};
|
||||
const editCredential = () => {
|
||||
assert(props.selectedCredentialId);
|
||||
uiStore.openExistingCredential(props.selectedCredentialId);
|
||||
};
|
||||
|
||||
listenForCredentialChanges({
|
||||
store: credentialsStore,
|
||||
onCredentialCreated: (credential) => {
|
||||
// TODO: We should have a better way to detect if credential created was due to
|
||||
// user opening the credential modal from this component, as there might be
|
||||
// two CredentialPicker components on the same page with same credential type.
|
||||
if (credential.type !== props.credentialType) {
|
||||
return;
|
||||
}
|
||||
|
||||
$emit('credentialSelected', credential.id);
|
||||
},
|
||||
onCredentialDeleted: (deletedCredentialId) => {
|
||||
if (deletedCredentialId !== props.selectedCredentialId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const optionsWoDeleted = credentialOptions.value
|
||||
.map((credential) => credential.id)
|
||||
.filter((id) => id !== deletedCredentialId);
|
||||
if (optionsWoDeleted.length > 0) {
|
||||
$emit('credentialSelected', optionsWoDeleted[0]);
|
||||
} else {
|
||||
$emit('credentialDeselected');
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="credentialOptions.length > 0" :class="$style.dropdown">
|
||||
<CredentialsDropdown
|
||||
:credential-type="props.credentialType"
|
||||
:credential-options="credentialOptions"
|
||||
:selected-credential-id="props.selectedCredentialId"
|
||||
@credential-selected="onCredentialSelected"
|
||||
@new-credential="createNewCredential"
|
||||
/>
|
||||
|
||||
<n8n-icon-button
|
||||
icon="pen"
|
||||
type="secondary"
|
||||
:class="{
|
||||
[$style.edit]: true,
|
||||
[$style.invisible]: !props.selectedCredentialId,
|
||||
}"
|
||||
:title="i18n.baseText('nodeCredentials.updateCredential')"
|
||||
@click="editCredential()"
|
||||
data-test-id="credential-edit-button"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<n8n-button
|
||||
v-else
|
||||
:label="`Create new ${props.appName} credential`"
|
||||
@click="createNewCredential"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.dropdown {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.edit {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-width: 20px;
|
||||
margin-left: var(--spacing-2xs);
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { useI18n } from '@/composables';
|
||||
|
||||
export type CredentialOption = {
|
||||
id: string;
|
||||
name: string;
|
||||
typeDisplayName: string | undefined;
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
credentialOptions: {
|
||||
type: Array as PropType<CredentialOption[]>,
|
||||
required: true,
|
||||
},
|
||||
selectedCredentialId: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const $emit = defineEmits({
|
||||
credentialSelected: (_credentialId: string) => true,
|
||||
newCredential: () => true,
|
||||
});
|
||||
|
||||
const i18n = useI18n();
|
||||
|
||||
const NEW_CREDENTIALS_TEXT = `- ${i18n.baseText('nodeCredentials.createNew')} -`;
|
||||
|
||||
const onCredentialSelected = (credentialId: string) => {
|
||||
if (credentialId === NEW_CREDENTIALS_TEXT) {
|
||||
$emit('newCredential');
|
||||
} else {
|
||||
$emit('credentialSelected', credentialId);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n8n-select
|
||||
size="small"
|
||||
:modelValue="props.selectedCredentialId"
|
||||
@update:modelValue="onCredentialSelected"
|
||||
>
|
||||
<n8n-option
|
||||
v-for="item in props.credentialOptions"
|
||||
:data-test-id="`node-credentials-select-item-${item.id}`"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
>
|
||||
<div :class="[$style.credentialOption, 'mt-2xs mb-2xs']">
|
||||
<n8n-text bold>{{ item.name }}</n8n-text>
|
||||
<n8n-text size="small">{{ item.typeDisplayName }}</n8n-text>
|
||||
</div>
|
||||
</n8n-option>
|
||||
<n8n-option
|
||||
data-test-id="node-credentials-select-item-new"
|
||||
:key="NEW_CREDENTIALS_TEXT"
|
||||
:value="NEW_CREDENTIALS_TEXT"
|
||||
:label="NEW_CREDENTIALS_TEXT"
|
||||
>
|
||||
</n8n-option>
|
||||
</n8n-select>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.credentialOption {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
@@ -206,9 +206,7 @@ export default defineComponent({
|
||||
methods: {
|
||||
async initView(loadWorkflow: boolean): Promise<void> {
|
||||
if (loadWorkflow) {
|
||||
if (this.nodeTypesStore.allNodeTypes.length === 0) {
|
||||
await this.nodeTypesStore.getNodeTypes();
|
||||
}
|
||||
await this.nodeTypesStore.loadNodeTypesIfNotLoaded();
|
||||
await this.openWorkflow(this.$route.params.name);
|
||||
this.uiStore.nodeViewInitialized = false;
|
||||
if (this.workflowsStore.currentWorkflowExecutions.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user