From 703451914043ecc7b13aff32f01c1f98eaacdb71 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Thu, 4 Dec 2025 18:32:50 +0100 Subject: [PATCH] feat: add exclude-if-present option --- app/client/api-client/types.gen.ts | 7 + .../components/create-schedule-form.tsx | 65 +- .../modules/backups/routes/backup-details.tsx | 6 +- .../modules/backups/routes/create-backup.tsx | 1 + app/drizzle/0020_even_dexter_bennett.sql | 1 + app/drizzle/meta/0020_snapshot.json | 815 ++++++++++++++++++ app/drizzle/meta/_journal.json | 7 + app/server/db/schema.ts | 1 + app/server/modules/backups/backups.dto.ts | 3 + app/server/modules/backups/backups.service.ts | 6 + app/server/utils/restic.ts | 7 + 11 files changed, 913 insertions(+), 6 deletions(-) create mode 100644 app/drizzle/0020_even_dexter_bennett.sql create mode 100644 app/drizzle/meta/0020_snapshot.json diff --git a/app/client/api-client/types.gen.ts b/app/client/api-client/types.gen.ts index 6a8bc0d6..2790e840 100644 --- a/app/client/api-client/types.gen.ts +++ b/app/client/api-client/types.gen.ts @@ -1291,6 +1291,7 @@ export type ListBackupSchedulesResponses = { createdAt: number; cronExpression: string; enabled: boolean; + excludeIfPresent: Array | null; excludePatterns: Array | null; id: number; includePatterns: Array | null; @@ -1439,6 +1440,7 @@ export type CreateBackupScheduleData = { name: string; repositoryId: string; volumeId: number; + excludeIfPresent?: Array; excludePatterns?: Array; includePatterns?: Array; retentionPolicy?: { @@ -1465,6 +1467,7 @@ export type CreateBackupScheduleResponses = { createdAt: number; cronExpression: string; enabled: boolean; + excludeIfPresent: Array | null; excludePatterns: Array | null; id: number; includePatterns: Array | null; @@ -1527,6 +1530,7 @@ export type GetBackupScheduleResponses = { createdAt: number; cronExpression: string; enabled: boolean; + excludeIfPresent: Array | null; excludePatterns: Array | null; id: number; includePatterns: Array | null; @@ -1673,6 +1677,7 @@ export type UpdateBackupScheduleData = { cronExpression: string; repositoryId: string; enabled?: boolean; + excludeIfPresent?: Array; excludePatterns?: Array; includePatterns?: Array; name?: string; @@ -1702,6 +1707,7 @@ export type UpdateBackupScheduleResponses = { createdAt: number; cronExpression: string; enabled: boolean; + excludeIfPresent: Array | null; excludePatterns: Array | null; id: number; includePatterns: Array | null; @@ -1744,6 +1750,7 @@ export type GetBackupScheduleForVolumeResponses = { createdAt: number; cronExpression: string; enabled: boolean; + excludeIfPresent: Array | null; excludePatterns: Array | null; id: number; includePatterns: Array | null; diff --git a/app/client/modules/backups/components/create-schedule-form.tsx b/app/client/modules/backups/components/create-schedule-form.tsx index 2a141524..fd10c190 100644 --- a/app/client/modules/backups/components/create-schedule-form.tsx +++ b/app/client/modules/backups/components/create-schedule-form.tsx @@ -26,6 +26,7 @@ const internalFormSchema = type({ name: "1 <= string <= 32", repositoryId: "string", excludePatternsText: "string?", + excludeIfPresentText: "string?", includePatternsText: "string?", includePatterns: "string[]?", frequency: "string", @@ -52,8 +53,12 @@ export const weeklyDays = [ type InternalFormValues = typeof internalFormSchema.infer; -export type BackupScheduleFormValues = Omit & { +export type BackupScheduleFormValues = Omit< + InternalFormValues, + "excludePatternsText" | "excludeIfPresentText" | "includePatternsText" +> & { excludePatterns?: string[]; + excludeIfPresent?: string[]; }; type Props = { @@ -95,6 +100,7 @@ const backupScheduleToFormValues = (schedule?: BackupSchedule): InternalFormValu includePatterns: fileBrowserPaths.length > 0 ? fileBrowserPaths : undefined, includePatternsText: textPatterns.length > 0 ? textPatterns.join("\n") : undefined, excludePatternsText: schedule.excludePatterns?.join("\n") || undefined, + excludeIfPresentText: schedule.excludeIfPresent?.join("\n") || undefined, ...schedule.retentionPolicy, }; }; @@ -107,13 +113,26 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }: const handleSubmit = useCallback( (data: InternalFormValues) => { - const { excludePatternsText, includePatternsText, includePatterns: fileBrowserPatterns, ...rest } = data; + const { + excludePatternsText, + excludeIfPresentText, + includePatternsText, + includePatterns: fileBrowserPatterns, + ...rest + } = data; const excludePatterns = excludePatternsText ? excludePatternsText .split("\n") .map((p) => p.trim()) .filter(Boolean) - : undefined; + : []; + + const excludeIfPresent = excludeIfPresentText + ? excludeIfPresentText + .split("\n") + .map((p) => p.trim()) + .filter(Boolean) + : []; const textPatterns = includePatternsText ? includePatternsText @@ -125,8 +144,9 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }: onSubmit({ ...rest, - includePatterns: includePatterns.length > 0 ? includePatterns : undefined, + includePatterns: includePatterns.length > 0 ? includePatterns : [], excludePatterns, + excludeIfPresent, }); }, [onSubmit], @@ -373,6 +393,28 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }: )} /> + ( + + Exclude if file present + +