diff --git a/web/src/lib/components/maintenance/integrity/IntegrityReportTableItem.svelte b/web/src/lib/components/maintenance/integrity/IntegrityReportTableItem.svelte
new file mode 100644
index 0000000000..cb3674ab3a
--- /dev/null
+++ b/web/src/lib/components/maintenance/integrity/IntegrityReportTableItem.svelte
@@ -0,0 +1,41 @@
+
+
+
+
+
+ {path}
+
+
+
+
diff --git a/web/src/lib/managers/event-manager.svelte.ts b/web/src/lib/managers/event-manager.svelte.ts
index 7124fc4524..216d3c2971 100644
--- a/web/src/lib/managers/event-manager.svelte.ts
+++ b/web/src/lib/managers/event-manager.svelte.ts
@@ -5,6 +5,7 @@ import type {
AlbumResponseDto,
ApiKeyResponseDto,
AssetResponseDto,
+ IntegrityReportType,
LibraryResponseDto,
LoginResponseDto,
PersonResponseDto,
@@ -64,6 +65,8 @@ export type Events = {
SystemConfigUpdate: [SystemConfigDto];
+ IntegrityReportDelete: [{ type?: IntegrityReportType; id?: string; isDeleting: boolean; isDeleted: boolean }];
+
LibraryCreate: [LibraryResponseDto];
LibraryUpdate: [LibraryResponseDto];
LibraryDelete: [{ id: string }];
diff --git a/web/src/lib/services/integrity.service.ts b/web/src/lib/services/integrity.service.ts
new file mode 100644
index 0000000000..e741c23a33
--- /dev/null
+++ b/web/src/lib/services/integrity.service.ts
@@ -0,0 +1,151 @@
+import { eventManager } from '$lib/managers/event-manager.svelte';
+import { handleError } from '$lib/utils/handle-error';
+import { getFormatter } from '$lib/utils/i18n';
+import { createJob, deleteIntegrityReport, getBaseUrl, IntegrityReportType, ManualJobName } from '@immich/sdk';
+import { modalManager, toastManager, type ActionItem } from '@immich/ui';
+import { mdiDownload, mdiTrashCanOutline } from '@mdi/js';
+import type { MessageFormatter } from 'svelte-i18n';
+
+export const getIntegrityReportActions = ($t: MessageFormatter, reportType: IntegrityReportType) => {
+ const Download: ActionItem = {
+ type: $t('command'),
+ title: $t('admin.download_csv'),
+ icon: mdiDownload,
+ onAction() {
+ handleDownloadIntegrityReportCsv(reportType);
+ },
+ };
+
+ const Delete: ActionItem = {
+ type: $t('command'),
+ title: $t('trash_page_delete_all'),
+ icon: mdiTrashCanOutline,
+ color: 'danger',
+ onAction() {
+ void handleRemoveAllIntegrityReportItems(reportType);
+ },
+ };
+
+ return { Download, Delete };
+};
+
+export const getIntegrityReportItemActions = (
+ $t: MessageFormatter,
+ reportId: string,
+ reportType: IntegrityReportType,
+) => {
+ const Download =
+ reportType === IntegrityReportType.UntrackedFile || reportType === IntegrityReportType.ChecksumMismatch
+ ? {
+ title: $t('download'),
+ icon: mdiDownload,
+ onAction() {
+ void handleDownloadIntegrityReportFile(reportId);
+ },
+ }
+ : undefined;
+
+ const Delete = {
+ title: $t('delete'),
+ icon: mdiTrashCanOutline,
+ color: 'danger',
+ onAction() {
+ void handleRemoveIntegrityReportItem(reportId);
+ },
+ };
+
+ return { Download, Delete };
+};
+
+export const handleDownloadIntegrityReportFile = (reportId: string) => {
+ location.href = `${getBaseUrl()}/admin/integrity/report/${reportId}/file`;
+};
+
+export const handleDownloadIntegrityReportCsv = (reportType: IntegrityReportType) => {
+ location.href = `${getBaseUrl()}/admin/integrity/report/${reportType}/csv`;
+};
+
+export const handleRemoveAllIntegrityReportItems = async (reportType: IntegrityReportType) => {
+ const $t = await getFormatter();
+ const confirm = await modalManager.showDialog({
+ confirmText: $t('delete'),
+ });
+
+ if (confirm) {
+ let name: ManualJobName;
+ switch (reportType) {
+ case IntegrityReportType.UntrackedFile: {
+ name = ManualJobName.IntegrityUntrackedFilesDeleteAll;
+ break;
+ }
+ case IntegrityReportType.MissingFile: {
+ name = ManualJobName.IntegrityMissingFilesDeleteAll;
+ break;
+ }
+ case IntegrityReportType.ChecksumMismatch: {
+ name = ManualJobName.IntegrityChecksumMismatchDeleteAll;
+ break;
+ }
+ }
+
+ try {
+ eventManager.emit('IntegrityReportDelete', {
+ type: reportType,
+ isDeleting: true,
+ isDeleted: false,
+ });
+
+ await createJob({ jobCreateDto: { name } });
+ toastManager.success($t('admin.job_created'));
+
+ eventManager.emit('IntegrityReportDelete', {
+ type: reportType,
+ isDeleting: false,
+ isDeleted: true,
+ });
+ } catch (error) {
+ handleError(error, $t('failed_to_delete_file'));
+
+ eventManager.emit('IntegrityReportDelete', {
+ type: reportType,
+ isDeleting: false,
+ isDeleted: false,
+ });
+ }
+ }
+};
+
+export const handleRemoveIntegrityReportItem = async (reportId: string) => {
+ const $t = await getFormatter();
+ const confirm = await modalManager.showDialog({
+ confirmText: $t('delete'),
+ });
+
+ if (confirm) {
+ try {
+ eventManager.emit('IntegrityReportDelete', {
+ id: reportId,
+ isDeleting: true,
+ isDeleted: false,
+ });
+
+ await deleteIntegrityReport({
+ id: reportId,
+ });
+
+ eventManager.emit('IntegrityReportDelete', {
+ id: reportId,
+ isDeleting: false,
+ isDeleted: true,
+ });
+ } catch (error) {
+ handleError(error, $t('failed_to_delete_file'));
+
+ eventManager.emit('IntegrityReportDelete', {
+ id: reportId,
+ isDeleting: false,
+ isDeleted: false,
+ });
+ }
+ }
+};
diff --git a/web/src/lib/services/maintenance.service.ts b/web/src/lib/services/maintenance.service.ts
new file mode 100644
index 0000000000..848d40ec35
--- /dev/null
+++ b/web/src/lib/services/maintenance.service.ts
@@ -0,0 +1,31 @@
+import { handleError } from '$lib/utils/handle-error';
+import { getFormatter } from '$lib/utils/i18n';
+import { MaintenanceAction, setMaintenanceMode, type SetMaintenanceModeDto } from '@immich/sdk';
+import type { ActionItem } from '@immich/ui';
+import { mdiProgressWrench } from '@mdi/js';
+import type { MessageFormatter } from 'svelte-i18n';
+
+export const getMaintenanceAdminActions = ($t: MessageFormatter) => {
+ const StartMaintenance: ActionItem = {
+ title: $t('admin.maintenance_start'),
+ onAction: () =>
+ handleSetMaintenanceMode({
+ action: MaintenanceAction.Start,
+ }),
+ icon: mdiProgressWrench,
+ };
+
+ return { StartMaintenance };
+};
+
+export const handleSetMaintenanceMode = async (dto: SetMaintenanceModeDto) => {
+ const $t = await getFormatter();
+
+ try {
+ await setMaintenanceMode({
+ setMaintenanceModeDto: dto,
+ });
+ } catch (error) {
+ handleError(error, $t('admin.maintenance_start_error'));
+ }
+};
diff --git a/web/src/routes/admin/maintenance/+page.svelte b/web/src/routes/admin/maintenance/+page.svelte
index 402734c7d6..3425812cbd 100644
--- a/web/src/routes/admin/maintenance/+page.svelte
+++ b/web/src/routes/admin/maintenance/+page.svelte
@@ -2,6 +2,7 @@
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
import ServerStatisticsCard from '$lib/components/server-statistics/ServerStatisticsCard.svelte';
import { AppRoute } from '$lib/constants';
+ import { getMaintenanceAdminActions } from '$lib/services/maintenance.service';
import { asyncTimeout } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import {
@@ -9,14 +10,11 @@
getIntegrityReportSummary,
getQueuesLegacy,
IntegrityReportType,
- MaintenanceAction,
ManualJobName,
- setMaintenanceMode,
type IntegrityReportSummaryResponseDto,
type QueuesResponseLegacyDto,
} from '@immich/sdk';
import { Button, HStack, toastManager } from '@immich/ui';
- import { mdiProgressWrench } from '@mdi/js';
import { onDestroy, onMount } from 'svelte';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
@@ -25,7 +23,8 @@
data: PageData;
}
- let { data }: Props = $props();
+ const { data }: Props = $props();
+ const { StartMaintenance } = $derived(getMaintenanceAdminActions($t));
let integrityReport: IntegrityReportSummaryResponseDto = $state(data.integrityReport);
@@ -35,18 +34,6 @@
IntegrityReportType.ChecksumMismatch,
];
- async function switchToMaintenance() {
- try {
- await setMaintenanceMode({
- setMaintenanceModeDto: {
- action: MaintenanceAction.Start,
- },
- });
- } catch (error) {
- handleError(error, $t('admin.maintenance_start_error'));
- }
- }
-
let jobs: QueuesResponseLegacyDto | undefined = $state();
let expectingUpdate: boolean = $state(false);
@@ -106,16 +93,7 @@
});
-
+
diff --git a/web/src/routes/admin/maintenance/integrity-report/[type]/+page.svelte b/web/src/routes/admin/maintenance/integrity-report/[type]/+page.svelte
index 8873ef12f9..b8ef9efa7e 100644
--- a/web/src/routes/admin/maintenance/integrity-report/[type]/+page.svelte
+++ b/web/src/routes/admin/maintenance/integrity-report/[type]/+page.svelte
@@ -1,36 +1,14 @@
+
+
{
- location.href = `${getBaseUrl()}/admin/maintenance/integrity/report/${data.type}/csv`;
- },
- },
- {
- title: $t('trash_page_delete_all'),
- onAction: removeAll,
- icon: mdiTrashCanOutline,
- },
- ]}
+ actions={[Download, Delete]}
>
-
+
{$t('filename')}
@@ -197,19 +100,7 @@
{#each integrityReport.items as { id, path } (id)}
-
- {path}
- handleOpen(event, { position: 'top-right' }, id)}
- aria-label={$t('open')}
- disabled={deleting.has(id) || deleting.has('all')}
- />
-
+
{/each}