165 lines
4.8 KiB
TypeScript
165 lines
4.8 KiB
TypeScript
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "~/client/components/ui/form";
|
|
import { Textarea } from "~/client/components/ui/textarea";
|
|
import { type UseFormReturn } from "react-hook-form";
|
|
import type { InternalFormValues } from "./types";
|
|
import { Input } from "~/client/components/ui/input";
|
|
|
|
type AdvancedSectionProps = {
|
|
form: UseFormReturn<InternalFormValues>;
|
|
};
|
|
|
|
type WebhookFieldsProps = {
|
|
form: UseFormReturn<InternalFormValues>;
|
|
phase: "pre" | "post";
|
|
urlPlaceholder: string;
|
|
bodyPlaceholder: string;
|
|
description: string;
|
|
};
|
|
|
|
const WebhookFields = ({ form, phase, urlPlaceholder, bodyPlaceholder, description }: WebhookFieldsProps) => {
|
|
const labelPrefix = phase === "pre" ? "Pre-backup" : "Post-backup";
|
|
const fieldPrefix = phase === "pre" ? "preBackupWebhook" : "postBackupWebhook";
|
|
|
|
return (
|
|
<>
|
|
<FormField
|
|
control={form.control}
|
|
name={`${fieldPrefix}Url`}
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>{labelPrefix} webhook</FormLabel>
|
|
<FormControl>
|
|
<Input {...field} type="url" placeholder={urlPlaceholder} />
|
|
</FormControl>
|
|
<FormDescription>
|
|
{description} The URL origin must be listed in WEBHOOK_ALLOWED_ORIGINS; redirects are not followed.
|
|
</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={form.control}
|
|
name={`${fieldPrefix}HeadersText`}
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>{labelPrefix} webhook headers</FormLabel>
|
|
<FormControl>
|
|
<Textarea
|
|
{...field}
|
|
placeholder="Authorization: Bearer token X-Custom-Header: value"
|
|
className="font-mono text-sm min-h-24"
|
|
/>
|
|
</FormControl>
|
|
<FormDescription>
|
|
One header per line in Key: Value format. Values are stored as plain text.
|
|
</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={form.control}
|
|
name={`${fieldPrefix}Body`}
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>{labelPrefix} webhook body</FormLabel>
|
|
<FormControl>
|
|
<Textarea {...field} placeholder={bodyPlaceholder} className="font-mono text-sm min-h-24" />
|
|
</FormControl>
|
|
<FormDescription>Optional raw POST body. Leave empty to send the backup context JSON.</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export const AdvancedSection = ({ form }: AdvancedSectionProps) => {
|
|
return (
|
|
<>
|
|
<FormField
|
|
control={form.control}
|
|
name="maxRetries"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Maximum retries</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
{...field}
|
|
type="number"
|
|
min={0}
|
|
max={32}
|
|
value={field.value ?? ""}
|
|
placeholder="e.g., 2"
|
|
onChange={(e) => field.onChange(e.target.value ? Number(e.target.value) : undefined)}
|
|
/>
|
|
</FormControl>
|
|
<FormDescription>Maximum number of retry attempts if a backup fails (default: 2).</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={form.control}
|
|
name="retryDelay"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Retry delay</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
{...field}
|
|
type="number"
|
|
min={1}
|
|
max={1440}
|
|
step={1}
|
|
placeholder="e.g., 15"
|
|
value={field.value ?? ""}
|
|
onChange={(e) => field.onChange(e.target.value ? Number(e.target.value) : undefined)}
|
|
/>
|
|
</FormControl>
|
|
<FormDescription>Delay in minutes before retrying a failed backup (default: 15 minutes).</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<WebhookFields
|
|
form={form}
|
|
phase="pre"
|
|
urlPlaceholder="http://host.docker.internal:8080/stop"
|
|
bodyPlaceholder='{"action":"stop"}'
|
|
description="Called with POST before restic starts. A non-2xx response stops the backup."
|
|
/>
|
|
<WebhookFields
|
|
form={form}
|
|
phase="post"
|
|
urlPlaceholder="http://host.docker.internal:8080/start"
|
|
bodyPlaceholder='{"action":"start"}'
|
|
description="Called with POST after restic finishes, including failed or cancelled runs."
|
|
/>
|
|
<FormField
|
|
control={form.control}
|
|
name="customResticParamsText"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Custom restic parameters</FormLabel>
|
|
<FormControl>
|
|
<Textarea
|
|
{...field}
|
|
placeholder="--exclude-larger-than 500M --no-scan --read-concurrency 8"
|
|
className="font-mono text-sm min-h-24"
|
|
/>
|
|
</FormControl>
|
|
<FormDescription>
|
|
Advanced: enter one restic flag per line (e.g.{" "}
|
|
<code className="bg-muted px-1 rounded">--exclude-larger-than 500M</code>). Only the supported flag list
|
|
is accepted.
|
|
</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</>
|
|
);
|
|
};
|