n8n/packages/editor-ui/src/components/ExternalSecretsProviderCard.ee.vue
Alex Grozav ed927d34b2
feat: External Secrets storage for credentials (#6477)
Github issue / Community forum post (link here to close automatically):

---------

Co-authored-by: Romain Minaud <romain.minaud@gmail.com>
Co-authored-by: Valya Bullions <valya@n8n.io>
Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
Co-authored-by: Giulio Andreini <g.andreini@gmail.com>
Co-authored-by: Omar Ajoue <krynble@gmail.com>
2023-08-25 10:33:46 +02:00

186 lines
4.6 KiB
Vue

<script lang="ts" setup>
import type { PropType, Ref } from 'vue';
import type { ExternalSecretsProvider } from '@/Interface';
import ExternalSecretsProviderImage from '@/components/ExternalSecretsProviderImage.ee.vue';
import ExternalSecretsProviderConnectionSwitch from '@/components/ExternalSecretsProviderConnectionSwitch.ee.vue';
import { useExternalSecretsStore, useUIStore } from '@/stores';
import { useExternalSecretsProvider, useI18n, useToast } from '@/composables';
import { EXTERNAL_SECRETS_PROVIDER_MODAL_KEY } from '@/constants';
import { DateTime } from 'luxon';
import { computed, nextTick, onMounted, toRefs } from 'vue';
const props = defineProps({
provider: {
type: Object as PropType<ExternalSecretsProvider>,
required: true,
},
});
const externalSecretsStore = useExternalSecretsStore();
const i18n = useI18n();
const uiStore = useUIStore();
const toast = useToast();
const { provider } = toRefs(props) as Ref<ExternalSecretsProvider>;
const providerData = computed(() => provider.value.data);
const {
connectionState,
initialConnectionState,
normalizedProviderData,
testConnection,
setConnectionState,
} = useExternalSecretsProvider(provider, providerData);
const actionDropdownOptions = computed(() => [
{
value: 'setup',
label: i18n.baseText('settings.externalSecrets.card.actionDropdown.setup'),
},
...(props.provider.connected
? [
{
value: 'reload',
label: i18n.baseText('settings.externalSecrets.card.actionDropdown.reload'),
},
]
: []),
]);
const canConnect = computed(() => {
return props.provider.connected || Object.keys(props.provider.data).length > 0;
});
const formattedDate = computed((provider: ExternalSecretsProvider) => {
return DateTime.fromISO(props.provider.connectedAt!).toFormat('dd LLL yyyy');
});
onMounted(() => {
setConnectionState(props.provider.state);
});
async function onBeforeConnectionUpdate() {
if (props.provider.connected) {
return true;
}
await externalSecretsStore.getProvider(props.provider.name);
await nextTick();
const status = await testConnection();
return status !== 'error';
}
function openExternalSecretProvider() {
uiStore.openModalWithData({
name: EXTERNAL_SECRETS_PROVIDER_MODAL_KEY,
data: { name: props.provider.name },
});
}
async function reloadProvider() {
try {
await externalSecretsStore.reloadProvider(props.provider.name);
toast.showMessage({
title: i18n.baseText('settings.externalSecrets.card.reload.success.title'),
message: i18n.baseText('settings.externalSecrets.card.reload.success.description', {
interpolate: { provider: props.provider.displayName },
}),
type: 'success',
});
} catch (error) {
toast.showError(error, i18n.baseText('error'));
}
}
async function onActionDropdownClick(id: string) {
switch (id) {
case 'setup':
openExternalSecretProvider();
break;
case 'reload':
await reloadProvider();
break;
}
}
</script>
<template>
<n8n-card :class="$style.card">
<div :class="$style.cardBody">
<ExternalSecretsProviderImage :class="$style.cardImage" :provider="provider" />
<div :class="$style.cardContent">
<n8n-text bold>{{ provider.displayName }}</n8n-text>
<n8n-text color="text-light" size="small" v-if="provider.connected">
<span>
{{
i18n.baseText('settings.externalSecrets.card.secretsCount', {
interpolate: {
count: `${externalSecretsStore.secrets[provider.name]?.length}`,
},
})
}}
</span>
|
<span>
{{
i18n.baseText('settings.externalSecrets.card.connectedAt', {
interpolate: {
date: formattedDate,
},
})
}}
</span>
</n8n-text>
</div>
<div :class="$style.cardActions" v-if="canConnect">
<ExternalSecretsProviderConnectionSwitch
:provider="provider"
:beforeUpdate="onBeforeConnectionUpdate"
:disabled="connectionState === 'error' && !provider.connected"
/>
<n8n-action-toggle
class="ml-s"
theme="dark"
:actions="actionDropdownOptions"
@action="onActionDropdownClick"
/>
</div>
<n8n-button v-else type="tertiary" @click="openExternalSecretProvider()">
{{ i18n.baseText('settings.externalSecrets.card.setUp') }}
</n8n-button>
</div>
</n8n-card>
</template>
<style lang="scss" module>
.card {
position: relative;
margin-bottom: var(--spacing-2xs);
}
.cardImage {
width: 28px;
height: 28px;
}
.cardBody {
display: flex;
flex-direction: row;
align-items: center;
}
.cardContent {
display: flex;
flex-direction: column;
flex-grow: 1;
margin-left: var(--spacing-s);
}
.cardActions {
display: flex;
flex-direction: row;
align-items: center;
margin-left: var(--spacing-s);
}
</style>