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 2f0662805..01b38bd3f 100644
--- a/apps/dokploy/components/dashboard/application/general/show.tsx
+++ b/apps/dokploy/components/dashboard/application/general/show.tsx
@@ -10,14 +10,14 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
-import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
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
-
-
-
-
-
-
-
- Downloads the source code and performs a complete build
-
-
-
-
-
+
+
+
+
+ Deploy
+
+
+
+
+
+ Downloads the source code and performs a complete build
+
+
+
+
{
});
}}
>
-
- Reload
-
-
+
+
+
+
+ Reload
+
+
+
+
+ Reload the application without rebuilding it
+
+
+
{
});
}}
>
-
- Rebuild
-
-
-
-
-
-
-
-
- Only rebuilds the application without downloading new
- code
-
-
-
-
-
+
+
+
+
+ Rebuild
+
+
+
+
+
+ Only rebuilds the application without downloading new code
+
+
+
+
{data?.applicationStatus === "idle" ? (
@@ -177,27 +188,26 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
});
}}
>
-
- Start
-
-
-
-
-
-
-
-
- Start the application (requires a previous successful
- build)
-
-
-
-
-
+
+
+
+
+ Start
+
+
+
+
+
+ Start the application (requires a previous successful
+ build)
+
+
+
+
) : (
{
});
}}
>
-
- Stop
-
-
-
-
-
-
-
- Stop the currently running application
-
-
-
-
+
+
+
+
+ Stop
+
+
+
+
+ Stop the currently running application
+
+
+
)}
@@ -241,15 +250,18 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
-
-
+
+
Open Terminal
Autodeploy
{
await update({
@@ -264,14 +276,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 +298,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 f77619204..fc5daec78 100644
--- a/apps/dokploy/components/dashboard/compose/general/actions.tsx
+++ b/apps/dokploy/components/dashboard/compose/general/actions.tsx
@@ -7,9 +7,9 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
-import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
-import { Ban, CheckCircle2, Hammer, HelpCircle, Terminal } from "lucide-react";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
+import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
import { useRouter } from "next/router";
import { toast } from "sonner";
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
@@ -34,7 +34,7 @@ export const ComposeActions = ({ composeId }: Props) => {
api.compose.stop.useMutation();
return (
-
+
{
});
}}
>
-
- Deploy
-
-
-
-
-
-
- Downloads the source code and performs a complete build
-
-
-
-
+
+
+
+
+ Deploy
+
+
+
+
+ Downloads the source code and performs a complete build
+
+
+
{
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
-
-
-
-
-
-
-
- Only rebuilds the compose without downloading new code
-
-
-
-
+
+
+
+
+ Reload
+
+
+
+
+ Reload the compose without rebuilding it
+
+
+
{data?.composeType === "docker-compose" &&
data?.composeStatus === "idle" ? (
@@ -128,26 +127,25 @@ export const ComposeActions = ({ composeId }: Props) => {
});
}}
>
-
- Start
-
-
-
-
-
-
-
-
- Start the compose (requires a previous successful build)
-
-
-
-
-
+
+
+
+
+ Start
+
+
+
+
+
+ Start the compose (requires a previous successful build)
+
+
+
+
) : (
{
});
}}
>
-
- Stop
-
-
-
-
-
-
-
- Stop the currently running compose
-
-
-
-
+
+
+
+
+ Stop
+
+
+
+
+ Stop the currently running compose
+
+
+
)}
@@ -191,15 +188,18 @@ export const ComposeActions = ({ composeId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
-
-
+
+
Open Terminal
Autodeploy
{
await update({
@@ -214,7 +214,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 b46065834..402cce955 100644
--- a/apps/dokploy/components/dashboard/database/backups/show-backups.tsx
+++ b/apps/dokploy/components/dashboard/database/backups/show-backups.tsx
@@ -20,6 +20,7 @@ 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 {
@@ -27,7 +28,9 @@ interface Props {
type: Exclude;
}
export const ShowBackups = ({ id, type }: Props) => {
- const [activeManualBackup, setActiveManualBackup] = useState();
+ const [activeManualBackup, setActiveManualBackup] = useState<
+ string | undefined
+ >();
const queryMap = {
postgres: () =>
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
@@ -69,7 +72,10 @@ export const ShowBackups = ({ id, type }: Props) => {
{postgres && postgres?.backups?.length > 0 && (
-
+
)}
@@ -96,11 +102,14 @@ export const ShowBackups = ({ id, type }: Props) => {
No backups configured
-
+
) : (
@@ -142,7 +151,7 @@ export const ShowBackups = ({ id, type }: Props) => {
Keep Latest
- {backup.keepLatestCount || 'All'}
+ {backup.keepLatestCount || "All"}
@@ -153,7 +162,10 @@ export const ShowBackups = ({ id, type }: Props) => {
{
setActiveManualBackup(backup.backupId);
await manualBackup({
@@ -178,6 +190,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 a281f1253..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
@@ -1,6 +1,6 @@
+import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button";
-import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
@@ -20,153 +20,152 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
+import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
-import Link from "next/link";
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 f4489ca2d..c222b2565 100644
--- a/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx
+++ b/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx
@@ -8,242 +8,245 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
-import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
-import {
- Ban,
- CheckCircle2,
- HelpCircle,
- RefreshCcw,
- Terminal,
-} from "lucide-react";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
+import { Ban, CheckCircle2, 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 {
- mariadbId: string;
+ mariadbId: string;
}
export const ShowGeneralMariadb = ({ mariadbId }: Props) => {
- const { data, refetch } = api.mariadb.one.useQuery(
- {
- mariadbId,
- },
- { enabled: !!mariadbId },
- );
+ const { data, refetch } = api.mariadb.one.useQuery(
+ {
+ mariadbId,
+ },
+ { enabled: !!mariadbId }
+ );
- const { mutateAsync: reload, isLoading: isReloading } =
- api.mariadb.reload.useMutation();
+ const { mutateAsync: reload, isLoading: isReloading } =
+ api.mariadb.reload.useMutation();
- const { mutateAsync: start, isLoading: isStarting } =
- api.mariadb.start.useMutation();
+ const { mutateAsync: start, isLoading: isStarting } =
+ api.mariadb.start.useMutation();
- const { mutateAsync: stop, isLoading: isStopping } =
- api.mariadb.stop.useMutation();
+ const { mutateAsync: stop, isLoading: isStopping } =
+ api.mariadb.stop.useMutation();
- const [isDrawerOpen, setIsDrawerOpen] = useState(false);
- const [filteredLogs, setFilteredLogs] = useState([]);
- const [isDeploying, setIsDeploying] = useState(false);
- api.mariadb.deployWithLogs.useSubscription(
- {
- mariadbId: mariadbId,
- },
- {
- enabled: isDeploying,
- onData(log) {
- if (!isDrawerOpen) {
- setIsDrawerOpen(true);
- }
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false);
+ const [filteredLogs, setFilteredLogs] = useState([]);
+ const [isDeploying, setIsDeploying] = useState(false);
+ api.mariadb.deployWithLogs.useSubscription(
+ {
+ mariadbId: mariadbId,
+ },
+ {
+ enabled: isDeploying,
+ onData(log) {
+ if (!isDrawerOpen) {
+ setIsDrawerOpen(true);
+ }
- 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);
- },
- },
- );
+ 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 MariaDB database
-
-
-
-
-
- {
- await reload({
- mariadbId: mariadbId,
- appName: data?.appName || "",
- })
- .then(() => {
- toast.success("Mariadb reloaded successfully");
- refetch();
- })
- .catch(() => {
- toast.error("Error reloading Mariadb");
- });
- }}
- >
-
- Reload
-
-
-
-
-
-
-
- Restart the MariaDB service without rebuilding
-
-
-
-
-
- {data?.applicationStatus === "idle" ? (
- {
- await start({
- mariadbId: mariadbId,
- })
- .then(() => {
- toast.success("Mariadb started successfully");
- refetch();
- })
- .catch(() => {
- toast.error("Error starting Mariadb");
- });
- }}
- >
-
- Start
-
-
-
-
-
-
-
-
- Start the MariaDB database (requires a previous
- successful setup)
-
-
-
-
-
-
- ) : (
- {
- await stop({
- mariadbId: mariadbId,
- })
- .then(() => {
- toast.success("Mariadb stopped successfully");
- refetch();
- })
- .catch(() => {
- toast.error("Error stopping Mariadb");
- });
- }}
- >
-
- Stop
-
-
-
-
-
-
-
- Stop the currently running MariaDB database
-
-
-
-
-
- )}
-
-
-
-
- Open Terminal
-
-
-
-
-
{
- setIsDrawerOpen(false);
- setFilteredLogs([]);
- setIsDeploying(false);
- refetch();
- }}
- filteredLogs={filteredLogs}
- />
-
- >
- );
+ return (
+ <>
+
+
+
+ Deploy Settings
+
+
+
+ {
+ setIsDeploying(true);
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ refetch();
+ }}
+ >
+
+
+
+
+ Deploy
+
+
+
+
+ Downloads and sets up the MariaDB database
+
+
+
+
+ {
+ await reload({
+ mariadbId: mariadbId,
+ appName: data?.appName || "",
+ })
+ .then(() => {
+ toast.success("Mariadb reloaded successfully");
+ refetch();
+ })
+ .catch(() => {
+ toast.error("Error reloading Mariadb");
+ });
+ }}
+ >
+
+
+
+
+ Reload
+
+
+
+
+ Restart the MariaDB service without rebuilding
+
+
+
+
+ {data?.applicationStatus === "idle" ? (
+ {
+ await start({
+ mariadbId: mariadbId,
+ })
+ .then(() => {
+ toast.success("Mariadb started successfully");
+ refetch();
+ })
+ .catch(() => {
+ toast.error("Error starting Mariadb");
+ });
+ }}
+ >
+
+
+
+
+ Start
+
+
+
+
+
+ Start the MariaDB database (requires a previous
+ successful setup)
+
+
+
+
+
+ ) : (
+ {
+ await stop({
+ mariadbId: mariadbId,
+ })
+ .then(() => {
+ toast.success("Mariadb stopped successfully");
+ refetch();
+ })
+ .catch(() => {
+ toast.error("Error stopping Mariadb");
+ });
+ }}
+ >
+
+
+
+
+ Stop
+
+
+
+
+ Stop the currently running MariaDB database
+
+
+
+
+ )}
+
+
+
+
+
+
+ Open Terminal
+
+
+
+
+ Open a terminal to the MariaDB container
+
+
+
+
+
+
+
{
+ setIsDrawerOpen(false);
+ setFilteredLogs([]);
+ setIsDeploying(false);
+ refetch();
+ }}
+ filteredLogs={filteredLogs}
+ />
+
+ >
+ );
};
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 b845004fa..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
@@ -1,6 +1,6 @@
+import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button";
-import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
@@ -20,152 +20,151 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
+import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
-import Link from "next/link";
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 644d1a266..dfbf501eb 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,249 @@ 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 * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
-import {
- Ban,
- CheckCircle2,
- HelpCircle,
- RefreshCcw,
- Terminal,
-} from "lucide-react";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
+import { Ban, CheckCircle2, 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 12b3eb065..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
@@ -1,6 +1,6 @@
+import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button";
-import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
@@ -20,152 +20,151 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
+import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
-import Link from "next/link";
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 3c8ae3ea9..7002ff783 100644
--- a/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx
+++ b/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx
@@ -8,239 +8,242 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
-import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
-import {
- Ban,
- CheckCircle2,
- HelpCircle,
- RefreshCcw,
- Terminal,
-} from "lucide-react";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
+import { Ban, CheckCircle2, 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 {
- mysqlId: string;
+ mysqlId: string;
}
export const ShowGeneralMysql = ({ mysqlId }: Props) => {
- const { data, refetch } = api.mysql.one.useQuery(
- {
- mysqlId,
- },
- { enabled: !!mysqlId },
- );
+ const { data, refetch } = api.mysql.one.useQuery(
+ {
+ mysqlId,
+ },
+ { enabled: !!mysqlId }
+ );
- const { mutateAsync: reload, isLoading: isReloading } =
- api.mysql.reload.useMutation();
- const { mutateAsync: start, isLoading: isStarting } =
- api.mysql.start.useMutation();
+ const { mutateAsync: reload, isLoading: isReloading } =
+ api.mysql.reload.useMutation();
+ const { mutateAsync: start, isLoading: isStarting } =
+ api.mysql.start.useMutation();
- const { mutateAsync: stop, isLoading: isStopping } =
- api.mysql.stop.useMutation();
+ const { mutateAsync: stop, isLoading: isStopping } =
+ api.mysql.stop.useMutation();
- const [isDrawerOpen, setIsDrawerOpen] = useState(false);
- const [filteredLogs, setFilteredLogs] = useState([]);
- const [isDeploying, setIsDeploying] = useState(false);
- api.mysql.deployWithLogs.useSubscription(
- {
- mysqlId: mysqlId,
- },
- {
- enabled: isDeploying,
- onData(log) {
- if (!isDrawerOpen) {
- setIsDrawerOpen(true);
- }
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false);
+ const [filteredLogs, setFilteredLogs] = useState([]);
+ const [isDeploying, setIsDeploying] = useState(false);
+ api.mysql.deployWithLogs.useSubscription(
+ {
+ mysqlId: mysqlId,
+ },
+ {
+ enabled: isDeploying,
+ onData(log) {
+ if (!isDrawerOpen) {
+ setIsDrawerOpen(true);
+ }
- 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 MySQL database
-
-
-
-
-
- {
- await reload({
- mysqlId: mysqlId,
- appName: data?.appName || "",
- })
- .then(() => {
- toast.success("Mysql reloaded successfully");
- refetch();
- })
- .catch(() => {
- toast.error("Error reloading Mysql");
- });
- }}
- >
-
- Reload
-
-
-
-
-
-
-
- Restart the MySQL service without rebuilding
-
-
-
-
-
- {data?.applicationStatus === "idle" ? (
- {
- await start({
- mysqlId: mysqlId,
- })
- .then(() => {
- toast.success("Mysql started successfully");
- refetch();
- })
- .catch(() => {
- toast.error("Error starting Mysql");
- });
- }}
- >
-
- Start
-
-
-
-
-
-
-
-
- Start the MySQL database (requires a previous
- successful setup)
-
-
-
-
-
-
- ) : (
- {
- await stop({
- mysqlId: mysqlId,
- })
- .then(() => {
- toast.success("Mysql stopped successfully");
- refetch();
- })
- .catch(() => {
- toast.error("Error stopping Mysql");
- });
- }}
- >
-
- Stop
-
-
-
-
-
-
-
- Stop the currently running MySQL database
-
-
-
-
-
- )}
-
-
-
-
- Open Terminal
-
-
-
-
-
{
- setIsDrawerOpen(false);
- setFilteredLogs([]);
- setIsDeploying(false);
- refetch();
- }}
- filteredLogs={filteredLogs}
- />
-
- >
- );
+ 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 MySQL database
+
+
+
+
+ {
+ await reload({
+ mysqlId: mysqlId,
+ appName: data?.appName || "",
+ })
+ .then(() => {
+ toast.success("Mysql reloaded successfully");
+ refetch();
+ })
+ .catch(() => {
+ toast.error("Error reloading Mysql");
+ });
+ }}
+ >
+
+
+
+
+ Reload
+
+
+
+
+ Restart the MySQL service without rebuilding
+
+
+
+
+ {data?.applicationStatus === "idle" ? (
+ {
+ await start({
+ mysqlId: mysqlId,
+ })
+ .then(() => {
+ toast.success("Mysql started successfully");
+ refetch();
+ })
+ .catch(() => {
+ toast.error("Error starting Mysql");
+ });
+ }}
+ >
+
+
+
+
+ Start
+
+
+
+
+
+ Start the MySQL database (requires a previous
+ successful setup)
+
+
+
+
+
+ ) : (
+ {
+ await stop({
+ mysqlId: mysqlId,
+ })
+ .then(() => {
+ toast.success("Mysql stopped successfully");
+ refetch();
+ })
+ .catch(() => {
+ toast.error("Error stopping Mysql");
+ });
+ }}
+ >
+
+
+
+
+ Stop
+
+
+
+
+ Stop the currently running MySQL database
+
+
+
+
+ )}
+
+
+
+
+
+
+ Open Terminal
+
+
+
+
+ Open a terminal to the MySQL container
+
+
+
+
+
+
+
{
+ setIsDrawerOpen(false);
+ setFilteredLogs([]);
+ setIsDeploying(false);
+ refetch();
+ }}
+ filteredLogs={filteredLogs}
+ />
+
+ >
+ );
};
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 7643be2d7..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
@@ -1,6 +1,6 @@
+import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button";
-import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
@@ -20,154 +20,153 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
+import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
-import Link from "next/link";
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 a22155451..999bc8504 100644
--- a/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx
+++ b/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx
@@ -8,15 +8,9 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
-import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
-import {
- Ban,
- CheckCircle2,
- HelpCircle,
- RefreshCcw,
- Terminal,
-} from "lucide-react";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
+import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { type LogLine, parseLogs } from "../../docker/logs/utils";
@@ -78,7 +72,7 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
Deploy Settings
-
+
{
refetch();
}}
>
-
- Deploy
-
-
-
-
-
-
- Downloads and sets up the PostgreSQL database
-
-
-
-
+
+
+
+
+ Deploy
+
+
+
+
+ Downloads and sets up the PostgreSQL database
+
+
+
{
});
}}
>
-
- Reload
-
-
-
-
-
-
-
- Restart the PostgreSQL service without rebuilding
-
-
-
-
+
+
+
+
+ Reload
+
+
+
+
+ Reload the PostgreSQL without rebuilding it
+
+
+
{data?.applicationStatus === "idle" ? (
{
});
}}
>
-
- Start
-
-
-
-
-
-
-
-
- Start the PostgreSQL database (requires a previous
- successful setup)
-
-
-
-
-
+
+
+
+
+ Start
+
+
+
+
+
+ Start the PostgreSQL database (requires a previous
+ successful setup)
+
+
+
+
) : (
{
});
}}
>
-
- Stop
-
-
-
-
-
-
-
- Stop the currently running PostgreSQL database
-
-
-
-
+
+
+
+
+ Stop
+
+
+
+
+ Stop the currently running PostgreSQL database
+
+
+
)}
@@ -226,8 +217,11 @@ export const ShowGeneralPostgres = ({ postgresId }: Props) => {
appName={data?.appName || ""}
serverId={data?.serverId || ""}
>
-
-
+
+
Open Terminal
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 5abf0bc12..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
@@ -1,6 +1,6 @@
+import { AlertBlock } from "@/components/shared/alert-block";
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
import { Button } from "@/components/ui/button";
-import { AlertBlock } from "@/components/shared/alert-block";
import {
Card,
CardContent,
@@ -20,146 +20,145 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
+import Link from "next/link";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
-import Link from "next/link";
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 d9c2e10aa..fabd10266 100644
--- a/apps/dokploy/components/dashboard/redis/general/show-general-redis.tsx
+++ b/apps/dokploy/components/dashboard/redis/general/show-general-redis.tsx
@@ -8,241 +8,244 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
-import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { api } from "@/utils/api";
-import {
- Ban,
- CheckCircle2,
- HelpCircle,
- RefreshCcw,
- Terminal,
-} from "lucide-react";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
+import { Ban, CheckCircle2, 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 {
- redisId: string;
+ redisId: string;
}
export const ShowGeneralRedis = ({ redisId }: Props) => {
- const { data, refetch } = api.redis.one.useQuery(
- {
- redisId,
- },
- { enabled: !!redisId },
- );
+ const { data, refetch } = api.redis.one.useQuery(
+ {
+ redisId,
+ },
+ { enabled: !!redisId }
+ );
- const { mutateAsync: reload, isLoading: isReloading } =
- api.redis.reload.useMutation();
- const { mutateAsync: start, isLoading: isStarting } =
- api.redis.start.useMutation();
+ const { mutateAsync: reload, isLoading: isReloading } =
+ api.redis.reload.useMutation();
+ const { mutateAsync: start, isLoading: isStarting } =
+ api.redis.start.useMutation();
- const { mutateAsync: stop, isLoading: isStopping } =
- api.redis.stop.useMutation();
+ const { mutateAsync: stop, isLoading: isStopping } =
+ api.redis.stop.useMutation();
- const [isDrawerOpen, setIsDrawerOpen] = useState(false);
- const [filteredLogs, setFilteredLogs] = useState([]);
- const [isDeploying, setIsDeploying] = useState(false);
- api.redis.deployWithLogs.useSubscription(
- {
- redisId: redisId,
- },
- {
- enabled: isDeploying,
- onData(log) {
- if (!isDrawerOpen) {
- setIsDrawerOpen(true);
- }
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false);
+ const [filteredLogs, setFilteredLogs] = useState([]);
+ const [isDeploying, setIsDeploying] = useState(false);
+ api.redis.deployWithLogs.useSubscription(
+ {
+ redisId: redisId,
+ },
+ {
+ enabled: isDeploying,
+ onData(log) {
+ if (!isDrawerOpen) {
+ setIsDrawerOpen(true);
+ }
- 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);
- },
- },
- );
+ 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 Redis database
-
-
-
-
-
- {
- await reload({
- redisId: redisId,
- appName: data?.appName || "",
- })
- .then(() => {
- toast.success("Redis reloaded successfully");
- refetch();
- })
- .catch(() => {
- toast.error("Error reloading Redis");
- });
- }}
- >
-
- Reload
-
-
-
-
-
-
-
- Restart the Redis service without rebuilding
-
-
-
-
-
- {data?.applicationStatus === "idle" ? (
- {
- await start({
- redisId: redisId,
- })
- .then(() => {
- toast.success("Redis started successfully");
- refetch();
- })
- .catch(() => {
- toast.error("Error starting Redis");
- });
- }}
- >
-
- Start
-
-
-
-
-
-
-
-
- Start the Redis database (requires a previous
- successful setup)
-
-
-
-
-
-
- ) : (
- {
- await stop({
- redisId: redisId,
- })
- .then(() => {
- toast.success("Redis stopped successfully");
- refetch();
- })
- .catch(() => {
- toast.error("Error stopping Redis");
- });
- }}
- >
-
- Stop
-
-
-
-
-
-
-
- Stop the currently running Redis database
-
-
-
-
-
- )}
-
-
-
-
- Open Terminal
-
-
-
-
-
{
- setIsDrawerOpen(false);
- setFilteredLogs([]);
- setIsDeploying(false);
- refetch();
- }}
- filteredLogs={filteredLogs}
- />
-
- >
- );
+ return (
+ <>
+
+
+
+ Deploy Settings
+
+
+
+ {
+ setIsDeploying(true);
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ refetch();
+ }}
+ >
+
+
+
+
+ Deploy
+
+
+
+
+ Downloads and sets up the Redis database
+
+
+
+
+ {
+ await reload({
+ redisId: redisId,
+ appName: data?.appName || "",
+ })
+ .then(() => {
+ toast.success("Redis reloaded successfully");
+ refetch();
+ })
+ .catch(() => {
+ toast.error("Error reloading Redis");
+ });
+ }}
+ >
+
+
+
+
+ Reload
+
+
+
+
+ Restart the Redis service without rebuilding
+
+
+
+
+ {data?.applicationStatus === "idle" ? (
+ {
+ await start({
+ redisId: redisId,
+ })
+ .then(() => {
+ toast.success("Redis started successfully");
+ refetch();
+ })
+ .catch(() => {
+ toast.error("Error starting Redis");
+ });
+ }}
+ >
+
+
+
+
+ Start
+
+
+
+
+
+ Start the Redis database (requires a previous
+ successful setup)
+
+
+
+
+
+ ) : (
+ {
+ await stop({
+ redisId: redisId,
+ })
+ .then(() => {
+ toast.success("Redis stopped successfully");
+ refetch();
+ })
+ .catch(() => {
+ toast.error("Error stopping Redis");
+ });
+ }}
+ >
+
+
+
+
+ Stop
+
+
+
+
+ Stop the currently running Redis database
+
+
+
+
+ )}
+
+
+
+
+
+
+ Open Terminal
+
+
+
+
+ Open a terminal to the Redis container
+
+
+
+
+
+
+
{
+ setIsDrawerOpen(false);
+ setFilteredLogs([]);
+ setIsDeploying(false);
+ refetch();
+ }}
+ filteredLogs={filteredLogs}
+ />
+
+ >
+ );
};
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 82ec5727e..84d248975 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;
+ }
+};