mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
349 lines
9.0 KiB
Vue
349 lines
9.0 KiB
Vue
<script lang="ts" setup>
|
|
import Modal from '@/components/Modal.vue';
|
|
import { API_KEY_CREATE_OR_EDIT_MODAL_KEY } from '@/constants';
|
|
import { computed, onMounted, ref } from 'vue';
|
|
import { useUIStore } from '@/stores/ui.store';
|
|
import { createEventBus } from '@n8n/utils/event-bus';
|
|
import { useI18n } from '@/composables/useI18n';
|
|
import { useRootStore } from '@/stores/root.store';
|
|
import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
|
import { useApiKeysStore } from '@/stores/apiKeys.store';
|
|
import { useToast } from '@/composables/useToast';
|
|
import type { BaseTextKey } from '@/plugins/i18n';
|
|
import { N8nText } from '@n8n/design-system';
|
|
import { DateTime } from 'luxon';
|
|
import type { ApiKey, ApiKeyWithRawValue, CreateApiKeyRequestDto } from '@n8n/api-types';
|
|
|
|
const EXPIRATION_OPTIONS = {
|
|
'7_DAYS': 7,
|
|
'30_DAYS': 30,
|
|
'60_DAYS': 60,
|
|
'90_DAYS': 90,
|
|
CUSTOM: 1,
|
|
NO_EXPIRATION: 0,
|
|
};
|
|
|
|
const i18n = useI18n();
|
|
const { showError, showMessage } = useToast();
|
|
|
|
const uiStore = useUIStore();
|
|
const rootStore = useRootStore();
|
|
const { createApiKey, updateApiKey, apiKeysById } = useApiKeysStore();
|
|
const documentTitle = useDocumentTitle();
|
|
|
|
const label = ref('');
|
|
const expirationDaysFromNow = ref(EXPIRATION_OPTIONS['30_DAYS']);
|
|
const modalBus = createEventBus();
|
|
const newApiKey = ref<ApiKeyWithRawValue | null>(null);
|
|
const loading = ref(false);
|
|
const rawApiKey = ref('');
|
|
const customExpirationDate = ref('');
|
|
const showExpirationDateSelector = ref(false);
|
|
const apiKeyCreationDate = ref('');
|
|
|
|
const calculateExpirationDate = (daysFromNow: number) => {
|
|
const date = DateTime.now()
|
|
.setZone(rootStore.timezone)
|
|
.startOf('day')
|
|
.plus({ days: daysFromNow });
|
|
return date;
|
|
};
|
|
|
|
const getExpirationOptionLabel = (value: number) => {
|
|
if (EXPIRATION_OPTIONS.CUSTOM === value) {
|
|
return i18n.baseText('settings.api.view.modal.form.expiration.custom');
|
|
}
|
|
|
|
if (EXPIRATION_OPTIONS.NO_EXPIRATION === value) {
|
|
return i18n.baseText('settings.api.view.modal.form.expiration.none');
|
|
}
|
|
|
|
return i18n.baseText('settings.api.view.modal.form.expiration.days', {
|
|
interpolate: {
|
|
numberOfDays: value,
|
|
},
|
|
});
|
|
};
|
|
|
|
const expirationDate = ref(
|
|
calculateExpirationDate(expirationDaysFromNow.value).toFormat('ccc, MMM d yyyy'),
|
|
);
|
|
|
|
const inputRef = ref<HTMLTextAreaElement | null>(null);
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
mode?: 'new' | 'edit';
|
|
activeId?: string;
|
|
}>(),
|
|
{
|
|
mode: 'new',
|
|
activeId: '',
|
|
},
|
|
);
|
|
|
|
const allFormFieldsAreSet = computed(() => {
|
|
const isExpirationDateSet =
|
|
expirationDaysFromNow.value === EXPIRATION_OPTIONS.NO_EXPIRATION ||
|
|
(expirationDaysFromNow.value === EXPIRATION_OPTIONS.CUSTOM && customExpirationDate.value) ||
|
|
expirationDate.value;
|
|
|
|
return label.value && (props.mode === 'edit' ? true : isExpirationDateSet);
|
|
});
|
|
|
|
const isCustomDateInThePast = (date: Date) => Date.now() > date.getTime();
|
|
|
|
onMounted(() => {
|
|
documentTitle.set(i18n.baseText('settings.api'));
|
|
|
|
setTimeout(() => {
|
|
inputRef.value?.focus();
|
|
});
|
|
|
|
if (props.mode === 'edit') {
|
|
const apiKey = apiKeysById[props.activeId];
|
|
label.value = apiKey.label ?? '';
|
|
apiKeyCreationDate.value = getApiKeyCreationTime(apiKey);
|
|
}
|
|
});
|
|
|
|
function onInput(value: string): void {
|
|
label.value = value;
|
|
}
|
|
|
|
const getApiKeyCreationTime = (apiKey: ApiKey): string => {
|
|
const time = DateTime.fromMillis(Date.parse(apiKey.createdAt)).toFormat('ccc, MMM d yyyy');
|
|
return i18n.baseText('settings.api.creationTime', { interpolate: { time } });
|
|
};
|
|
|
|
async function onEdit() {
|
|
try {
|
|
loading.value = true;
|
|
await updateApiKey(props.activeId, { label: label.value });
|
|
showMessage({
|
|
type: 'success',
|
|
title: i18n.baseText('settings.api.update.toast'),
|
|
});
|
|
closeModal();
|
|
} catch (error) {
|
|
showError(error, i18n.baseText('settings.api.edit.error'));
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
function closeModal() {
|
|
uiStore.closeModal(API_KEY_CREATE_OR_EDIT_MODAL_KEY);
|
|
}
|
|
|
|
const onSave = async () => {
|
|
if (!label.value) {
|
|
return;
|
|
}
|
|
|
|
let expirationUnixTimestamp = null;
|
|
|
|
if (expirationDaysFromNow.value === EXPIRATION_OPTIONS.CUSTOM) {
|
|
expirationUnixTimestamp = parseInt(customExpirationDate.value, 10);
|
|
} else if (expirationDaysFromNow.value !== EXPIRATION_OPTIONS.NO_EXPIRATION) {
|
|
expirationUnixTimestamp = calculateExpirationDate(expirationDaysFromNow.value).toUnixInteger();
|
|
}
|
|
|
|
const payload: CreateApiKeyRequestDto = {
|
|
label: label.value,
|
|
expiresAt: expirationUnixTimestamp,
|
|
};
|
|
|
|
try {
|
|
loading.value = true;
|
|
newApiKey.value = await createApiKey(payload);
|
|
rawApiKey.value = newApiKey.value.rawApiKey;
|
|
|
|
showMessage({
|
|
type: 'success',
|
|
title: i18n.baseText('settings.api.create.toast'),
|
|
});
|
|
} catch (error) {
|
|
showError(error, i18n.baseText('settings.api.create.error'));
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
const modalTitle = computed(() => {
|
|
let path = 'edit';
|
|
if (props.mode === 'new') {
|
|
if (newApiKey.value) {
|
|
path = 'created';
|
|
} else {
|
|
path = 'create';
|
|
}
|
|
}
|
|
return i18n.baseText(`settings.api.view.modal.title.${path}` as BaseTextKey);
|
|
});
|
|
|
|
const onSelect = (value: number) => {
|
|
if (value === EXPIRATION_OPTIONS.CUSTOM) {
|
|
showExpirationDateSelector.value = true;
|
|
expirationDate.value = '';
|
|
return;
|
|
}
|
|
|
|
if (value !== EXPIRATION_OPTIONS.NO_EXPIRATION) {
|
|
expirationDate.value = calculateExpirationDate(value).toFormat('ccc, MMM d yyyy');
|
|
showExpirationDateSelector.value = false;
|
|
return;
|
|
}
|
|
|
|
expirationDate.value = '';
|
|
showExpirationDateSelector.value = false;
|
|
};
|
|
|
|
async function handleEnterKey(event: KeyboardEvent) {
|
|
if (event.key === 'Enter') {
|
|
if (props.mode === 'new') {
|
|
await onSave();
|
|
} else {
|
|
await onEdit();
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Modal
|
|
:title="modalTitle"
|
|
:event-bus="modalBus"
|
|
:name="API_KEY_CREATE_OR_EDIT_MODAL_KEY"
|
|
width="600px"
|
|
:lock-scroll="false"
|
|
:close-on-esc="true"
|
|
:close-on-click-outside="true"
|
|
:show-close="true"
|
|
>
|
|
<template #content>
|
|
<div @keyup.enter="handleEnterKey">
|
|
<n8n-card v-if="newApiKey" class="mb-4xs">
|
|
<CopyInput
|
|
:label="newApiKey.label"
|
|
:value="newApiKey.rawApiKey"
|
|
:redact-value="true"
|
|
:copy-button-text="i18n.baseText('generic.clickToCopy')"
|
|
:toast-title="i18n.baseText('settings.api.view.copy.toast')"
|
|
:hint="i18n.baseText('settings.api.view.copy')"
|
|
/>
|
|
</n8n-card>
|
|
|
|
<div v-else :class="$style.form">
|
|
<N8nInputLabel
|
|
:label="i18n.baseText('settings.api.view.modal.form.label')"
|
|
color="text-dark"
|
|
>
|
|
<N8nInput
|
|
ref="inputRef"
|
|
required
|
|
:model-value="label"
|
|
size="large"
|
|
type="text"
|
|
:placeholder="i18n.baseText('settings.api.view.modal.form.label.placeholder')"
|
|
:maxlength="50"
|
|
data-test-id="api-key-label"
|
|
@update:model-value="onInput"
|
|
/>
|
|
</N8nInputLabel>
|
|
<div v-if="mode === 'new'" :class="$style.expirationSection">
|
|
<N8nInputLabel
|
|
:label="i18n.baseText('settings.api.view.modal.form.expiration')"
|
|
color="text-dark"
|
|
>
|
|
<N8nSelect
|
|
v-model="expirationDaysFromNow"
|
|
size="large"
|
|
filterable
|
|
data-test-id="expiration-select"
|
|
@update:model-value="onSelect"
|
|
>
|
|
<N8nOption
|
|
v-for="key in Object.keys(EXPIRATION_OPTIONS)"
|
|
:key="key"
|
|
:value="EXPIRATION_OPTIONS[key as keyof typeof EXPIRATION_OPTIONS]"
|
|
:label="
|
|
getExpirationOptionLabel(
|
|
EXPIRATION_OPTIONS[key as keyof typeof EXPIRATION_OPTIONS],
|
|
)
|
|
"
|
|
>
|
|
</N8nOption>
|
|
</N8nSelect>
|
|
</N8nInputLabel>
|
|
<N8nText v-if="expirationDate" class="mb-xs">{{
|
|
i18n.baseText('settings.api.view.modal.form.expirationText', {
|
|
interpolate: { expirationDate },
|
|
})
|
|
}}</N8nText>
|
|
<el-date-picker
|
|
v-if="showExpirationDateSelector"
|
|
v-model="customExpirationDate"
|
|
type="date"
|
|
:teleported="false"
|
|
placeholder="yyyy-mm-dd"
|
|
value-format="X"
|
|
:disabled-date="isCustomDateInThePast"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #footer>
|
|
<div :class="$style.footer">
|
|
<N8nButton
|
|
v-if="mode === 'new' && !newApiKey"
|
|
:loading="loading"
|
|
:disabled="!allFormFieldsAreSet"
|
|
:label="i18n.baseText('settings.api.view.modal.save.button')"
|
|
@click="onSave"
|
|
/>
|
|
<N8nButton
|
|
v-else-if="mode === 'new'"
|
|
:label="i18n.baseText('settings.api.view.modal.done.button')"
|
|
@click="closeModal"
|
|
/>
|
|
<N8nButton
|
|
v-if="mode === 'edit'"
|
|
:disabled="!allFormFieldsAreSet"
|
|
:label="i18n.baseText('settings.api.view.modal.edit.button')"
|
|
@click="onEdit"
|
|
/>
|
|
<N8nText v-if="mode === 'edit'" size="small" color="text-light">{{
|
|
apiKeyCreationDate
|
|
}}</N8nText>
|
|
</div>
|
|
</template>
|
|
</Modal>
|
|
</template>
|
|
<style module lang="scss">
|
|
.notice {
|
|
margin: 0;
|
|
}
|
|
|
|
.form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-xs);
|
|
}
|
|
|
|
.expirationSection {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: flex-end;
|
|
gap: var(--spacing-xs);
|
|
}
|
|
|
|
.footer {
|
|
display: flex;
|
|
flex-direction: row-reverse;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
</style>
|