mirror of
https://github.com/immich-app/yucca.git
synced 2026-09-30 21:37:50 +08:00
feat(yucca sdk): warn users if immich db dumps enabled (#703)
* feat(yucca sdk): add hooks for Immich database dump config * feat(yucca sdk): warn users when immich db dump in use
This commit is contained in:
@@ -24,6 +24,10 @@ async function bootstrap() {
|
||||
return 'dev-database-backup.sql';
|
||||
},
|
||||
async cleanupDatabaseBackups() {},
|
||||
async getImmichDatabaseDumpConfig() {
|
||||
return { enabled: true, keepLastAmount: 14 };
|
||||
},
|
||||
async configureImmichDatabaseDump() {},
|
||||
async enterMaintenanceRollback() {
|
||||
return {
|
||||
jwt: 'abc',
|
||||
|
||||
@@ -317,6 +317,44 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/yucca/integrations/immich/database-dump/ignore-warning": {
|
||||
"post": {
|
||||
"operationId": "ignoreImmichDatabaseDumpWarning",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Integrations"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/yucca/integrations/immich/database-dump": {
|
||||
"post": {
|
||||
"operationId": "configureImmichDatabaseDump",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ConfigureImmichDatabaseDumpRequestDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Integrations"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/yucca/integrations/immich/rollback": {
|
||||
"post": {
|
||||
"operationId": "startImmichRollback",
|
||||
@@ -1921,6 +1959,23 @@
|
||||
"forget"
|
||||
]
|
||||
},
|
||||
"ImmichDatabaseDumpConfigDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepLastAmount": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 9007199254740991
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled",
|
||||
"keepLastAmount"
|
||||
]
|
||||
},
|
||||
"ImmichBackupStatusDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1938,6 +1993,12 @@
|
||||
},
|
||||
"latestBackupRun": {
|
||||
"$ref": "#/components/schemas/RunDto"
|
||||
},
|
||||
"databaseDump": {
|
||||
"$ref": "#/components/schemas/ImmichDatabaseDumpConfigDto"
|
||||
},
|
||||
"databaseDumpWarningIgnored": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2013,6 +2074,19 @@
|
||||
"repositoryId"
|
||||
]
|
||||
},
|
||||
"ConfigureImmichDatabaseDumpRequestDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepLastAmount": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 9007199254740991
|
||||
}
|
||||
}
|
||||
},
|
||||
"ImmichRollbackRequestDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Body, Controller, Get, Post, Res } from '@nestjs/common';
|
||||
import { ApiCreatedResponse, ApiOkResponse } from '@nestjs/swagger';
|
||||
import {
|
||||
ConfigureImmichDatabaseDumpRequestDto,
|
||||
ConfigureImmichIntegrationRequestDto,
|
||||
ConfigureImmichIntegrationResponseDto,
|
||||
ImmichBackupStatusDto,
|
||||
@@ -36,6 +37,16 @@ export class IntegrationsController {
|
||||
return this.service.configureImmichIntegration(dto);
|
||||
}
|
||||
|
||||
@Post('immich/database-dump/ignore-warning')
|
||||
ignoreImmichDatabaseDumpWarning(): Promise<void> {
|
||||
return this.service.ignoreImmichDatabaseDumpWarning();
|
||||
}
|
||||
|
||||
@Post('immich/database-dump')
|
||||
configureImmichDatabaseDump(@Body() dto: ConfigureImmichDatabaseDumpRequestDto): Promise<void> {
|
||||
return this.service.configureImmichDatabaseDump(dto);
|
||||
}
|
||||
|
||||
@Post('immich/rollback')
|
||||
async startImmichRollback(@Body() dto: ImmichRollbackRequestDto, @Res({ passthrough: true }) response: Response) {
|
||||
const { jwt } = await this.service.enterImmichMaintenanceRollback(dto);
|
||||
|
||||
@@ -46,6 +46,13 @@ const IntegrationsResponseSchema = z
|
||||
})
|
||||
.meta({ id: 'IntegrationsResponseDto' });
|
||||
|
||||
const ImmichDatabaseDumpConfigSchema = z
|
||||
.object({
|
||||
enabled: z.boolean(),
|
||||
keepLastAmount: z.int().min(1),
|
||||
})
|
||||
.meta({ id: 'ImmichDatabaseDumpConfigDto' });
|
||||
|
||||
const ImmichBackupStatusSchema = z
|
||||
.object({
|
||||
integration: ImmichIntegrationSchema.optional(),
|
||||
@@ -53,6 +60,8 @@ const ImmichBackupStatusSchema = z
|
||||
backend: BackendSchema.optional(),
|
||||
schedule: ScheduleSchema.optional(),
|
||||
latestBackupRun: RunSchema.optional(),
|
||||
databaseDump: ImmichDatabaseDumpConfigSchema.optional(),
|
||||
databaseDumpWarningIgnored: z.boolean().optional(),
|
||||
})
|
||||
.meta({ id: 'ImmichBackupStatusDto' });
|
||||
|
||||
@@ -76,6 +85,10 @@ const ConfigureImmichIntegrationResponseSchema = z
|
||||
})
|
||||
.meta({ id: 'ConfigureImmichIntegrationResponseDto' });
|
||||
|
||||
const ConfigureImmichDatabaseDumpRequestSchema = ImmichDatabaseDumpConfigSchema.partial().meta({
|
||||
id: 'ConfigureImmichDatabaseDumpRequestDto',
|
||||
});
|
||||
|
||||
const ImmichRollbackRequestSchema = z
|
||||
.object({
|
||||
repositoryId: z.string(),
|
||||
@@ -92,4 +105,6 @@ export class IntegrationsResponseDto extends createZodDto(IntegrationsResponseSc
|
||||
export class ImmichBackupStatusDto extends createZodDto(ImmichBackupStatusSchema) {}
|
||||
export class ConfigureImmichIntegrationRequestDto extends createZodDto(ConfigureImmichIntegrationRequestSchema) {}
|
||||
export class ConfigureImmichIntegrationResponseDto extends createZodDto(ConfigureImmichIntegrationResponseSchema) {}
|
||||
export class ImmichDatabaseDumpConfigDto extends createZodDto(ImmichDatabaseDumpConfigSchema) {}
|
||||
export class ConfigureImmichDatabaseDumpRequestDto extends createZodDto(ConfigureImmichDatabaseDumpRequestSchema) {}
|
||||
export class ImmichRollbackRequestDto extends createZodDto(ImmichRollbackRequestSchema) {}
|
||||
|
||||
@@ -22,6 +22,7 @@ export enum ConfigurationKey {
|
||||
OnboardedKey = 'onboarded-key',
|
||||
Telemetry = 'telemetry',
|
||||
SkippedOnboardingExtraConfig = 'skipped-onboarding-extra-config',
|
||||
IgnoredImmichDatabaseDumpWarning = 'ignored-immich-database-dump-warning',
|
||||
ResticOptionRestConnections = 'restic-o-rest-connections',
|
||||
SessionSecret = 'session-secret',
|
||||
StatePath = 'state-path',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './const';
|
||||
export { EventsGateway, type GatewayEvent } from './events/events.gateway';
|
||||
export type { ImmichDatabaseDumpConfig } from './moduleConfig';
|
||||
export * from './orchestrationApi.module';
|
||||
export { LoggingRepository } from './repositories/logging.repository';
|
||||
export { ModuleConfigRepository } from './repositories/moduleConfig.repository';
|
||||
|
||||
@@ -7,6 +7,11 @@ export const ModuleConfigProvider = Symbol('ModuleConfig');
|
||||
export type AuthenticatedUser = { user: { isAdmin: boolean } };
|
||||
export type AuthenticateFn = (client: Socket) => Promise<AuthenticatedUser>;
|
||||
|
||||
export type ImmichDatabaseDumpConfig = {
|
||||
enabled: boolean;
|
||||
keepLastAmount: number;
|
||||
};
|
||||
|
||||
export type ImmichIntegration = {
|
||||
dataPath: string;
|
||||
dataFolders: string[];
|
||||
@@ -19,6 +24,8 @@ export type ImmichIntegration = {
|
||||
hooks: {
|
||||
createDatabaseBackup(): Promise<string>;
|
||||
cleanupDatabaseBackups(): Promise<void>;
|
||||
getImmichDatabaseDumpConfig(): Promise<ImmichDatabaseDumpConfig>;
|
||||
configureImmichDatabaseDump(config: Partial<ImmichDatabaseDumpConfig>): Promise<void>;
|
||||
enterMaintenanceRollback(
|
||||
repositoryId: string,
|
||||
snapshotId: string,
|
||||
|
||||
@@ -188,6 +188,14 @@ export class ConfigRepository {
|
||||
return this.set(ConfigurationKey.SkippedOnboardingExtraConfig, '1');
|
||||
}
|
||||
|
||||
async hasIgnoredImmichDatabaseDumpWarning() {
|
||||
return this.has(ConfigurationKey.IgnoredImmichDatabaseDumpWarning);
|
||||
}
|
||||
|
||||
async ignoreImmichDatabaseDumpWarning() {
|
||||
return this.set(ConfigurationKey.IgnoredImmichDatabaseDumpWarning, '1');
|
||||
}
|
||||
|
||||
async hasSessionSecret() {
|
||||
return this.has(ConfigurationKey.SessionSecret);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/comm
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { join } from 'node:path';
|
||||
import {
|
||||
ConfigureImmichDatabaseDumpRequestDto,
|
||||
ConfigureImmichIntegrationRequestDto,
|
||||
ImmichBackupStatusDto,
|
||||
ImmichRollbackRequestDto,
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
import { InternalEvent, TaskType } from '../enum';
|
||||
import { EventsGateway } from '../events/events.gateway';
|
||||
import type { ImmichIntegration, ModuleConfig } from '../moduleConfig';
|
||||
import { ConfigRepository } from '../repositories/config.repository';
|
||||
import { LockRepository } from '../repositories/lock.repository';
|
||||
import { ModuleConfigRepository } from '../repositories/moduleConfig.repository';
|
||||
import { RepositoryRepository } from '../repositories/repository.repository';
|
||||
@@ -29,6 +31,7 @@ export class IntegrationsService {
|
||||
private readonly backendService: BackendService,
|
||||
private readonly events: EventsGateway,
|
||||
private readonly moduleConfig: ModuleConfigRepository,
|
||||
private readonly config: ConfigRepository,
|
||||
private readonly repositoryIntegrationImmich: RepositoryIntegrationImmichRepository,
|
||||
private readonly repositoryPath: RepositoryPathRepository,
|
||||
private readonly repositoryService: RepositoryService,
|
||||
@@ -63,17 +66,21 @@ export class IntegrationsService {
|
||||
}
|
||||
|
||||
async getImmichBackupStatus(): Promise<ImmichBackupStatusDto> {
|
||||
const { immichIntegration } = this.moduleConfig.get();
|
||||
const integration = await this.repositoryIntegrationImmich.get();
|
||||
if (!integration) {
|
||||
if (!integration || !immichIntegration) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const [{ repositories }, { backends }, schedule, latestBackupRun] = await Promise.all([
|
||||
this.repositoryService.getRepositories(),
|
||||
this.backendService.getBackends(),
|
||||
this.schedule.get(integration.scheduleId),
|
||||
this.runHistory.getLatest(integration.id, TaskType.Backup),
|
||||
]);
|
||||
const [{ repositories }, { backends }, schedule, latestBackupRun, databaseDump, databaseDumpWarningIgnored] =
|
||||
await Promise.all([
|
||||
this.repositoryService.getRepositories(),
|
||||
this.backendService.getBackends(),
|
||||
this.schedule.get(integration.scheduleId),
|
||||
this.runHistory.getLatest(integration.id, TaskType.Backup),
|
||||
immichIntegration.hooks.getImmichDatabaseDumpConfig(),
|
||||
this.config.hasIgnoredImmichDatabaseDumpWarning(),
|
||||
]);
|
||||
|
||||
const repository = repositories.find((entry) => entry.id === integration.id);
|
||||
|
||||
@@ -83,6 +90,8 @@ export class IntegrationsService {
|
||||
backend: backends.find((entry) => entry.id === repository?.backends?.primary.id),
|
||||
schedule,
|
||||
latestBackupRun,
|
||||
databaseDump,
|
||||
databaseDumpWarningIgnored,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -164,6 +173,19 @@ export class IntegrationsService {
|
||||
});
|
||||
}
|
||||
|
||||
async ignoreImmichDatabaseDumpWarning(): Promise<void> {
|
||||
await this.config.ignoreImmichDatabaseDumpWarning();
|
||||
}
|
||||
|
||||
async configureImmichDatabaseDump(dto: ConfigureImmichDatabaseDumpRequestDto): Promise<void> {
|
||||
const { immichIntegration } = this.moduleConfig.get();
|
||||
if (!immichIntegration) {
|
||||
throw new BadRequestException('Immich integration is not enabled.');
|
||||
}
|
||||
|
||||
await immichIntegration.hooks.configureImmichDatabaseDump(dto);
|
||||
}
|
||||
|
||||
async enterImmichMaintenanceRollback(dto: ImmichRollbackRequestDto): Promise<{ jwt: string }> {
|
||||
const { immichIntegration } = this.moduleConfig.get();
|
||||
if (!immichIntegration) {
|
||||
|
||||
@@ -155,6 +155,10 @@ describe('Integrations', () => {
|
||||
moduleConfig.update({ immichIntegration: undefined });
|
||||
});
|
||||
|
||||
const hooks = {
|
||||
getImmichDatabaseDumpConfig: jest.fn().mockResolvedValue({ enabled: true, keepLastAmount: 14 }),
|
||||
configureImmichDatabaseDump: jest.fn(),
|
||||
};
|
||||
it('pauses and resumes the managed schedule', async () => {
|
||||
const moduleConfig = ctx.module.get(ModuleConfigRepository);
|
||||
const integrationsService = ctx.module.get(IntegrationsService);
|
||||
@@ -166,6 +170,7 @@ describe('Integrations', () => {
|
||||
dataPath: '/data/immich',
|
||||
dataFolders: ['upload'],
|
||||
libraries: [],
|
||||
hooks: hooks as never,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -189,6 +194,16 @@ describe('Integrations', () => {
|
||||
|
||||
expect((await getSchedule())?.paused).toBe(false);
|
||||
|
||||
const status = await integrationsService.getImmichBackupStatus();
|
||||
expect(status.databaseDump).toEqual({ enabled: true, keepLastAmount: 14 });
|
||||
|
||||
await integrationsService.configureImmichDatabaseDump({ enabled: false });
|
||||
expect(hooks.configureImmichDatabaseDump).toHaveBeenCalledWith({ enabled: false });
|
||||
|
||||
expect(status.databaseDumpWarningIgnored).toBe(false);
|
||||
await integrationsService.ignoreImmichDatabaseDumpWarning();
|
||||
expect((await integrationsService.getImmichBackupStatus()).databaseDumpWarningIgnored).toBe(true);
|
||||
|
||||
await integrationsService.configureImmichIntegration({ ...configuration, paused: true });
|
||||
|
||||
expect((await getSchedule())?.paused).toBe(true);
|
||||
|
||||
+8
-1
@@ -7,6 +7,7 @@
|
||||
} from "$lib/services/immich.integration.service";
|
||||
import { HStack, Icon, LoadingSpinner, Text } from "@immich/ui";
|
||||
import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiChevronRight,
|
||||
mdiCloudAlertOutline,
|
||||
mdiCloudCheckVariantOutline,
|
||||
@@ -29,6 +30,10 @@
|
||||
const configured = $derived(status.kind !== "unconfigured");
|
||||
|
||||
const appearance = $derived.by(() => {
|
||||
if (backup.needsAttention) {
|
||||
return { color: "warning", icon: mdiAlertCircleOutline } as const;
|
||||
}
|
||||
|
||||
switch (status.kind) {
|
||||
case "loading": {
|
||||
return { color: "secondary", icon: undefined } as const;
|
||||
@@ -85,7 +90,9 @@
|
||||
{/if}
|
||||
|
||||
<Text size="tiny" class="flex-1 leading-tight">
|
||||
{#if status.kind === "loading"}
|
||||
{#if backup.needsAttention}
|
||||
Needs attention
|
||||
{:else if status.kind === "loading"}
|
||||
Checking backup status
|
||||
{:else if status.kind === "offline"}
|
||||
Backup service is offline
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
useConfigureImmichDatabaseDump,
|
||||
useIgnoreImmichDatabaseDumpWarning,
|
||||
useImmichBackupStatus,
|
||||
} from "$lib/services/immich.integration.service";
|
||||
import { Alert, Button, HStack, Stack, Text } from "@immich/ui";
|
||||
|
||||
const backup = useImmichBackupStatus();
|
||||
const configure = useConfigureImmichDatabaseDump();
|
||||
const ignore = useIgnoreImmichDatabaseDumpWarning();
|
||||
</script>
|
||||
|
||||
{#if backup.needsAttention}
|
||||
<Alert color="warning" title="Immich database backups are still enabled">
|
||||
<Stack gap={3}>
|
||||
<Text size="small">
|
||||
FUTO Backups already creates the same database backups as Immich.
|
||||
<br />
|
||||
Most users should select "use FUTO Backups only".
|
||||
</Text>
|
||||
<HStack gap={2}>
|
||||
<Button
|
||||
size="small"
|
||||
color="primary"
|
||||
loading={configure.isPending}
|
||||
onclick={() => configure.mutate({ enabled: false })}
|
||||
>
|
||||
Use FUTO Backups only
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
variant="ghost"
|
||||
loading={ignore.isPending}
|
||||
onclick={() => ignore.mutate()}
|
||||
>
|
||||
Ignore
|
||||
</Button>
|
||||
</HStack>
|
||||
</Stack>
|
||||
</Alert>
|
||||
{/if}
|
||||
+2
@@ -10,6 +10,7 @@
|
||||
useImmichBackupStatusEventHandler,
|
||||
} from "$lib/services/immich.integration.service";
|
||||
import { Container, Stack } from "@immich/ui";
|
||||
import ImmichDatabaseDumpAlert from "./ImmichDatabaseDumpAlert.svelte";
|
||||
import ImmichManageBackupOverview from "./ImmichManageBackupOverview.svelte";
|
||||
|
||||
type Props = {
|
||||
@@ -35,6 +36,7 @@
|
||||
<Container size="medium" center>
|
||||
{#if repository && schedule}
|
||||
<Stack class="mt-4" gap={6}>
|
||||
<ImmichDatabaseDumpAlert />
|
||||
<ImmichManageBackupOverview {repository} {schedule} status={backup.status} />
|
||||
<BackendsList {repository} />
|
||||
<RepositoryRunHistory {repository} onViewAll={onViewAttempts} />
|
||||
|
||||
@@ -161,12 +161,18 @@ export type RunDto = {
|
||||
status: RunStatus;
|
||||
"type": RunType;
|
||||
};
|
||||
export type ImmichDatabaseDumpConfigDto = {
|
||||
enabled: boolean;
|
||||
keepLastAmount: number;
|
||||
};
|
||||
export type ImmichBackupStatusDto = {
|
||||
integration?: ImmichIntegrationDto;
|
||||
repository?: LocalRepositoryDto;
|
||||
backend?: BackendDto;
|
||||
schedule?: ScheduleDto;
|
||||
latestBackupRun?: RunDto;
|
||||
databaseDump?: ImmichDatabaseDumpConfigDto;
|
||||
databaseDumpWarningIgnored?: boolean;
|
||||
};
|
||||
export type ConfigureImmichIntegrationRequestDto = {
|
||||
name: string;
|
||||
@@ -182,6 +188,10 @@ export type ConfigureImmichIntegrationRequestDto = {
|
||||
export type ConfigureImmichIntegrationResponseDto = {
|
||||
repositoryId: string;
|
||||
};
|
||||
export type ConfigureImmichDatabaseDumpRequestDto = {
|
||||
enabled?: boolean;
|
||||
keepLastAmount?: number;
|
||||
};
|
||||
export type ImmichRollbackRequestDto = {
|
||||
repositoryId: string;
|
||||
snapshotId: string;
|
||||
@@ -435,6 +445,19 @@ export function configureImmichIntegration(configureImmichIntegrationRequestDto:
|
||||
body: configureImmichIntegrationRequestDto
|
||||
})));
|
||||
}
|
||||
export function ignoreImmichDatabaseDumpWarning(opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText("/api/yucca/integrations/immich/database-dump/ignore-warning", {
|
||||
...opts,
|
||||
method: "POST"
|
||||
}));
|
||||
}
|
||||
export function configureImmichDatabaseDump(configureImmichDatabaseDumpRequestDto: ConfigureImmichDatabaseDumpRequestDto, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText("/api/yucca/integrations/immich/database-dump", oazapfts.json({
|
||||
...opts,
|
||||
method: "POST",
|
||||
body: configureImmichDatabaseDumpRequestDto
|
||||
})));
|
||||
}
|
||||
export function startImmichRollback(immichRollbackRequestDto: ImmichRollbackRequestDto, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText("/api/yucca/integrations/immich/rollback", oazapfts.json({
|
||||
...opts,
|
||||
|
||||
@@ -2,12 +2,16 @@ import BackupsRecoveryKeyModal from '$lib/components/onboarding/dialogs/BackupsR
|
||||
import { modalManager, type ActionItem } from '@immich/ui';
|
||||
import { mdiCloudUploadOutline, mdiCogOutline, mdiKeyOutline } from '@mdi/js';
|
||||
import {
|
||||
configureImmichDatabaseDump,
|
||||
type ConfigureImmichDatabaseDumpRequestDto,
|
||||
getImmichBackupStatus,
|
||||
ignoreImmichDatabaseDumpWarning,
|
||||
type ImmichBackupStatusDto,
|
||||
} from '$lib/fetch-client';
|
||||
import { queryClient } from '$lib/query-client';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { getBackupOutcome } from '$lib/utils/backup-status';
|
||||
import { createQuery } from '@tanstack/svelte-query';
|
||||
import { createMutation, createQuery } from '@tanstack/svelte-query';
|
||||
import { handleCreateBackup } from './repository.service';
|
||||
|
||||
export const getBackupPageActions = (
|
||||
@@ -45,6 +49,14 @@ const invalidateImmichBackupStatus = () =>
|
||||
queryKey: immichBackupStatusKeys.all,
|
||||
});
|
||||
|
||||
const updateImmichBackupStatus = (
|
||||
update: (data: ImmichBackupStatusDto) => ImmichBackupStatusDto,
|
||||
) =>
|
||||
queryClient.setQueryData<ImmichBackupStatusDto>(
|
||||
immichBackupStatusKeys.all,
|
||||
(data) => data && update(data),
|
||||
);
|
||||
|
||||
export const useImmichBackupStatusEventHandler = () => ({
|
||||
onIntegrationUpdate: invalidateImmichBackupStatus,
|
||||
onRepositoryUpdate: invalidateImmichBackupStatus,
|
||||
@@ -123,5 +135,45 @@ export const useImmichBackupStatus = () => {
|
||||
get status() {
|
||||
return toImmichBackupStatus(query.data, query.isLoading);
|
||||
},
|
||||
get needsAttention() {
|
||||
const usingImmichDbDump =
|
||||
query.data?.databaseDump?.enabled === true &&
|
||||
query.data.schedule !== undefined &&
|
||||
!query.data.schedule.paused &&
|
||||
query.data.databaseDumpWarningIgnored !== true;
|
||||
|
||||
return usingImmichDbDump;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const useConfigureImmichDatabaseDump = () =>
|
||||
createMutation(
|
||||
() => ({
|
||||
mutationFn: (dto: ConfigureImmichDatabaseDumpRequestDto) =>
|
||||
configureImmichDatabaseDump(dto),
|
||||
onSuccess: (_, dto) =>
|
||||
updateImmichBackupStatus((data) => ({
|
||||
...data,
|
||||
databaseDump: data.databaseDump && { ...data.databaseDump, ...dto },
|
||||
})),
|
||||
onError: (error) =>
|
||||
handleError(error, 'Failed to update Immich database dump settings'),
|
||||
}),
|
||||
() => queryClient,
|
||||
);
|
||||
|
||||
export const useIgnoreImmichDatabaseDumpWarning = () =>
|
||||
createMutation(
|
||||
() => ({
|
||||
mutationFn: () => ignoreImmichDatabaseDumpWarning(),
|
||||
onSuccess: () =>
|
||||
updateImmichBackupStatus((data) => ({
|
||||
...data,
|
||||
databaseDumpWarningIgnored: true,
|
||||
})),
|
||||
onError: (error) =>
|
||||
handleError(error, 'Failed to ignore Immich database dump warning'),
|
||||
}),
|
||||
() => queryClient,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user