diff --git a/e2e/src/web/specs/user-admin.e2e-spec.ts b/e2e/src/web/specs/user-admin.e2e-spec.ts index e2badb4fa2..7a2cd77177 100644 --- a/e2e/src/web/specs/user-admin.e2e-spec.ts +++ b/e2e/src/web/specs/user-admin.e2e-spec.ts @@ -54,7 +54,7 @@ test.describe('User Administration', () => { await page.getByRole('button', { name: 'Edit' }).click(); await expect(page.getByLabel('Admin User')).not.toBeChecked(); - await page.getByText('Admin User').click(); + await page.getByLabel('Admin User').click(); await expect(page.getByLabel('Admin User')).toBeChecked(); await page.getByRole('button', { name: 'Confirm' }).click(); @@ -83,7 +83,7 @@ test.describe('User Administration', () => { await page.getByRole('button', { name: 'Edit' }).click(); await expect(page.getByLabel('Admin User')).toBeChecked(); - await page.getByText('Admin User').click(); + await page.getByLabel('Admin User').click(); await expect(page.getByLabel('Admin User')).not.toBeChecked(); await page.getByRole('button', { name: 'Confirm' }).click(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9975a44d17..ff91f7b316 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -717,8 +717,8 @@ importers: specifier: file:../open-api/typescript-sdk version: link:../open-api/typescript-sdk '@immich/ui': - specifier: ^0.45.0 - version: 0.45.1(svelte@5.43.12) + specifier: ^0.49.1 + version: 0.49.1(svelte@5.43.12) '@mapbox/mapbox-gl-rtl-text': specifier: 0.2.3 version: 0.2.3(mapbox-gl@1.13.3) @@ -2983,8 +2983,8 @@ packages: peerDependencies: svelte: ^5.0.0 - '@immich/ui@0.45.1': - resolution: {integrity: sha512-LanpfRI7cJLXExRxaYd4xMRvq/SWZlHmBhSsw56l0aAI6Mltm+IcFMD6LF+jvSzGOSLGLcNxIAYsqqWAPmn8+g==} + '@immich/ui@0.49.1': + resolution: {integrity: sha512-E8x3iLnGRvkso1XeG3qZGPPjX8l8CoKcrTKxDvn59OjhnK0aZDs1Fv+Nq0lyOhSsH6qyV9vjDbLmhLje6D+thg==} peerDependencies: svelte: ^5.0.0 @@ -14708,7 +14708,7 @@ snapshots: dependencies: svelte: 5.43.12 - '@immich/ui@0.45.1(svelte@5.43.12)': + '@immich/ui@0.49.1(svelte@5.43.12)': dependencies: '@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.43.12) '@internationalized/date': 3.10.0 diff --git a/web/package.json b/web/package.json index b6d1b0c353..9cf1b6b981 100644 --- a/web/package.json +++ b/web/package.json @@ -28,7 +28,7 @@ "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/justified-layout-wasm": "^0.4.3", "@immich/sdk": "file:../open-api/typescript-sdk", - "@immich/ui": "^0.45.0", + "@immich/ui": "^0.49.1", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", "@photo-sphere-viewer/core": "^5.14.0", diff --git a/web/src/app.css b/web/src/app.css index f66743f736..bf7601f63b 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -76,14 +76,6 @@ --immich-dark-gray: 33 33 33; } - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: rgb(var(--immich-ui-default-border)); - } - button:not(:disabled), [role='button']:not(:disabled) { cursor: pointer; diff --git a/web/src/lib/components/ActionButton.svelte b/web/src/lib/components/ActionButton.svelte index 37bc09fb73..e0e7e1eff7 100644 --- a/web/src/lib/components/ActionButton.svelte +++ b/web/src/lib/components/ActionButton.svelte @@ -1,16 +1,14 @@ {#if action.$if?.() ?? true} - + onAction(action)} /> {/if} diff --git a/web/src/lib/components/HeaderButton.svelte b/web/src/lib/components/HeaderButton.svelte index 9021d2d1cb..c4189c06c0 100644 --- a/web/src/lib/components/HeaderButton.svelte +++ b/web/src/lib/components/HeaderButton.svelte @@ -1,18 +1,17 @@ {#if action.$if?.() ?? true} - {/if} diff --git a/web/src/lib/components/TableButton.svelte b/web/src/lib/components/TableButton.svelte index 4bd82e4dd9..e2aead4b22 100644 --- a/web/src/lib/components/TableButton.svelte +++ b/web/src/lib/components/TableButton.svelte @@ -1,16 +1,14 @@ {#if action.$if?.() ?? true} - + onAction(action)} /> {/if} diff --git a/web/src/lib/components/album-page/album-shared-link.svelte b/web/src/lib/components/album-page/album-shared-link.svelte index 1b6db6ff69..a9e3471241 100644 --- a/web/src/lib/components/album-page/album-shared-link.svelte +++ b/web/src/lib/components/album-page/album-shared-link.svelte @@ -32,7 +32,7 @@ .filter(Boolean) .join(' • '); - const SharedLinkActions = $derived(getSharedLinkActions($t, sharedLink)); + const { ViewQrCode, Copy } = $derived(getSharedLinkActions($t, sharedLink));
@@ -41,7 +41,7 @@ {getShareProperties()}
- - + +
diff --git a/web/src/lib/components/layouts/AdminPageLayout.svelte b/web/src/lib/components/layouts/AdminPageLayout.svelte index a74a6aee35..45d21c9139 100644 --- a/web/src/lib/components/layouts/AdminPageLayout.svelte +++ b/web/src/lib/components/layouts/AdminPageLayout.svelte @@ -4,16 +4,16 @@ import NavigationBar from '$lib/components/shared-components/navigation-bar/navigation-bar.svelte'; import AdminSidebar from '$lib/sidebars/AdminSidebar.svelte'; import { sidebarStore } from '$lib/stores/sidebar.svelte'; - import { AppShell, AppShellHeader, AppShellSidebar, Scrollable } from '@immich/ui'; + import { AppShell, AppShellHeader, AppShellSidebar, Scrollable, type BreadcrumbItem } from '@immich/ui'; import type { Snippet } from 'svelte'; type Props = { - title: string; + breadcrumbs: BreadcrumbItem[]; buttons?: Snippet; children?: Snippet; }; - let { title, buttons, children }: Props = $props(); + let { breadcrumbs, buttons, children }: Props = $props(); @@ -24,7 +24,7 @@ - + {@render children?.()} diff --git a/web/src/lib/components/layouts/TitleLayout.svelte b/web/src/lib/components/layouts/TitleLayout.svelte index 1beab45586..2d867bab2f 100644 --- a/web/src/lib/components/layouts/TitleLayout.svelte +++ b/web/src/lib/components/layouts/TitleLayout.svelte @@ -1,26 +1,20 @@
-
-
{title}
- {#if description} - {description} - {/if} -
+ {@render buttons?.()}
{@render children?.()} diff --git a/web/src/lib/components/sharedlinks-page/shared-link-card.svelte b/web/src/lib/components/sharedlinks-page/shared-link-card.svelte index b2c6cf296d..6de97e94f9 100644 --- a/web/src/lib/components/sharedlinks-page/shared-link-card.svelte +++ b/web/src/lib/components/sharedlinks-page/shared-link-card.svelte @@ -6,6 +6,7 @@ import { getSharedLinkActions } from '$lib/services/shared-link.service'; import { locale } from '$lib/stores/preferences.store'; import { SharedLinkType, type SharedLinkResponseDto } from '@immich/sdk'; + import { ContextMenuButton, MenuItemType } from '@immich/ui'; import { DateTime, type ToRelativeUnit } from 'luxon'; import { t } from 'svelte-i18n'; @@ -31,7 +32,7 @@ } }; - const SharedLinkActions = $derived(getSharedLinkActions($t, sharedLink)); + const { Edit, Copy, Delete } = $derived(getSharedLinkActions($t, sharedLink));
- +
diff --git a/web/src/lib/services/library.service.ts b/web/src/lib/services/library.service.ts index 415d6dae42..93cf836c82 100644 --- a/web/src/lib/services/library.service.ts +++ b/web/src/lib/services/library.service.ts @@ -7,7 +7,6 @@ import LibraryFolderAddModal from '$lib/modals/LibraryFolderAddModal.svelte'; import LibraryFolderEditModal from '$lib/modals/LibraryFolderEditModal.svelte'; import LibraryRenameModal from '$lib/modals/LibraryRenameModal.svelte'; import LibraryUserPickerModal from '$lib/modals/LibraryUserPickerModal.svelte'; -import type { ActionItem } from '$lib/types'; import { handleError } from '$lib/utils/handle-error'; import { getFormatter } from '$lib/utils/i18n'; import { @@ -20,7 +19,7 @@ import { updateLibrary, type LibraryResponseDto, } from '@immich/sdk'; -import { modalManager, toastManager } from '@immich/ui'; +import { modalManager, toastManager, type ActionItem } from '@immich/ui'; import { mdiPencilOutline, mdiPlusBoxOutline, mdiSync, mdiTrashCanOutline } from '@mdi/js'; import type { MessageFormatter } from 'svelte-i18n'; @@ -28,13 +27,13 @@ export const getLibrariesActions = ($t: MessageFormatter) => { const ScanAll: ActionItem = { title: $t('scan_all_libraries'), icon: mdiSync, - onSelect: () => void handleScanAllLibraries(), + onAction: () => void handleScanAllLibraries(), }; const Create: ActionItem = { title: $t('create_library'), icon: mdiPlusBoxOutline, - onSelect: () => void handleCreateLibrary(), + onAction: () => void handleCreateLibrary(), }; return { ScanAll, Create }; @@ -44,32 +43,32 @@ export const getLibraryActions = ($t: MessageFormatter, library: LibraryResponse const Rename: ActionItem = { icon: mdiPencilOutline, title: $t('rename'), - onSelect: () => void modalManager.show(LibraryRenameModal, { library }), + onAction: () => void modalManager.show(LibraryRenameModal, { library }), }; const Delete: ActionItem = { icon: mdiTrashCanOutline, title: $t('delete'), color: 'danger', - onSelect: () => void handleDeleteLibrary(library), + onAction: () => void handleDeleteLibrary(library), }; const AddFolder: ActionItem = { icon: mdiPlusBoxOutline, title: $t('add'), - onSelect: () => void modalManager.show(LibraryFolderAddModal, { library }), + onAction: () => void modalManager.show(LibraryFolderAddModal, { library }), }; const AddExclusionPattern: ActionItem = { icon: mdiPlusBoxOutline, title: $t('add'), - onSelect: () => void modalManager.show(LibraryExclusionPatternAddModal, { library }), + onAction: () => void modalManager.show(LibraryExclusionPatternAddModal, { library }), }; const Scan: ActionItem = { icon: mdiSync, title: $t('scan_library'), - onSelect: () => void handleScanLibrary(library), + onAction: () => void handleScanLibrary(library), }; return { Rename, Delete, AddFolder, AddExclusionPattern, Scan }; @@ -79,13 +78,13 @@ export const getLibraryFolderActions = ($t: MessageFormatter, library: LibraryRe const Edit: ActionItem = { icon: mdiPencilOutline, title: $t('edit'), - onSelect: () => void modalManager.show(LibraryFolderEditModal, { folder, library }), + onAction: () => void modalManager.show(LibraryFolderEditModal, { folder, library }), }; const Delete: ActionItem = { icon: mdiTrashCanOutline, title: $t('delete'), - onSelect: () => void handleDeleteLibraryFolder(library, folder), + onAction: () => void handleDeleteLibraryFolder(library, folder), }; return { Edit, Delete }; @@ -99,13 +98,13 @@ export const getLibraryExclusionPatternActions = ( const Edit: ActionItem = { icon: mdiPencilOutline, title: $t('edit'), - onSelect: () => void modalManager.show(LibraryExclusionPatternEditModal, { exclusionPattern, library }), + onAction: () => void modalManager.show(LibraryExclusionPatternEditModal, { exclusionPattern, library }), }; const Delete: ActionItem = { icon: mdiTrashCanOutline, title: $t('delete'), - onSelect: () => void handleDeleteExclusionPattern(library, exclusionPattern), + onAction: () => void handleDeleteExclusionPattern(library, exclusionPattern), }; return { Edit, Delete }; diff --git a/web/src/lib/services/shared-link.service.ts b/web/src/lib/services/shared-link.service.ts index 3ce6f4222d..ea7f158db8 100644 --- a/web/src/lib/services/shared-link.service.ts +++ b/web/src/lib/services/shared-link.service.ts @@ -16,48 +16,37 @@ import { type SharedLinkEditDto, type SharedLinkResponseDto, } from '@immich/sdk'; -import { MenuItemType, menuManager, modalManager, toastManager, type MenuItem } from '@immich/ui'; -import { mdiCircleEditOutline, mdiContentCopy, mdiDelete, mdiDotsVertical, mdiQrcode } from '@mdi/js'; +import { modalManager, toastManager, type ActionItem } from '@immich/ui'; +import { mdiCircleEditOutline, mdiContentCopy, mdiDelete, mdiQrcode } from '@mdi/js'; import type { MessageFormatter } from 'svelte-i18n'; export const getSharedLinkActions = ($t: MessageFormatter, sharedLink: SharedLinkResponseDto) => { - const Edit: MenuItem = { + const Edit: ActionItem = { title: $t('edit_link'), icon: mdiCircleEditOutline, - onSelect: () => void goto(`${AppRoute.SHARED_LINKS}/${sharedLink.id}`), + onAction: () => void goto(`${AppRoute.SHARED_LINKS}/${sharedLink.id}`), }; - const Delete: MenuItem = { + const Delete: ActionItem = { title: $t('delete_link'), icon: mdiDelete, color: 'danger', - onSelect: () => void handleDeleteSharedLink(sharedLink), + onAction: () => void handleDeleteSharedLink(sharedLink), }; - const Copy: MenuItem = { + const Copy: ActionItem = { title: $t('copy_link'), icon: mdiContentCopy, - onSelect: () => void copyToClipboard(asUrl(sharedLink)), + onAction: () => void copyToClipboard(asUrl(sharedLink)), }; - const ViewQrCode: MenuItem = { + const ViewQrCode: ActionItem = { title: $t('view_qr_code'), icon: mdiQrcode, - onSelect: () => void handleShowSharedLinkQrCode(sharedLink), + onAction: () => void handleShowSharedLinkQrCode(sharedLink), }; - const ContextMenu: MenuItem = { - title: $t('shared_link_options'), - icon: mdiDotsVertical, - onSelect: ({ event }) => - void menuManager.show({ - target: event.currentTarget as HTMLElement, - position: 'top-right', - items: [Edit, Copy, MenuItemType.Divider, Delete], - }), - }; - - return { Edit, Delete, Copy, ViewQrCode, ContextMenu }; + return { Edit, Delete, Copy, ViewQrCode }; }; const asUrl = (sharedLink: SharedLinkResponseDto) => { diff --git a/web/src/lib/services/system-config.service.ts b/web/src/lib/services/system-config.service.ts index b555c425ef..62034886b9 100644 --- a/web/src/lib/services/system-config.service.ts +++ b/web/src/lib/services/system-config.service.ts @@ -1,12 +1,11 @@ import { downloadManager } from '$lib/managers/download-manager.svelte'; import { eventManager } from '$lib/managers/event-manager.svelte'; -import type { ActionItem } from '$lib/types'; import { copyToClipboard } from '$lib/utils'; import { downloadBlob } from '$lib/utils/asset-utils'; import { handleError } from '$lib/utils/handle-error'; import { getFormatter } from '$lib/utils/i18n'; import { getConfig, updateConfig, type ServerFeaturesDto, type SystemConfigDto } from '@immich/sdk'; -import { toastManager } from '@immich/ui'; +import { toastManager, type ActionItem } from '@immich/ui'; import { mdiContentCopy, mdiDownload, mdiUpload } from '@mdi/js'; import { isEqual } from 'lodash-es'; import type { MessageFormatter } from 'svelte-i18n'; @@ -19,20 +18,20 @@ export const getSystemConfigActions = ( const CopyToClipboard: ActionItem = { title: $t('copy_to_clipboard'), icon: mdiContentCopy, - onSelect: () => void handleCopyToClipboard(config), + onAction: () => void handleCopyToClipboard(config), }; const Download: ActionItem = { title: $t('export_as_json'), icon: mdiDownload, - onSelect: () => handleDownloadConfig(config), + onAction: () => handleDownloadConfig(config), }; const Upload: ActionItem = { title: $t('import_from_json'), icon: mdiUpload, $if: () => !featureFlags.configFile, - onSelect: () => handleUploadConfig(), + onAction: () => handleUploadConfig(), }; return { CopyToClipboard, Download, Upload }; diff --git a/web/src/lib/services/user-admin.service.ts b/web/src/lib/services/user-admin.service.ts index 93b8800b11..b8a4c648c1 100644 --- a/web/src/lib/services/user-admin.service.ts +++ b/web/src/lib/services/user-admin.service.ts @@ -1,13 +1,11 @@ import { goto } from '$app/navigation'; import { eventManager } from '$lib/managers/event-manager.svelte'; -import { serverConfigManager } from '$lib/managers/server-config-manager.svelte'; import PasswordResetSuccessModal from '$lib/modals/PasswordResetSuccessModal.svelte'; import UserCreateModal from '$lib/modals/UserCreateModal.svelte'; import UserDeleteConfirmModal from '$lib/modals/UserDeleteConfirmModal.svelte'; import UserEditModal from '$lib/modals/UserEditModal.svelte'; import UserRestoreConfirmModal from '$lib/modals/UserRestoreConfirmModal.svelte'; import { user as authUser } from '$lib/stores/user.store'; -import type { ActionItem } from '$lib/types'; import { handleError } from '$lib/utils/handle-error'; import { getFormatter } from '$lib/utils/i18n'; import { @@ -21,45 +19,33 @@ import { type UserAdminResponseDto, type UserAdminUpdateDto, } from '@immich/sdk'; -import { MenuItemType, menuManager, modalManager, toastManager } from '@immich/ui'; +import { modalManager, toastManager, type ActionItem } from '@immich/ui'; import { mdiDeleteRestore, - mdiDotsVertical, - mdiEyeOutline, mdiLockReset, mdiLockSmart, mdiPencilOutline, mdiPlusBoxOutline, mdiTrashCanOutline, } from '@mdi/js'; -import { DateTime } from 'luxon'; import type { MessageFormatter } from 'svelte-i18n'; import { get } from 'svelte/store'; -const getDeleteDate = (deletedAt: string): Date => - DateTime.fromISO(deletedAt).plus({ days: serverConfigManager.value.userDeleteDelay }).toJSDate(); - export const getUserAdminsActions = ($t: MessageFormatter) => { const Create: ActionItem = { title: $t('create_user'), icon: mdiPlusBoxOutline, - onSelect: () => void modalManager.show(UserCreateModal, {}), + onAction: () => void modalManager.show(UserCreateModal, {}), }; return { Create }; }; export const getUserAdminActions = ($t: MessageFormatter, user: UserAdminResponseDto) => { - const View: ActionItem = { - icon: mdiEyeOutline, - title: $t('view'), - onSelect: () => void goto(`/admin/users/${user.id}`), - }; - const Update: ActionItem = { icon: mdiPencilOutline, title: $t('edit'), - onSelect: () => void modalManager.show(UserEditModal, { user }), + onAction: () => void modalManager.show(UserEditModal, { user }), }; const Delete: ActionItem = { @@ -67,7 +53,7 @@ export const getUserAdminActions = ($t: MessageFormatter, user: UserAdminRespons title: $t('delete'), color: 'danger', $if: () => get(authUser).id !== user.id && !user.deletedAt, - onSelect: () => void modalManager.show(UserDeleteConfirmModal, { user }), + onAction: () => void modalManager.show(UserDeleteConfirmModal, { user }), }; const Restore: ActionItem = { @@ -75,47 +61,23 @@ export const getUserAdminActions = ($t: MessageFormatter, user: UserAdminRespons title: $t('restore'), color: 'primary', $if: () => !!user.deletedAt && user.status === UserStatus.Deleted, - onSelect: () => void modalManager.show(UserRestoreConfirmModal, { user }), - props: { - title: $t('admin.user_restore_scheduled_removal', { - values: { date: getDeleteDate(user.deletedAt!) }, - }), - }, + onAction: () => void modalManager.show(UserRestoreConfirmModal, { user }), }; const ResetPassword: ActionItem = { icon: mdiLockReset, title: $t('reset_password'), $if: () => get(authUser).id !== user.id, - onSelect: () => void handleResetPasswordUserAdmin(user), + onAction: () => void handleResetPasswordUserAdmin(user), }; const ResetPinCode: ActionItem = { icon: mdiLockSmart, title: $t('reset_pin_code'), - onSelect: () => void handleResetPinCodeUserAdmin(user), + onAction: () => void handleResetPinCodeUserAdmin(user), }; - const ContextMenu: ActionItem = { - icon: mdiDotsVertical, - title: $t('actions'), - onSelect: ({ event }) => - void menuManager.show({ - target: event.currentTarget as HTMLElement, - position: 'top-right', - items: [ - View, - Update, - ResetPassword, - ResetPinCode, - get(authUser).id === user.id ? undefined : MenuItemType.Divider, - Restore, - Delete, - ].filter(Boolean), - }), - }; - - return { View, Update, Delete, Restore, ResetPassword, ResetPinCode, ContextMenu }; + return { Update, Delete, Restore, ResetPassword, ResetPinCode }; }; export const handleCreateUserAdmin = async (dto: UserAdminCreateDto) => { @@ -172,6 +134,10 @@ export const handleRestoreUserAdmin = async (user: UserAdminResponseDto) => { } }; +export const handleNavigateUserAdmin = async (user: UserAdminResponseDto) => { + await goto(`/admin/users/${user.id}`); +}; + // TODO move password reset server-side const generatePassword = (length: number = 16) => { let generatedPassword = ''; diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index 4e6e8a45f4..d95e7b7cf2 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -1,8 +1,4 @@ import type { ServerVersionResponseDto } from '@immich/sdk'; -import type { MenuItem } from '@immich/ui'; -import type { HTMLAttributes } from 'svelte/elements'; - -export type ActionItem = MenuItem & { props?: Omit, 'color'> }; export interface ReleaseEvent { isAvailable: boolean; diff --git a/web/src/routes/admin/jobs-status/+page.svelte b/web/src/routes/admin/jobs-status/+page.svelte index 808a5b57ca..6a3195f447 100644 --- a/web/src/routes/admin/jobs-status/+page.svelte +++ b/web/src/routes/admin/jobs-status/+page.svelte @@ -58,7 +58,7 @@ }); - + {#snippet buttons()} {#if pausedJobs.length > 0} diff --git a/web/src/routes/admin/library-management/+page.svelte b/web/src/routes/admin/library-management/+page.svelte index 37153d5003..6aa2b3007a 100644 --- a/web/src/routes/admin/library-management/+page.svelte +++ b/web/src/routes/admin/library-management/+page.svelte @@ -58,7 +58,7 @@ onLibraryDelete={handleDeleteLibrary} /> - + {#snippet buttons()}
{#if libraries.length > 0} diff --git a/web/src/routes/admin/library-management/[id]/+page.svelte b/web/src/routes/admin/library-management/[id]/+page.svelte index c6fffbbd95..32367e78a8 100644 --- a/web/src/routes/admin/library-management/[id]/+page.svelte +++ b/web/src/routes/admin/library-management/[id]/+page.svelte @@ -39,7 +39,12 @@ onLibraryDelete={({ id }) => id === library.id && goto(AppRoute.ADMIN_LIBRARY_MANAGEMENT)} /> - + {#snippet buttons()}
diff --git a/web/src/routes/admin/server-status/+page.svelte b/web/src/routes/admin/server-status/+page.svelte index e33a792322..31b193d952 100644 --- a/web/src/routes/admin/server-status/+page.svelte +++ b/web/src/routes/admin/server-status/+page.svelte @@ -24,7 +24,7 @@ }); - +
diff --git a/web/src/routes/admin/system-settings/+page.svelte b/web/src/routes/admin/system-settings/+page.svelte index 7fb7559be7..71035b90ea 100644 --- a/web/src/routes/admin/system-settings/+page.svelte +++ b/web/src/routes/admin/system-settings/+page.svelte @@ -215,7 +215,7 @@ ); - + {#snippet buttons()}