Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { InputArray } from "@/components/ui/input-array";
import {
Popover,
PopoverContent,
Expand Down Expand Up @@ -106,6 +107,7 @@ const Schema = z
.optional(),
})
.optional(),
additionalOptions: z.array(z.string()).nullable(),
})
.superRefine((data, ctx) => {
if (data.backupType === "compose" && !data.databaseType) {
Expand Down Expand Up @@ -219,6 +221,7 @@ export const HandleBackup = ({
databaseType: backupType === "compose" ? undefined : databaseType,
backupType: backupType,
metadata: {},
additionalOptions: [],
},
resolver: zodResolver(Schema),
});
Expand Down Expand Up @@ -256,6 +259,7 @@ export const HandleBackup = ({
databaseType: backup?.databaseType ?? databaseType,
backupType: backup?.backupType ?? backupType,
metadata: backup?.metadata ?? {},
additionalOptions: backup?.additionalOptions ?? [],
});
}, [form, form.reset, backupId, backup]);

Expand Down Expand Up @@ -300,6 +304,7 @@ export const HandleBackup = ({
backupId: backupId ?? "",
backupType,
metadata: data.metadata,
additionalOptions: data.additionalOptions,
})
.then(async () => {
toast.success(`Backup ${backupId ? "Updated" : "Created"}`);
Expand Down Expand Up @@ -753,6 +758,25 @@ export const HandleBackup = ({
)}
</>
)}
<FormField
control={form.control}
name="additionalOptions"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Additional Options</FormLabel>
<FormControl>
<InputArray placeholder={"dokploy/"} {...field} />
</FormControl>
<FormDescription>
Use if you want to pass additional options to the backup command
</FormDescription>

<FormMessage />
</FormItem>
);
}}
/>
</div>
<DialogFooter>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { InputArray } from "@/components/ui/input-array";
import {
Popover,
PopoverContent,
Expand Down Expand Up @@ -130,6 +131,7 @@ const RestoreBackupSchema = z
serviceName: z.string().optional(),
})
.optional(),
additionalOptions: z.array(z.string()).nullable(),
})
.superRefine((data, ctx) => {
if (data.backupType === "compose" && !data.databaseType) {
Expand Down Expand Up @@ -228,13 +230,15 @@ export const RestoreBackup = ({
backupType === "compose" ? ("postgres" as DatabaseType) : databaseType,
backupType: backupType,
metadata: {},
additionalOptions: [],
},
resolver: zodResolver(RestoreBackupSchema),
});

const destionationId = form.watch("destinationId");
const currentDatabaseType = form.watch("databaseType");
const metadata = form.watch("metadata");
const additionalOptions = form.watch("additionalOptions");

const debouncedSetSearch = _.debounce((value: string) => {
setDebouncedSearchTerm(value);
Expand Down Expand Up @@ -269,6 +273,7 @@ export const RestoreBackup = ({
destinationId: form.watch("destinationId"),
backupType: backupType,
metadata: metadata,
additionalOptions: additionalOptions,
},
{
enabled: isDeploying,
Expand Down Expand Up @@ -788,6 +793,26 @@ export const RestoreBackup = ({
</>
)}

<FormField
control={form.control}
name="additionalOptions"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Additional Options</FormLabel>
<FormControl>
<InputArray placeholder={"dokploy/"} {...field} />
</FormControl>
<FormDescription>
Use if you want to pass additional options to the backup command
</FormDescription>

<FormMessage />
</FormItem>
);
}}
/>

<DialogFooter>
<Button
isLoading={isDeploying}
Expand Down
78 changes: 78 additions & 0 deletions apps/dokploy/components/ui/input-array.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import * as React from "react";
import { PlusIcon, TrashIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { Input, type InputProps } from "@/components/ui/input";

export interface InputArrayProps
extends Omit<InputProps, "value" | "onChange"> {
value?: string[] | null;
onChange?: (value: string[]) => void;
}

const InputArray = React.forwardRef<
HTMLInputElement,
InputArrayProps
>(({ className, errorMessage, value, onChange, disabled, ...props }, ref) => {
if (!value) value = [];
const updateAt = (index: number, newValue: string) => {
const next = [...value];
next[index] = newValue;
onChange?.(next);
};

const addItem = () => {
onChange?.([...value, ""]);
};

const removeItem = (index: number) => {
onChange?.(value.filter((_, i) => i !== index));
};

return (
<>
<div className="flex w-full flex-col gap-2">
{value.map((item, index) => (
<div key={index} className="flex items-center gap-2">
<Input
ref={index === value.length - 1 ? ref : undefined}
value={item}
disabled={disabled}
className={cn("flex-1", className)}
onChange={(e) => updateAt(index, e.target.value)}
{...props}
/>

<button
type="button"
disabled={disabled}
onClick={() => removeItem(index)}
className="flex h-10 w-10 items-center justify-center rounded-md border border-input text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50"
>
<TrashIcon className="h-4 w-4" />
</button>
</div>
))}

<button
type="button"
onClick={addItem}
disabled={disabled}
className="flex h-10 items-center gap-2 rounded-md border border-dashed border-input px-3 text-sm text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50"
>
<PlusIcon className="h-4 w-4" />
Add
</button>
</div>

{errorMessage && (
<span className="text-sm text-red-600 text-secondary-foreground">
{errorMessage}
</span>
)}
</>
);
});

InputArray.displayName = "InputArray";

export { InputArray };
36 changes: 21 additions & 15 deletions packages/server/src/db/schema/backups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,23 +78,25 @@ export const backups = pgTable("backup", {
// Only for compose backups
metadata: jsonb("metadata").$type<
| {
postgres?: {
databaseUser: string;
};
mariadb?: {
databaseUser: string;
databasePassword: string;
};
mongo?: {
databaseUser: string;
databasePassword: string;
};
mysql?: {
databaseRootPassword: string;
};
}
postgres?: {
databaseUser: string;
};
mariadb?: {
databaseUser: string;
databasePassword: string;
};
mongo?: {
databaseUser: string;
databasePassword: string;
};
mysql?: {
databaseRootPassword: string;
};
}
| undefined
>(),
// additional options to be passed to the backup command
additionalOptions: jsonb("additionalOptions").$type<string[]>().default([]),
});

export const backupsRelations = relations(backups, ({ one, many }) => ({
Expand Down Expand Up @@ -144,6 +146,7 @@ const createSchema = createInsertSchema(backups, {
mongoId: z.string().optional(),
userId: z.string().optional(),
metadata: z.any().optional(),
additionalOptions: z.array(z.string()).optional(),
});

export const apiCreateBackup = createSchema.pick({
Expand All @@ -163,6 +166,7 @@ export const apiCreateBackup = createSchema.pick({
composeId: true,
serviceName: true,
metadata: true,
additionalOptions: true,
});

export const apiFindOneBackup = createSchema
Expand All @@ -189,6 +193,7 @@ export const apiUpdateBackup = createSchema
serviceName: true,
metadata: true,
databaseType: true,
additionalOptions: true,
})
.required();

Expand Down Expand Up @@ -226,4 +231,5 @@ export const apiRestoreBackup = z.object({
.optional(),
})
.optional(),
additionalOptions: z.array(z.string()).optional(),
});
Loading