From e2b155280a72e40d21f62707034e4a89fbfa9ef5 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Sat, 21 Dec 2024 20:08:08 +0100 Subject: [PATCH 1/9] feat: add enter terminal translation --- apps/dokploy/public/locales/en/settings.json | 1 + apps/dokploy/public/locales/pl/settings.json | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/dokploy/public/locales/en/settings.json b/apps/dokploy/public/locales/en/settings.json index 2103ecc0..f71e56ff 100644 --- a/apps/dokploy/public/locales/en/settings.json +++ b/apps/dokploy/public/locales/en/settings.json @@ -13,6 +13,7 @@ "settings.server.webServer.description": "Reload or clean the web server.", "settings.server.webServer.actions": "Actions", "settings.server.webServer.reload": "Reload", + "settings.server.webServer.enterTerminal": "Enter the terminal", "settings.server.webServer.watchLogs": "Watch logs", "settings.server.webServer.updateServerIp": "Update Server IP", "settings.server.webServer.server.label": "Server", diff --git a/apps/dokploy/public/locales/pl/settings.json b/apps/dokploy/public/locales/pl/settings.json index 48531e69..a354a871 100644 --- a/apps/dokploy/public/locales/pl/settings.json +++ b/apps/dokploy/public/locales/pl/settings.json @@ -13,6 +13,7 @@ "settings.server.webServer.description": "Przeładuj lub wyczyść serwer", "settings.server.webServer.actions": "Akcje", "settings.server.webServer.reload": "Przeładuj", + "settings.server.webServer.enterTerminal": "Otwórz terminal", "settings.server.webServer.watchLogs": "Obserwuj logi", "settings.server.webServer.updateServerIp": "Zaktualizuj IP serwera", "settings.server.webServer.server.label": "Serwer", From 0d58935ad1261c94720c91b88b559eb238333764 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Sun, 22 Dec 2024 16:30:53 +0100 Subject: [PATCH 2/9] refactor: translations for common terminal stuff --- .../dashboard/settings/servers/add-server.tsx | 10 ++++++---- .../dashboard/settings/servers/show-servers.tsx | 6 +++++- .../dashboard/settings/servers/update-server.tsx | 13 +++++++------ apps/dokploy/public/locales/en/settings.json | 9 +++++++-- apps/dokploy/public/locales/pl/settings.json | 9 +++++++-- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/servers/add-server.tsx b/apps/dokploy/components/dashboard/settings/servers/add-server.tsx index b8413303..36f93f5d 100644 --- a/apps/dokploy/components/dashboard/settings/servers/add-server.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/add-server.tsx @@ -31,8 +31,8 @@ import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { PlusIcon } from "lucide-react"; +import { useTranslation } from "next-i18next"; import Link from "next/link"; -import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -56,6 +56,8 @@ const Schema = z.object({ type Schema = z.infer; export const AddServer = () => { + const { t } = useTranslation("settings"); + const utils = api.useUtils(); const [isOpen, setIsOpen] = useState(false); const { data: canCreateMoreServers, refetch } = @@ -212,7 +214,7 @@ export const AddServer = () => { name="ipAddress" render={({ field }) => ( - IP Address + {t("settings.terminal.ipAddress")} @@ -226,7 +228,7 @@ export const AddServer = () => { name="port" render={({ field }) => ( - Port + {t("settings.terminal.port")} { name="username" render={({ field }) => ( - Username + {t("settings.terminal.username")} diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx index a174cd9c..7527670b 100644 --- a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx @@ -33,8 +33,10 @@ import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal"; import { UpdateServer } from "./update-server"; import { useRouter } from "next/router"; import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription"; +import { useTranslation } from "next-i18next"; export const ShowServers = () => { + const { t } = useTranslation("settings"); const router = useRouter(); const query = router.query; const { data, refetch } = api.server.all.useQuery(); @@ -190,7 +192,9 @@ export const ShowServers = () => { <> {server.sshKeyId && ( - Enter the terminal + + {t("settings.common.enterTerminal")} + )} diff --git a/apps/dokploy/components/dashboard/settings/servers/update-server.tsx b/apps/dokploy/components/dashboard/settings/servers/update-server.tsx index 16eb5ba7..543cacf6 100644 --- a/apps/dokploy/components/dashboard/settings/servers/update-server.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/update-server.tsx @@ -31,8 +31,7 @@ import { import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; -import { PlusIcon } from "lucide-react"; -import { useRouter } from "next/router"; +import { useTranslation } from "next-i18next"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; @@ -60,6 +59,8 @@ interface Props { } export const UpdateServer = ({ serverId }: Props) => { + const { t } = useTranslation("settings"); + const utils = api.useUtils(); const [isOpen, setIsOpen] = useState(false); const { data, isLoading } = api.server.one.useQuery( @@ -212,7 +213,7 @@ export const UpdateServer = ({ serverId }: Props) => { name="ipAddress" render={({ field }) => ( - IP Address + {t("settings.terminal.ipAddress")} @@ -226,7 +227,7 @@ export const UpdateServer = ({ serverId }: Props) => { name="port" render={({ field }) => ( - Port + {t("settings.terminal.port")} { name="username" render={({ field }) => ( - Username + {t("settings.terminal.username")} @@ -273,7 +274,7 @@ export const UpdateServer = ({ serverId }: Props) => { form="hook-form-update-server" type="submit" > - Update + {t("settings.common.save")} diff --git a/apps/dokploy/public/locales/en/settings.json b/apps/dokploy/public/locales/en/settings.json index f71e56ff..c70c614d 100644 --- a/apps/dokploy/public/locales/en/settings.json +++ b/apps/dokploy/public/locales/en/settings.json @@ -1,5 +1,6 @@ { "settings.common.save": "Save", + "settings.common.enterTerminal": "Enter the terminal", "settings.server.domain.title": "Server Domain", "settings.server.domain.description": "Add a domain to your server application.", "settings.server.domain.form.domain": "Domain", @@ -13,7 +14,6 @@ "settings.server.webServer.description": "Reload or clean the web server.", "settings.server.webServer.actions": "Actions", "settings.server.webServer.reload": "Reload", - "settings.server.webServer.enterTerminal": "Enter the terminal", "settings.server.webServer.watchLogs": "Watch logs", "settings.server.webServer.updateServerIp": "Update Server IP", "settings.server.webServer.server.label": "Server", @@ -41,5 +41,10 @@ "settings.appearance.themes.dark": "Dark", "settings.appearance.themes.system": "System", "settings.appearance.language": "Language", - "settings.appearance.languageDescription": "Select a language for your dashboard" + "settings.appearance.languageDescription": "Select a language for your dashboard", + + "settings.terminal.connectionSettings": "Connection settings", + "settings.terminal.ipAddress": "IP Address", + "settings.terminal.port": "Port", + "settings.terminal.username": "Username" } diff --git a/apps/dokploy/public/locales/pl/settings.json b/apps/dokploy/public/locales/pl/settings.json index a354a871..93414825 100644 --- a/apps/dokploy/public/locales/pl/settings.json +++ b/apps/dokploy/public/locales/pl/settings.json @@ -1,5 +1,6 @@ { "settings.common.save": "Zapisz", + "settings.common.enterTerminal": "Otwórz terminal", "settings.server.domain.title": "Domena", "settings.server.domain.description": "Dodaj domenę do aplikacji", "settings.server.domain.form.domain": "Domena", @@ -13,7 +14,6 @@ "settings.server.webServer.description": "Przeładuj lub wyczyść serwer", "settings.server.webServer.actions": "Akcje", "settings.server.webServer.reload": "Przeładuj", - "settings.server.webServer.enterTerminal": "Otwórz terminal", "settings.server.webServer.watchLogs": "Obserwuj logi", "settings.server.webServer.updateServerIp": "Zaktualizuj IP serwera", "settings.server.webServer.server.label": "Serwer", @@ -41,5 +41,10 @@ "settings.appearance.themes.dark": "Ciemny", "settings.appearance.themes.system": "System", "settings.appearance.language": "Język", - "settings.appearance.languageDescription": "Wybierz język swojego pulpitu" + "settings.appearance.languageDescription": "Wybierz język swojego pulpitu", + + "settings.terminal.connectionSettings": "Ustawienia połączenia", + "settings.terminal.ipAddress": "Adres IP", + "settings.terminal.port": "Port", + "settings.terminal.username": "Nazwa użytkownika" } From ec8eaf624916a7d38446cd6ca342a4664f6f44cf Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Sun, 22 Dec 2024 16:31:52 +0100 Subject: [PATCH 3/9] feat: add util to configure and get private ssh key for local ssh connection --- apps/dokploy/server/wss/utils.ts | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/apps/dokploy/server/wss/utils.ts b/apps/dokploy/server/wss/utils.ts index b5567127..3172e36e 100644 --- a/apps/dokploy/server/wss/utils.ts +++ b/apps/dokploy/server/wss/utils.ts @@ -1,4 +1,17 @@ +import { execAsync } from "@dokploy/server/utils/process/execAsync"; import os from "node:os"; +import path from "node:path"; +import fs from "node:fs"; + +const HOME_PATH = process.env.HOME || process.env.USERPROFILE || "/"; + +const LOCAL_SSH_KEY_PATH = path.join( + HOME_PATH, + ".ssh", + "auto_generated-dokploy-local", +); + +const AUTHORIZED_KEYS_PATH = path.join(HOME_PATH, ".ssh", "authorized_keys"); export const getShell = () => { switch (os.platform()) { @@ -10,3 +23,40 @@ export const getShell = () => { return "bash"; } }; + +/** Returns private SSH key for dokploy local server terminal. Uses already created SSH key or generates a new SSH key, also automatically appends the public key to `authorized_keys`, creating the file if needed. */ +export const getLocalServerPrivateKey = async () => { + try { + if (!fs.existsSync(LOCAL_SSH_KEY_PATH)) { + // Generate new SSH key if it hasn't been created yet + await execAsync( + `ssh-keygen -t rsa -b 4096 -f ${LOCAL_SSH_KEY_PATH} -N ""`, + ); + } + + const privateKey = fs.readFileSync(LOCAL_SSH_KEY_PATH, "utf8"); + const publicKey = fs.readFileSync(`${LOCAL_SSH_KEY_PATH}.pub`, "utf8"); + const authKeyContent = `${publicKey}\n`; + + if (!fs.existsSync(AUTHORIZED_KEYS_PATH)) { + // Create authorized_keys if it doesn't exist yet + fs.writeFileSync(AUTHORIZED_KEYS_PATH, authKeyContent, { mode: 0o600 }); + return privateKey; + } + + const existingAuthKeys = fs.readFileSync(AUTHORIZED_KEYS_PATH, "utf8"); + if (existingAuthKeys.includes(publicKey)) { + return privateKey; + } + + // Append the public key to authorized_keys + fs.appendFileSync(AUTHORIZED_KEYS_PATH, authKeyContent, { + mode: 0o600, + }); + + return privateKey; + } catch (error) { + console.error("Error getting private SSH key for local terminal:", error); + return ""; + } +}; From a53929a78762516f41d06b999c2ffbd663b48766 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Sun, 22 Dec 2024 16:32:20 +0100 Subject: [PATCH 4/9] feat: add enter terminal option to server dropdown --- .../settings/servers/actions/show-dokploy-actions.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx index ee749244..0fa4638b 100644 --- a/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/actions/show-dokploy-actions.tsx @@ -16,6 +16,7 @@ import { useTranslation } from "next-i18next"; import { toast } from "sonner"; import { ShowModalLogs } from "../../web-server/show-modal-logs"; import { GPUSupportModal } from "../gpu-support-modal"; +import { TerminalModal } from "../../web-server/terminal-modal"; export const ShowDokployActions = () => { const { t } = useTranslation("settings"); @@ -49,6 +50,9 @@ export const ShowDokployActions = () => { > {t("settings.server.webServer.reload")} + + {t("settings.common.enterTerminal")} + Date: Sun, 22 Dec 2024 16:35:28 +0100 Subject: [PATCH 5/9] refactor: adapt terminal wss to allow local server connection, add status logs --- apps/dokploy/server/wss/terminal.ts | 72 +++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts index b1ecb908..fa268124 100644 --- a/apps/dokploy/server/wss/terminal.ts +++ b/apps/dokploy/server/wss/terminal.ts @@ -1,8 +1,9 @@ import type http from "node:http"; import { findServerById, validateWebSocketRequest } from "@dokploy/server"; import { publicIpv4, publicIpv6 } from "public-ip"; -import { Client } from "ssh2"; +import { Client, type ConnectConfig } from "ssh2"; import { WebSocketServer } from "ws"; +import { getLocalServerPrivateKey } from "./utils"; export const getPublicIpWithFallback = async () => { // @ts-ignore @@ -55,21 +56,67 @@ export const setupTerminalWebSocketServer = ( return; } - const server = await findServerById(serverId); + let connectionDetails: ConnectConfig = {}; - if (!server) { - ws.close(); - return; + const isLocalServer = serverId === "local"; + + if (isLocalServer) { + const port = Number(url.searchParams.get("port")); + const username = url.searchParams.get("username"); + + if (!port || !username) { + ws.close(); + return; + } + + ws.send("Getting private SSH key...\n"); + const privateKey = await getLocalServerPrivateKey(); + + if (!privateKey) { + ws.close(); + return; + } + + connectionDetails = { + host: "localhost", + port, + username, + privateKey, + }; + } else { + ws.send("Getting server data...\n"); + const server = await findServerById(serverId); + + if (!server) { + ws.close(); + return; + } + + const { ipAddress, port, username, sshKey, sshKeyId } = server; + + if (!sshKeyId) { + throw new Error("No SSH key available for this server"); + } + + connectionDetails = { + host: ipAddress, + port: port, + username: username, + privateKey: sshKey?.privateKey, + }; } - if (!server.sshKeyId) - throw new Error("No SSH key available for this server"); - const conn = new Client(); let stdout = ""; let stderr = ""; + + ws.send("Connecting...\n"); + conn .once("ready", () => { + // Clear terminal content once connected + ws.send("\x1bc"); + conn.shell({}, (err, stream) => { if (err) throw err; @@ -112,18 +159,13 @@ export const setupTerminalWebSocketServer = ( .on("error", (err) => { if (err.level === "client-authentication") { ws.send( - `Authentication failed: Invalid SSH private key. ❌ Error: ${err.message} ${err.level}`, + `Authentication failed: Unauthorized ${isLocalServer ? "" : "private SSH key or "}username.\n❌ Error: ${err.message} ${err.level}`, ); } else { ws.send(`SSH connection error: ${err.message}`); } conn.end(); }) - .connect({ - host: server.ipAddress, - port: server.port, - username: server.username, - privateKey: server.sshKey?.privateKey, - }); + .connect(connectionDetails); }); }; From 7e08c8881eb5904bbfbffb45874dcba3da8b0e4f Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Sun, 22 Dec 2024 16:37:26 +0100 Subject: [PATCH 6/9] refactor: adapt terminal component to pass local server data and add initialized check --- .../settings/web-server/terminal.tsx | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx b/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx index d38f5d9e..b6757b9c 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx @@ -5,6 +5,7 @@ import { FitAddon } from "xterm-addon-fit"; import "@xterm/xterm/css/xterm.css"; import { AttachAddon } from "@xterm/addon-attach"; import { useTheme } from "next-themes"; +import { getLocalServerData } from "./local-server-config"; interface Props { id: string; @@ -12,9 +13,16 @@ interface Props { } export const Terminal: React.FC = ({ id, serverId }) => { - const termRef = useRef(null); + const termRef = useRef(null); + const initialized = useRef(false); const { resolvedTheme } = useTheme(); useEffect(() => { + if (initialized.current) { + // Required in strict mode to avoid issues due to double wss connection + return; + } + + initialized.current = true; const container = document.getElementById(id); if (container) { container.innerHTML = ""; @@ -33,7 +41,18 @@ export const Terminal: React.FC = ({ id, serverId }) => { const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; - const wsUrl = `${protocol}//${window.location.host}/terminal?serverId=${serverId}`; + const isLocalServer = serverId === "local"; + + const urlParams = new URLSearchParams(); + urlParams.set("serverId", serverId); + + if (isLocalServer) { + const { port, username } = getLocalServerData(); + urlParams.set("port", port.toString()); + urlParams.set("username", username); + } + + const wsUrl = `${protocol}//${window.location.host}/terminal?${urlParams}`; const ws = new WebSocket(wsUrl); const addonAttach = new AttachAddon(ws); From 1ea7d2e1bf38c67b29bc141fc7cc427ec41332b6 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Sun, 22 Dec 2024 18:44:38 +0100 Subject: [PATCH 7/9] feat: enable server translations for servers page --- apps/dokploy/pages/dashboard/settings/servers.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/dokploy/pages/dashboard/settings/servers.tsx b/apps/dokploy/pages/dashboard/settings/servers.tsx index 7aea531b..d25a3513 100644 --- a/apps/dokploy/pages/dashboard/settings/servers.tsx +++ b/apps/dokploy/pages/dashboard/settings/servers.tsx @@ -2,6 +2,7 @@ import { ShowServers } from "@/components/dashboard/settings/servers/show-server import { DashboardLayout } from "@/components/layouts/dashboard-layout"; import { SettingsLayout } from "@/components/layouts/settings-layout"; import { appRouter } from "@/server/api/root"; +import { getLocale, serverSideTranslations } from "@/utils/i18n"; import { validateRequest } from "@dokploy/server"; import { createServerSideHelpers } from "@trpc/react-query/server"; import type { GetServerSidePropsContext } from "next"; @@ -29,6 +30,7 @@ export async function getServerSideProps( ctx: GetServerSidePropsContext<{ serviceId: string }>, ) { const { req, res } = ctx; + const locale = await getLocale(req.cookies); const { user, session } = await validateRequest(req, res); if (!user) { return { @@ -64,6 +66,7 @@ export async function getServerSideProps( return { props: { trpcState: helpers.dehydrate(), + ...(await serverSideTranslations(locale, ["settings"])), }, }; } From fd0a9b8b585682519bf0f199e163a73c2f30b78c Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Sun, 22 Dec 2024 18:46:02 +0100 Subject: [PATCH 8/9] feat: add local server config, add local server support in terminal modal, add fixed terminal container height --- .../web-server/local-server-config.tsx | 154 ++++++++++++++++++ .../settings/web-server/terminal-modal.tsx | 26 ++- 2 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 apps/dokploy/components/dashboard/settings/web-server/local-server-config.tsx diff --git a/apps/dokploy/components/dashboard/settings/web-server/local-server-config.tsx b/apps/dokploy/components/dashboard/settings/web-server/local-server-config.tsx new file mode 100644 index 00000000..fde03ffb --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/web-server/local-server-config.tsx @@ -0,0 +1,154 @@ +import { Button, buttonVariants } from "@/components/ui/button"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Input } from "@/components/ui/input"; +import { + FormControl, + FormLabel, + FormField, + FormMessage, + FormItem, + Form, +} from "@/components/ui/form"; +import { cn } from "@/lib/utils"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Settings } from "lucide-react"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { useTranslation } from "next-i18next"; + +const Schema = z.object({ + port: z.number().min(1, "Port must be higher than 0"), + username: z.string().min(1, "Username is required"), +}); + +type Schema = z.infer; + +const DEFAULT_LOCAL_SERVER_DATA: Schema = { + port: 22, + username: "root", +}; + +/** Returns local server data for use with local server terminal */ +export const getLocalServerData = () => { + try { + const localServerData = localStorage.getItem("localServerData"); + const parsedLocalServerData = localServerData + ? (JSON.parse(localServerData) as typeof DEFAULT_LOCAL_SERVER_DATA) + : DEFAULT_LOCAL_SERVER_DATA; + + return parsedLocalServerData; + } catch { + return DEFAULT_LOCAL_SERVER_DATA; + } +}; + +interface Props { + onSave: () => void; +} + +const LocalServerConfig = ({ onSave }: Props) => { + const { t } = useTranslation("settings"); + + const form = useForm({ + defaultValues: getLocalServerData(), + resolver: zodResolver(Schema), + }); + + const onSubmit = (data: Schema) => { + localStorage.setItem("localServerData", JSON.stringify(data)); + form.reset(data); + onSave(); + }; + + return ( + + + +
+
+ + + {t("settings.terminal.connectionSettings")} + +
+
+
+ + +
+ + ( + + {t("settings.terminal.port")} + + { + const value = e.target.value; + if (value === "") { + field.onChange(1); + } else { + const number = Number.parseInt(value, 10); + if (!Number.isNaN(number)) { + field.onChange(number); + } + } + }} + /> + + + + + )} + /> + + ( + + {t("settings.terminal.username")} + + + + + + + )} + /> + + + + +
+
+
+ ); +}; + +export default LocalServerConfig; diff --git a/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx b/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx index 5bdba8b8..2bfd00a0 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx @@ -10,24 +10,38 @@ import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; import { api } from "@/utils/api"; import dynamic from "next/dynamic"; import type React from "react"; +import { useState } from "react"; +import LocalServerConfig from "./local-server-config"; const Terminal = dynamic(() => import("./terminal").then((e) => e.Terminal), { ssr: false, }); +const getTerminalKey = () => { + return `terminal-${Date.now()}`; +}; + interface Props { children?: React.ReactNode; serverId: string; } export const TerminalModal = ({ children, serverId }: Props) => { + const [terminalKey, setTerminalKey] = useState(getTerminalKey()); + const isLocalServer = serverId === "local"; + const { data } = api.server.one.useQuery( { serverId, }, - { enabled: !!serverId }, + { enabled: !!serverId && !isLocalServer }, ); + const handleLocalServerConfigSave = () => { + // Rerender Terminal component to reconnect using new component key when saving local server config + setTerminalKey(getTerminalKey()); + }; + return ( @@ -43,12 +57,16 @@ export const TerminalModal = ({ children, serverId }: Props) => { onEscapeKeyDown={(event) => event.preventDefault()} > - Terminal ({data?.name}) + Terminal ({data?.name ?? serverId}) Easy way to access the server -
- + {isLocalServer && ( + + )} + +
+
From 228d12a61c5f6a1e58e75fc875c4a8a1f90e7a8f Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Sun, 22 Dec 2024 19:01:02 +0100 Subject: [PATCH 9/9] refactor: code cleanup --- .../settings/web-server/terminal-modal.tsx | 2 +- .../dashboard/settings/web-server/terminal.tsx | 4 +--- apps/dokploy/server/wss/terminal.ts | 14 +++++++------- apps/dokploy/server/wss/utils.ts | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx b/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx index 2bfd00a0..7c64ebc0 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/terminal-modal.tsx @@ -66,7 +66,7 @@ export const TerminalModal = ({ children, serverId }: Props) => { )}
- +
diff --git a/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx b/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx index b6757b9c..e45b73d2 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/terminal.tsx @@ -41,12 +41,10 @@ export const Terminal: React.FC = ({ id, serverId }) => { const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; - const isLocalServer = serverId === "local"; - const urlParams = new URLSearchParams(); urlParams.set("serverId", serverId); - if (isLocalServer) { + if (serverId === "local") { const { port, username } = getLocalServerData(); urlParams.set("port", port.toString()); urlParams.set("username", username); diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts index fa268124..78cda7ac 100644 --- a/apps/dokploy/server/wss/terminal.ts +++ b/apps/dokploy/server/wss/terminal.ts @@ -3,7 +3,7 @@ import { findServerById, validateWebSocketRequest } from "@dokploy/server"; import { publicIpv4, publicIpv6 } from "public-ip"; import { Client, type ConnectConfig } from "ssh2"; import { WebSocketServer } from "ws"; -import { getLocalServerPrivateKey } from "./utils"; +import { setupLocalServerSSHKey } from "./utils"; export const getPublicIpWithFallback = async () => { // @ts-ignore @@ -69,8 +69,8 @@ export const setupTerminalWebSocketServer = ( return; } - ws.send("Getting private SSH key...\n"); - const privateKey = await getLocalServerPrivateKey(); + ws.send("Setting up private SSH key...\n"); + const privateKey = await setupLocalServerSSHKey(); if (!privateKey) { ws.close(); @@ -92,16 +92,16 @@ export const setupTerminalWebSocketServer = ( return; } - const { ipAddress, port, username, sshKey, sshKeyId } = server; + const { ipAddress: host, port, username, sshKey, sshKeyId } = server; if (!sshKeyId) { throw new Error("No SSH key available for this server"); } connectionDetails = { - host: ipAddress, - port: port, - username: username, + host, + port, + username, privateKey: sshKey?.privateKey, }; } diff --git a/apps/dokploy/server/wss/utils.ts b/apps/dokploy/server/wss/utils.ts index 3172e36e..4971bac2 100644 --- a/apps/dokploy/server/wss/utils.ts +++ b/apps/dokploy/server/wss/utils.ts @@ -25,7 +25,7 @@ export const getShell = () => { }; /** Returns private SSH key for dokploy local server terminal. Uses already created SSH key or generates a new SSH key, also automatically appends the public key to `authorized_keys`, creating the file if needed. */ -export const getLocalServerPrivateKey = async () => { +export const setupLocalServerSSHKey = async () => { try { if (!fs.existsSync(LOCAL_SSH_KEY_PATH)) { // Generate new SSH key if it hasn't been created yet