diff --git a/apps/dokploy/__test__/drop/drop.test.test.ts b/apps/dokploy/__test__/drop/drop.test.test.ts
index f5b68c1df..74c803655 100644
--- a/apps/dokploy/__test__/drop/drop.test.test.ts
+++ b/apps/dokploy/__test__/drop/drop.test.test.ts
@@ -27,6 +27,7 @@ if (typeof window === "undefined") {
const baseApp: ApplicationNested = {
applicationId: "",
herokuVersion: "",
+ cleanCache: false,
watchPaths: [],
applicationStatus: "done",
appName: "",
diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts
index a64103ff3..74b0e265b 100644
--- a/apps/dokploy/__test__/traefik/traefik.test.ts
+++ b/apps/dokploy/__test__/traefik/traefik.test.ts
@@ -7,6 +7,7 @@ import { expect, test } from "vitest";
const baseApp: ApplicationNested = {
applicationId: "",
herokuVersion: "",
+ cleanCache: false,
applicationStatus: "done",
appName: "",
autoDeploy: true,
diff --git a/apps/dokploy/components/dashboard/application/general/show.tsx b/apps/dokploy/components/dashboard/application/general/show.tsx
index 41961c152..c917d7ab7 100644
--- a/apps/dokploy/components/dashboard/application/general/show.tsx
+++ b/apps/dokploy/components/dashboard/application/general/show.tsx
@@ -16,8 +16,8 @@ import {
Ban,
CheckCircle2,
Hammer,
- HelpCircle,
RefreshCcw,
+ Rocket,
Terminal,
} from "lucide-react";
import { useRouter } from "next/router";
@@ -55,7 +55,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
Deploy Settings
-
+
{
- Deploy
-
+
+
+ Deploy
+
@@ -114,9 +116,24 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
});
}}
>
-
- Reload
-
+
+
+
+
+
+ Reload
+
+
+
+
+ Reload the application without rebuilding it
+
+
+
{
- Rebuild
-
-
+
+
+ Rebuild
+
@@ -180,13 +198,14 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
- Start
-
-
+
+
+ Start
+
@@ -219,13 +238,14 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
- Stop
-
-
+
+
+ Stop
+
@@ -241,15 +261,18 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
-
-
+
+
Open Terminal
Autodeploy
{
await update({
@@ -264,14 +287,14 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
toast.error("Error updating Auto Deploy");
});
}}
- className="flex flex-row gap-2 items-center"
+ className="flex flex-row gap-2 items-center data-[state=checked]:bg-primary"
/>
Clean Cache
{
await update({
@@ -286,7 +309,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
toast.error("Error updating Clean Cache");
});
}}
- className="flex flex-row gap-2 items-center"
+ className="flex flex-row gap-2 items-center data-[state=checked]:bg-primary"
/>
diff --git a/apps/dokploy/components/dashboard/compose/general/actions.tsx b/apps/dokploy/components/dashboard/compose/general/actions.tsx
index de5aa50df..ee619387e 100644
--- a/apps/dokploy/components/dashboard/compose/general/actions.tsx
+++ b/apps/dokploy/components/dashboard/compose/general/actions.tsx
@@ -8,8 +8,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
-import * as TooltipPrimitive from "@radix-ui/react-tooltip";
-import { Ban, CheckCircle2, Hammer, HelpCircle, Terminal } from "lucide-react";
+import { Ban, CheckCircle2, Hammer, HelpCircle, Terminal, RefreshCcw, Rocket } from "lucide-react";
import { useRouter } from "next/router";
import { toast } from "sonner";
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
@@ -34,7 +33,7 @@ export const ComposeActions = ({ composeId }: Props) => {
api.compose.stop.useMutation();
return (
-
+
{
- Deploy
-
+
+
+ Deploy
+
@@ -74,36 +75,37 @@ export const ComposeActions = ({ composeId }: Props) => {
{
await redeploy({
composeId: composeId,
})
.then(() => {
- toast.success("Compose rebuilt successfully");
+ toast.success("Compose reloaded successfully");
refetch();
})
.catch(() => {
- toast.error("Error rebuilding compose");
+ toast.error("Error reloading compose");
});
}}
>
- Rebuild
-
-
+
+
+ Reload
+
- Only rebuilds the compose without downloading new code
+ Reload the compose without rebuilding it
@@ -131,13 +133,14 @@ export const ComposeActions = ({ composeId }: Props) => {
- Start
-
-
+
+
+ Start
+
@@ -169,13 +172,14 @@ export const ComposeActions = ({ composeId }: Props) => {
- Stop
-
-
+
+
+ Stop
+
@@ -191,15 +195,18 @@ export const ComposeActions = ({ composeId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
-
-
+
+
Open Terminal
Autodeploy
{
await update({
@@ -214,7 +221,7 @@ export const ComposeActions = ({ composeId }: Props) => {
toast.error("Error updating Auto Deploy");
});
}}
- className="flex flex-row gap-2 items-center"
+ className="flex flex-row gap-2 items-center data-[state=checked]:bg-primary"
/>
diff --git a/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx b/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx
new file mode 100644
index 000000000..c761fc701
--- /dev/null
+++ b/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx
@@ -0,0 +1,367 @@
+import { Button } from "@/components/ui/button";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+} from "@/components/ui/command";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import { cn } from "@/lib/utils";
+import { api } from "@/utils/api";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { CheckIcon, ChevronsUpDown, Copy, RotateCcw } from "lucide-react";
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import type { ServiceType } from "../../application/advanced/show-resources";
+import { debounce } from "lodash";
+import { Input } from "@/components/ui/input";
+import { type LogLine, parseLogs } from "../../docker/logs/utils";
+import { DrawerLogs } from "@/components/shared/drawer-logs";
+import { Badge } from "@/components/ui/badge";
+import copy from "copy-to-clipboard";
+import { toast } from "sonner";
+
+interface Props {
+ databaseId: string;
+ databaseType: Exclude;
+}
+
+const RestoreBackupSchema = z.object({
+ destinationId: z
+ .string({
+ required_error: "Please select a destination",
+ })
+ .min(1, {
+ message: "Destination is required",
+ }),
+ backupFile: z
+ .string({
+ required_error: "Please select a backup file",
+ })
+ .min(1, {
+ message: "Backup file is required",
+ }),
+ databaseName: z
+ .string({
+ required_error: "Please enter a database name",
+ })
+ .min(1, {
+ message: "Database name is required",
+ }),
+});
+
+type RestoreBackup = z.infer;
+
+export const RestoreBackup = ({ databaseId, databaseType }: Props) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [search, setSearch] = useState("");
+
+ const { data: destinations = [] } = api.destination.all.useQuery();
+
+ const form = useForm({
+ defaultValues: {
+ destinationId: "",
+ backupFile: "",
+ databaseName: "",
+ },
+ resolver: zodResolver(RestoreBackupSchema),
+ });
+
+ const destionationId = form.watch("destinationId");
+
+ const debouncedSetSearch = debounce((value: string) => {
+ setSearch(value);
+ }, 300);
+
+ const { data: files = [], isLoading } = api.backup.listBackupFiles.useQuery(
+ {
+ destinationId: destionationId,
+ search,
+ },
+ {
+ enabled: isOpen && !!destionationId,
+ },
+ );
+
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false);
+ const [filteredLogs, setFilteredLogs] = useState([]);
+ const [isDeploying, setIsDeploying] = useState(false);
+
+ // const { mutateAsync: restore, isLoading: isRestoring } =
+ // api.backup.restoreBackup.useMutation();
+
+ api.backup.restoreBackupWithLogs.useSubscription(
+ {
+ databaseId,
+ databaseType,
+ databaseName: form.watch("databaseName"),
+ backupFile: form.watch("backupFile"),
+ destinationId: form.watch("destinationId"),
+ },
+ {
+ enabled: isDeploying,
+ onData(log) {
+ if (!isDrawerOpen) {
+ setIsDrawerOpen(true);
+ }
+
+ if (log === "Restore completed successfully!") {
+ setIsDeploying(false);
+ }
+ const parsedLogs = parseLogs(log);
+ setFilteredLogs((prev) => [...prev, ...parsedLogs]);
+ },
+ onError(error) {
+ console.error("Restore logs error:", error);
+ setIsDeploying(false);
+ },
+ },
+ );
+
+ const onSubmit = async (_data: RestoreBackup) => {
+ setIsDeploying(true);
+ };
+
+ return (
+
+
+
+
+ Restore Backup
+
+
+
+
+
+
+ Restore Backup
+
+
+ Select a destination and search for backup files
+
+
+
+
+
+
+ {
+ setIsDrawerOpen(false);
+ setFilteredLogs([]);
+ setIsDeploying(false);
+ // refetch();
+ }}
+ filteredLogs={filteredLogs}
+ />
+
+
+ );
+};
diff --git a/apps/dokploy/components/dashboard/database/backups/show-backups.tsx b/apps/dokploy/components/dashboard/database/backups/show-backups.tsx
index f9835e69e..91c0387e8 100644
--- a/apps/dokploy/components/dashboard/database/backups/show-backups.tsx
+++ b/apps/dokploy/components/dashboard/database/backups/show-backups.tsx
@@ -21,6 +21,8 @@ import { toast } from "sonner";
import type { ServiceType } from "../../application/advanced/show-resources";
import { AddBackup } from "./add-backup";
import { UpdateBackup } from "./update-backup";
+import { RestoreBackup } from "./restore-backup";
+import { useState } from "react";
interface Props {
id: string;
@@ -71,7 +73,10 @@ export const ShowBackups = ({ id, type }: Props) => {
{postgres && postgres?.backups?.length > 0 && (
-
+
)}
@@ -98,11 +103,14 @@ export const ShowBackups = ({ id, type }: Props) => {
No backups configured
-
+
) : (
@@ -183,6 +191,7 @@ export const ShowBackups = ({ id, type }: Props) => {
Run Manual Backup
+
= ({
const wsUrl = `${protocol}//${
window.location.host
}/docker-container-logs?${params.toString()}`;
- console.log("Connecting to WebSocket:", wsUrl);
const ws = new WebSocket(wsUrl);
const resetNoDataTimeout = () => {
@@ -136,7 +135,6 @@ export const DockerLogsId: React.FC = ({
ws.close();
return;
}
- console.log("WebSocket connected");
resetNoDataTimeout();
};
diff --git a/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx b/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx
index f61d6f89a..9f5b63c31 100644
--- a/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx
+++ b/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx
@@ -27,146 +27,145 @@ import { toast } from "sonner";
import { z } from "zod";
const DockerProviderSchema = z.object({
- externalPort: z.preprocess((a) => {
- if (a !== null) {
- const parsed = Number.parseInt(z.string().parse(a), 10);
- return Number.isNaN(parsed) ? null : parsed;
- }
- return null;
- }, z
- .number()
- .gte(0, "Range must be 0 - 65535")
- .lte(65535, "Range must be 0 - 65535")
- .nullable()),
+ externalPort: z.preprocess((a) => {
+ if (a !== null) {
+ const parsed = Number.parseInt(z.string().parse(a), 10);
+ return Number.isNaN(parsed) ? null : parsed;
+ }
+ return null;
+ }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
});
type DockerProvider = z.infer;
interface Props {
- mariadbId: string;
+ mariadbId: string;
}
export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
- const { data: ip } = api.settings.getIp.useQuery();
- const { data, refetch } = api.mariadb.one.useQuery({ mariadbId });
- const { mutateAsync, isLoading } = api.mariadb.saveExternalPort.useMutation();
- const [connectionUrl, setConnectionUrl] = useState("");
- const getIp = data?.server?.ipAddress || ip;
- const form = useForm({
- defaultValues: {},
- resolver: zodResolver(DockerProviderSchema),
- });
+ const { data: ip } = api.settings.getIp.useQuery();
+ const { data, refetch } = api.mariadb.one.useQuery({ mariadbId });
+ const { mutateAsync, isLoading } = api.mariadb.saveExternalPort.useMutation();
+ const [connectionUrl, setConnectionUrl] = useState("");
+ const getIp = data?.server?.ipAddress || ip;
+ const form = useForm({
+ defaultValues: {},
+ resolver: zodResolver(DockerProviderSchema),
+ });
- useEffect(() => {
- if (data?.externalPort) {
- form.reset({
- externalPort: data.externalPort,
- });
- }
- }, [form.reset, data, form]);
+ useEffect(() => {
+ if (data?.externalPort) {
+ form.reset({
+ externalPort: data.externalPort,
+ });
+ }
+ }, [form.reset, data, form]);
- const onSubmit = async (values: DockerProvider) => {
- await mutateAsync({
- externalPort: values.externalPort,
- mariadbId,
- })
- .then(async () => {
- toast.success("External Port updated");
- await refetch();
- })
- .catch(() => {
- toast.error("Error saving the external port");
- });
- };
+ const onSubmit = async (values: DockerProvider) => {
+ await mutateAsync({
+ externalPort: values.externalPort,
+ mariadbId,
+ })
+ .then(async () => {
+ toast.success("External Port updated");
+ await refetch();
+ })
+ .catch(() => {
+ toast.error("Error saving the external port");
+ });
+ };
- useEffect(() => {
- const buildConnectionUrl = () => {
- const port = form.watch("externalPort") || data?.externalPort;
+ useEffect(() => {
+ const buildConnectionUrl = () => {
+ const port = form.watch("externalPort") || data?.externalPort;
- return `mariadb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
- };
+ return `mariadb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
+ };
- setConnectionUrl(buildConnectionUrl());
- }, [
- data?.appName,
- data?.externalPort,
- data?.databasePassword,
- form,
- data?.databaseName,
- data?.databaseUser,
- getIp,
- ]);
- return (
- <>
-
-
-
- External Credentials
-
- In order to make the database reachable trought internet is
- required to set a port, make sure the port is not used by another
- application or database
-
-
-
- {!getIp && (
-
- You need to set an IP address in your{" "}
-
- {data?.serverId
- ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
- : "Web Server -> Server -> Update Server IP"}
- {" "}
- to fix the database url connection.
-
- )}
-
+
+
+
+
+ >
+ );
};
diff --git a/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx b/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx
index 5c3bf8427..7e07babd9 100644
--- a/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx
+++ b/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx
@@ -11,11 +11,12 @@ import {
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
- Ban,
- CheckCircle2,
- HelpCircle,
- RefreshCcw,
- Terminal,
+ Ban,
+ CheckCircle2,
+ HelpCircle,
+ RefreshCcw,
+ Rocket,
+ Terminal,
} from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
@@ -92,12 +93,14 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
- Deploy
-
+
+
+ Deploy
+
@@ -107,6 +110,8 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
+
+
{
- Reload
-
-
+
+
+ Reload
+
@@ -144,7 +150,9 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
- {data?.applicationStatus === "idle" ? (
+
+ {data?.applicationStatus === "idle" ? (
+
{
- Start
-
-
+
+
+ Start
+
@@ -184,7 +193,9 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
- ) : (
+
+ ) : (
+
{
- Stop
-
-
+
+
+ Stop
+
@@ -220,15 +232,29 @@ export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
- )}
-
+
+ )}
-
-
- Open Terminal
+
+
+
+
+
+ Open Terminal
+
+
+
+
+ Open a terminal to the MariaDB container
+
+
+
diff --git a/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx b/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx
index 02a504418..b5ed9f863 100644
--- a/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx
+++ b/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx
@@ -27,145 +27,144 @@ import { toast } from "sonner";
import { z } from "zod";
const DockerProviderSchema = z.object({
- externalPort: z.preprocess((a) => {
- if (a !== null) {
- const parsed = Number.parseInt(z.string().parse(a), 10);
- return Number.isNaN(parsed) ? null : parsed;
- }
- return null;
- }, z
- .number()
- .gte(0, "Range must be 0 - 65535")
- .lte(65535, "Range must be 0 - 65535")
- .nullable()),
+ externalPort: z.preprocess((a) => {
+ if (a !== null) {
+ const parsed = Number.parseInt(z.string().parse(a), 10);
+ return Number.isNaN(parsed) ? null : parsed;
+ }
+ return null;
+ }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
});
type DockerProvider = z.infer;
interface Props {
- mongoId: string;
+ mongoId: string;
}
export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
- const { data: ip } = api.settings.getIp.useQuery();
- const { data, refetch } = api.mongo.one.useQuery({ mongoId });
- const { mutateAsync, isLoading } = api.mongo.saveExternalPort.useMutation();
- const [connectionUrl, setConnectionUrl] = useState("");
- const getIp = data?.server?.ipAddress || ip;
- const form = useForm({
- defaultValues: {},
- resolver: zodResolver(DockerProviderSchema),
- });
+ const { data: ip } = api.settings.getIp.useQuery();
+ const { data, refetch } = api.mongo.one.useQuery({ mongoId });
+ const { mutateAsync, isLoading } = api.mongo.saveExternalPort.useMutation();
+ const [connectionUrl, setConnectionUrl] = useState("");
+ const getIp = data?.server?.ipAddress || ip;
+ const form = useForm({
+ defaultValues: {},
+ resolver: zodResolver(DockerProviderSchema),
+ });
- useEffect(() => {
- if (data?.externalPort) {
- form.reset({
- externalPort: data.externalPort,
- });
- }
- }, [form.reset, data, form]);
+ useEffect(() => {
+ if (data?.externalPort) {
+ form.reset({
+ externalPort: data.externalPort,
+ });
+ }
+ }, [form.reset, data, form]);
- const onSubmit = async (values: DockerProvider) => {
- await mutateAsync({
- externalPort: values.externalPort,
- mongoId,
- })
- .then(async () => {
- toast.success("External Port updated");
- await refetch();
- })
- .catch(() => {
- toast.error("Error saving the external port");
- });
- };
+ const onSubmit = async (values: DockerProvider) => {
+ await mutateAsync({
+ externalPort: values.externalPort,
+ mongoId,
+ })
+ .then(async () => {
+ toast.success("External Port updated");
+ await refetch();
+ })
+ .catch(() => {
+ toast.error("Error saving the external port");
+ });
+ };
- useEffect(() => {
- const buildConnectionUrl = () => {
- const port = form.watch("externalPort") || data?.externalPort;
+ useEffect(() => {
+ const buildConnectionUrl = () => {
+ const port = form.watch("externalPort") || data?.externalPort;
- return `mongodb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}`;
- };
+ return `mongodb://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}`;
+ };
- setConnectionUrl(buildConnectionUrl());
- }, [
- data?.appName,
- data?.externalPort,
- data?.databasePassword,
- form,
- data?.databaseUser,
- getIp,
- ]);
+ setConnectionUrl(buildConnectionUrl());
+ }, [
+ data?.appName,
+ data?.externalPort,
+ data?.databasePassword,
+ form,
+ data?.databaseUser,
+ getIp,
+ ]);
- return (
- <>
-
-
-
- External Credentials
-
- In order to make the database reachable trought internet is
- required to set a port, make sure the port is not used by another
- application or database
-
-
-
- {!getIp && (
-
- You need to set an IP address in your{" "}
-
- {data?.serverId
- ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
- : "Web Server -> Server -> Update Server IP"}
- {" "}
- to fix the database url connection.
-
- )}
-
-
-
-
- {
- return (
-
- External Port (Internet)
-
-
-
-
-
- );
- }}
- />
-
-
- {!!data?.externalPort && (
-
- )}
+ return (
+ <>
+
+
+
+ External Credentials
+
+ In order to make the database reachable trought internet is
+ required to set a port, make sure the port is not used by another
+ application or database
+
+
+
+ {!getIp && (
+
+ You need to set an IP address in your{" "}
+
+ {data?.serverId
+ ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
+ : "Web Server -> Server -> Update Server IP"}
+ {" "}
+ to fix the database url connection.
+
+ )}
+
+
+
+
+ {
+ return (
+
+ External Port (Internet)
+
+
+
+
+
+ );
+ }}
+ />
+
+
+ {!!data?.externalPort && (
+
+ )}
-
-
- Save
-
-
-
-
-
-
-
- >
- );
+
+
+ Save
+
+
+
+
+
+
+
+ >
+ );
};
diff --git a/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx b/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx
index c2e073267..a53fa6376 100644
--- a/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx
+++ b/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx
@@ -3,246 +3,256 @@ import { DrawerLogs } from "@/components/shared/drawer-logs";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
- Ban,
- CheckCircle2,
- HelpCircle,
- RefreshCcw,
- Terminal,
+ Ban,
+ CheckCircle2,
+ HelpCircle,
+ RefreshCcw,
+ Rocket,
+ Terminal,
} from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
interface Props {
- mongoId: string;
+ mongoId: string;
}
export const ShowGeneralMongo = ({ mongoId }: Props) => {
- const { data, refetch } = api.mongo.one.useQuery(
- {
- mongoId,
- },
- { enabled: !!mongoId },
- );
+ const { data, refetch } = api.mongo.one.useQuery(
+ {
+ mongoId,
+ },
+ { enabled: !!mongoId }
+ );
- const { mutateAsync: reload, isLoading: isReloading } =
- api.mongo.reload.useMutation();
+ const { mutateAsync: reload, isLoading: isReloading } =
+ api.mongo.reload.useMutation();
- const { mutateAsync: start, isLoading: isStarting } =
- api.mongo.start.useMutation();
+ const { mutateAsync: start, isLoading: isStarting } =
+ api.mongo.start.useMutation();
- const { mutateAsync: stop, isLoading: isStopping } =
- api.mongo.stop.useMutation();
+ const { mutateAsync: stop, isLoading: isStopping } =
+ api.mongo.stop.useMutation();
- const [isDrawerOpen, setIsDrawerOpen] = useState(false);
- const [filteredLogs, setFilteredLogs] = useState([]);
- const [isDeploying, setIsDeploying] = useState(false);
- api.mongo.deployWithLogs.useSubscription(
- {
- mongoId: mongoId,
- },
- {
- enabled: isDeploying,
- onData(log) {
- if (!isDrawerOpen) {
- setIsDrawerOpen(true);
- }
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false);
+ const [filteredLogs, setFilteredLogs] = useState([]);
+ const [isDeploying, setIsDeploying] = useState(false);
+ api.mongo.deployWithLogs.useSubscription(
+ {
+ mongoId: mongoId,
+ },
+ {
+ enabled: isDeploying,
+ onData(log) {
+ if (!isDrawerOpen) {
+ setIsDrawerOpen(true);
+ }
- if (log === "Deployment completed successfully!") {
- setIsDeploying(false);
- }
+ if (log === "Deployment completed successfully!") {
+ setIsDeploying(false);
+ }
- const parsedLogs = parseLogs(log);
- setFilteredLogs((prev) => [...prev, ...parsedLogs]);
- },
- onError(error) {
- console.error("Deployment logs error:", error);
- setIsDeploying(false);
- },
- },
- );
- return (
- <>
-
-
-
- Deploy Settings
-
-
-
- {
- setIsDeploying(true);
- await new Promise((resolve) => setTimeout(resolve, 1000));
- refetch();
- }}
- >
-
- Deploy
-
-
-
-
-
-
- Downloads and sets up the MongoDB database
-
-
-
-
-
- {
- await reload({
- mongoId: mongoId,
- appName: data?.appName || "",
- })
- .then(() => {
- toast.success("Mongo reloaded successfully");
- refetch();
- })
- .catch(() => {
- toast.error("Error reloading Mongo");
- });
- }}
- >
-
- Reload
-
-
-
-
-
-
-
- Restart the MongoDB service without rebuilding
-
-
-
-
-
- {data?.applicationStatus === "idle" ? (
- {
- await start({
- mongoId: mongoId,
- })
- .then(() => {
- toast.success("Mongo started successfully");
- refetch();
- })
- .catch(() => {
- toast.error("Error starting Mongo");
- });
- }}
- >
-
- Start
-
-
-
-
-
-
-
-
- Start the MongoDB database (requires a previous
- successful setup)
-
-
-
-
-
-
- ) : (
- {
- await stop({
- mongoId: mongoId,
- })
- .then(() => {
- toast.success("Mongo stopped successfully");
- refetch();
- })
- .catch(() => {
- toast.error("Error stopping Mongo");
- });
- }}
- >
-
- Stop
-
-
-
-
-
-
-
- Stop the currently running MongoDB database
-
-
-
-
-
- )}
-
-
-
-
- Open Terminal
-
-
-
-
-
{
- setIsDrawerOpen(false);
- setFilteredLogs([]);
- setIsDeploying(false);
- refetch();
- }}
- filteredLogs={filteredLogs}
- />
-
- >
- );
+ const parsedLogs = parseLogs(log);
+ setFilteredLogs((prev) => [...prev, ...parsedLogs]);
+ },
+ onError(error) {
+ console.error("Deployment logs error:", error);
+ setIsDeploying(false);
+ },
+ }
+ );
+ return (
+ <>
+
+
+
+ Deploy Settings
+
+
+
+ {
+ setIsDeploying(true);
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ refetch();
+ }}
+ >
+
+
+
+
+ Deploy
+
+
+
+
+ Downloads and sets up the MongoDB database
+
+
+
+
+ {
+ await reload({
+ mongoId: mongoId,
+ appName: data?.appName || "",
+ })
+ .then(() => {
+ toast.success("Mongo reloaded successfully");
+ refetch();
+ })
+ .catch(() => {
+ toast.error("Error reloading Mongo");
+ });
+ }}
+ >
+
+
+
+
+ Reload
+
+
+
+
+ Restart the MongoDB service without rebuilding
+
+
+
+
+ {data?.applicationStatus === "idle" ? (
+ {
+ await start({
+ mongoId: mongoId,
+ })
+ .then(() => {
+ toast.success("Mongo started successfully");
+ refetch();
+ })
+ .catch(() => {
+ toast.error("Error starting Mongo");
+ });
+ }}
+ >
+
+
+
+
+ Start
+
+
+
+
+
+ Start the MongoDB database (requires a previous
+ successful setup)
+
+
+
+
+
+ ) : (
+ {
+ await stop({
+ mongoId: mongoId,
+ })
+ .then(() => {
+ toast.success("Mongo stopped successfully");
+ refetch();
+ })
+ .catch(() => {
+ toast.error("Error stopping Mongo");
+ });
+ }}
+ >
+
+
+
+
+ Stop
+
+
+
+
+ Stop the currently running MongoDB database
+
+
+
+
+ )}
+
+
+
+
+
+
+ Open Terminal
+
+
+
+
+ Open a terminal to the MongoDB container
+
+
+
+
+
+
+
{
+ setIsDrawerOpen(false);
+ setFilteredLogs([]);
+ setIsDeploying(false);
+ refetch();
+ }}
+ filteredLogs={filteredLogs}
+ />
+
+ >
+ );
};
diff --git a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx
index ede1460f5..2c8ed5f5b 100644
--- a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx
+++ b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx
@@ -27,145 +27,144 @@ import { toast } from "sonner";
import { z } from "zod";
const DockerProviderSchema = z.object({
- externalPort: z.preprocess((a) => {
- if (a !== null) {
- const parsed = Number.parseInt(z.string().parse(a), 10);
- return Number.isNaN(parsed) ? null : parsed;
- }
- return null;
- }, z
- .number()
- .gte(0, "Range must be 0 - 65535")
- .lte(65535, "Range must be 0 - 65535")
- .nullable()),
+ externalPort: z.preprocess((a) => {
+ if (a !== null) {
+ const parsed = Number.parseInt(z.string().parse(a), 10);
+ return Number.isNaN(parsed) ? null : parsed;
+ }
+ return null;
+ }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
});
type DockerProvider = z.infer;
interface Props {
- mysqlId: string;
+ mysqlId: string;
}
export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
- const { data: ip } = api.settings.getIp.useQuery();
- const { data, refetch } = api.mysql.one.useQuery({ mysqlId });
- const { mutateAsync, isLoading } = api.mysql.saveExternalPort.useMutation();
- const [connectionUrl, setConnectionUrl] = useState("");
- const getIp = data?.server?.ipAddress || ip;
- const form = useForm({
- defaultValues: {},
- resolver: zodResolver(DockerProviderSchema),
- });
+ const { data: ip } = api.settings.getIp.useQuery();
+ const { data, refetch } = api.mysql.one.useQuery({ mysqlId });
+ const { mutateAsync, isLoading } = api.mysql.saveExternalPort.useMutation();
+ const [connectionUrl, setConnectionUrl] = useState("");
+ const getIp = data?.server?.ipAddress || ip;
+ const form = useForm({
+ defaultValues: {},
+ resolver: zodResolver(DockerProviderSchema),
+ });
- useEffect(() => {
- if (data?.externalPort) {
- form.reset({
- externalPort: data.externalPort,
- });
- }
- }, [form.reset, data, form]);
+ useEffect(() => {
+ if (data?.externalPort) {
+ form.reset({
+ externalPort: data.externalPort,
+ });
+ }
+ }, [form.reset, data, form]);
- const onSubmit = async (values: DockerProvider) => {
- await mutateAsync({
- externalPort: values.externalPort,
- mysqlId,
- })
- .then(async () => {
- toast.success("External Port updated");
- await refetch();
- })
- .catch(() => {
- toast.error("Error saving the external port");
- });
- };
+ const onSubmit = async (values: DockerProvider) => {
+ await mutateAsync({
+ externalPort: values.externalPort,
+ mysqlId,
+ })
+ .then(async () => {
+ toast.success("External Port updated");
+ await refetch();
+ })
+ .catch(() => {
+ toast.error("Error saving the external port");
+ });
+ };
- useEffect(() => {
- const buildConnectionUrl = () => {
- const port = form.watch("externalPort") || data?.externalPort;
+ useEffect(() => {
+ const buildConnectionUrl = () => {
+ const port = form.watch("externalPort") || data?.externalPort;
- return `mysql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
- };
+ return `mysql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
+ };
- setConnectionUrl(buildConnectionUrl());
- }, [
- data?.appName,
- data?.externalPort,
- data?.databasePassword,
- data?.databaseName,
- data?.databaseUser,
- form,
- getIp,
- ]);
- return (
- <>
-
-
-
- External Credentials
-
- In order to make the database reachable trought internet is
- required to set a port, make sure the port is not used by another
- application or database
-
-
-
- {!getIp && (
-
- You need to set an IP address in your{" "}
-
- {data?.serverId
- ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
- : "Web Server -> Server -> Update Server IP"}
- {" "}
- to fix the database url connection.
-
- )}
-
-
-
-
- {
- return (
-
- External Port (Internet)
-
-
-
-
-
- );
- }}
- />
-
-
- {!!data?.externalPort && (
-
- )}
+ setConnectionUrl(buildConnectionUrl());
+ }, [
+ data?.appName,
+ data?.externalPort,
+ data?.databasePassword,
+ data?.databaseName,
+ data?.databaseUser,
+ form,
+ getIp,
+ ]);
+ return (
+ <>
+
+
+
+ External Credentials
+
+ In order to make the database reachable trought internet is
+ required to set a port, make sure the port is not used by another
+ application or database
+
+
+
+ {!getIp && (
+
+ You need to set an IP address in your{" "}
+
+ {data?.serverId
+ ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
+ : "Web Server -> Server -> Update Server IP"}
+ {" "}
+ to fix the database url connection.
+
+ )}
+
+
+
+
+ {
+ return (
+
+ External Port (Internet)
+
+
+
+
+
+ );
+ }}
+ />
+
+
+ {!!data?.externalPort && (
+
+ )}
-
-
- Save
-
-
-
-
-
-
-
- >
- );
+
+
+ Save
+
+
+
+
+
+
+
+ >
+ );
};
diff --git a/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx b/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx
index 703ec6e34..711bb47e6 100644
--- a/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx
+++ b/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx
@@ -11,11 +11,12 @@ import {
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
- Ban,
- CheckCircle2,
- HelpCircle,
- RefreshCcw,
- Terminal,
+ Ban,
+ CheckCircle2,
+ HelpCircle,
+ RefreshCcw,
+ Rocket,
+ Terminal,
} from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
@@ -77,7 +78,7 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
{
@@ -89,12 +90,14 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
- Deploy
-
+
+
+ Deploy
+
@@ -105,7 +108,7 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
{
@@ -114,24 +117,25 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
appName: data?.appName || "",
})
.then(() => {
- toast.success("Mysql reloaded successfully");
+ toast.success("MySQL reloaded successfully");
refetch();
})
.catch(() => {
- toast.error("Error reloading Mysql");
+ toast.error("Error reloading MySQL");
});
}}
>
- Reload
-
-
+
+
+ Reload
+
@@ -143,7 +147,7 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
{data?.applicationStatus === "idle" ? (
{
@@ -151,24 +155,25 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
mysqlId: mysqlId,
})
.then(() => {
- toast.success("Mysql started successfully");
+ toast.success("MySQL started successfully");
refetch();
})
.catch(() => {
- toast.error("Error starting Mysql");
+ toast.error("Error starting MySQL");
});
}}
>
- Start
-
-
+
+
+ Start
+
@@ -183,31 +188,32 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
) : (
{
await stop({
mysqlId: mysqlId,
})
.then(() => {
- toast.success("Mysql stopped successfully");
+ toast.success("MySQL stopped successfully");
refetch();
})
.catch(() => {
- toast.error("Error stopping Mysql");
+ toast.error("Error stopping MySQL");
});
}}
>
- Stop
-
-
+
+
+ Stop
+
@@ -223,9 +229,23 @@ export const ShowGeneralMysql = ({ mysqlId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
-
-
- Open Terminal
+
+
+
+
+
+ Open Terminal
+
+
+
+
+ Open a terminal to the MySQL container
+
+
+
diff --git a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx
index 4d8e7c3c4..0c87a7bcd 100644
--- a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx
+++ b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx
@@ -27,147 +27,146 @@ import { toast } from "sonner";
import { z } from "zod";
const DockerProviderSchema = z.object({
- externalPort: z.preprocess((a) => {
- if (a !== null) {
- const parsed = Number.parseInt(z.string().parse(a), 10);
- return Number.isNaN(parsed) ? null : parsed;
- }
- return null;
- }, z
- .number()
- .gte(0, "Range must be 0 - 65535")
- .lte(65535, "Range must be 0 - 65535")
- .nullable()),
+ externalPort: z.preprocess((a) => {
+ if (a !== null) {
+ const parsed = Number.parseInt(z.string().parse(a), 10);
+ return Number.isNaN(parsed) ? null : parsed;
+ }
+ return null;
+ }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
});
type DockerProvider = z.infer;
interface Props {
- postgresId: string;
+ postgresId: string;
}
export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
- const { data: ip } = api.settings.getIp.useQuery();
- const { data, refetch } = api.postgres.one.useQuery({ postgresId });
- const { mutateAsync, isLoading } =
- api.postgres.saveExternalPort.useMutation();
- const getIp = data?.server?.ipAddress || ip;
- const [connectionUrl, setConnectionUrl] = useState("");
+ const { data: ip } = api.settings.getIp.useQuery();
+ const { data, refetch } = api.postgres.one.useQuery({ postgresId });
+ const { mutateAsync, isLoading } =
+ api.postgres.saveExternalPort.useMutation();
+ const getIp = data?.server?.ipAddress || ip;
+ const [connectionUrl, setConnectionUrl] = useState("");
- const form = useForm({
- defaultValues: {},
- resolver: zodResolver(DockerProviderSchema),
- });
+ const form = useForm({
+ defaultValues: {},
+ resolver: zodResolver(DockerProviderSchema),
+ });
- useEffect(() => {
- if (data?.externalPort) {
- form.reset({
- externalPort: data.externalPort,
- });
- }
- }, [form.reset, data, form]);
+ useEffect(() => {
+ if (data?.externalPort) {
+ form.reset({
+ externalPort: data.externalPort,
+ });
+ }
+ }, [form.reset, data, form]);
- const onSubmit = async (values: DockerProvider) => {
- await mutateAsync({
- externalPort: values.externalPort,
- postgresId,
- })
- .then(async () => {
- toast.success("External Port updated");
- await refetch();
- })
- .catch(() => {
- toast.error("Error saving the external port");
- });
- };
+ const onSubmit = async (values: DockerProvider) => {
+ await mutateAsync({
+ externalPort: values.externalPort,
+ postgresId,
+ })
+ .then(async () => {
+ toast.success("External Port updated");
+ await refetch();
+ })
+ .catch(() => {
+ toast.error("Error saving the external port");
+ });
+ };
- useEffect(() => {
- const buildConnectionUrl = () => {
- const port = form.watch("externalPort") || data?.externalPort;
+ useEffect(() => {
+ const buildConnectionUrl = () => {
+ const port = form.watch("externalPort") || data?.externalPort;
- return `postgresql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
- };
+ return `postgresql://${data?.databaseUser}:${data?.databasePassword}@${getIp}:${port}/${data?.databaseName}`;
+ };
- setConnectionUrl(buildConnectionUrl());
- }, [
- data?.appName,
- data?.externalPort,
- data?.databasePassword,
- form,
- data?.databaseName,
- getIp,
- ]);
+ setConnectionUrl(buildConnectionUrl());
+ }, [
+ data?.appName,
+ data?.externalPort,
+ data?.databasePassword,
+ form,
+ data?.databaseName,
+ getIp,
+ ]);
- return (
- <>
-
-
-
- External Credentials
-
- In order to make the database reachable trought internet is
- required to set a port, make sure the port is not used by another
- application or database
-
-
-
- {!getIp && (
-
- You need to set an IP address in your{" "}
-
- {data?.serverId
- ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
- : "Web Server -> Server -> Update Server IP"}
- {" "}
- to fix the database url connection.
-
- )}
-
-
-
-
- {
- return (
-
- External Port (Internet)
-
-
-
-
-
- );
- }}
- />
-
-
- {!!data?.externalPort && (
-
- )}
+ return (
+ <>
+
+
+
+ External Credentials
+
+ In order to make the database reachable trought internet is
+ required to set a port, make sure the port is not used by another
+ application or database
+
+
+
+ {!getIp && (
+
+ You need to set an IP address in your{" "}
+
+ {data?.serverId
+ ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
+ : "Web Server -> Server -> Update Server IP"}
+ {" "}
+ to fix the database url connection.
+
+ )}
+
+
+
+
+ {
+ return (
+
+ External Port (Internet)
+
+
+
+
+
+ );
+ }}
+ />
+
+
+ {!!data?.externalPort && (
+
+ )}
-
-
- Save
-
-
-
-
-
-
-
- >
- );
+
+
+ Save
+
+
+
+
+
+
+
+ >
+ );
};
diff --git a/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx b/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx
index 0c448553a..cf301b301 100644
--- a/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx
+++ b/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx
@@ -11,11 +11,12 @@ import {
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
- Ban,
- CheckCircle2,
- HelpCircle,
- RefreshCcw,
- Terminal,
+ Ban,
+ CheckCircle2,
+ HelpCircle,
+ RefreshCcw,
+ Rocket,
+ Terminal,
} from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
@@ -78,9 +79,9 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
Deploy Settings
-
+
{
@@ -92,12 +93,14 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
- Deploy
-
+
+
+ Deploy
+
@@ -108,7 +111,7 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
{
@@ -117,24 +120,25 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
appName: data?.appName || "",
})
.then(() => {
- toast.success("Postgres reloaded successfully");
+ toast.success("PostgreSQL reloaded successfully");
refetch();
})
.catch(() => {
- toast.error("Error reloading Postgres");
+ toast.error("Error reloading PostgreSQL");
});
}}
>
- Reload
-
-
+
+
+ Reload
+
@@ -146,7 +150,7 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
{data?.applicationStatus === "idle" ? (
{
@@ -154,24 +158,25 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
postgresId: postgresId,
})
.then(() => {
- toast.success("Postgres started successfully");
+ toast.success("PostgreSQL started successfully");
refetch();
})
.catch(() => {
- toast.error("Error starting Postgres");
+ toast.error("Error starting PostgreSQL");
});
}}
>
- Start
-
-
+
+
+ Start
+
@@ -186,31 +191,32 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
) : (
{
await stop({
postgresId: postgresId,
})
.then(() => {
- toast.success("Postgres stopped successfully");
+ toast.success("PostgreSQL stopped successfully");
refetch();
})
.catch(() => {
- toast.error("Error stopping Postgres");
+ toast.error("Error stopping PostgreSQL");
});
}}
>
- Stop
-
-
+
+
+ Stop
+
@@ -226,9 +232,23 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
-
-
- Open Terminal
+
+
+
+
+
+ Open Terminal
+
+
+
+
+ Open a terminal to the PostgreSQL container
+
+
+
diff --git a/apps/dokploy/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx b/apps/dokploy/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx
index 545150f87..cff00a998 100644
--- a/apps/dokploy/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx
+++ b/apps/dokploy/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx
@@ -5,58 +5,58 @@ import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
interface Props {
- postgresId: string;
+ postgresId: string;
}
export const ShowInternalPostgresCredentials = ({ postgresId }: Props) => {
- const { data } = api.postgres.one.useQuery({ postgresId });
- return (
- <>
-
-
-
- Internal Credentials
-
-
-
-
- User
-
-
-
- Database Name
-
-
-
-
- Internal Port (Container)
-
-
+ const { data } = api.postgres.one.useQuery({ postgresId });
+ return (
+ <>
+
+
+
+ Internal Credentials
+
+
+
-
-
-
- >
- );
+
+ Internal Connection URL
+
+
+
+
+
+
+ >
+ );
};
// ReplyError: MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-w
diff --git a/apps/dokploy/components/dashboard/postgres/update-postgres.tsx b/apps/dokploy/components/dashboard/postgres/update-postgres.tsx
index 2b8804a57..33ed7a60e 100644
--- a/apps/dokploy/components/dashboard/postgres/update-postgres.tsx
+++ b/apps/dokploy/components/dashboard/postgres/update-postgres.tsx
@@ -21,145 +21,146 @@ import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
-import { PenBoxIcon } from "lucide-react";
+import { PenBox } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const updatePostgresSchema = z.object({
- name: z.string().min(1, {
- message: "Name is required",
- }),
- description: z.string().optional(),
+ name: z.string().min(1, {
+ message: "Name is required",
+ }),
+ description: z.string().optional(),
});
type UpdatePostgres = z.infer;
interface Props {
- postgresId: string;
+ postgresId: string;
}
export const UpdatePostgres = ({ postgresId }: Props) => {
- const [isOpen, setIsOpen] = useState(false);
- const utils = api.useUtils();
- const { mutateAsync, error, isError, isLoading } =
- api.postgres.update.useMutation();
- const { data } = api.postgres.one.useQuery(
- {
- postgresId,
- },
- {
- enabled: !!postgresId,
- },
- );
- const form = useForm({
- defaultValues: {
- description: data?.description ?? "",
- name: data?.name ?? "",
- },
- resolver: zodResolver(updatePostgresSchema),
- });
- useEffect(() => {
- if (data) {
- form.reset({
- description: data.description ?? "",
- name: data.name,
- });
- }
- }, [data, form, form.reset]);
+ const [isOpen, setIsOpen] = useState(false);
+ const utils = api.useUtils();
+ const { mutateAsync, error, isError, isLoading } =
+ api.postgres.update.useMutation();
+ const { data } = api.postgres.one.useQuery(
+ {
+ postgresId,
+ },
+ {
+ enabled: !!postgresId,
+ }
+ );
+ const form = useForm({
+ defaultValues: {
+ description: data?.description ?? "",
+ name: data?.name ?? "",
+ },
+ resolver: zodResolver(updatePostgresSchema),
+ });
+ useEffect(() => {
+ if (data) {
+ form.reset({
+ description: data.description ?? "",
+ name: data.name,
+ });
+ }
+ }, [data, form, form.reset]);
- const onSubmit = async (formData: UpdatePostgres) => {
- await mutateAsync({
- name: formData.name,
- postgresId: postgresId,
- description: formData.description || "",
- })
- .then(() => {
- toast.success("Postgres updated successfully");
- utils.postgres.one.invalidate({
- postgresId: postgresId,
- });
- setIsOpen(false);
- })
- .catch(() => {
- toast.error("Error updating Postgres");
- })
- .finally(() => {});
- };
+ const onSubmit = async (formData: UpdatePostgres) => {
+ await mutateAsync({
+ name: formData.name,
+ postgresId: postgresId,
+ description: formData.description || "",
+ })
+ .then(() => {
+ toast.success("Postgres updated successfully");
+ utils.postgres.one.invalidate({
+ postgresId: postgresId,
+ });
+ setIsOpen(false);
+ })
+ .catch(() => {
+ toast.error("Error updating Postgres");
+ })
+ .finally(() => {});
+ };
- return (
-
-
-
-
-
-
-
-
- Modify Postgres
- Update the Postgres data
-
- {isError && {error?.message} }
+ return (
+
+
+
+
+
+
+
+
+ Modify Postgres
+ Update the Postgres data
+
+ {isError && {error?.message} }
-
+
+
+ );
};
diff --git a/apps/dokploy/components/dashboard/redis/general/show-external-redis-credentials.tsx b/apps/dokploy/components/dashboard/redis/general/show-external-redis-credentials.tsx
index f74129f02..75b5478aa 100644
--- a/apps/dokploy/components/dashboard/redis/general/show-external-redis-credentials.tsx
+++ b/apps/dokploy/components/dashboard/redis/general/show-external-redis-credentials.tsx
@@ -27,139 +27,138 @@ import { toast } from "sonner";
import { z } from "zod";
const DockerProviderSchema = z.object({
- externalPort: z.preprocess((a) => {
- if (a !== null) {
- const parsed = Number.parseInt(z.string().parse(a), 10);
- return Number.isNaN(parsed) ? null : parsed;
- }
- return null;
- }, z
- .number()
- .gte(0, "Range must be 0 - 65535")
- .lte(65535, "Range must be 0 - 65535")
- .nullable()),
+ externalPort: z.preprocess((a) => {
+ if (a !== null) {
+ const parsed = Number.parseInt(z.string().parse(a), 10);
+ return Number.isNaN(parsed) ? null : parsed;
+ }
+ return null;
+ }, z.number().gte(0, "Range must be 0 - 65535").lte(65535, "Range must be 0 - 65535").nullable()),
});
type DockerProvider = z.infer;
interface Props {
- redisId: string;
+ redisId: string;
}
export const ShowExternalRedisCredentials = ({ redisId }: Props) => {
- const { data: ip } = api.settings.getIp.useQuery();
- const { data, refetch } = api.redis.one.useQuery({ redisId });
- const { mutateAsync, isLoading } = api.redis.saveExternalPort.useMutation();
- const [connectionUrl, setConnectionUrl] = useState("");
- const getIp = data?.server?.ipAddress || ip;
+ const { data: ip } = api.settings.getIp.useQuery();
+ const { data, refetch } = api.redis.one.useQuery({ redisId });
+ const { mutateAsync, isLoading } = api.redis.saveExternalPort.useMutation();
+ const [connectionUrl, setConnectionUrl] = useState("");
+ const getIp = data?.server?.ipAddress || ip;
- const form = useForm({
- defaultValues: {},
- resolver: zodResolver(DockerProviderSchema),
- });
+ const form = useForm({
+ defaultValues: {},
+ resolver: zodResolver(DockerProviderSchema),
+ });
- useEffect(() => {
- if (data?.externalPort) {
- form.reset({
- externalPort: data.externalPort,
- });
- }
- }, [form.reset, data, form]);
+ useEffect(() => {
+ if (data?.externalPort) {
+ form.reset({
+ externalPort: data.externalPort,
+ });
+ }
+ }, [form.reset, data, form]);
- const onSubmit = async (values: DockerProvider) => {
- await mutateAsync({
- externalPort: values.externalPort,
- redisId,
- })
- .then(async () => {
- toast.success("External Port updated");
- await refetch();
- })
- .catch(() => {
- toast.error("Error saving the external port");
- });
- };
+ const onSubmit = async (values: DockerProvider) => {
+ await mutateAsync({
+ externalPort: values.externalPort,
+ redisId,
+ })
+ .then(async () => {
+ toast.success("External Port updated");
+ await refetch();
+ })
+ .catch(() => {
+ toast.error("Error saving the external port");
+ });
+ };
- useEffect(() => {
- const buildConnectionUrl = () => {
- const _hostname = window.location.hostname;
- const port = form.watch("externalPort") || data?.externalPort;
+ useEffect(() => {
+ const buildConnectionUrl = () => {
+ const _hostname = window.location.hostname;
+ const port = form.watch("externalPort") || data?.externalPort;
- return `redis://default:${data?.databasePassword}@${getIp}:${port}`;
- };
+ return `redis://default:${data?.databasePassword}@${getIp}:${port}`;
+ };
- setConnectionUrl(buildConnectionUrl());
- }, [data?.appName, data?.externalPort, data?.databasePassword, form, getIp]);
- return (
- <>
-
-
-
- External Credentials
-
- In order to make the database reachable trought internet is
- required to set a port, make sure the port is not used by another
- application or database
-
-
-
- {!getIp && (
-
- You need to set an IP address in your{" "}
-
- {data?.serverId
- ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
- : "Web Server -> Server -> Update Server IP"}
- {" "}
- to fix the database url connection.
-
- )}
-
-
-
-
- {
- return (
-
- External Port (Internet)
-
-
-
-
-
- );
- }}
- />
-
-
- {!!data?.externalPort && (
-
- )}
+ setConnectionUrl(buildConnectionUrl());
+ }, [data?.appName, data?.externalPort, data?.databasePassword, form, getIp]);
+ return (
+ <>
+
+
+
+ External Credentials
+
+ In order to make the database reachable trought internet is
+ required to set a port, make sure the port is not used by another
+ application or database
+
+
+
+ {!getIp && (
+
+ You need to set an IP address in your{" "}
+
+ {data?.serverId
+ ? "Remote Servers -> Server -> Edit Server -> Update IP Address"
+ : "Web Server -> Server -> Update Server IP"}
+ {" "}
+ to fix the database url connection.
+
+ )}
+
+
+
+
+ {
+ return (
+
+ External Port (Internet)
+
+
+
+
+
+ );
+ }}
+ />
+
+
+ {!!data?.externalPort && (
+
+ )}
-
-
- Save
-
-
-
-
-
-
-
- >
- );
+
+
+ Save
+
+
+
+
+
+
+
+ >
+ );
};
diff --git a/apps/dokploy/components/dashboard/redis/general/show-general-redis.tsx b/apps/dokploy/components/dashboard/redis/general/show-general-redis.tsx
index 59226e513..bec7997fb 100644
--- a/apps/dokploy/components/dashboard/redis/general/show-general-redis.tsx
+++ b/apps/dokploy/components/dashboard/redis/general/show-general-redis.tsx
@@ -11,11 +11,12 @@ import {
import { api } from "@/utils/api";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {
- Ban,
- CheckCircle2,
- HelpCircle,
- RefreshCcw,
- Terminal,
+ Ban,
+ CheckCircle2,
+ HelpCircle,
+ RefreshCcw,
+ Rocket,
+ Terminal,
} from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
@@ -91,12 +92,14 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
- Deploy
-
+
+
+ Deploy
+
@@ -127,13 +130,14 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
- Reload
-
-
+
+
+ Reload
+
@@ -164,13 +168,14 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
- Start
-
-
+
+
+ Start
+
@@ -203,13 +208,14 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
- Stop
-
-
+
+
+ Stop
+
@@ -225,9 +231,23 @@ export const ShowGeneralRedis = ({ redisId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
-
-
- Open Terminal
+
+
+
+
+
+ Open Terminal
+
+
+
+
+ Open a terminal to the Redis container
+
+
+
diff --git a/apps/dokploy/components/ui/input.tsx b/apps/dokploy/components/ui/input.tsx
index 18b713af5..7339d21a2 100644
--- a/apps/dokploy/components/ui/input.tsx
+++ b/apps/dokploy/components/ui/input.tsx
@@ -39,7 +39,7 @@ const NumberInput = React.forwardRef(
className={cn("text-left", className)}
ref={ref}
{...props}
- value={props.value === undefined ? undefined : String(props.value)}
+ value={props.value === undefined || props.value === "" ? "" : String(props.value)}
onChange={(e) => {
const value = e.target.value;
if (value === "") {
@@ -60,6 +60,21 @@ const NumberInput = React.forwardRef(
}
}
}}
+ onBlur={(e) => {
+ // If input is empty, make 0 when focus is lost
+ if (e.target.value === "") {
+ const syntheticEvent = {
+ ...e,
+ target: {
+ ...e.target,
+ value: "0",
+ },
+ };
+ props.onChange?.(
+ syntheticEvent as unknown as React.ChangeEvent,
+ );
+ }
+ }}
/>
);
},
diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json
index 44201c119..02815018e 100644
--- a/apps/dokploy/package.json
+++ b/apps/dokploy/package.json
@@ -1,6 +1,6 @@
{
"name": "dokploy",
- "version": "v0.20.4",
+ "version": "v0.20.5",
"private": true,
"license": "Apache-2.0",
"type": "module",
diff --git a/apps/dokploy/pages/api/deploy/github.ts b/apps/dokploy/pages/api/deploy/github.ts
index 77ce8bda8..b32f959ee 100644
--- a/apps/dokploy/pages/api/deploy/github.ts
+++ b/apps/dokploy/pages/api/deploy/github.ts
@@ -93,6 +93,7 @@ export default async function handler(
try {
const branchName = githubBody?.ref?.replace("refs/heads/", "");
const repository = githubBody?.repository?.name;
+
const deploymentTitle = extractCommitMessage(req.headers, req.body);
const deploymentHash = extractHash(req.headers, req.body);
const owner = githubBody?.repository?.owner?.name;
@@ -107,6 +108,7 @@ export default async function handler(
eq(applications.branch, branchName),
eq(applications.repository, repository),
eq(applications.owner, owner),
+ eq(applications.githubId, githubResult.githubId),
),
});
@@ -151,6 +153,7 @@ export default async function handler(
eq(compose.branch, branchName),
eq(compose.repository, repository),
eq(compose.owner, owner),
+ eq(compose.githubId, githubResult.githubId),
),
});
@@ -240,6 +243,7 @@ export default async function handler(
eq(applications.branch, branch),
eq(applications.isPreviewDeploymentsActive, true),
eq(applications.owner, owner),
+ eq(applications.githubId, githubResult.githubId),
),
with: {
previewDeployments: true,
diff --git a/apps/dokploy/server/api/routers/backup.ts b/apps/dokploy/server/api/routers/backup.ts
index 8a7a5f22f..8e585b7c9 100644
--- a/apps/dokploy/server/api/routers/backup.ts
+++ b/apps/dokploy/server/api/routers/backup.ts
@@ -11,9 +11,13 @@ import {
createBackup,
findBackupById,
findMariadbByBackupId,
+ findMariadbById,
findMongoByBackupId,
+ findMongoById,
findMySqlByBackupId,
+ findMySqlById,
findPostgresByBackupId,
+ findPostgresById,
findServerById,
removeBackupById,
removeScheduleBackup,
@@ -26,6 +30,17 @@ import {
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
+import { z } from "zod";
+import { execAsync } from "@dokploy/server/utils/process/execAsync";
+import { getS3Credentials } from "@dokploy/server/utils/backups/utils";
+import { findDestinationById } from "@dokploy/server/services/destination";
+import {
+ restoreMariadbBackup,
+ restoreMongoBackup,
+ restoreMySqlBackup,
+ restorePostgresBackup,
+} from "@dokploy/server/utils/restore";
+import { observable } from "@trpc/server/observable";
export const backupRouter = createTRPCRouter({
create: protectedProcedure
@@ -209,27 +224,136 @@ export const backupRouter = createTRPCRouter({
});
}
}),
+ listBackupFiles: protectedProcedure
+ .input(
+ z.object({
+ destinationId: z.string(),
+ search: z.string(),
+ }),
+ )
+ .query(async ({ input }) => {
+ try {
+ const destination = await findDestinationById(input.destinationId);
+ const rcloneFlags = getS3Credentials(destination);
+ const bucketPath = `:s3:${destination.bucket}`;
+
+ const lastSlashIndex = input.search.lastIndexOf("/");
+ const baseDir =
+ lastSlashIndex !== -1
+ ? input.search.slice(0, lastSlashIndex + 1)
+ : "";
+ const searchTerm =
+ lastSlashIndex !== -1
+ ? input.search.slice(lastSlashIndex + 1)
+ : input.search;
+
+ const searchPath = baseDir ? `${bucketPath}/${baseDir}` : bucketPath;
+ const listCommand = `rclone lsf ${rcloneFlags.join(" ")} "${searchPath}" | head -n 100`;
+
+ const { stdout } = await execAsync(listCommand);
+ const files = stdout.split("\n").filter(Boolean);
+
+ const results = baseDir
+ ? files.map((file) => `${baseDir}${file}`)
+ : files;
+
+ if (searchTerm) {
+ return results.filter((file) =>
+ file.toLowerCase().includes(searchTerm.toLowerCase()),
+ );
+ }
+
+ return results;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ error instanceof Error
+ ? error.message
+ : "Error listing backup files",
+ cause: error,
+ });
+ }
+ }),
+
+ restoreBackupWithLogs: protectedProcedure
+ .meta({
+ openapi: {
+ enabled: false,
+ path: "/restore-backup-with-logs",
+ method: "POST",
+ override: true,
+ },
+ })
+ .input(
+ z.object({
+ databaseId: z.string(),
+ databaseType: z.enum(["postgres", "mysql", "mariadb", "mongo"]),
+ databaseName: z.string().min(1),
+ backupFile: z.string().min(1),
+ destinationId: z.string().min(1),
+ }),
+ )
+ .subscription(async ({ input }) => {
+ const destination = await findDestinationById(input.destinationId);
+ if (input.databaseType === "postgres") {
+ const postgres = await findPostgresById(input.databaseId);
+
+ return observable((emit) => {
+ restorePostgresBackup(
+ postgres,
+ destination,
+ input.databaseName,
+ input.backupFile,
+ (log) => {
+ emit.next(log);
+ },
+ );
+ });
+ }
+ if (input.databaseType === "mysql") {
+ const mysql = await findMySqlById(input.databaseId);
+ return observable((emit) => {
+ restoreMySqlBackup(
+ mysql,
+ destination,
+ input.databaseName,
+ input.backupFile,
+ (log) => {
+ emit.next(log);
+ },
+ );
+ });
+ }
+ if (input.databaseType === "mariadb") {
+ const mariadb = await findMariadbById(input.databaseId);
+ return observable((emit) => {
+ restoreMariadbBackup(
+ mariadb,
+ destination,
+ input.databaseName,
+ input.backupFile,
+ (log) => {
+ emit.next(log);
+ },
+ );
+ });
+ }
+ if (input.databaseType === "mongo") {
+ const mongo = await findMongoById(input.databaseId);
+ return observable((emit) => {
+ restoreMongoBackup(
+ mongo,
+ destination,
+ input.databaseName,
+ input.backupFile,
+ (log) => {
+ emit.next(log);
+ },
+ );
+ });
+ }
+
+ return true;
+ }),
});
-
-// export const getAdminId = async (backupId: string) => {
-// const backup = await findBackupById(backupId);
-
-// if (backup.databaseType === "postgres" && backup.postgresId) {
-// const postgres = await findPostgresById(backup.postgresId);
-// return postgres.project.adminId;
-// }
-// if (backup.databaseType === "mariadb" && backup.mariadbId) {
-// const mariadb = await findMariadbById(backup.mariadbId);
-// return mariadb.project.adminId;
-// }
-// if (backup.databaseType === "mysql" && backup.mysqlId) {
-// const mysql = await findMySqlById(backup.mysqlId);
-// return mysql.project.adminId;
-// }
-// if (backup.databaseType === "mongo" && backup.mongoId) {
-// const mongo = await findMongoById(backup.mongoId);
-// return mongo.project.adminId;
-// }
-
-// return null;
-// };
diff --git a/packages/server/src/utils/restore/index.ts b/packages/server/src/utils/restore/index.ts
new file mode 100644
index 000000000..e8adbbe16
--- /dev/null
+++ b/packages/server/src/utils/restore/index.ts
@@ -0,0 +1,4 @@
+export { restorePostgresBackup } from "./postgres";
+export { restoreMySqlBackup } from "./mysql";
+export { restoreMariadbBackup } from "./mariadb";
+export { restoreMongoBackup } from "./mongo";
diff --git a/packages/server/src/utils/restore/mariadb.ts b/packages/server/src/utils/restore/mariadb.ts
new file mode 100644
index 000000000..2fea53214
--- /dev/null
+++ b/packages/server/src/utils/restore/mariadb.ts
@@ -0,0 +1,56 @@
+import type { Mariadb } from "@dokploy/server/services/mariadb";
+import type { Destination } from "@dokploy/server/services/destination";
+import {
+ getRemoteServiceContainer,
+ getServiceContainer,
+} from "../docker/utils";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
+import { getS3Credentials } from "../backups/utils";
+
+export const restoreMariadbBackup = async (
+ mariadb: Mariadb,
+ destination: Destination,
+ database: string,
+ backupFile: string,
+ emit: (log: string) => void,
+) => {
+ try {
+ const { appName, databasePassword, databaseUser, serverId } = mariadb;
+
+ const rcloneFlags = getS3Credentials(destination);
+ const bucketPath = `:s3:${destination.bucket}`;
+ const backupPath = `${bucketPath}/${backupFile}`;
+
+ const { Id: containerName } = serverId
+ ? await getRemoteServiceContainer(serverId, appName)
+ : await getServiceContainer(appName);
+
+ const restoreCommand = `
+ rclone cat ${rcloneFlags.join(" ")} "${backupPath}" | gunzip | docker exec -i ${containerName} mariadb -u ${databaseUser} -p${databasePassword} ${database}
+ `;
+
+ emit("Starting restore...");
+
+ emit(`Executing command: ${restoreCommand}`);
+
+ if (serverId) {
+ await execAsyncRemote(serverId, restoreCommand);
+ } else {
+ await execAsync(restoreCommand);
+ }
+
+ emit("Restore completed successfully!");
+ } catch (error) {
+ console.error(error);
+ emit(
+ `Error: ${
+ error instanceof Error
+ ? error.message
+ : "Error restoring mariadb backup"
+ }`,
+ );
+ throw new Error(
+ error instanceof Error ? error.message : "Error restoring mariadb backup",
+ );
+ }
+};
diff --git a/packages/server/src/utils/restore/mongo.ts b/packages/server/src/utils/restore/mongo.ts
new file mode 100644
index 000000000..3110e0450
--- /dev/null
+++ b/packages/server/src/utils/restore/mongo.ts
@@ -0,0 +1,64 @@
+import type { Mongo } from "@dokploy/server/services/mongo";
+import type { Destination } from "@dokploy/server/services/destination";
+import {
+ getRemoteServiceContainer,
+ getServiceContainer,
+} from "../docker/utils";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
+import { getS3Credentials } from "../backups/utils";
+
+export const restoreMongoBackup = async (
+ mongo: Mongo,
+ destination: Destination,
+ database: string,
+ backupFile: string,
+ emit: (log: string) => void,
+) => {
+ try {
+ const { appName, databasePassword, databaseUser, serverId } = mongo;
+
+ const rcloneFlags = getS3Credentials(destination);
+ const bucketPath = `:s3:${destination.bucket}`;
+ const backupPath = `${bucketPath}/${backupFile}`;
+
+ const { Id: containerName } = serverId
+ ? await getRemoteServiceContainer(serverId, appName)
+ : await getServiceContainer(appName);
+
+ // For MongoDB, we need to first download the backup file since mongorestore expects a directory
+ const tempDir = "/tmp/dokploy-restore";
+ const fileName = backupFile.split("/").pop() || "backup.dump.gz";
+ const decompressedName = fileName.replace(".gz", "");
+
+ const downloadCommand = `\
+rm -rf ${tempDir} && \
+mkdir -p ${tempDir} && \
+rclone copy ${rcloneFlags.join(" ")} "${backupPath}" ${tempDir} && \
+cd ${tempDir} && \
+gunzip -f "${fileName}" && \
+docker exec -i ${containerName} mongorestore --username ${databaseUser} --password ${databasePassword} --authenticationDatabase admin --db ${database} --archive < "${decompressedName}" && \
+rm -rf ${tempDir}`;
+
+ emit("Starting restore...");
+
+ emit(`Executing command: ${downloadCommand}`);
+
+ if (serverId) {
+ await execAsyncRemote(serverId, downloadCommand);
+ } else {
+ await execAsync(downloadCommand);
+ }
+
+ emit("Restore completed successfully!");
+ } catch (error) {
+ console.error(error);
+ emit(
+ `Error: ${
+ error instanceof Error ? error.message : "Error restoring mongo backup"
+ }`,
+ );
+ throw new Error(
+ error instanceof Error ? error.message : "Error restoring mongo backup",
+ );
+ }
+};
diff --git a/packages/server/src/utils/restore/mysql.ts b/packages/server/src/utils/restore/mysql.ts
new file mode 100644
index 000000000..09ef0bf7e
--- /dev/null
+++ b/packages/server/src/utils/restore/mysql.ts
@@ -0,0 +1,54 @@
+import type { MySql } from "@dokploy/server/services/mysql";
+import type { Destination } from "@dokploy/server/services/destination";
+import {
+ getRemoteServiceContainer,
+ getServiceContainer,
+} from "../docker/utils";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
+import { getS3Credentials } from "../backups/utils";
+
+export const restoreMySqlBackup = async (
+ mysql: MySql,
+ destination: Destination,
+ database: string,
+ backupFile: string,
+ emit: (log: string) => void,
+) => {
+ try {
+ const { appName, databaseRootPassword, serverId } = mysql;
+
+ const rcloneFlags = getS3Credentials(destination);
+ const bucketPath = `:s3:${destination.bucket}`;
+ const backupPath = `${bucketPath}/${backupFile}`;
+
+ const { Id: containerName } = serverId
+ ? await getRemoteServiceContainer(serverId, appName)
+ : await getServiceContainer(appName);
+
+ const restoreCommand = `
+ rclone cat ${rcloneFlags.join(" ")} "${backupPath}" | gunzip | docker exec -i ${containerName} mysql -u root -p${databaseRootPassword} ${database}
+ `;
+
+ emit("Starting restore...");
+
+ emit(`Executing command: ${restoreCommand}`);
+
+ if (serverId) {
+ await execAsyncRemote(serverId, restoreCommand);
+ } else {
+ await execAsync(restoreCommand);
+ }
+
+ emit("Restore completed successfully!");
+ } catch (error) {
+ console.error(error);
+ emit(
+ `Error: ${
+ error instanceof Error ? error.message : "Error restoring mysql backup"
+ }`,
+ );
+ throw new Error(
+ error instanceof Error ? error.message : "Error restoring mysql backup",
+ );
+ }
+};
diff --git a/packages/server/src/utils/restore/postgres.ts b/packages/server/src/utils/restore/postgres.ts
new file mode 100644
index 000000000..88f829ca7
--- /dev/null
+++ b/packages/server/src/utils/restore/postgres.ts
@@ -0,0 +1,60 @@
+import type { Postgres } from "@dokploy/server/services/postgres";
+import type { Destination } from "@dokploy/server/services/destination";
+import {
+ getRemoteServiceContainer,
+ getServiceContainer,
+} from "../docker/utils";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
+import { getS3Credentials } from "../backups/utils";
+
+export const restorePostgresBackup = async (
+ postgres: Postgres,
+ destination: Destination,
+ database: string,
+ backupFile: string,
+ emit: (log: string) => void,
+) => {
+ try {
+ const { appName, databaseUser, serverId } = postgres;
+
+ const rcloneFlags = getS3Credentials(destination);
+ const bucketPath = `:s3:${destination.bucket}`;
+
+ const backupPath = `${bucketPath}/${backupFile}`;
+
+ const { Id: containerName } = serverId
+ ? await getRemoteServiceContainer(serverId, appName)
+ : await getServiceContainer(appName);
+
+ emit("Starting restore...");
+ emit(`Backup path: ${backupPath}`);
+
+ const command = `\
+rclone cat ${rcloneFlags.join(" ")} "${backupPath}" | gunzip | docker exec -i ${containerName} pg_restore -U ${databaseUser} -d ${database} --clean --if-exists`;
+
+ emit(`Executing command: ${command}`);
+
+ if (serverId) {
+ const { stdout, stderr } = await execAsyncRemote(serverId, command);
+ emit(stdout);
+ emit(stderr);
+ } else {
+ const { stdout, stderr } = await execAsync(command);
+ console.log("stdout", stdout);
+ console.log("stderr", stderr);
+ emit(stdout);
+ emit(stderr);
+ }
+
+ emit("Restore completed successfully!");
+ } catch (error) {
+ emit(
+ `Error: ${
+ error instanceof Error
+ ? error.message
+ : "Error restoring postgres backup"
+ }`,
+ );
+ throw error;
+ }
+};