mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-21 07:05:21 +02:00
refactor: update volume backup schema and form handling
- Modified the volume backup schema to enforce non-null constraints on volumeName and added serviceName. - Removed unnecessary fields (type, hostPath) from the schema and updated related API and form handling. - Enhanced form validation to ensure required fields are properly checked. - Updated the UI components to reflect changes in the volume backup management interface.
This commit is contained in:
@@ -53,16 +53,12 @@ const formSchema = z
|
||||
.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
cronExpression: z.string().min(1, "Cron expression is required"),
|
||||
volumeName: z.string(),
|
||||
hostPath: z.string(),
|
||||
volumeName: z.string().min(1, "Volume name is required"),
|
||||
prefix: z.string(),
|
||||
keepLatestCount: z.coerce.number().optional(),
|
||||
turnOff: z.boolean().default(false),
|
||||
volumeBackupType: z.enum(["bind", "volume"]),
|
||||
enabled: z.boolean().default(true),
|
||||
serviceName: z.string(),
|
||||
destinationId: z.string().min(1, "Destination required"),
|
||||
scheduleType: z.enum([
|
||||
serviceType: z.enum([
|
||||
"application",
|
||||
"compose",
|
||||
"postgres",
|
||||
@@ -71,9 +67,11 @@ const formSchema = z
|
||||
"mysql",
|
||||
"redis",
|
||||
]),
|
||||
serviceName: z.string(),
|
||||
destinationId: z.string().min(1, "Destination required"),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.scheduleType === "compose" && !data.serviceName) {
|
||||
if (data.serviceType === "compose" && !data.serviceName) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Service name is required",
|
||||
@@ -110,18 +108,17 @@ export const HandleVolumeBackups = ({
|
||||
name: "",
|
||||
cronExpression: "",
|
||||
volumeName: "",
|
||||
hostPath: "",
|
||||
prefix: "",
|
||||
keepLatestCount: undefined,
|
||||
turnOff: false,
|
||||
volumeBackupType: "volume",
|
||||
enabled: true,
|
||||
serviceName: "",
|
||||
serviceType: volumeBackupType,
|
||||
},
|
||||
});
|
||||
|
||||
const scheduleTypeForm = form.watch("scheduleType");
|
||||
|
||||
const serviceTypeForm = volumeBackupType;
|
||||
const { data: destinations } = api.destination.all.useQuery();
|
||||
const { data: volumeBackup } = api.volumeBackups.one.useQuery(
|
||||
{ volumeBackupId: volumeBackupId || "" },
|
||||
{ enabled: !!volumeBackupId },
|
||||
@@ -150,14 +147,13 @@ export const HandleVolumeBackups = ({
|
||||
name: volumeBackup.name,
|
||||
cronExpression: volumeBackup.cronExpression,
|
||||
volumeName: volumeBackup.volumeName || "",
|
||||
hostPath: volumeBackup.hostPath || "",
|
||||
prefix: volumeBackup.prefix,
|
||||
keepLatestCount: volumeBackup.keepLatestCount,
|
||||
keepLatestCount: volumeBackup.keepLatestCount || undefined,
|
||||
turnOff: volumeBackup.turnOff,
|
||||
volumeBackupType: volumeBackup.type,
|
||||
enabled: volumeBackup.enabled,
|
||||
enabled: volumeBackup.enabled || true,
|
||||
serviceName: volumeBackup.serviceName || "",
|
||||
destinationId: volumeBackup.destinationId,
|
||||
serviceType: volumeBackup.serviceType,
|
||||
});
|
||||
}
|
||||
}, [form, volumeBackup, volumeBackupId]);
|
||||
@@ -173,6 +169,7 @@ export const HandleVolumeBackups = ({
|
||||
...values,
|
||||
destinationId: values.destinationId,
|
||||
volumeBackupId: volumeBackupId || "",
|
||||
serviceType: volumeBackupType,
|
||||
...(volumeBackupType === "application" && {
|
||||
applicationId: id || "",
|
||||
}),
|
||||
@@ -251,7 +248,7 @@ export const HandleVolumeBackups = ({
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
{scheduleTypeForm === "compose" && (
|
||||
{serviceTypeForm === "compose" && (
|
||||
<div className="flex flex-col w-full gap-4">
|
||||
{errorServices && (
|
||||
<AlertBlock
|
||||
@@ -440,6 +437,119 @@ export const HandleVolumeBackups = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="destinationId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Destination</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a destination" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{destinations?.map((destination) => (
|
||||
<SelectItem
|
||||
key={destination.destinationId}
|
||||
value={destination.destinationId}
|
||||
>
|
||||
{destination.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormDescription>
|
||||
Choose the backup destination where files will be stored
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="volumeName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Volume Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="my-volume-name" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The name of the Docker volume to backup
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="prefix"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Backup Prefix</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="backup-" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Prefix for backup files (optional)
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="keepLatestCount"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Keep Latest Count</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="5"
|
||||
{...field}
|
||||
onChange={(e) =>
|
||||
field.onChange(Number(e.target.value) || undefined)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Number of backup files to keep (optional)
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="turnOff"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
Turn Off Container During Backup
|
||||
</FormLabel>
|
||||
<FormDescription className="text-amber-600 dark:text-amber-400">
|
||||
⚠️ The container will be temporarily stopped during backup to
|
||||
prevent file corruption. This ensures data integrity but may
|
||||
cause temporary service interruption.
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="enabled"
|
||||
|
||||
4
apps/dokploy/drizzle/0104_robust_nighthawk.sql
Normal file
4
apps/dokploy/drizzle/0104_robust_nighthawk.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE "volume_backup" ALTER COLUMN "volumeName" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "volume_backup" DROP COLUMN "type";--> statement-breakpoint
|
||||
ALTER TABLE "volume_backup" DROP COLUMN "hostPath";--> statement-breakpoint
|
||||
DROP TYPE "public"."volumeBackupType";
|
||||
1
apps/dokploy/drizzle/0105_unknown_firelord.sql
Normal file
1
apps/dokploy/drizzle/0105_unknown_firelord.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "volume_backup" ADD COLUMN "serviceName" text;
|
||||
6061
apps/dokploy/drizzle/meta/0104_snapshot.json
Normal file
6061
apps/dokploy/drizzle/meta/0104_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
6067
apps/dokploy/drizzle/meta/0105_snapshot.json
Normal file
6067
apps/dokploy/drizzle/meta/0105_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -729,6 +729,20 @@
|
||||
"when": 1751256796247,
|
||||
"tag": "0103_mean_jimmy_woo",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 104,
|
||||
"version": "7",
|
||||
"when": 1751259486912,
|
||||
"tag": "0104_robust_nighthawk",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 105,
|
||||
"version": "7",
|
||||
"when": 1751259917258,
|
||||
"tag": "0105_unknown_firelord",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -70,7 +70,7 @@ export const volumeBackupsRouter = createTRPCRouter({
|
||||
|
||||
runManually: protectedProcedure
|
||||
.input(z.object({ volumeBackupId: z.string().min(1) }))
|
||||
.mutation(async ({ input }) => {
|
||||
.mutation(async () => {
|
||||
// return await runVolumeBackupManually(input.volumeBackupId);
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
@@ -13,19 +13,16 @@ import { postgres } from "./postgres";
|
||||
import { mariadb } from "./mariadb";
|
||||
import { destinations } from "./destination";
|
||||
|
||||
export const volumeBackupType = pgEnum("volumeBackupType", ["bind", "volume"]);
|
||||
|
||||
export const volumeBackups = pgTable("volume_backup", {
|
||||
volumeBackupId: text("volumeBackupId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
name: text("name").notNull(),
|
||||
type: volumeBackupType("type").notNull().default("volume"),
|
||||
volumeName: text("volumeName"),
|
||||
hostPath: text("hostPath"),
|
||||
volumeName: text("volumeName").notNull(),
|
||||
prefix: text("prefix").notNull(),
|
||||
serviceType: serviceType("serviceType").notNull().default("application"),
|
||||
serviceName: text("serviceName"),
|
||||
turnOff: boolean("turnOff").notNull().default(false),
|
||||
cronExpression: text("cronExpression").notNull(),
|
||||
keepLatestCount: integer("keepLatestCount"),
|
||||
@@ -95,10 +92,8 @@ export const volumeBackupsRelations = relations(volumeBackups, ({ one }) => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
export const createVolumeBackupSchema = createInsertSchema(
|
||||
volumeBackups,
|
||||
).extend({
|
||||
volumeName: z.string().min(1),
|
||||
export const createVolumeBackupSchema = createInsertSchema(volumeBackups).omit({
|
||||
volumeBackupId: true,
|
||||
});
|
||||
|
||||
export const updateVolumeBackupSchema = createVolumeBackupSchema.extend({
|
||||
|
||||
Reference in New Issue
Block a user