diff --git a/web/src/lib/components/server-statistics/ServerStatisticsCard.svelte b/web/src/lib/components/server-statistics/ServerStatisticsCard.svelte
index b978ae9b33..ba3dd80357 100644
--- a/web/src/lib/components/server-statistics/ServerStatisticsCard.svelte
+++ b/web/src/lib/components/server-statistics/ServerStatisticsCard.svelte
@@ -1,24 +1,37 @@
@@ -29,14 +42,26 @@
{title}
-
- {#if value === undefined}
-
- {:else}
-
{zeros()}{value}
- {#if unit}
-
{unit}
- {/if}
- {/if}
+
+ {zeros()}{#if !isLoading && data}{data.value}
+ {#if data.unit}{data.unit}{/if}{/if}
+
+
diff --git a/web/src/lib/components/server-statistics/ServerStatisticsPanel.svelte b/web/src/lib/components/server-statistics/ServerStatisticsPanel.svelte
index 3ae5e4283a..bb5b81f7cd 100644
--- a/web/src/lib/components/server-statistics/ServerStatisticsPanel.svelte
+++ b/web/src/lib/components/server-statistics/ServerStatisticsPanel.svelte
@@ -1,8 +1,8 @@
@@ -40,48 +62,52 @@
{$t('total_usage')}
-
-
-
+
+
+
-
-
-
-
- {$t('photos')}
-
+ {#if stats}
+
+
+
+
+ {$t('photos')}
+
-
- {zeros(stats.photos)}{stats.photos}
-
-
-
-
-
-
{$t('videos')}
+
+ {zeros(stats.photos)}{stats.photos}
+
+
+
+
+ {$t('videos')}
+
-
- {zeros(stats.videos)}{stats.videos}
-
-
-
-
-
-
{$t('storage')}
+
+ {zeros(stats.videos)}{stats.videos}
+
+
+
+
+ {$t('storage')}
+
-
-
{zeros(statsUsage)}{statsUsage}
+
+
{zeros(storageUsageWithUnit[0])}{storageUsageWithUnit[0]}
-
-
{statsUsageUnit}
+
+ {storageUsageWithUnit[1]}
+
-
+ {/if}
@@ -95,34 +121,82 @@
{$t('usage')}
- {#each stats.usageByUser as user (user.userId)}
-
- {user.userName}
-
- {user.photos.toLocaleString($locale)} ()
-
- {user.videos.toLocaleString($locale)} ()
-
-
- {#if user.quotaSizeInBytes !== null}
- /
- {/if}
-
- {#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0}
- ({(user.quotaSizeInBytes === 0 ? 1 : user.usage / user.quotaSizeInBytes).toLocaleString($locale, {
- style: 'percent',
- maximumFractionDigits: 0,
- })})
- {:else}
- ({$t('unlimited')})
+ {#if stats}
+ {#each stats.usageByUser as user (user.userId)}
+
+ {user.userName}
+
+ {user.photos.toLocaleString($locale)} ()
+
+ {user.videos.toLocaleString($locale)} ()
+
+
+ {#if user.quotaSizeInBytes !== null}
+ /
{/if}
-
-
-
- {/each}
+
+ {#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0}
+ ({(user.quotaSizeInBytes === 0 ? 1 : user.usage / user.quotaSizeInBytes).toLocaleString($locale, {
+ style: 'percent',
+ maximumFractionDigits: 0,
+ })})
+ {:else}
+ ({$t('unlimited')})
+ {/if}
+
+
+
+ {/each}
+ {:else if users.length}
+ {#each users as user (user.id)}
+
+ {user.name}
+
+
+
+
+ {/each}
+ {/if}
+
+
diff --git a/web/src/routes/admin/library-management/(list)/+layout.svelte b/web/src/routes/admin/library-management/(list)/+layout.svelte
index 4eb6590a59..f06d19feb0 100644
--- a/web/src/routes/admin/library-management/(list)/+layout.svelte
+++ b/web/src/routes/admin/library-management/(list)/+layout.svelte
@@ -13,7 +13,6 @@
Container,
ContextMenuButton,
Link,
- LoadingSpinner,
MenuItemType,
Table,
TableBody,
@@ -116,29 +115,29 @@
{owner.name}
-
- {#if stats}
+ {#if stats}
+
{stats.photos.toLocaleString($locale)}
- {:else}
-
- {/if}
-
-
- {#if stats}
+
+
{stats.videos.toLocaleString($locale)}
- {:else}
-
- {/if}
-
-
- {#if stats}
+
+
{@const [diskUsage, diskUsageUnit] = getBytesWithUnit(stats.usage, 0)}
{diskUsage}
{diskUsageUnit}
- {:else}
-
- {/if}
-
+
+ {:else}
+
+
+
+
+
+
+
+
+
+ {/if}
@@ -159,3 +158,37 @@
+
+
diff --git a/web/src/routes/admin/library-management/[id]/+layout.svelte b/web/src/routes/admin/library-management/[id]/+layout.svelte
index 495cc27455..70d5336233 100644
--- a/web/src/routes/admin/library-management/[id]/+layout.svelte
+++ b/web/src/routes/admin/library-management/[id]/+layout.svelte
@@ -14,7 +14,6 @@
getLibraryExclusionPatternActions,
getLibraryFolderActions,
} from '$lib/services/library.service';
- import type { ByteUnit } from '$lib/utils/byte-units';
import { getBytesWithUnit } from '$lib/utils/byte-units';
import type { LibraryResponseDto, LibraryStatsResponseDto } from '@immich/sdk';
@@ -36,40 +35,28 @@
data: LayoutData;
};
- const { children, data }: Props = $props();
+ let { children, data }: Props = $props();
+ const statisticsPromise = $derived.by(() => data.statisticsPromise as Promise
);
- let statistics = $state(undefined);
- let storageUsage = $state(undefined);
- let unit = $state(undefined);
+ const photosPromise = $derived.by(() => statisticsPromise.then((stats) => ({ value: stats.photos })));
- $effect(() => {
- if (statistics) {
- const [usage, u] = getBytesWithUnit(statistics.usage);
- storageUsage = usage;
- unit = u;
- } else {
- storageUsage = undefined;
- unit = undefined;
- }
- });
+ const videosPromise = $derived.by(() => statisticsPromise.then((stats) => ({ value: stats.videos })));
- const loadStatistics = async () => {
- try {
- statistics = await data.statisticsPromise;
- } catch (error) {
- console.error('Failed to load statistics:', error);
- }
- };
+ const usagePromise = $derived.by(() =>
+ statisticsPromise.then((stats) => {
+ const [value, unit] = getBytesWithUnit(stats.usage);
+ return { value, unit };
+ }),
+ );
- $effect(() => {
- void loadStatistics();
- });
+ const offlinePromise = $derived.by(() => statisticsPromise.then((stats) => ({ value: stats.offline })));
- let library = $state(data.library);
+ let updatedLibrary = $state(undefined);
+ const library = $derived.by(() => (updatedLibrary?.id === data.library.id ? updatedLibrary : data.library));
const onLibraryUpdate = (newLibrary: LibraryResponseDto) => {
if (newLibrary.id === library.id) {
- library = newLibrary;
+ updatedLibrary = newLibrary;
}
};
@@ -94,9 +81,9 @@
{library.name}
-
-
-
+
+
+
@@ -147,7 +134,7 @@
-
+
{@render children?.()}
diff --git a/web/src/routes/admin/server-status/+page.svelte b/web/src/routes/admin/server-status/+page.svelte
index 6e0868b831..01f5009daa 100644
--- a/web/src/routes/admin/server-status/+page.svelte
+++ b/web/src/routes/admin/server-status/+page.svelte
@@ -2,7 +2,7 @@
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
import ServerStatisticsPanel from '$lib/components/server-statistics/ServerStatisticsPanel.svelte';
import { getServerStatistics, type ServerStatsResponseDto } from '@immich/sdk';
- import { Container, LoadingSpinner } from '@immich/ui';
+ import { Container } from '@immich/ui';
import { onMount } from 'svelte';
import type { PageData } from './$types';
@@ -14,20 +14,18 @@
let stats = $state(undefined);
- const loadStatistics = async () => {
- try {
- stats = await data.statsPromise;
- } catch (error) {
- console.error('Failed to load server statistics:', error);
+ const statsPromise = $derived.by(() => {
+ if (stats) {
+ return Promise.resolve(stats);
}
- };
+ return data.statsPromise;
+ });
const updateStatistics = async () => {
stats = await getServerStatistics();
};
onMount(() => {
- void loadStatistics();
const interval = setInterval(() => void updateStatistics(), 5000);
return () => clearInterval(interval);
@@ -36,10 +34,6 @@
- {#if stats}
-
- {:else}
-
- {/if}
+
diff --git a/web/src/routes/admin/server-status/+page.ts b/web/src/routes/admin/server-status/+page.ts
index 7359f8c130..d717a876a4 100644
--- a/web/src/routes/admin/server-status/+page.ts
+++ b/web/src/routes/admin/server-status/+page.ts
@@ -1,15 +1,17 @@
import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n';
-import { getServerStatistics } from '@immich/sdk';
+import { getServerStatistics, searchUsersAdmin } from '@immich/sdk';
import type { PageLoad } from './$types';
export const load = (async ({ url }) => {
await authenticate(url, { admin: true });
const statsPromise = getServerStatistics();
+ const users = await searchUsersAdmin({ withDeleted: false });
const $t = await getFormatter();
return {
statsPromise,
+ users,
meta: {
title: $t('server_stats'),
},
diff --git a/web/src/routes/admin/users/[id]/+layout.svelte b/web/src/routes/admin/users/[id]/+layout.svelte
index db86d05e72..d4556dbea5 100644
--- a/web/src/routes/admin/users/[id]/+layout.svelte
+++ b/web/src/routes/admin/users/[id]/+layout.svelte
@@ -123,9 +123,21 @@