mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-20 13:25:32 +02:00
Enhance backup functionality by introducing support for compose backups. Update backup schema to include serviceName and backupType fields. Modify related components to handle new backup types and integrate service selection for compose backups. Update API routes and database schema accordingly.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
@@ -31,42 +32,59 @@ import {
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { DatabaseZap, PlusIcon, RefreshCw } from "lucide-react";
|
||||
import { CheckIcon, ChevronsUpDown } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
type CacheType = "cache" | "fetch";
|
||||
|
||||
const AddPostgresBackup1Schema = z.object({
|
||||
destinationId: z.string().min(1, "Destination required"),
|
||||
schedule: z.string().min(1, "Schedule (Cron) required"),
|
||||
// .regex(
|
||||
// new RegExp(
|
||||
// /^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))$/,
|
||||
// ),
|
||||
// "Invalid Cron",
|
||||
// ),
|
||||
prefix: z.string().min(1, "Prefix required"),
|
||||
enabled: z.boolean(),
|
||||
database: z.string().min(1, "Database required"),
|
||||
keepLatestCount: z.coerce.number().optional(),
|
||||
serviceName: z.string().nullable(),
|
||||
});
|
||||
|
||||
type AddPostgresBackup = z.infer<typeof AddPostgresBackup1Schema>;
|
||||
|
||||
interface Props {
|
||||
databaseId: string;
|
||||
id: string;
|
||||
databaseType: "postgres" | "mariadb" | "mysql" | "mongo" | "web-server";
|
||||
refetch: () => void;
|
||||
backupType: "database" | "compose";
|
||||
}
|
||||
|
||||
export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => {
|
||||
export const AddBackup = ({
|
||||
id,
|
||||
databaseType,
|
||||
refetch,
|
||||
backupType = "database",
|
||||
}: Props) => {
|
||||
const { data, isLoading } = api.destination.all.useQuery();
|
||||
const [cacheType, setCacheType] = useState<CacheType>("cache");
|
||||
|
||||
const { mutateAsync: createBackup, isLoading: isCreatingPostgresBackup } =
|
||||
api.backup.create.useMutation();
|
||||
@@ -79,10 +97,28 @@ export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => {
|
||||
prefix: "/",
|
||||
schedule: "",
|
||||
keepLatestCount: undefined,
|
||||
serviceName: null,
|
||||
},
|
||||
resolver: zodResolver(AddPostgresBackup1Schema),
|
||||
});
|
||||
|
||||
const {
|
||||
data: services,
|
||||
isFetching: isLoadingServices,
|
||||
error: errorServices,
|
||||
refetch: refetchServices,
|
||||
} = api.compose.loadServices.useQuery(
|
||||
{
|
||||
composeId: id,
|
||||
type: cacheType,
|
||||
},
|
||||
{
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false,
|
||||
enabled: backupType === "compose",
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
database: databaseType === "web-server" ? "dokploy" : "",
|
||||
@@ -91,32 +127,45 @@ export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => {
|
||||
prefix: "/",
|
||||
schedule: "",
|
||||
keepLatestCount: undefined,
|
||||
serviceName: null,
|
||||
});
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
||||
}, [form, form.reset, form.formState.isSubmitSuccessful, databaseType]);
|
||||
|
||||
const onSubmit = async (data: AddPostgresBackup) => {
|
||||
if (backupType === "compose" && !data.serviceName) {
|
||||
form.setError("serviceName", {
|
||||
type: "manual",
|
||||
message: "Service name is required for compose backups",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const getDatabaseId =
|
||||
databaseType === "postgres"
|
||||
backupType === "compose"
|
||||
? {
|
||||
postgresId: databaseId,
|
||||
composeId: id,
|
||||
}
|
||||
: databaseType === "mariadb"
|
||||
: databaseType === "postgres"
|
||||
? {
|
||||
mariadbId: databaseId,
|
||||
postgresId: id,
|
||||
}
|
||||
: databaseType === "mysql"
|
||||
: databaseType === "mariadb"
|
||||
? {
|
||||
mysqlId: databaseId,
|
||||
mariadbId: id,
|
||||
}
|
||||
: databaseType === "mongo"
|
||||
: databaseType === "mysql"
|
||||
? {
|
||||
mongoId: databaseId,
|
||||
mysqlId: id,
|
||||
}
|
||||
: databaseType === "web-server"
|
||||
: databaseType === "mongo"
|
||||
? {
|
||||
userId: databaseId,
|
||||
mongoId: id,
|
||||
}
|
||||
: undefined;
|
||||
: databaseType === "web-server"
|
||||
? {
|
||||
userId: id,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
await createBackup({
|
||||
destinationId: data.destinationId,
|
||||
@@ -125,8 +174,10 @@ export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => {
|
||||
enabled: data.enabled,
|
||||
database: data.database,
|
||||
keepLatestCount: data.keepLatestCount,
|
||||
databaseType,
|
||||
databaseType: databaseType,
|
||||
serviceName: data.serviceName,
|
||||
...getDatabaseId,
|
||||
backupType,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Backup Created");
|
||||
@@ -157,6 +208,11 @@ export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => {
|
||||
className="grid w-full gap-4"
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{errorServices && (
|
||||
<AlertBlock type="warning" className="[overflow-wrap:anywhere]">
|
||||
{errorServices?.message}
|
||||
</AlertBlock>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="destinationId"
|
||||
@@ -232,6 +288,110 @@ export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{backupType === "compose" && (
|
||||
<div className="flex flex-row items-end w-full gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="serviceName"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormLabel>Service Name</FormLabel>
|
||||
<div className="flex gap-2">
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
value={field.value || undefined}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a service name" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
|
||||
<SelectContent>
|
||||
{services?.map((service, index) => (
|
||||
<SelectItem
|
||||
value={service}
|
||||
key={`${service}-${index}`}
|
||||
>
|
||||
{service}
|
||||
</SelectItem>
|
||||
))}
|
||||
{(!services || services.length === 0) && (
|
||||
<SelectItem value="none" disabled>
|
||||
Empty
|
||||
</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
isLoading={isLoadingServices}
|
||||
onClick={() => {
|
||||
if (cacheType === "fetch") {
|
||||
refetchServices();
|
||||
} else {
|
||||
setCacheType("fetch");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RefreshCw className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="left"
|
||||
sideOffset={5}
|
||||
className="max-w-[10rem]"
|
||||
>
|
||||
<p>
|
||||
Fetch: Will clone the repository and load the
|
||||
services
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
isLoading={isLoadingServices}
|
||||
onClick={() => {
|
||||
if (cacheType === "cache") {
|
||||
refetchServices();
|
||||
} else {
|
||||
setCacheType("cache");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DatabaseZap className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="left"
|
||||
sideOffset={5}
|
||||
className="max-w-[10rem]"
|
||||
>
|
||||
<p>
|
||||
Cache: If you previously deployed this
|
||||
compose, it will read the services from the
|
||||
last deployment/fetch from the repository
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="database"
|
||||
|
||||
@@ -46,7 +46,7 @@ import type { ServiceType } from "../../application/advanced/show-resources";
|
||||
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
||||
|
||||
interface Props {
|
||||
databaseId: string;
|
||||
id: string;
|
||||
databaseType: Exclude<ServiceType, "application" | "redis"> | "web-server";
|
||||
serverId?: string | null;
|
||||
}
|
||||
@@ -85,11 +85,7 @@ const formatBytes = (bytes: number): string => {
|
||||
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
export const RestoreBackup = ({
|
||||
databaseId,
|
||||
databaseType,
|
||||
serverId,
|
||||
}: Props) => {
|
||||
export const RestoreBackup = ({ id, databaseType, serverId }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
|
||||
@@ -136,7 +132,7 @@ export const RestoreBackup = ({
|
||||
|
||||
api.backup.restoreBackupWithLogs.useSubscription(
|
||||
{
|
||||
databaseId,
|
||||
databaseId: id,
|
||||
databaseType,
|
||||
databaseName: form.watch("databaseName"),
|
||||
backupFile: form.watch("backupFile"),
|
||||
|
||||
@@ -22,41 +22,60 @@ import type { ServiceType } from "../../application/advanced/show-resources";
|
||||
import { AddBackup } from "./add-backup";
|
||||
import { RestoreBackup } from "./restore-backup";
|
||||
import { UpdateBackup } from "./update-backup";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
type: Exclude<ServiceType, "application" | "redis"> | "web-server";
|
||||
databaseType: Exclude<ServiceType, "application" | "redis"> | "web-server";
|
||||
backupType?: "database" | "compose";
|
||||
}
|
||||
export const ShowBackups = ({ id, type }: Props) => {
|
||||
export const ShowBackups = ({
|
||||
id,
|
||||
databaseType,
|
||||
backupType = "database",
|
||||
}: Props) => {
|
||||
const [activeManualBackup, setActiveManualBackup] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const queryMap = {
|
||||
postgres: () =>
|
||||
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
||||
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||
mariadb: () =>
|
||||
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||
"web-server": () => api.user.getBackups.useQuery(),
|
||||
};
|
||||
const queryMap =
|
||||
backupType === "database"
|
||||
? {
|
||||
postgres: () =>
|
||||
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
|
||||
mysql: () =>
|
||||
api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
|
||||
mariadb: () =>
|
||||
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
|
||||
mongo: () =>
|
||||
api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
|
||||
"web-server": () => api.user.getBackups.useQuery(),
|
||||
}
|
||||
: {
|
||||
compose: () =>
|
||||
api.compose.one.useQuery({ composeId: id }, { enabled: !!id }),
|
||||
};
|
||||
const { data } = api.destination.all.useQuery();
|
||||
const { data: postgres, refetch } = queryMap[type]
|
||||
? queryMap[type]()
|
||||
const key = backupType === "database" ? databaseType : "compose";
|
||||
const query = queryMap[key as keyof typeof queryMap];
|
||||
const { data: postgres, refetch } = query
|
||||
? query()
|
||||
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
|
||||
|
||||
console.log(postgres);
|
||||
|
||||
const mutationMap = {
|
||||
postgres: () => api.backup.manualBackupPostgres.useMutation(),
|
||||
mysql: () => api.backup.manualBackupMySql.useMutation(),
|
||||
mariadb: () => api.backup.manualBackupMariadb.useMutation(),
|
||||
mongo: () => api.backup.manualBackupMongo.useMutation(),
|
||||
"web-server": () => api.backup.manualBackupWebServer.useMutation(),
|
||||
compose: () => api.backup.manualBackupCompose.useMutation(),
|
||||
};
|
||||
|
||||
const { mutateAsync: manualBackup, isLoading: isManualBackup } = mutationMap[
|
||||
type
|
||||
databaseType
|
||||
]
|
||||
? mutationMap[type]()
|
||||
? mutationMap[databaseType]()
|
||||
: api.backup.manualBackupMongo.useMutation();
|
||||
|
||||
const { mutateAsync: deleteBackup, isLoading: isRemoving } =
|
||||
@@ -78,16 +97,17 @@ export const ShowBackups = ({ id, type }: Props) => {
|
||||
|
||||
{postgres && postgres?.backups?.length > 0 && (
|
||||
<div className="flex flex-col lg:flex-row gap-4 w-full lg:w-auto">
|
||||
{type !== "web-server" && (
|
||||
{databaseType !== "web-server" && (
|
||||
<AddBackup
|
||||
databaseId={id}
|
||||
databaseType={type}
|
||||
id={id}
|
||||
databaseType={databaseType}
|
||||
backupType={backupType}
|
||||
refetch={refetch}
|
||||
/>
|
||||
)}
|
||||
<RestoreBackup
|
||||
databaseId={id}
|
||||
databaseType={type}
|
||||
id={id}
|
||||
databaseType={databaseType}
|
||||
serverId={"serverId" in postgres ? postgres.serverId : undefined}
|
||||
/>
|
||||
</div>
|
||||
@@ -110,7 +130,7 @@ export const ShowBackups = ({ id, type }: Props) => {
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
{postgres?.backups.length === 0 ? (
|
||||
<div className="flex w-full flex-col items-center justify-center gap-3 pt-10">
|
||||
<DatabaseBackup className="size-8 text-muted-foreground" />
|
||||
@@ -119,13 +139,14 @@ export const ShowBackups = ({ id, type }: Props) => {
|
||||
</span>
|
||||
<div className="flex flex-col sm:flex-row gap-4 w-full sm:w-auto">
|
||||
<AddBackup
|
||||
databaseId={id}
|
||||
databaseType={type}
|
||||
id={id}
|
||||
databaseType={databaseType}
|
||||
backupType={backupType}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<RestoreBackup
|
||||
databaseId={id}
|
||||
databaseType={type}
|
||||
id={id}
|
||||
databaseType={databaseType}
|
||||
serverId={
|
||||
"serverId" in postgres ? postgres.serverId : undefined
|
||||
}
|
||||
@@ -133,12 +154,41 @@ export const ShowBackups = ({ id, type }: Props) => {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col pt-2">
|
||||
<div className="flex flex-col pt-2 gap-4">
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
{backupType === "compose" && (
|
||||
<AlertBlock type="info">
|
||||
Deploy is required to apply changes after creating or
|
||||
updating a backup.
|
||||
</AlertBlock>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-6">
|
||||
{postgres?.backups.map((backup) => (
|
||||
<div key={backup.backupId}>
|
||||
<div className="flex w-full flex-col md:flex-row md:items-center justify-between gap-4 md:gap-10 border rounded-lg p-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 xl:grid-cols-6 flex-col gap-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 xl:grid-cols-8 flex-col gap-8">
|
||||
{backup.backupType === "compose" && (
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">
|
||||
Service Name
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{backup.serviceName}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">
|
||||
Database Type
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{backup.databaseType}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="font-medium">Destination</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
@@ -31,16 +32,37 @@ import {
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { CheckIcon, ChevronsUpDown, PenBoxIcon } from "lucide-react";
|
||||
import {
|
||||
CheckIcon,
|
||||
ChevronsUpDown,
|
||||
DatabaseZap,
|
||||
PenBoxIcon,
|
||||
RefreshCw,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
type CacheType = "cache" | "fetch";
|
||||
|
||||
const UpdateBackupSchema = z.object({
|
||||
destinationId: z.string().min(1, "Destination required"),
|
||||
schedule: z.string().min(1, "Schedule (Cron) required"),
|
||||
@@ -48,6 +70,7 @@ const UpdateBackupSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
database: z.string().min(1, "Database required"),
|
||||
keepLatestCount: z.coerce.number().optional(),
|
||||
serviceName: z.string().nullable(),
|
||||
});
|
||||
|
||||
type UpdateBackup = z.infer<typeof UpdateBackupSchema>;
|
||||
@@ -59,6 +82,7 @@ interface Props {
|
||||
|
||||
export const UpdateBackup = ({ backupId, refetch }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [cacheType, setCacheType] = useState<CacheType>("cache");
|
||||
const { data, isLoading } = api.destination.all.useQuery();
|
||||
const { data: backup } = api.backup.one.useQuery(
|
||||
{
|
||||
@@ -69,6 +93,24 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
data: services,
|
||||
isFetching: isLoadingServices,
|
||||
error: errorServices,
|
||||
refetch: refetchServices,
|
||||
} = api.compose.loadServices.useQuery(
|
||||
{
|
||||
composeId: backup?.composeId || "",
|
||||
type: cacheType,
|
||||
},
|
||||
{
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false,
|
||||
enabled:
|
||||
isOpen && backup?.backupType === "compose" && !!backup?.composeId,
|
||||
},
|
||||
);
|
||||
|
||||
const { mutateAsync, isLoading: isLoadingUpdate } =
|
||||
api.backup.update.useMutation();
|
||||
|
||||
@@ -80,6 +122,7 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
|
||||
prefix: "/",
|
||||
schedule: "",
|
||||
keepLatestCount: undefined,
|
||||
serviceName: null,
|
||||
},
|
||||
resolver: zodResolver(UpdateBackupSchema),
|
||||
});
|
||||
@@ -92,6 +135,7 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
|
||||
enabled: backup.enabled || false,
|
||||
prefix: backup.prefix,
|
||||
schedule: backup.schedule,
|
||||
serviceName: backup.serviceName || null,
|
||||
keepLatestCount: backup.keepLatestCount
|
||||
? Number(backup.keepLatestCount)
|
||||
: undefined,
|
||||
@@ -100,6 +144,14 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
|
||||
}, [form, form.reset, backup]);
|
||||
|
||||
const onSubmit = async (data: UpdateBackup) => {
|
||||
if (backup?.backupType === "compose" && !data.serviceName) {
|
||||
form.setError("serviceName", {
|
||||
type: "manual",
|
||||
message: "Service name is required for compose backups",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await mutateAsync({
|
||||
backupId,
|
||||
destinationId: data.destinationId,
|
||||
@@ -107,6 +159,7 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
|
||||
schedule: data.schedule,
|
||||
enabled: data.enabled,
|
||||
database: data.database,
|
||||
serviceName: data.serviceName,
|
||||
keepLatestCount: data.keepLatestCount as number | null,
|
||||
})
|
||||
.then(async () => {
|
||||
@@ -143,6 +196,11 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
|
||||
className="grid w-full gap-4"
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{errorServices && (
|
||||
<AlertBlock type="warning" className="[overflow-wrap:anywhere]">
|
||||
{errorServices?.message}
|
||||
</AlertBlock>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="destinationId"
|
||||
@@ -218,6 +276,110 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{backup?.backupType === "compose" && (
|
||||
<div className="flex flex-row items-end w-full gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="serviceName"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormLabel>Service Name</FormLabel>
|
||||
<div className="flex gap-2">
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
value={field.value || undefined}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a service name" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
|
||||
<SelectContent>
|
||||
{services?.map((service, index) => (
|
||||
<SelectItem
|
||||
value={service}
|
||||
key={`${service}-${index}`}
|
||||
>
|
||||
{service}
|
||||
</SelectItem>
|
||||
))}
|
||||
{(!services || services.length === 0) && (
|
||||
<SelectItem value="none" disabled>
|
||||
Empty
|
||||
</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
isLoading={isLoadingServices}
|
||||
onClick={() => {
|
||||
if (cacheType === "fetch") {
|
||||
refetchServices();
|
||||
} else {
|
||||
setCacheType("fetch");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RefreshCw className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="left"
|
||||
sideOffset={5}
|
||||
className="max-w-[10rem]"
|
||||
>
|
||||
<p>
|
||||
Fetch: Will clone the repository and load the
|
||||
services
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
isLoading={isLoadingServices}
|
||||
onClick={() => {
|
||||
if (cacheType === "cache") {
|
||||
refetchServices();
|
||||
} else {
|
||||
setCacheType("cache");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DatabaseZap className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="left"
|
||||
sideOffset={5}
|
||||
className="max-w-[10rem]"
|
||||
>
|
||||
<p>
|
||||
Cache: If you previously deployed this
|
||||
compose, it will read the services from the
|
||||
last deployment/fetch from the repository
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="database"
|
||||
|
||||
Reference in New Issue
Block a user