From ea5349c8445826252fc998085c430a750efd2007 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 8 Sep 2024 23:11:39 -0600 Subject: [PATCH] feat: add logs for each application --- .../dashboard/application/logs/show.tsx | 5 +- .../dashboard/compose/logs/show.tsx | 9 +- .../dashboard/compose/monitoring/show.tsx | 4 + .../dashboard/docker/logs/docker-logs-id.tsx | 9 +- .../services/application/[applicationId].tsx | 5 +- .../services/compose/[composeId].tsx | 2 + .../services/mariadb/[mariadbId].tsx | 5 +- .../[projectId]/services/mongo/[mongoId].tsx | 5 +- .../[projectId]/services/mysql/[mysqlId].tsx | 5 +- .../services/postgres/[postgresId].tsx | 5 +- .../[projectId]/services/redis/[redisId].tsx | 5 +- apps/dokploy/server/api/routers/docker.ts | 7 +- apps/dokploy/server/api/services/docker.ts | 63 ++++++++-- .../server/wss/docker-container-logs.ts | 117 +++++++++++++----- 14 files changed, 193 insertions(+), 53 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/logs/show.tsx b/apps/dokploy/components/dashboard/application/logs/show.tsx index 780eac40..99e33bce 100644 --- a/apps/dokploy/components/dashboard/application/logs/show.tsx +++ b/apps/dokploy/components/dashboard/application/logs/show.tsx @@ -30,12 +30,14 @@ export const DockerLogs = dynamic( interface Props { appName: string; + serverId?: string; } -export const ShowDockerLogs = ({ appName }: Props) => { +export const ShowDockerLogs = ({ appName, serverId }: Props) => { const { data } = api.docker.getContainersByAppNameMatch.useQuery( { appName, + serverId, }, { enabled: !!appName, @@ -79,6 +81,7 @@ export const ShowDockerLogs = ({ appName }: Props) => { diff --git a/apps/dokploy/components/dashboard/compose/logs/show.tsx b/apps/dokploy/components/dashboard/compose/logs/show.tsx index 19a7c4e6..736f70be 100644 --- a/apps/dokploy/components/dashboard/compose/logs/show.tsx +++ b/apps/dokploy/components/dashboard/compose/logs/show.tsx @@ -30,14 +30,20 @@ export const DockerLogs = dynamic( interface Props { appName: string; + serverId?: string; appType: "stack" | "docker-compose"; } -export const ShowDockerLogsCompose = ({ appName, appType }: Props) => { +export const ShowDockerLogsCompose = ({ + appName, + appType, + serverId, +}: Props) => { const { data } = api.docker.getContainersByAppNameMatch.useQuery( { appName, appType, + serverId, }, { enabled: !!appName, @@ -81,6 +87,7 @@ export const ShowDockerLogsCompose = ({ appName, appType }: Props) => { diff --git a/apps/dokploy/components/dashboard/compose/monitoring/show.tsx b/apps/dokploy/components/dashboard/compose/monitoring/show.tsx index 428c74e4..1d7a724e 100644 --- a/apps/dokploy/components/dashboard/compose/monitoring/show.tsx +++ b/apps/dokploy/components/dashboard/compose/monitoring/show.tsx @@ -23,17 +23,20 @@ import { DockerMonitoring } from "../../monitoring/docker/show"; interface Props { appName: string; + serverId?: string; appType: "stack" | "docker-compose"; } export const ShowMonitoringCompose = ({ appName, appType = "stack", + serverId, }: Props) => { const { data } = api.docker.getContainersByAppNameMatch.useQuery( { appName: appName, appType, + serverId, }, { enabled: !!appName, @@ -108,6 +111,7 @@ export const ShowMonitoringCompose = ({ diff --git a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx index a269cc0c..cfffe8cb 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -8,9 +8,14 @@ import "@xterm/xterm/css/xterm.css"; interface Props { id: string; containerId: string; + serverId?: string; } -export const DockerLogsId: React.FC = ({ id, containerId }) => { +export const DockerLogsId: React.FC = ({ + id, + containerId, + serverId, +}) => { const [term, setTerm] = React.useState(); const [lines, setLines] = React.useState(40); @@ -38,7 +43,7 @@ export const DockerLogsId: React.FC = ({ id, containerId }) => { const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; - const wsUrl = `${protocol}//${window.location.host}/docker-container-logs?containerId=${containerId}&tail=${lines}`; + const wsUrl = `${protocol}//${window.location.host}/docker-container-logs?containerId=${containerId}&tail=${lines}&serverId=${serverId}`; const ws = new WebSocket(wsUrl); const fitAddon = new FitAddon(); diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx index 942332cd..05f7ab65 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx @@ -159,7 +159,10 @@ const Service = (
- +
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx index 225517e5..82f1fa27 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx @@ -151,6 +151,7 @@ const Service = (
@@ -160,6 +161,7 @@ const Service = (
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx index 8d43e4cf..3c16e27f 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx @@ -143,7 +143,10 @@ const Mariadb = (
- +
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx index 409b9bd6..f179e706 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx @@ -145,7 +145,10 @@ const Mongo = (
- +
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx index 21f4036e..5f4fe119 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx @@ -144,7 +144,10 @@ const MySql = (
- +
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx index e0342ee4..4901f1e4 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx @@ -145,7 +145,10 @@ const Postgresql = (
- +
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx index f092ec06..2b560e58 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx @@ -143,7 +143,10 @@ const Redis = (
- +
diff --git a/apps/dokploy/server/api/routers/docker.ts b/apps/dokploy/server/api/routers/docker.ts index 22720095..54fe00db 100644 --- a/apps/dokploy/server/api/routers/docker.ts +++ b/apps/dokploy/server/api/routers/docker.ts @@ -40,10 +40,15 @@ export const dockerRouter = createTRPCRouter({ .union([z.literal("stack"), z.literal("docker-compose")]) .optional(), appName: z.string().min(1), + serverId: z.string().optional(), }), ) .query(async ({ input }) => { - return await getContainersByAppNameMatch(input.appName, input.appType); + return await getContainersByAppNameMatch( + input.appName, + input.appType, + input.serverId, + ); }), getContainersByAppLabel: protectedProcedure diff --git a/apps/dokploy/server/api/services/docker.ts b/apps/dokploy/server/api/services/docker.ts index 379bebe4..7263ed67 100644 --- a/apps/dokploy/server/api/services/docker.ts +++ b/apps/dokploy/server/api/services/docker.ts @@ -1,4 +1,9 @@ +import { readSSHKey } from "@/server/utils/filesystem/ssh"; import { execAsync } from "@/server/utils/process/execAsync"; +import { tail } from "lodash"; +import { stderr, stdout } from "node:process"; +import { Client } from "ssh2"; +import { findServerById } from "./server"; export const getContainers = async () => { try { @@ -69,25 +74,65 @@ export const getConfig = async (containerId: string) => { export const getContainersByAppNameMatch = async ( appName: string, appType?: "stack" | "docker-compose", + serverId?: string, ) => { try { + let result: string[] = []; const cmd = "docker ps -a --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'"; - const { stdout, stderr } = await execAsync( + const command = appType === "docker-compose" ? `${cmd} --filter='label=com.docker.compose.project=${appName}'` - : `${cmd} | grep ${appName}`, - ); + : `${cmd} | grep ${appName}`; + if (serverId) { + const server = await findServerById(serverId); - if (stderr) { - return []; + if (!server.sshKeyId) return; + const keys = await readSSHKey(server.sshKeyId); + const client = new Client(); + result = await new Promise((resolve, reject) => { + let output = ""; + client + .on("ready", () => { + client.exec(command, (err, stream) => { + if (err) { + console.error("Execution error:", err); + reject(err); + return; + } + stream + .on("close", () => { + client.end(); + resolve(output.trim().split("\n")); + }) + .on("data", (data: string) => { + output += data.toString(); + }) + .stderr.on("data", (data) => {}); + }); + }) + .connect({ + host: server.ipAddress, + port: server.port, + username: server.username, + privateKey: keys.privateKey, + timeout: 99999, + }); + }); + } else { + const { stdout, stderr } = await execAsync(command); + + if (stderr) { + return []; + } + + if (!stdout) return []; + + result = stdout.trim().split("\n"); } - if (!stdout) return []; - - const lines = stdout.trim().split("\n"); - const containers = lines.map((line) => { + const containers = result.map((line) => { const parts = line.split(" | "); const containerId = parts[0] ? parts[0].replace("CONTAINER ID : ", "").trim() diff --git a/apps/dokploy/server/wss/docker-container-logs.ts b/apps/dokploy/server/wss/docker-container-logs.ts index 0c40ecef..e0c8fba6 100644 --- a/apps/dokploy/server/wss/docker-container-logs.ts +++ b/apps/dokploy/server/wss/docker-container-logs.ts @@ -3,6 +3,9 @@ import { spawn } from "node-pty"; import { WebSocketServer } from "ws"; import { validateWebSocketRequest } from "../auth/auth"; import { getShell } from "./utils"; +import { Client } from "ssh2"; +import { findServerById } from "../api/services/server"; +import { readSSHKey } from "../utils/filesystem/ssh"; export const setupDockerContainerLogsWebSocketServer = ( server: http.Server, @@ -30,6 +33,7 @@ export const setupDockerContainerLogsWebSocketServer = ( const url = new URL(req.url || "", `http://${req.headers.host}`); const containerId = url.searchParams.get("containerId"); const tail = url.searchParams.get("tail"); + const serverId = url.searchParams.get("serverId"); const { user, session } = await validateWebSocketRequest(req); if (!containerId) { @@ -42,41 +46,88 @@ export const setupDockerContainerLogsWebSocketServer = ( return; } try { - const shell = getShell(); - const ptyProcess = spawn( - shell, - ["-c", `docker container logs --tail ${tail} --follow ${containerId}`], - { - name: "xterm-256color", - cwd: process.env.HOME, - env: process.env, - encoding: "utf8", - cols: 80, - rows: 30, - }, - ); + if (serverId) { + const server = await findServerById(serverId); - ptyProcess.onData((data) => { - ws.send(data); - }); - ws.on("close", () => { - ptyProcess.kill(); - }); - ws.on("message", (message) => { - try { - let command: string | Buffer[] | Buffer | ArrayBuffer; - if (Buffer.isBuffer(message)) { - command = message.toString("utf8"); - } else { - command = message; + if (!server.sshKeyId) return; + const keys = await readSSHKey(server.sshKeyId); + const client = new Client(); + new Promise((resolve, reject) => { + client + .on("ready", () => { + const command = ` + bash -c "docker container logs --tail ${tail} --follow ${containerId}" + `; + client.exec(command, (err, stream) => { + if (err) { + console.error("Execution error:", err); + reject(err); + return; + } + stream + .on("close", () => { + console.log("Connection closed ✅"); + client.end(); + resolve(); + }) + .on("data", (data: string) => { + ws.send(data.toString()); + // console.log(`OUTPUT: ${data.toString()}`); + }) + .stderr.on("data", (data) => { + ws.send(data.toString()); + // console.error(`STDERR: ${data.toString()}`); + }); + }); + }) + .connect({ + host: server.ipAddress, + port: server.port, + username: server.username, + privateKey: keys.privateKey, + timeout: 99999, + }); + }); + } else { + const shell = getShell(); + const ptyProcess = spawn( + shell, + [ + "-c", + `docker container logs --tail ${tail} --follow ${containerId}`, + ], + { + name: "xterm-256color", + cwd: process.env.HOME, + env: process.env, + encoding: "utf8", + cols: 80, + rows: 30, + }, + ); + + ptyProcess.onData((data) => { + ws.send(data); + }); + ws.on("close", () => { + ptyProcess.kill(); + }); + ws.on("message", (message) => { + try { + let command: string | Buffer[] | Buffer | ArrayBuffer; + if (Buffer.isBuffer(message)) { + command = message.toString("utf8"); + } else { + command = message; + } + ptyProcess.write(command.toString()); + } catch (error) { + // @ts-ignore + const errorMessage = error?.message as unknown as string; + ws.send(errorMessage); } - ptyProcess.write(command.toString()); - } catch (error) { - // @ts-ignore - const errorMessage = error?.message as unknown as string; - ws.send(errorMessage); - } - }); + }); + } } catch (error) { // @ts-ignore const errorMessage = error?.message as unknown as string;