<script lang="ts"> import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import { dialogController } from '$lib/components/shared-components/dialog/dialog'; import { modalManager } from '$lib/managers/modal-manager.svelte'; import ApiKeyModal from '$lib/modals/ApiKeyModal.svelte'; import ApiKeySecretModal from '$lib/modals/ApiKeySecretModal.svelte'; import { locale } from '$lib/stores/preferences.store'; import { createApiKey, deleteApiKey, getApiKeys, Permission, updateApiKey, type ApiKeyResponseDto, } from '@immich/sdk'; import { Button } from '@immich/ui'; import { mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; import { fade } from 'svelte/transition'; import { handleError } from '../../utils/handle-error'; import { notificationController, NotificationType } from '../shared-components/notification/notification'; interface Props { keys: ApiKeyResponseDto[]; } let { keys = $bindable() }: Props = $props(); const format: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric', year: 'numeric', }; async function refreshKeys() { keys = await getApiKeys(); } const handleCreate = async () => { const result = await modalManager.open(ApiKeyModal, { title: $t('new_api_key'), apiKey: { name: 'API Key' }, submitText: $t('create'), }); if (!result) { return; } try { const { secret } = await createApiKey({ apiKeyCreateDto: { name: result.name, permissions: [Permission.All], }, }); await modalManager.open(ApiKeySecretModal, { secret }); } catch (error) { handleError(error, $t('errors.unable_to_create_api_key')); } finally { await refreshKeys(); } }; const handleUpdate = async (key: ApiKeyResponseDto) => { const result = await modalManager.open(ApiKeyModal, { title: $t('api_key'), submitText: $t('save'), apiKey: key, }); if (!result) { return; } try { await updateApiKey({ id: key.id, apiKeyUpdateDto: { name: result.name } }); notificationController.show({ message: $t('saved_api_key'), type: NotificationType.Info, }); } catch (error) { handleError(error, $t('errors.unable_to_save_api_key')); } finally { await refreshKeys(); } }; const handleDelete = async (key: ApiKeyResponseDto) => { const isConfirmed = await dialogController.show({ prompt: $t('delete_api_key_prompt') }); if (!isConfirmed) { return; } try { await deleteApiKey({ id: key.id }); notificationController.show({ message: $t('removed_api_key', { values: { name: key.name } }), type: NotificationType.Info, }); } catch (error) { handleError(error, $t('errors.unable_to_remove_api_key')); } finally { await refreshKeys(); } }; </script> <section class="my-4"> <div class="flex flex-col gap-2" in:fade={{ duration: 500 }}> <div class="mb-2 flex justify-end"> <Button shape="round" size="small" onclick={() => handleCreate()}>{$t('new_api_key')}</Button> </div> {#if keys.length > 0} <table class="w-full text-start"> <thead class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary" > <tr class="flex w-full place-items-center"> <th class="w-1/3 text-center text-sm font-medium">{$t('name')}</th> <th class="w-1/3 text-center text-sm font-medium">{$t('created')}</th> <th class="w-1/3 text-center text-sm font-medium">{$t('action')}</th> </tr> </thead> <tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray"> {#each keys as key, index (key.id)} <tr class={`flex h-[80px] w-full place-items-center text-center dark:text-immich-dark-fg ${ index % 2 == 0 ? 'bg-subtle' : 'bg-immich-bg dark:bg-immich-dark-gray/50' }`} > <td class="w-1/3 text-ellipsis px-4 text-sm">{key.name}</td> <td class="w-1/3 text-ellipsis px-4 text-sm" >{new Date(key.createdAt).toLocaleDateString($locale, format)} </td> <td class="flex flex-row flex-wrap justify-center gap-x-2 gap-y-1 w-1/3"> <CircleIconButton color="primary" icon={mdiPencilOutline} title={$t('edit_key')} size="16" onclick={() => handleUpdate(key)} /> <CircleIconButton color="primary" icon={mdiTrashCanOutline} title={$t('delete_key')} size="16" onclick={() => handleDelete(key)} /> </td> </tr> {/each} </tbody> </table> {/if} </div> </section>