diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts
index 73f272d7fc..1d1606868b 100644
--- a/web/src/lib/constants.ts
+++ b/web/src/lib/constants.ts
@@ -23,7 +23,8 @@ export enum AssetAction {
export enum AppRoute {
ADMIN_USERS = '/admin/users',
ADMIN_USERS_NEW = '/admin/users/new',
- ADMIN_LIBRARY_MANAGEMENT = '/admin/library-management',
+ 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 557d01bfb0..5fd4ff99fd 100644
--- a/web/src/lib/services/user-admin.service.ts
+++ b/web/src/lib/services/user-admin.service.ts
@@ -4,7 +4,6 @@ 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 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';
@@ -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 = {
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/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 92%
rename from web/src/routes/admin/library-management/+page.svelte
rename to web/src/routes/admin/library-management/(list)/+layout.svelte
index e61b4433be..3c2c1f9d57 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';
type Props = {
+ children?: Snippet;
data: PageData;
};
- 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 100%
rename from web/src/routes/admin/library-management/+page.ts
rename to web/src/routes/admin/library-management/(list)/+layout.ts
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..3e7dd05e14
--- /dev/null
+++ b/web/src/routes/admin/library-management/[id]/+layout.svelte
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
{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)}
+
+
+ {folder}
+ |
+
+
+
+ |
+
+ {/each}
+
+
+ {/if}
+
+
+
+
+
+ {#each library.exclusionPatterns as exclusionPattern (exclusionPattern)}
+ {@const { Edit, Delete } = getLibraryExclusionPatternActions($t, library, exclusionPattern)}
+
+
+ {exclusionPattern}
+ |
+
+
+
+ |
+
+ {/each}
+
+
+
+
+ {@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..242053bcb1
--- /dev/null
+++ b/web/src/routes/admin/library-management/[id]/+layout.ts
@@ -0,0 +1,30 @@
+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 });
+ console.log(`Fetched latest library: ${library.name}`);
+ } 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)}
-
-
- {folder}
- |
-
-
-
- |
-
- {/each}
-
-
- {/if}
-
-
-
-
-
- {#each library.exclusionPatterns as exclusionPattern (exclusionPattern)}
- {@const { Edit, Delete } = getLibraryExclusionPatternActions($t, library, exclusionPattern)}
-
-
- {exclusionPattern}
- |
-
-
-
- |
-
- {/each}
-
-
-
-
-
-
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..091eb421ab
--- /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
index 8f25307e15..bec0e2c316 100644
--- a/web/src/routes/admin/users/(list)/+layout.svelte
+++ b/web/src/routes/admin/users/(list)/+layout.svelte
@@ -18,19 +18,19 @@
let { children, data }: Props = $props();
- let allUsers: UserAdminResponseDto[] = $state(data.allUsers);
+ let users: UserAdminResponseDto[] = $state(data.users);
const onUpdate = async (user: UserAdminResponseDto) => {
- const index = allUsers.findIndex(({ id }) => id === user.id);
+ const index = users.findIndex(({ id }) => id === user.id);
if (index === -1) {
- allUsers = await searchUsersAdmin({ withDeleted: true });
+ users = await searchUsersAdmin({ withDeleted: true });
} else {
- allUsers[index] = user;
+ users[index] = user;
}
};
const onUserAdminDeleted = ({ id: userId }: { id: string }) => {
- allUsers = allUsers.filter(({ id }) => id !== userId);
+ users = users.filter(({ id }) => id !== userId);
};
const { Create } = $derived(getUserAdminsActions($t));
@@ -62,7 +62,7 @@
- {#each allUsers as user (user.id)}
+ {#each users as user (user.id)}
+ import { goto } from '$app/navigation';
+ import AdminCard from '$lib/components/AdminCard.svelte';
+ import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
+ import OnEvents from '$lib/components/OnEvents.svelte';
+ import ServerStatisticsCard from '$lib/components/server-statistics/ServerStatisticsCard.svelte';
+ import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
+ import DeviceCard from '$lib/components/user-settings-page/device-card.svelte';
+ import FeatureSetting from '$lib/components/users/FeatureSetting.svelte';
+ import { AppRoute } from '$lib/constants';
+ import { getUserAdminActions } from '$lib/services/user-admin.service';
+ import { locale } from '$lib/stores/preferences.store';
+ import { createDateFormatter, findLocale } from '$lib/utils';
+ import { getBytesWithUnit } from '$lib/utils/byte-units';
+ import { type UserAdminResponseDto } from '@immich/sdk';
+ import {
+ Alert,
+ Badge,
+ Code,
+ CommandPaletteContext,
+ Container,
+ getByteUnitString,
+ Heading,
+ Icon,
+ MenuItemType,
+ Stack,
+ Text,
+ } from '@immich/ui';
+ import {
+ mdiAccountOutline,
+ mdiCameraIris,
+ mdiChartPie,
+ mdiChartPieOutline,
+ mdiCheckCircle,
+ mdiDevices,
+ mdiFeatureSearchOutline,
+ mdiPlayCircle,
+ mdiTrashCanOutline,
+ } from '@mdi/js';
+ import type { Snippet } from 'svelte';
+ import { t } from 'svelte-i18n';
+ import type { LayoutData } from './$types';
+
+ type Props = {
+ children?: Snippet;
+ data: LayoutData;
+ };
+
+ const { children, data }: Props = $props();
+
+ let user = $state(data.user);
+ const userPreferences = $state(data.userPreferences);
+ const userStatistics = $state(data.userStatistics);
+ const userSessions = $state(data.userSessions);
+ const TiB = 1024 ** 4;
+ const usage = $derived(user.quotaUsageInBytes ?? 0);
+ let [statsUsage, statsUsageUnit] = $derived(getBytesWithUnit(usage, usage > TiB ? 2 : 0));
+ const usedBytes = $derived(user.quotaUsageInBytes ?? 0);
+ const availableBytes = $derived(user.quotaSizeInBytes ?? 1);
+ let usedPercentage = $derived(Math.min(Math.round((usedBytes / availableBytes) * 100), 100));
+
+ let editedLocale = $derived(findLocale($locale).code);
+ let createAtDate: Date = $derived(new Date(user.createdAt));
+ let updatedAtDate: Date = $derived(new Date(user.updatedAt));
+ let userCreatedAtDateAndTime: string = $derived(createDateFormatter(editedLocale).formatDateTime(createAtDate));
+ let userUpdatedAtDateAndTime: string = $derived(createDateFormatter(editedLocale).formatDateTime(updatedAtDate));
+
+ const getUsageClass = () => {
+ if (usedPercentage >= 95) {
+ return 'bg-red-500';
+ }
+
+ if (usedPercentage > 80) {
+ return 'bg-yellow-500';
+ }
+
+ return 'bg-primary';
+ };
+
+ const { ResetPassword, ResetPinCode, Update, Delete, Restore } = $derived(getUserAdminActions($t, user));
+
+ const onUpdate = (update: UserAdminResponseDto) => {
+ if (update.id === user.id) {
+ user = update;
+ }
+ };
+
+ const onUserAdminDeleted = async ({ id }: { id: string }) => {
+ if (id === user.id) {
+ await goto(AppRoute.ADMIN_USERS);
+ }
+ };
+
+
+
+
+
+
+
+
+
+ {#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}
+
+ {/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}
-
- {/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 df1524177e..775f9662d2 100644
--- a/web/src/routes/admin/users/[id]/+page.ts
+++ b/web/src/routes/admin/users/[id]/+page.ts
@@ -1,36 +1,12 @@
-import { AppRoute, UUID_REGEX } 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();
-
- 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'),
},
diff --git a/web/src/lib/modals/UserEditModal.svelte b/web/src/routes/admin/users/[id]/edit/+page.svelte
similarity index 86%
rename from web/src/lib/modals/UserEditModal.svelte
rename to web/src/routes/admin/users/[id]/edit/+page.svelte
index 4b4878e46d..8a472f9452 100644
--- a/web/src/lib/modals/UserEditModal.svelte
+++ b/web/src/routes/admin/users/[id]/edit/+page.svelte
@@ -1,10 +1,11 @@
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;