From 6b6338291bd3686c5dc33efcfb87e52dd0059507 Mon Sep 17 00:00:00 2001 From: Nico <47644445+nicotsx@users.noreply.github.com> Date: Thu, 4 Dec 2025 18:44:34 +0100 Subject: [PATCH] feat: custom include patterns (#104) * feat: add custom include patterns * feat: add exclude-if-present option --- app/client/api-client/types.gen.ts | 7 + .../components/create-schedule-form.tsx | 120 ++- .../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, 963 insertions(+), 11 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 6a8bc0d..2790e84 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 1fdede6..fd10c19 100644 --- a/app/client/modules/backups/components/create-schedule-form.tsx +++ b/app/client/modules/backups/components/create-schedule-form.tsx @@ -26,6 +26,8 @@ const internalFormSchema = type({ name: "1 <= string <= 32", repositoryId: "string", excludePatternsText: "string?", + excludeIfPresentText: "string?", + includePatternsText: "string?", includePatterns: "string[]?", frequency: "string", dailyTime: "string?", @@ -51,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 = { @@ -80,14 +86,21 @@ const backupScheduleToFormValues = (schedule?: BackupSchedule): InternalFormValu const weeklyDay = frequency === "weekly" ? dayOfWeekPart : undefined; + const patterns = schedule.includePatterns || []; + const isGlobPattern = (p: string) => /[*?[\]]/.test(p); + const fileBrowserPaths = patterns.filter((p) => !isGlobPattern(p)); + const textPatterns = patterns.filter(isGlobPattern); + return { name: schedule.name, repositoryId: schedule.repositoryId, frequency, dailyTime, weeklyDay, - includePatterns: schedule.includePatterns || undefined, + 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, }; }; @@ -100,18 +113,40 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }: const handleSubmit = useCallback( (data: InternalFormValues) => { - // Convert excludePatternsText string to excludePatterns array - const { excludePatternsText, ...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 + .split("\n") + .map((p) => p.trim()) + .filter(Boolean) + : []; + const includePatterns = [...(fileBrowserPatterns || []), ...textPatterns]; onSubmit({ ...rest, + includePatterns: includePatterns.length > 0 ? includePatterns : [], excludePatterns, + excludeIfPresent, }); }, [onSubmit], @@ -296,6 +331,27 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }: )} + ( + + Additional include patterns + +