mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
fix comments
This commit is contained in:
@@ -13,7 +13,6 @@
|
||||
Container,
|
||||
ContextMenuButton,
|
||||
Link,
|
||||
LoadingSpinner,
|
||||
MenuItemType,
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -116,29 +115,29 @@
|
||||
<TableCell class={classes.column2}>
|
||||
<Link href={Route.viewUser(owner)}>{owner.name}</Link>
|
||||
</TableCell>
|
||||
<TableCell class={classes.column3}>
|
||||
{#if stats}
|
||||
{#if stats}
|
||||
<TableCell class={classes.column3}>
|
||||
{stats.photos.toLocaleString($locale)}
|
||||
{:else}
|
||||
<LoadingSpinner />
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCell class={classes.column4}>
|
||||
{#if stats}
|
||||
</TableCell>
|
||||
<TableCell class={classes.column4}>
|
||||
{stats.videos.toLocaleString($locale)}
|
||||
{:else}
|
||||
<LoadingSpinner />
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCell class={classes.column5}>
|
||||
{#if stats}
|
||||
</TableCell>
|
||||
<TableCell class={classes.column5}>
|
||||
{@const [diskUsage, diskUsageUnit] = getBytesWithUnit(stats.usage, 0)}
|
||||
{diskUsage}
|
||||
{diskUsageUnit}
|
||||
{:else}
|
||||
<LoadingSpinner />
|
||||
{/if}
|
||||
</TableCell>
|
||||
</TableCell>
|
||||
{:else}
|
||||
<TableCell class={classes.column3}>
|
||||
<span class="skeleton-loader inline-block h-4 w-14"></span>
|
||||
</TableCell>
|
||||
<TableCell class={classes.column4}>
|
||||
<span class="skeleton-loader inline-block h-4 w-14"></span>
|
||||
</TableCell>
|
||||
<TableCell class={classes.column5}>
|
||||
<span class="skeleton-loader inline-block h-4 w-20"></span>
|
||||
</TableCell>
|
||||
{/if}
|
||||
<TableCell class={classes.column6}>
|
||||
<ContextMenuButton color="primary" aria-label={$t('open')} items={getActionsForLibrary(library)} />
|
||||
</TableCell>
|
||||
@@ -159,3 +158,37 @@
|
||||
</div>
|
||||
</Container>
|
||||
</AdminPageLayout>
|
||||
|
||||
<style>
|
||||
.skeleton-loader {
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background-color: rgba(156, 163, 175, 0.35);
|
||||
}
|
||||
|
||||
.skeleton-loader::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0),
|
||||
rgba(255, 255, 255, 0.8) 50%,
|
||||
rgba(255, 255, 255, 0)
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
background-position: 200% 0;
|
||||
animation: skeleton-animation 2000ms infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-animation {
|
||||
from {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
to {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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<LibraryStatsResponseDto>);
|
||||
|
||||
let statistics = $state<LibraryStatsResponseDto | undefined>(undefined);
|
||||
let storageUsage = $state<number | undefined>(undefined);
|
||||
let unit = $state<ByteUnit | undefined>(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<LibraryResponseDto | undefined>(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 @@
|
||||
<div class="grid gap-4 grid-cols-1 lg:grid-cols-2 w-full">
|
||||
<Heading tag="h1" size="large" class="col-span-full my-4">{library.name}</Heading>
|
||||
<div class="flex flex-col lg:flex-row gap-4 col-span-full">
|
||||
<ServerStatisticsCard icon={mdiCameraIris} title={$t('photos')} value={statistics?.photos} />
|
||||
<ServerStatisticsCard icon={mdiPlayCircle} title={$t('videos')} value={statistics?.videos} />
|
||||
<ServerStatisticsCard icon={mdiChartPie} title={$t('usage')} value={storageUsage} {unit} />
|
||||
<ServerStatisticsCard icon={mdiCameraIris} title={$t('photos')} valuePromise={photosPromise} />
|
||||
<ServerStatisticsCard icon={mdiPlayCircle} title={$t('videos')} valuePromise={videosPromise} />
|
||||
<ServerStatisticsCard icon={mdiChartPie} title={$t('usage')} valuePromise={usagePromise} />
|
||||
</div>
|
||||
|
||||
<AdminCard icon={mdiFolderOutline} title={$t('folders')} headerAction={AddFolder}>
|
||||
@@ -147,7 +134,7 @@
|
||||
</AdminCard>
|
||||
|
||||
<div class="flex flex-col lg:flex-row gap-4">
|
||||
<ServerStatisticsCard icon={mdiFileDocumentRemoveOutline} title={$t('offline')} value={statistics?.offline} />
|
||||
<ServerStatisticsCard icon={mdiFileDocumentRemoveOutline} title={$t('offline')} valuePromise={offlinePromise} />
|
||||
</div>
|
||||
</div>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -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<ServerStatsResponseDto | undefined>(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 @@
|
||||
|
||||
<AdminPageLayout breadcrumbs={[{ title: data.meta.title }]}>
|
||||
<Container size="large" center>
|
||||
{#if stats}
|
||||
<ServerStatisticsPanel {stats} />
|
||||
{:else}
|
||||
<LoadingSpinner />
|
||||
{/if}
|
||||
<ServerStatisticsPanel {statsPromise} users={data.users} />
|
||||
</Container>
|
||||
</AdminPageLayout>
|
||||
|
||||
@@ -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'),
|
||||
},
|
||||
|
||||
@@ -123,9 +123,21 @@
|
||||
</div>
|
||||
<div class="col-span-full">
|
||||
<div class="flex flex-col lg:flex-row gap-4 w-full">
|
||||
<ServerStatisticsCard icon={mdiCameraIris} title={$t('photos')} value={userStatistics.images} />
|
||||
<ServerStatisticsCard icon={mdiPlayCircle} title={$t('videos')} value={userStatistics.videos} />
|
||||
<ServerStatisticsCard icon={mdiChartPie} title={$t('storage')} value={statsUsage} unit={statsUsageUnit} />
|
||||
<ServerStatisticsCard
|
||||
icon={mdiCameraIris}
|
||||
title={$t('photos')}
|
||||
valuePromise={Promise.resolve({ value: userStatistics.images })}
|
||||
/>
|
||||
<ServerStatisticsCard
|
||||
icon={mdiPlayCircle}
|
||||
title={$t('videos')}
|
||||
valuePromise={Promise.resolve({ value: userStatistics.videos })}
|
||||
/>
|
||||
<ServerStatisticsCard
|
||||
icon={mdiChartPie}
|
||||
title={$t('storage')}
|
||||
valuePromise={Promise.resolve({ value: statsUsage, unit: statsUsageUnit })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user