mirror of
https://github.com/nicotsx/zerobyte.git
synced 2025-12-23 21:47:47 -05:00
add monthly backup frequency with multi-day selection
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { arktypeResolver } from "@hookform/resolvers/arktype";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { type } from "arktype";
|
||||
import { useCallback, useState } from "react";
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
} from "~/client/components/ui/form";
|
||||
import { Input } from "~/client/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
|
||||
import { Button } from "~/client/components/ui/button";
|
||||
import { Textarea } from "~/client/components/ui/textarea";
|
||||
import { VolumeFileBrowser } from "~/client/components/volume-file-browser";
|
||||
import type { BackupSchedule, Volume } from "~/client/lib/types";
|
||||
@@ -33,6 +35,7 @@ const internalFormSchema = type({
|
||||
frequency: "string",
|
||||
dailyTime: "string?",
|
||||
weeklyDay: "string?",
|
||||
monthlyDays: "string[]?",
|
||||
keepLast: "number?",
|
||||
keepHourly: "number?",
|
||||
keepDaily: "number?",
|
||||
@@ -78,15 +81,19 @@ const backupScheduleToFormValues = (schedule?: BackupSchedule): InternalFormValu
|
||||
}
|
||||
|
||||
const parts = schedule.cronExpression.split(" ");
|
||||
const [minutePart, hourPart, , , dayOfWeekPart] = parts;
|
||||
const [minutePart, hourPart, dayOfMonthPart, , dayOfWeekPart] = parts;
|
||||
|
||||
const isHourly = hourPart === "*";
|
||||
const isDaily = !isHourly && dayOfWeekPart === "*";
|
||||
const frequency = isHourly ? "hourly" : isDaily ? "daily" : "weekly";
|
||||
const isMonthly = !isHourly && dayOfMonthPart !== "*" && dayOfWeekPart === "*";
|
||||
const isDaily = !isHourly && dayOfMonthPart === "*" && dayOfWeekPart === "*";
|
||||
|
||||
const frequency = isHourly ? "hourly" : isMonthly ? "monthly" : isDaily ? "daily" : "weekly";
|
||||
|
||||
const dailyTime = isHourly ? undefined : `${hourPart.padStart(2, "0")}:${minutePart.padStart(2, "0")}`;
|
||||
|
||||
const weeklyDay = frequency === "weekly" ? dayOfWeekPart : undefined;
|
||||
|
||||
const monthlyDays = isMonthly ? dayOfMonthPart.split(",") : undefined;
|
||||
|
||||
const patterns = schedule.includePatterns || [];
|
||||
const isGlobPattern = (p: string) => /[*?[\]]/.test(p);
|
||||
@@ -96,7 +103,8 @@ const backupScheduleToFormValues = (schedule?: BackupSchedule): InternalFormValu
|
||||
return {
|
||||
name: schedule.name,
|
||||
repositoryId: schedule.repositoryId,
|
||||
frequency,
|
||||
frequency,
|
||||
monthlyDays,
|
||||
dailyTime,
|
||||
weeklyDay,
|
||||
includePatterns: fileBrowserPaths.length > 0 ? fileBrowserPaths : undefined,
|
||||
@@ -249,6 +257,7 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
||||
<SelectItem value="hourly">Hourly</SelectItem>
|
||||
<SelectItem value="daily">Daily</SelectItem>
|
||||
<SelectItem value="weekly">Weekly</SelectItem>
|
||||
<SelectItem value="monthly">Monthly</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
@@ -302,6 +311,42 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{frequency === "monthly" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="monthlyDays"
|
||||
render={({ field }) => (
|
||||
<FormItem className="md:col-span-2">
|
||||
<FormLabel>Days of the month</FormLabel>
|
||||
<FormControl>
|
||||
<div className="grid grid-cols-7 gap-4 w-max">
|
||||
{Array.from({ length: 31 }, (_, i) => {
|
||||
const day = (i + 1).toString();
|
||||
const isSelected = field.value?.includes(day);
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
key={day}
|
||||
variant={isSelected ? "primary" : "secondary"}
|
||||
size="icon"
|
||||
onClick={() => {
|
||||
const current = field.value || [];
|
||||
const next = isSelected ? current.filter((d) => d !== day) : [...current, day];
|
||||
field.onChange(next);
|
||||
}}
|
||||
>
|
||||
{day}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormDescription>Select one or more days when the backup should run.</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
|
||||
const handleSubmit = (formValues: BackupScheduleFormValues) => {
|
||||
if (!schedule) return;
|
||||
|
||||
const cronExpression = getCronExpression(formValues.frequency, formValues.dailyTime, formValues.weeklyDay);
|
||||
const cronExpression = getCronExpression(formValues.frequency, formValues.dailyTime, formValues.weeklyDay, formValues.monthlyDays);
|
||||
|
||||
const retentionPolicy: Record<string, number> = {};
|
||||
if (formValues.keepLast) retentionPolicy.keepLast = formValues.keepLast;
|
||||
|
||||
@@ -71,7 +71,7 @@ export default function CreateBackup({ loaderData }: Route.ComponentProps) {
|
||||
const handleSubmit = (formValues: BackupScheduleFormValues) => {
|
||||
if (!selectedVolumeId) return;
|
||||
|
||||
const cronExpression = getCronExpression(formValues.frequency, formValues.dailyTime, formValues.weeklyDay);
|
||||
const cronExpression = getCronExpression(formValues.frequency, formValues.dailyTime, formValues.weeklyDay, formValues.monthlyDays);
|
||||
|
||||
const retentionPolicy: Record<string, number> = {};
|
||||
if (formValues.keepLast) retentionPolicy.keepLast = formValues.keepLast;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { intervalToDuration } from "date-fns";
|
||||
|
||||
export const getCronExpression = (frequency: string, dailyTime?: string, weeklyDay?: string): string => {
|
||||
export const getCronExpression = (frequency: string, dailyTime?: string, weeklyDay?: string,monthlyDays?: string[]): string => {
|
||||
if (frequency === "hourly") {
|
||||
return "0 * * * *";
|
||||
}
|
||||
@@ -14,6 +14,12 @@ export const getCronExpression = (frequency: string, dailyTime?: string, weeklyD
|
||||
if (frequency === "daily") {
|
||||
return `${minutes} ${hours} * * *`;
|
||||
}
|
||||
|
||||
if (frequency === "monthly") {
|
||||
const sortedDays = (monthlyDays || []).map(Number).sort((a, b) => a - b);
|
||||
const days = sortedDays.length > 0 ? sortedDays.join(",") : "1";
|
||||
return `${minutes} ${hours} ${days} * *`;
|
||||
}
|
||||
|
||||
return `${minutes} ${hours} * * ${weeklyDay ?? "0"}`;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user