mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
317 lines
10 KiB
TypeScript
317 lines
10 KiB
TypeScript
import { DialogAction } from "@/components/shared/dialog-action";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip";
|
|
import { api } from "@/utils/api";
|
|
import { Database, DatabaseBackup, Play, Trash2 } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { useState } from "react";
|
|
import { toast } from "sonner";
|
|
import type { ServiceType } from "../../application/advanced/show-resources";
|
|
import { RestoreBackup } from "./restore-backup";
|
|
import { AlertBlock } from "@/components/shared/alert-block";
|
|
import { HandleBackup } from "./handle-backup";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface Props {
|
|
id: string;
|
|
databaseType: Exclude<ServiceType, "application" | "redis"> | "web-server";
|
|
backupType?: "database" | "compose";
|
|
}
|
|
export const ShowBackups = ({
|
|
id,
|
|
databaseType,
|
|
backupType = "database",
|
|
}: Props) => {
|
|
const [activeManualBackup, setActiveManualBackup] = useState<
|
|
string | undefined
|
|
>();
|
|
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 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 });
|
|
|
|
const mutationMap =
|
|
backupType === "database"
|
|
? {
|
|
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 mutation = mutationMap[key as keyof typeof mutationMap];
|
|
|
|
const { mutateAsync: manualBackup, isLoading: isManualBackup } = mutation
|
|
? mutation
|
|
: api.backup.manualBackupMongo.useMutation();
|
|
|
|
const { mutateAsync: deleteBackup, isLoading: isRemoving } =
|
|
api.backup.remove.useMutation();
|
|
|
|
return (
|
|
<Card className="bg-background">
|
|
<CardHeader className="flex flex-row justify-between gap-4 flex-wrap">
|
|
<div className="flex flex-col gap-0.5">
|
|
<CardTitle className="text-xl flex flex-row gap-2">
|
|
<Database className="size-6 text-muted-foreground" />
|
|
Backups
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Add backups to your database to save the data to a different
|
|
provider.
|
|
</CardDescription>
|
|
</div>
|
|
|
|
{postgres && postgres?.backups?.length > 0 && (
|
|
<div className="flex flex-col lg:flex-row gap-4 w-full lg:w-auto">
|
|
{databaseType !== "web-server" && (
|
|
<HandleBackup
|
|
id={id}
|
|
databaseType={databaseType}
|
|
backupType={backupType}
|
|
refetch={refetch}
|
|
/>
|
|
)}
|
|
<RestoreBackup
|
|
id={id}
|
|
databaseType={databaseType}
|
|
backupType={backupType}
|
|
serverId={"serverId" in postgres ? postgres.serverId : undefined}
|
|
/>
|
|
</div>
|
|
)}
|
|
</CardHeader>
|
|
<CardContent className="flex flex-col gap-4">
|
|
{data?.length === 0 ? (
|
|
<div className="flex flex-col items-center gap-3">
|
|
<DatabaseBackup className="size-8 text-muted-foreground" />
|
|
<span className="text-base text-muted-foreground text-center">
|
|
To create a backup it is required to set at least 1 provider.
|
|
Please, go to{" "}
|
|
<Link
|
|
href="/dashboard/settings/destinations"
|
|
className="text-foreground"
|
|
>
|
|
S3 Destinations
|
|
</Link>{" "}
|
|
to do so.
|
|
</span>
|
|
</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" />
|
|
<span className="text-base text-muted-foreground">
|
|
No backups configured
|
|
</span>
|
|
<div className="flex flex-col sm:flex-row gap-4 w-full sm:w-auto">
|
|
<HandleBackup
|
|
id={id}
|
|
databaseType={databaseType}
|
|
backupType={backupType}
|
|
refetch={refetch}
|
|
/>
|
|
<RestoreBackup
|
|
id={id}
|
|
databaseType={databaseType}
|
|
serverId={
|
|
"serverId" in postgres ? postgres.serverId : undefined
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<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 the service name in the 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={cn(
|
|
"grid grid-cols-1 md:grid-cols-3 xl:grid-cols-6 flex-col gap-8",
|
|
backup.backupType === "compose" && "xl:grid-cols-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">
|
|
{backup.destination.name}
|
|
</span>
|
|
</div>
|
|
<div className="flex flex-col gap-1">
|
|
<span className="font-medium">Database</span>
|
|
<span className="text-sm text-muted-foreground">
|
|
{backup.database}
|
|
</span>
|
|
</div>
|
|
<div className="flex flex-col gap-1">
|
|
<span className="font-medium">Scheduled</span>
|
|
<span className="text-sm text-muted-foreground">
|
|
{backup.schedule}
|
|
</span>
|
|
</div>
|
|
<div className="flex flex-col gap-1">
|
|
<span className="font-medium">Prefix Storage</span>
|
|
<span className="text-sm text-muted-foreground">
|
|
{backup.prefix}
|
|
</span>
|
|
</div>
|
|
<div className="flex flex-col gap-1">
|
|
<span className="font-medium">Enabled</span>
|
|
<span className="text-sm text-muted-foreground">
|
|
{backup.enabled ? "Yes" : "No"}
|
|
</span>
|
|
</div>
|
|
<div className="flex flex-col gap-1">
|
|
<span className="font-medium">Keep Latest</span>
|
|
<span className="text-sm text-muted-foreground">
|
|
{backup.keepLatestCount || "All"}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-row gap-4">
|
|
<TooltipProvider delayDuration={0}>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
isLoading={
|
|
isManualBackup &&
|
|
activeManualBackup === backup.backupId
|
|
}
|
|
onClick={async () => {
|
|
setActiveManualBackup(backup.backupId);
|
|
await manualBackup({
|
|
backupId: backup.backupId as string,
|
|
})
|
|
.then(async () => {
|
|
toast.success(
|
|
"Manual Backup Successful",
|
|
);
|
|
})
|
|
.catch(() => {
|
|
toast.error(
|
|
"Error creating the manual backup",
|
|
);
|
|
});
|
|
setActiveManualBackup(undefined);
|
|
}}
|
|
>
|
|
<Play className="size-5 text-muted-foreground" />
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent>Run Manual Backup</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
|
|
<HandleBackup
|
|
backupType={backup.backupType}
|
|
backupId={backup.backupId}
|
|
refetch={refetch}
|
|
/>
|
|
<DialogAction
|
|
title="Delete Backup"
|
|
description="Are you sure you want to delete this backup?"
|
|
type="destructive"
|
|
onClick={async () => {
|
|
await deleteBackup({
|
|
backupId: backup.backupId,
|
|
})
|
|
.then(() => {
|
|
refetch();
|
|
toast.success("Backup deleted successfully");
|
|
})
|
|
.catch(() => {
|
|
toast.error("Error deleting backup");
|
|
});
|
|
}}
|
|
>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="group hover:bg-red-500/10"
|
|
isLoading={isRemoving}
|
|
>
|
|
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
|
</Button>
|
|
</DialogAction>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|