diff --git a/web/src/lib/components/TableButton.svelte b/web/src/lib/components/TableButton.svelte index e2aead4b22..844c4c0bf8 100644 --- a/web/src/lib/components/TableButton.svelte +++ b/web/src/lib/components/TableButton.svelte @@ -1,14 +1,15 @@ {#if action.$if?.() ?? true} - onAction(action)} /> + onAction(action)} /> {/if} diff --git a/web/src/lib/components/user-settings-page/user-api-key-list.svelte b/web/src/lib/components/user-settings-page/user-api-key-list.svelte index 6828efbb33..ec21ab9872 100644 --- a/web/src/lib/components/user-settings-page/user-api-key-list.svelte +++ b/web/src/lib/components/user-settings-page/user-api-key-list.svelte @@ -1,68 +1,47 @@ + +
- +
{#if keys.length > 0} @@ -79,6 +58,7 @@ {#each keys as key (key.id)} + {@const { Update, Delete } = getApiKeyActions($t, key)} @@ -91,22 +71,8 @@ >{new Date(key.createdAt).toLocaleDateString($locale, dateFormats.settings)} - handleUpdate(key)} - /> - handleDelete(key)} - /> + + {/each} diff --git a/web/src/lib/managers/event-manager.svelte.ts b/web/src/lib/managers/event-manager.svelte.ts index dff27ef4fd..6bc26220a7 100644 --- a/web/src/lib/managers/event-manager.svelte.ts +++ b/web/src/lib/managers/event-manager.svelte.ts @@ -2,6 +2,7 @@ import type { ThemeSetting } from '$lib/managers/theme-manager.svelte'; import type { ReleaseEvent } from '$lib/types'; import type { AlbumResponseDto, + ApiKeyResponseDto, LibraryResponseDto, LoginResponseDto, QueueResponseDto, @@ -19,6 +20,10 @@ export type Events = { LanguageChange: [{ name: string; code: string; rtl?: boolean }]; ThemeChange: [ThemeSetting]; + ApiKeyCreate: [ApiKeyResponseDto]; + ApiKeyUpdate: [ApiKeyResponseDto]; + ApiKeyDelete: [ApiKeyResponseDto]; + AssetReplace: [{ oldAssetId: string; newAssetId: string }]; AlbumDelete: [AlbumResponseDto]; diff --git a/web/src/lib/modals/ApiKeyCreateModal.svelte b/web/src/lib/modals/ApiKeyCreateModal.svelte index 72019eb58a..c5078ca3dc 100644 --- a/web/src/lib/modals/ApiKeyCreateModal.svelte +++ b/web/src/lib/modals/ApiKeyCreateModal.svelte @@ -1,12 +1,12 @@ - - -
-
- - - -
- - -
- - - - - - - -
+ +
+ + + +
+ +
diff --git a/web/src/lib/modals/ApiKeyUpdateModal.svelte b/web/src/lib/modals/ApiKeyUpdateModal.svelte index a380c72a06..afc55c5dea 100644 --- a/web/src/lib/modals/ApiKeyUpdateModal.svelte +++ b/web/src/lib/modals/ApiKeyUpdateModal.svelte @@ -1,69 +1,44 @@ - - -
-
- - - -
- - -
- - - - - - - -
+ +
+ + + +
+ +
diff --git a/web/src/lib/services/api-key.service.ts b/web/src/lib/services/api-key.service.ts new file mode 100644 index 0000000000..4833bafadc --- /dev/null +++ b/web/src/lib/services/api-key.service.ts @@ -0,0 +1,110 @@ +import { eventManager } from '$lib/managers/event-manager.svelte'; +import ApiKeyCreateModal from '$lib/modals/ApiKeyCreateModal.svelte'; +import ApiKeySecretModal from '$lib/modals/ApiKeySecretModal.svelte'; +import ApiKeyUpdateModal from '$lib/modals/ApiKeyUpdateModal.svelte'; +import { handleError } from '$lib/utils/handle-error'; +import { getFormatter } from '$lib/utils/i18n'; +import { + createApiKey, + deleteApiKey, + updateApiKey, + type ApiKeyCreateDto, + type ApiKeyResponseDto, + type ApiKeyUpdateDto, +} from '@immich/sdk'; +import { modalManager, toastManager, type ActionItem } from '@immich/ui'; +import { mdiPencilOutline, mdiPlus, mdiTrashCanOutline } from '@mdi/js'; +import type { MessageFormatter } from 'svelte-i18n'; + +export const getApiKeysActions = ($t: MessageFormatter) => { + const Create: ActionItem = { + title: $t('new_api_key'), + icon: mdiPlus, + onAction: () => modalManager.show(ApiKeyCreateModal, {}), + }; + + return { Create }; +}; + +export const getApiKeyActions = ($t: MessageFormatter, apiKey: ApiKeyResponseDto) => { + const Update: ActionItem = { + title: $t('edit_key'), + icon: mdiPencilOutline, + onAction: () => modalManager.show(ApiKeyUpdateModal, { apiKey }), + }; + + const Delete: ActionItem = { + title: $t('delete_key'), + icon: mdiTrashCanOutline, + onAction: () => handleDeleteApiKey(apiKey), + }; + + return { Update, Delete }; +}; + +export const handleCreateApiKey = async (dto: ApiKeyCreateDto) => { + const $t = await getFormatter(); + + try { + if (!dto.name) { + toastManager.warning($t('api_key_empty')); + return; + } + + if (dto.permissions.length === 0) { + toastManager.warning($t('permission_empty')); + return; + } + + const { apiKey, secret } = await createApiKey({ apiKeyCreateDto: dto }); + + eventManager.emit('ApiKeyCreate', apiKey); + + // no nested modal + void modalManager.show(ApiKeySecretModal, { secret }); + + return true; + } catch (error) { + handleError(error, $t('errors.unable_to_create_api_key')); + } +}; + +export const handleUpdateApiKey = async (apiKey: { id: string }, dto: ApiKeyUpdateDto) => { + const $t = await getFormatter(); + + if (!dto.name) { + toastManager.warning($t('api_key_empty')); + return; + } + + if (dto.permissions && dto.permissions.length === 0) { + toastManager.warning($t('permission_empty')); + return; + } + + try { + const response = await updateApiKey({ id: apiKey.id, apiKeyUpdateDto: dto }); + eventManager.emit('ApiKeyUpdate', response); + toastManager.success($t('saved_api_key')); + return true; + } catch (error) { + handleError(error, $t('errors.unable_to_save_api_key')); + } +}; + +export const handleDeleteApiKey = async (apiKey: ApiKeyResponseDto) => { + const $t = await getFormatter(); + + const confirmed = await modalManager.showDialog({ prompt: $t('delete_api_key_prompt') }); + if (!confirmed) { + return; + } + + try { + await deleteApiKey({ id: apiKey.id }); + eventManager.emit('ApiKeyDelete', apiKey); + toastManager.success($t('removed_api_key', { values: { name: apiKey.name } })); + } catch (error) { + handleError(error, $t('errors.unable_to_remove_api_key')); + } +};