diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts index 38933aea85..6f7c0d8e1e 100644 --- a/web/src/lib/constants.ts +++ b/web/src/lib/constants.ts @@ -1,3 +1,5 @@ +export const UUID_REGEX = /^[\dA-Fa-f]{8}(?:\b-[\dA-Fa-f]{4}){3}\b-[\dA-Fa-f]{12}$/; + export enum AssetAction { ARCHIVE = 'archive', UNARCHIVE = 'unarchive', @@ -20,7 +22,9 @@ export enum AssetAction { export enum AppRoute { ADMIN_USERS = '/admin/users', - ADMIN_LIBRARY_MANAGEMENT = '/admin/library-management', + ADMIN_USERS_NEW = '/admin/users/new', + ADMIN_LIBRARIES = '/admin/library-management', + ADMIN_LIBRARIES_NEW = '/admin/library-management/new', ADMIN_SETTINGS = '/admin/system-settings', ADMIN_STATS = '/admin/server-status', ADMIN_QUEUES = '/admin/queues', diff --git a/web/src/lib/modals/LibraryRenameModal.svelte b/web/src/lib/modals/LibraryRenameModal.svelte deleted file mode 100644 index 0a7d675b11..0000000000 --- a/web/src/lib/modals/LibraryRenameModal.svelte +++ /dev/null @@ -1,41 +0,0 @@ - - - - -
- - - -
-
- - - - - - - -
diff --git a/web/src/lib/services/library.service.ts b/web/src/lib/services/library.service.ts index a7f2ba25e7..62371e2512 100644 --- a/web/src/lib/services/library.service.ts +++ b/web/src/lib/services/library.service.ts @@ -1,12 +1,10 @@ import { goto } from '$app/navigation'; import { AppRoute } from '$lib/constants'; import { eventManager } from '$lib/managers/event-manager.svelte'; -import LibraryCreateModal from '$lib/modals/LibraryCreateModal.svelte'; import LibraryExclusionPatternAddModal from '$lib/modals/LibraryExclusionPatternAddModal.svelte'; import LibraryExclusionPatternEditModal from '$lib/modals/LibraryExclusionPatternEditModal.svelte'; import LibraryFolderAddModal from '$lib/modals/LibraryFolderAddModal.svelte'; import LibraryFolderEditModal from '$lib/modals/LibraryFolderEditModal.svelte'; -import LibraryRenameModal from '$lib/modals/LibraryRenameModal.svelte'; import { handleError } from '$lib/utils/handle-error'; import { getFormatter } from '$lib/utils/i18n'; import { @@ -19,6 +17,7 @@ import { updateLibrary, type CreateLibraryDto, type LibraryResponseDto, + type UpdateLibraryDto, } from '@immich/sdk'; import { modalManager, toastManager, type ActionItem } from '@immich/ui'; import { mdiPencilOutline, mdiPlusBoxOutline, mdiSync, mdiTrashCanOutline } from '@mdi/js'; @@ -38,7 +37,7 @@ export const getLibrariesActions = ($t: MessageFormatter, libraries: LibraryResp title: $t('create_library'), type: $t('command'), icon: mdiPlusBoxOutline, - onAction: () => handleShowLibraryCreateModal(), + onAction: () => goto(AppRoute.ADMIN_LIBRARIES_NEW), shortcuts: { shift: true, key: 'n' }, }; @@ -46,11 +45,11 @@ export const getLibrariesActions = ($t: MessageFormatter, libraries: LibraryResp }; export const getLibraryActions = ($t: MessageFormatter, library: LibraryResponseDto) => { - const Rename: ActionItem = { + const Edit: ActionItem = { icon: mdiPencilOutline, type: $t('command'), - title: $t('rename'), - onAction: () => modalManager.show(LibraryRenameModal, { library }), + title: $t('edit'), + onAction: () => goto(`${AppRoute.ADMIN_LIBRARIES}/${library.id}/edit`), shortcuts: { key: 'r' }, }; @@ -85,7 +84,7 @@ export const getLibraryActions = ($t: MessageFormatter, library: LibraryResponse shortcuts: { shift: true, key: 'r' }, }; - return { Rename, Delete, AddFolder, AddExclusionPattern, Scan }; + return { Edit, Delete, AddFolder, AddExclusionPattern, Scan }; }; export const getLibraryFolderActions = ($t: MessageFormatter, library: LibraryResponseDto, folder: string) => { @@ -150,7 +149,7 @@ const handleScanLibrary = async (library: LibraryResponseDto) => { }; export const handleViewLibrary = async (library: LibraryResponseDto) => { - await goto(`${AppRoute.ADMIN_LIBRARY_MANAGEMENT}/${library.id}`); + await goto(`${AppRoute.ADMIN_LIBRARIES}/${library.id}`); }; export const handleCreateLibrary = async (dto: CreateLibraryDto) => { @@ -160,33 +159,24 @@ export const handleCreateLibrary = async (dto: CreateLibraryDto) => { const library = await createLibrary({ createLibraryDto: dto }); eventManager.emit('LibraryCreate', library); toastManager.success($t('admin.library_created', { values: { library: library.name } })); - return true; + return library; } catch (error) { handleError(error, $t('errors.unable_to_create_library')); - return false; } }; -export const handleRenameLibrary = async (library: { id: string }, name?: string) => { +export const handleUpdateLibrary = async (library: LibraryResponseDto, dto: UpdateLibraryDto) => { const $t = await getFormatter(); - if (!name) { - return false; - } - try { - const updatedLibrary = await updateLibrary({ - id: library.id, - updateLibraryDto: { name }, - }); + const updatedLibrary = await updateLibrary({ id: library.id, updateLibraryDto: dto }); eventManager.emit('LibraryUpdate', updatedLibrary); toastManager.success($t('admin.library_updated')); + return true; } catch (error) { handleError(error, $t('errors.unable_to_update_library')); return false; } - - return true; }; const handleDeleteLibrary = async (library: LibraryResponseDto) => { @@ -357,7 +347,3 @@ const handleDeleteExclusionPattern = async (library: LibraryResponseDto, exclusi handleError(error, $t('errors.unable_to_update_library')); } }; - -export const handleShowLibraryCreateModal = async () => { - await modalManager.show(LibraryCreateModal, {}); -}; diff --git a/web/src/lib/services/user-admin.service.ts b/web/src/lib/services/user-admin.service.ts index 997a43fc7f..5fd4ff99fd 100644 --- a/web/src/lib/services/user-admin.service.ts +++ b/web/src/lib/services/user-admin.service.ts @@ -1,10 +1,9 @@ import { goto } from '$app/navigation'; +import { AppRoute } from '$lib/constants'; 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 { HeaderButtonActionItem } from '$lib/types'; @@ -39,7 +38,7 @@ export const getUserAdminsActions = ($t: MessageFormatter) => { title: $t('create_user'), type: $t('command'), icon: mdiPlusBoxOutline, - onAction: () => modalManager.show(UserCreateModal, {}), + onAction: () => goto(AppRoute.ADMIN_USERS_NEW), shortcuts: { shift: true, key: 'n' }, }; @@ -50,7 +49,7 @@ export const getUserAdminActions = ($t: MessageFormatter, user: UserAdminRespons const Update: ActionItem = { icon: mdiPencilOutline, title: $t('edit'), - onAction: () => modalManager.show(UserEditModal, { user }), + onAction: () => goto(`${AppRoute.ADMIN_USERS}/${user.id}/edit`), }; const Delete: ActionItem = { @@ -103,7 +102,7 @@ export const handleCreateUserAdmin = async (dto: UserAdminCreateDto) => { const response = await createUserAdmin({ userAdminCreateDto: dto }); eventManager.emit('UserAdminCreate', response); toastManager.success(); - return true; + return response; } catch (error) { handleError(error, $t('errors.unable_to_create_user')); } diff --git a/web/src/lib/sidebars/AdminSidebar.svelte b/web/src/lib/sidebars/AdminSidebar.svelte index 919c072527..fa660d7e2f 100644 --- a/web/src/lib/sidebars/AdminSidebar.svelte +++ b/web/src/lib/sidebars/AdminSidebar.svelte @@ -9,7 +9,7 @@
- + diff --git a/web/src/params/id.ts b/web/src/params/id.ts index 6b16a651d1..b7e93be6ae 100644 --- a/web/src/params/id.ts +++ b/web/src/params/id.ts @@ -1,6 +1,7 @@ +import { UUID_REGEX } from '$lib/constants'; import type { ParamMatcher } from '@sveltejs/kit'; /* Returns true if the given param matches UUID format */ export const match: ParamMatcher = (param: string) => { - return /^[\dA-Fa-f]{8}(?:\b-[\dA-Fa-f]{4}){3}\b-[\dA-Fa-f]{12}$/.test(param); + return UUID_REGEX.test(param); }; diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 77a3d402b2..3a1d4f49f9 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -166,7 +166,7 @@ title: $t('external_libraries'), description: $t('admin.external_libraries_page_description'), icon: mdiBookshelf, - onAction: () => goto(AppRoute.ADMIN_LIBRARY_MANAGEMENT), + onAction: () => goto(AppRoute.ADMIN_LIBRARIES), }, { title: $t('server_stats'), diff --git a/web/src/routes/admin/library-management/+page.svelte b/web/src/routes/admin/library-management/(list)/+layout.svelte similarity index 90% rename from web/src/routes/admin/library-management/+page.svelte rename to web/src/routes/admin/library-management/(list)/+layout.svelte index e61b4433be..e741dbd610 100644 --- a/web/src/routes/admin/library-management/+page.svelte +++ b/web/src/routes/admin/library-management/(list)/+layout.svelte @@ -4,27 +4,29 @@ import OnEvents from '$lib/components/OnEvents.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import { AppRoute } from '$lib/constants'; - import { getLibrariesActions, handleShowLibraryCreateModal, handleViewLibrary } from '$lib/services/library.service'; + import { getLibrariesActions, handleViewLibrary } from '$lib/services/library.service'; import { locale } from '$lib/stores/preferences.store'; import { getBytesWithUnit } from '$lib/utils/byte-units'; import { getLibrary, getLibraryStatistics, type LibraryResponseDto } from '@immich/sdk'; import { Button, CommandPaletteContext } from '@immich/ui'; + import type { Snippet } from 'svelte'; import { t } from 'svelte-i18n'; import { fade } from 'svelte/transition'; - import type { PageData } from './$types'; + import type { LayoutData } from './$types'; type Props = { - data: PageData; + children?: Snippet; + data: LayoutData; }; - let { data }: Props = $props(); + let { children, data }: Props = $props(); let libraries = $state(data.libraries); let statistics = $state(data.statistics); let owners = $state(data.owners); const onLibraryCreate = async (library: LibraryResponseDto) => { - await goto(`${AppRoute.ADMIN_LIBRARY_MANAGEMENT}/${library.id}`); + await goto(`${AppRoute.ADMIN_LIBRARIES}/${library.id}`); }; const onLibraryUpdate = async (library: LibraryResponseDto) => { @@ -100,10 +102,12 @@ {:else} goto(AppRoute.ADMIN_LIBRARIES_NEW)} class="mt-10 mx-auto" /> {/if} + + {@render children?.()}
diff --git a/web/src/routes/admin/library-management/+page.ts b/web/src/routes/admin/library-management/(list)/+layout.ts similarity index 93% rename from web/src/routes/admin/library-management/+page.ts rename to web/src/routes/admin/library-management/(list)/+layout.ts index aa892f81ed..aee777a9e8 100644 --- a/web/src/routes/admin/library-management/+page.ts +++ b/web/src/routes/admin/library-management/(list)/+layout.ts @@ -1,7 +1,7 @@ import { authenticate, requestServerInfo } from '$lib/utils/auth'; import { getFormatter } from '$lib/utils/i18n'; import { getAllLibraries, getLibraryStatistics, getUserAdmin, searchUsersAdmin } from '@immich/sdk'; -import type { PageLoad } from './$types'; +import type { LayoutLoad } from './$types'; export const load = (async ({ url }) => { await authenticate(url, { admin: true }); @@ -26,4 +26,4 @@ export const load = (async ({ url }) => { title: $t('external_libraries'), }, }; -}) satisfies PageLoad; +}) satisfies LayoutLoad; diff --git a/web/src/routes/admin/library-management/(list)/+page.svelte b/web/src/routes/admin/library-management/(list)/+page.svelte new file mode 100644 index 0000000000..e69de29bb2 diff --git a/web/src/lib/modals/LibraryCreateModal.svelte b/web/src/routes/admin/library-management/(list)/new/+page.svelte similarity index 77% rename from web/src/lib/modals/LibraryCreateModal.svelte rename to web/src/routes/admin/library-management/(list)/new/+page.svelte index d2d7f62c02..b236b83ce8 100644 --- a/web/src/lib/modals/LibraryCreateModal.svelte +++ b/web/src/routes/admin/library-management/(list)/new/+page.svelte @@ -1,5 +1,7 @@ diff --git a/web/src/routes/admin/library-management/(list)/new/+page.ts b/web/src/routes/admin/library-management/(list)/new/+page.ts new file mode 100644 index 0000000000..da3756bcf6 --- /dev/null +++ b/web/src/routes/admin/library-management/(list)/new/+page.ts @@ -0,0 +1,14 @@ +import { authenticate } from '$lib/utils/auth'; +import { getFormatter } from '$lib/utils/i18n'; +import type { PageLoad } from './$types'; + +export const load = (async ({ url }) => { + await authenticate(url, { admin: true }); + const $t = await getFormatter(); + + return { + meta: { + title: $t('external_libraries'), + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/admin/library-management/[id]/+layout.svelte b/web/src/routes/admin/library-management/[id]/+layout.svelte new file mode 100644 index 0000000000..62b4891459 --- /dev/null +++ b/web/src/routes/admin/library-management/[id]/+layout.svelte @@ -0,0 +1,118 @@ + + + + + + + + +
+ {library.name} +
+ + + +
+ + + {#if library.importPaths.length === 0} + modalManager.show(LibraryFolderAddModal, { library })} + /> + {:else} + + + {#each library.importPaths as folder (folder)} + {@const { Edit, Delete } = getLibraryFolderActions($t, library, folder)} + + + + + {/each} + +
+ {folder} + + + +
+ {/if} +
+ + + + + {#each library.exclusionPatterns as exclusionPattern (exclusionPattern)} + {@const { Edit, Delete } = getLibraryExclusionPatternActions($t, library, exclusionPattern)} + + + + + {/each} + +
+ {exclusionPattern} + + + +
+
+
+ {@render children?.()} +
+
diff --git a/web/src/routes/admin/library-management/[id]/+layout.ts b/web/src/routes/admin/library-management/[id]/+layout.ts new file mode 100644 index 0000000000..d465d92c45 --- /dev/null +++ b/web/src/routes/admin/library-management/[id]/+layout.ts @@ -0,0 +1,29 @@ +import { AppRoute } from '$lib/constants'; +import { authenticate } from '$lib/utils/auth'; +import { getFormatter } from '$lib/utils/i18n'; +import { getLibrary, getLibraryStatistics, type LibraryResponseDto } from '@immich/sdk'; +import { redirect } from '@sveltejs/kit'; +import type { LayoutLoad } from './$types'; + +export const load = (async ({ params: { id }, url }) => { + await authenticate(url, { admin: true }); + + let library: LibraryResponseDto; + + try { + library = await getLibrary({ id }); + } catch { + redirect(302, AppRoute.ADMIN_LIBRARIES); + } + + const statistics = await getLibraryStatistics({ id }); + const $t = await getFormatter(); + + return { + library, + statistics, + meta: { + title: $t('admin.library_details'), + }, + }; +}) satisfies LayoutLoad; diff --git a/web/src/routes/admin/library-management/[id]/+page.svelte b/web/src/routes/admin/library-management/[id]/+page.svelte index 6bffbe39aa..e69de29bb2 100644 --- a/web/src/routes/admin/library-management/[id]/+page.svelte +++ b/web/src/routes/admin/library-management/[id]/+page.svelte @@ -1,105 +0,0 @@ - - - (library = newLibrary)} - onLibraryDelete={({ id }) => id === library.id && goto(AppRoute.ADMIN_LIBRARY_MANAGEMENT)} -/> - - - - - -
- {library.name} -
- - - -
- - - {#if library.importPaths.length === 0} - modalManager.show(LibraryFolderAddModal, { library })} - /> - {:else} - - - {#each library.importPaths as folder (folder)} - {@const { Edit, Delete } = getLibraryFolderActions($t, library, folder)} - - - - - {/each} - -
- {folder} - - - -
- {/if} -
- - - - - {#each library.exclusionPatterns as exclusionPattern (exclusionPattern)} - {@const { Edit, Delete } = getLibraryExclusionPatternActions($t, library, exclusionPattern)} - - - - - {/each} - -
- {exclusionPattern} - - - -
-
-
-
-
diff --git a/web/src/routes/admin/library-management/[id]/+page.ts b/web/src/routes/admin/library-management/[id]/+page.ts index 77ce1eb1c8..10f0f33e11 100644 --- a/web/src/routes/admin/library-management/[id]/+page.ts +++ b/web/src/routes/admin/library-management/[id]/+page.ts @@ -1,26 +1,13 @@ -import { AppRoute } from '$lib/constants'; import { authenticate } from '$lib/utils/auth'; import { getFormatter } from '$lib/utils/i18n'; -import { getLibrary, getLibraryStatistics, type LibraryResponseDto } from '@immich/sdk'; -import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const load = (async ({ params: { id }, url }) => { +export const load = (async ({ url }) => { await authenticate(url, { admin: true }); - let library: LibraryResponseDto; - try { - library = await getLibrary({ id }); - } catch { - redirect(302, AppRoute.ADMIN_LIBRARY_MANAGEMENT); - } - - const statistics = await getLibraryStatistics({ id }); const $t = await getFormatter(); return { - library, - statistics, meta: { title: $t('admin.library_details'), }, diff --git a/web/src/routes/admin/library-management/[id]/edit/+page.svelte b/web/src/routes/admin/library-management/[id]/edit/+page.svelte new file mode 100644 index 0000000000..06e0af13e6 --- /dev/null +++ b/web/src/routes/admin/library-management/[id]/edit/+page.svelte @@ -0,0 +1,35 @@ + + + + + + + diff --git a/web/src/routes/admin/library-management/[id]/edit/+page.ts b/web/src/routes/admin/library-management/[id]/edit/+page.ts new file mode 100644 index 0000000000..10f0f33e11 --- /dev/null +++ b/web/src/routes/admin/library-management/[id]/edit/+page.ts @@ -0,0 +1,15 @@ +import { authenticate } from '$lib/utils/auth'; +import { getFormatter } from '$lib/utils/i18n'; +import type { PageLoad } from './$types'; + +export const load = (async ({ url }) => { + await authenticate(url, { admin: true }); + + const $t = await getFormatter(); + + return { + meta: { + title: $t('admin.library_details'), + }, + }; +}) satisfies PageLoad; diff --git a/web/src/routes/admin/users/(list)/+layout.svelte b/web/src/routes/admin/users/(list)/+layout.svelte new file mode 100644 index 0000000000..a8c281690d --- /dev/null +++ b/web/src/routes/admin/users/(list)/+layout.svelte @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + {#each users as user (user.id)} + + + + + + + {/each} + +
{$t('email')}
+ {user.email} + + +
+ + {@render children?.()} +
+
diff --git a/web/src/routes/admin/users/+page.ts b/web/src/routes/admin/users/(list)/+layout.ts similarity index 72% rename from web/src/routes/admin/users/+page.ts rename to web/src/routes/admin/users/(list)/+layout.ts index 521f8573e1..cccc7e647a 100644 --- a/web/src/routes/admin/users/+page.ts +++ b/web/src/routes/admin/users/(list)/+layout.ts @@ -1,18 +1,18 @@ import { authenticate, requestServerInfo } from '$lib/utils/auth'; import { getFormatter } from '$lib/utils/i18n'; import { searchUsersAdmin } from '@immich/sdk'; -import type { PageLoad } from './$types'; +import type { LayoutLoad } from './$types'; export const load = (async ({ url }) => { await authenticate(url, { admin: true }); await requestServerInfo(); - const allUsers = await searchUsersAdmin({ withDeleted: true }); + const users = await searchUsersAdmin({ withDeleted: true }); const $t = await getFormatter(); return { - allUsers, + users, meta: { title: $t('admin.user_management'), }, }; -}) satisfies PageLoad; +}) satisfies LayoutLoad; diff --git a/web/src/routes/admin/users/(list)/+page.svelte b/web/src/routes/admin/users/(list)/+page.svelte new file mode 100644 index 0000000000..e69de29bb2 diff --git a/web/src/lib/modals/UserCreateModal.svelte b/web/src/routes/admin/users/(list)/new/+page.svelte similarity index 92% rename from web/src/lib/modals/UserCreateModal.svelte rename to web/src/routes/admin/users/(list)/new/+page.svelte index 7dd0449119..d4f753197a 100644 --- a/web/src/lib/modals/UserCreateModal.svelte +++ b/web/src/routes/admin/users/(list)/new/+page.svelte @@ -1,4 +1,6 @@ - - - - - - -
-
- - - - - - - - - - {#each allUsers as user (user.id)} - - - - - - - {/each} - -
{$t('email')}
- {user.email} - - -
-
-
-
diff --git a/web/src/routes/admin/users/[id]/+layout.svelte b/web/src/routes/admin/users/[id]/+layout.svelte new file mode 100644 index 0000000000..d3fe5fdcaf --- /dev/null +++ b/web/src/routes/admin/users/[id]/+layout.svelte @@ -0,0 +1,222 @@ + + + + + + + +
+ + {#if user.deletedAt} + + {/if} + +
+
+
+ + {user.name} +
+ {#if user.isAdmin} +
+ {$t('admin.admin_user')} +
+ {/if} +
+
+
+ + + +
+
+ + + +
+ {$t('name')} + {user.name} +
+
+ {$t('email')} + {user.email} +
+
+ {$t('created_at')} + {userCreatedAtDateAndTime} +
+
+ {$t('updated_at')} + {userUpdatedAtDateAndTime} +
+
+ {$t('id')} + {user.id} +
+
+
+ + + + + + + + + + + + + + + + + {#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0} + + {$t('storage_usage', { + values: { + used: getByteUnitString(usedBytes, $locale, 3), + available: getByteUnitString(availableBytes, $locale, 3), + }, + })} + + {:else} + + + {$t('unlimited')} + + {/if} + + {#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0} +
+

{$t('storage')}

+
+
+
+
+ {/if} +
+ + + + {#each userSessions as session (session.id)} + + {:else} + {$t('no_devices')} + {/each} + + +
+ + {@render children?.()} +
+
+
diff --git a/web/src/routes/admin/users/[id]/+layout.ts b/web/src/routes/admin/users/[id]/+layout.ts new file mode 100644 index 0000000000..32c41b0aca --- /dev/null +++ b/web/src/routes/admin/users/[id]/+layout.ts @@ -0,0 +1,38 @@ +import { AppRoute, UUID_REGEX } from '$lib/constants'; +import { authenticate, requestServerInfo } from '$lib/utils/auth'; +import { getFormatter } from '$lib/utils/i18n'; +import { getUserPreferencesAdmin, getUserSessionsAdmin, getUserStatisticsAdmin, searchUsersAdmin } from '@immich/sdk'; +import { redirect } from '@sveltejs/kit'; +import type { LayoutLoad } from './$types'; + +export const load = (async ({ params, url }) => { + await authenticate(url, { admin: true }); + await requestServerInfo(); + + if (!UUID_REGEX.test(params.id)) { + redirect(302, AppRoute.ADMIN_USERS); + } + + const [user] = await searchUsersAdmin({ id: params.id, withDeleted: true }).catch(() => []); + if (!user) { + redirect(302, AppRoute.ADMIN_USERS); + } + + const [userPreferences, userStatistics, userSessions] = await Promise.all([ + getUserPreferencesAdmin({ id: user.id }), + getUserStatisticsAdmin({ id: user.id }), + getUserSessionsAdmin({ id: user.id }), + ]); + + const $t = await getFormatter(); + + return { + user, + userPreferences, + userStatistics, + userSessions, + meta: { + title: $t('admin.user_details'), + }, + }; +}) satisfies LayoutLoad; diff --git a/web/src/routes/admin/users/[id]/+page.svelte b/web/src/routes/admin/users/[id]/+page.svelte index c41afba97c..e69de29bb2 100644 --- a/web/src/routes/admin/users/[id]/+page.svelte +++ b/web/src/routes/admin/users/[id]/+page.svelte @@ -1,218 +0,0 @@ - - - - - - - -
- - {#if user.deletedAt} - - {/if} - -
-
-
- - {user.name} -
- {#if user.isAdmin} -
- {$t('admin.admin_user')} -
- {/if} -
-
-
- - - -
-
- - - -
- {$t('name')} - {user.name} -
-
- {$t('email')} - {user.email} -
-
- {$t('created_at')} - {userCreatedAtDateAndTime} -
-
- {$t('updated_at')} - {userUpdatedAtDateAndTime} -
-
- {$t('id')} - {user.id} -
-
-
- - - - - - - - - - - - - - - - - {#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0} - - {$t('storage_usage', { - values: { - used: getByteUnitString(usedBytes, $locale, 3), - available: getByteUnitString(availableBytes, $locale, 3), - }, - })} - - {:else} - - - {$t('unlimited')} - - {/if} - - {#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0} -
-

{$t('storage')}

-
-
-
-
- {/if} -
- - - - {#each userSessions as session (session.id)} - - {:else} - {$t('no_devices')} - {/each} - - -
-
-
-
diff --git a/web/src/routes/admin/users/[id]/+page.ts b/web/src/routes/admin/users/[id]/+page.ts index bfc5bcefa9..775f9662d2 100644 --- a/web/src/routes/admin/users/[id]/+page.ts +++ b/web/src/routes/admin/users/[id]/+page.ts @@ -1,31 +1,12 @@ -import { AppRoute } from '$lib/constants'; -import { authenticate, requestServerInfo } from '$lib/utils/auth'; +import { authenticate } from '$lib/utils/auth'; import { getFormatter } from '$lib/utils/i18n'; -import { getUserPreferencesAdmin, getUserSessionsAdmin, getUserStatisticsAdmin, searchUsersAdmin } from '@immich/sdk'; -import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; -export const load = (async ({ params, url }) => { +export const load = (async ({ url }) => { await authenticate(url, { admin: true }); - await requestServerInfo(); - const [user] = await searchUsersAdmin({ id: params.id, withDeleted: true }).catch(() => []); - if (!user) { - redirect(302, AppRoute.ADMIN_USERS); - } - - const [userPreferences, userStatistics, userSessions] = await Promise.all([ - getUserPreferencesAdmin({ id: user.id }), - getUserStatisticsAdmin({ id: user.id }), - getUserSessionsAdmin({ id: user.id }), - ]); - const $t = await getFormatter(); return { - user, - userPreferences, - userStatistics, - userSessions, meta: { title: $t('admin.user_details'), }, diff --git a/web/src/lib/modals/UserEditModal.svelte b/web/src/routes/admin/users/[id]/edit/+page.svelte similarity index 84% rename from web/src/lib/modals/UserEditModal.svelte rename to web/src/routes/admin/users/[id]/edit/+page.svelte index 4b4878e46d..ea3bd827df 100644 --- a/web/src/lib/modals/UserEditModal.svelte +++ b/web/src/routes/admin/users/[id]/edit/+page.svelte @@ -1,10 +1,10 @@ diff --git a/web/src/routes/admin/users/[id]/edit/+page.ts b/web/src/routes/admin/users/[id]/edit/+page.ts new file mode 100644 index 0000000000..64420e91da --- /dev/null +++ b/web/src/routes/admin/users/[id]/edit/+page.ts @@ -0,0 +1,15 @@ +import { authenticate } from '$lib/utils/auth'; +import { getFormatter } from '$lib/utils/i18n'; +import type { PageLoad } from './$types'; + +export const load = (async ({ url }) => { + await authenticate(url, { admin: true }); + + const $t = await getFormatter(); + + return { + meta: { + title: $t('admin.user_details'), + }, + }; +}) satisfies PageLoad;