diff --git a/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx b/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx index d2dc1d78..23ec111a 100644 --- a/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx +++ b/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx @@ -18,6 +18,7 @@ import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { validateAndFormatYAML } from "../application/advanced/traefik/update-traefik-config"; +import { Loader2 } from "lucide-react"; const UpdateServerMiddlewareConfigSchema = z.object({ traefikConfig: z.string(), @@ -29,12 +30,18 @@ type UpdateServerMiddlewareConfig = z.infer< interface Props { path: string; + serverId?: string; } -export const ShowTraefikFile = ({ path }: Props) => { - const { data, refetch } = api.settings.readTraefikFile.useQuery( +export const ShowTraefikFile = ({ path, serverId }: Props) => { + const { + data, + refetch, + isLoading: isLoadingFile, + } = api.settings.readTraefikFile.useQuery( { path, + serverId, }, { enabled: !!path, @@ -54,11 +61,9 @@ export const ShowTraefikFile = ({ path }: Props) => { }); useEffect(() => { - if (data) { - form.reset({ - traefikConfig: data || "", - }); - } + form.reset({ + traefikConfig: data || "", + }); }, [form, form.reset, data]); const onSubmit = async (data: UpdateServerMiddlewareConfig) => { @@ -74,6 +79,7 @@ export const ShowTraefikFile = ({ path }: Props) => { await mutateAsync({ traefikConfig: data.traefikConfig, path, + serverId, }) .then(async () => { toast.success("Traefik config Updated"); @@ -93,20 +99,28 @@ export const ShowTraefikFile = ({ path }: Props) => { className="w-full relative z-[5]" >
- ( - - Traefik config - - {path} - - - + + Loading... + + +
+ ) : ( + ( + + Traefik config + + {path} + + + - + {...field} + /> + -
-										
-									
-
- -
-
- )} - /> +
+											
+										
+
+ +
+ + )} + /> + )}
-
diff --git a/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx b/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx index e3e874c5..c85fcdf5 100644 --- a/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx +++ b/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx @@ -1,20 +1,38 @@ import React from "react"; - import { Tree } from "@/components/ui/file-tree"; import { api } from "@/utils/api"; -import { FileIcon, Folder, Workflow } from "lucide-react"; - +import { FileIcon, Folder, Loader2, Workflow } from "lucide-react"; import { cn } from "@/lib/utils"; import { ShowTraefikFile } from "./show-traefik-file"; +import { AlertBlock } from "@/components/shared/alert-block"; -export const ShowTraefikSystem = () => { +interface Props { + serverId?: string; +} +export const ShowTraefikSystem = ({ serverId }: Props) => { const [file, setFile] = React.useState(null); - const { data: directories } = api.settings.readDirectories.useQuery(); + const { + data: directories, + isLoading, + error, + isError, + } = api.settings.readDirectories.useQuery({ + serverId, + }); return (
+ {isError && {error?.message}} + {isLoading && ( +
+ + Loading... + + +
+ )} {directories?.length === 0 && (
@@ -34,7 +52,7 @@ export const ShowTraefikSystem = () => { />
{file ? ( - + ) : (
diff --git a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx index 07c473a9..b232c5b8 100644 --- a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx @@ -49,13 +49,7 @@ export const SetupServer = ({ serverId }: Props) => { }, ); - const { mutateAsync, isLoading } = api.server.setup.useMutation({ - onMutate: async (variables) => { - console.log("Running...."); - refetch(); - // refetch(); - }, - }); + const { mutateAsync, isLoading } = api.server.setup.useMutation(); return ( @@ -99,7 +93,7 @@ export const SetupServer = ({ serverId }: Props) => { serverId: server?.serverId || "", }) .then(async () => { - // refetch(); + refetch(); toast.success("Server setup successfully"); }) .catch(() => { diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx index ecdf1456..9f7499c7 100644 --- a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx @@ -6,6 +6,7 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, + DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { @@ -26,11 +27,45 @@ import { TerminalModal } from "../web-server/terminal-modal"; import { AddServer } from "./add-server"; import { SetupServer } from "./setup-server"; import { UpdateServer } from "./update-server"; +import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal"; +import { ShowModalLogs } from "../web-server/show-modal-logs"; export const ShowServers = () => { const { data, refetch } = api.server.all.useQuery(); const { mutateAsync } = api.server.remove.useMutation(); const { data: sshKeys } = api.sshKey.all.useQuery(); + const { mutateAsync: toggleDashboard, isLoading: toggleDashboardIsLoading } = + api.settings.toggleDashboard.useMutation(); + + const { + mutateAsync: cleanDockerBuilder, + isLoading: cleanDockerBuilderIsLoading, + } = api.settings.cleanDockerBuilder.useMutation(); + + const { + mutateAsync: cleanUnusedImages, + isLoading: cleanUnusedImagesIsLoading, + } = api.settings.cleanUnusedImages.useMutation(); + + const { mutateAsync: reloadTraefik, isLoading: reloadTraefikIsLoading } = + api.settings.reloadTraefik.useMutation(); + + const { + mutateAsync: cleanUnusedVolumes, + isLoading: cleanUnusedVolumesIsLoading, + } = api.settings.cleanUnusedVolumes.useMutation(); + + const { + mutateAsync: cleanStoppedContainers, + isLoading: cleanStoppedContainersIsLoading, + } = api.settings.cleanStoppedContainers.useMutation(); + + const { mutateAsync: cleanAll, isLoading: cleanAllIsLoading } = + api.settings.cleanAll.useMutation(); + + const { data: haveTraefikDashboardPortEnabled, refetch: refetchDashboard } = + api.settings.haveTraefikDashboardPortEnabled.useQuery(); + return (
@@ -116,7 +151,17 @@ export const ShowServers = () => { - +
diff --git a/apps/dokploy/server/api/routers/docker.ts b/apps/dokploy/server/api/routers/docker.ts index 54fe00db..27671310 100644 --- a/apps/dokploy/server/api/routers/docker.ts +++ b/apps/dokploy/server/api/routers/docker.ts @@ -55,9 +55,10 @@ export const dockerRouter = createTRPCRouter({ .input( z.object({ appName: z.string().min(1), + serverId: z.string().optional(), }), ) .query(async ({ input }) => { - return await getContainersByAppLabel(input.appName); + return await getContainersByAppLabel(input.appName, input.serverId); }), }); diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index f47edab5..878882b9 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -6,6 +6,7 @@ import { apiReadStatsLogs, apiReadTraefikConfig, apiSaveSSHKey, + apiStorage, apiTraefikConfig, apiUpdateDockerCleanup, } from "@/server/db/schema"; @@ -20,11 +21,13 @@ import { cleanUpUnusedVolumes, prepareEnvironmentVariables, startService, + startServiceRemote, stopService, + stopServiceRemote, } from "@/server/utils/docker/utils"; import { recreateDirectory } from "@/server/utils/filesystem/directory"; import { sendDockerCleanupNotifications } from "@/server/utils/notifications/docker-cleanup"; -import { execAsync } from "@/server/utils/process/execAsync"; +import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync"; import { spawnAsync } from "@/server/utils/process/spawnAsync"; import { readConfig, @@ -63,16 +66,23 @@ export const settingsRouter = createTRPCRouter({ await execAsync(`docker service update --force ${stdout.trim()}`); return true; }), - reloadTraefik: adminProcedure.mutation(async () => { - try { - await stopService("dokploy-traefik"); - await startService("dokploy-traefik"); - } catch (err) { - console.error(err); - } + reloadTraefik: adminProcedure + .input(apiStorage) + .mutation(async ({ input }) => { + try { + if (input?.serverId) { + await stopServiceRemote(input.serverId, "dokploy-traefik"); + await startServiceRemote(input.serverId, "dokploy-traefik"); + } else { + await stopService("dokploy-traefik"); + await startService("dokploy-traefik"); + } + } catch (err) { + console.error(err); + } - return true; - }), + return true; + }), toggleDashboard: adminProcedure .input(apiEnableDashboard) .mutation(async ({ input }) => { @@ -82,31 +92,42 @@ export const settingsRouter = createTRPCRouter({ return true; }), - cleanUnusedImages: adminProcedure.mutation(async () => { - await cleanUpUnusedImages(); - return true; - }), - cleanUnusedVolumes: adminProcedure.mutation(async () => { - await cleanUpUnusedVolumes(); - return true; - }), - cleanStoppedContainers: adminProcedure.mutation(async () => { - await cleanStoppedContainers(); - return true; - }), - cleanDockerBuilder: adminProcedure.mutation(async () => { - await cleanUpDockerBuilder(); - }), - cleanDockerPrune: adminProcedure.mutation(async () => { - await cleanUpSystemPrune(); - await cleanUpDockerBuilder(); + cleanUnusedImages: adminProcedure + .input(apiStorage) + .mutation(async ({ input }) => { + await cleanUpUnusedImages(input?.serverId); + return true; + }), + cleanUnusedVolumes: adminProcedure + .input(apiStorage) + .mutation(async ({ input }) => { + await cleanUpUnusedVolumes(input?.serverId); + return true; + }), + cleanStoppedContainers: adminProcedure + .input(apiStorage) + .mutation(async ({ input }) => { + await cleanStoppedContainers(input?.serverId); + return true; + }), + cleanDockerBuilder: adminProcedure + .input(apiStorage) + .mutation(async ({ input }) => { + await cleanUpDockerBuilder(input?.serverId); + }), + cleanDockerPrune: adminProcedure + .input(apiStorage) + .mutation(async ({ input }) => { + await cleanUpSystemPrune(input?.serverId); + await cleanUpDockerBuilder(input?.serverId); - return true; - }), - cleanAll: adminProcedure.mutation(async () => { - await cleanUpUnusedImages(); - await cleanUpDockerBuilder(); - await cleanUpSystemPrune(); + return true; + }), + cleanAll: adminProcedure.input(apiStorage).mutation(async ({ input }) => { + await cleanUpUnusedImages(input?.serverId); + await cleanStoppedContainers(input?.serverId); + await cleanUpDockerBuilder(input?.serverId); + await cleanUpSystemPrune(input?.serverId); return true; }), @@ -230,18 +251,20 @@ export const settingsRouter = createTRPCRouter({ getDokployVersion: adminProcedure.query(() => { return getDokployVersion(); }), - readDirectories: protectedProcedure.query(async ({ ctx }) => { - if (ctx.user.rol === "user") { - const canAccess = await canAccessToTraefikFiles(ctx.user.authId); + readDirectories: protectedProcedure + .input(apiStorage) + .query(async ({ ctx, input }) => { + if (ctx.user.rol === "user") { + const canAccess = await canAccessToTraefikFiles(ctx.user.authId); - if (!canAccess) { - throw new TRPCError({ code: "UNAUTHORIZED" }); + if (!canAccess) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } } - } - const { MAIN_TRAEFIK_PATH } = paths(); - const result = readDirectory(MAIN_TRAEFIK_PATH); - return result || []; - }), + const { MAIN_TRAEFIK_PATH } = paths(!!input?.serverId); + const result = await readDirectory(MAIN_TRAEFIK_PATH, input?.serverId); + return result || []; + }), updateTraefikFile: protectedProcedure .input(apiModifyTraefikConfig) @@ -253,7 +276,11 @@ export const settingsRouter = createTRPCRouter({ throw new TRPCError({ code: "UNAUTHORIZED" }); } } - writeTraefikConfigInPath(input.path, input.traefikConfig); + await writeTraefikConfigInPath( + input.path, + input.traefikConfig, + input?.serverId, + ); return true; }), @@ -267,7 +294,7 @@ export const settingsRouter = createTRPCRouter({ throw new TRPCError({ code: "UNAUTHORIZED" }); } } - return readConfigInPath(input.path); + return readConfigInPath(input.path, input.serverId); }), getIp: protectedProcedure.query(async () => { const admin = await findAdmin(); @@ -324,16 +351,20 @@ export const settingsRouter = createTRPCRouter({ return openApiDocument; }, ), - readTraefikEnv: adminProcedure.query(async () => { - const { stdout } = await execAsync( - "docker service inspect --format='{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' dokploy-traefik", - ); + readTraefikEnv: adminProcedure.input(apiStorage).query(async ({ input }) => { + const command = + "docker service inspect --format='{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' dokploy-traefik"; - return stdout.trim(); + if (input?.serverId) { + const result = await execAsyncRemote(input.serverId, command); + return result.stdout.trim(); + } + const result = await execAsync(command); + return result.stdout.trim(); }), writeTraefikEnv: adminProcedure - .input(z.object({ env: z.string() })) + .input(z.object({ env: z.string(), serverId: z.string().optional() })) .mutation(async ({ input }) => { const envs = prepareEnvironmentVariables(input.env); await initializeTraefik({ diff --git a/apps/dokploy/server/api/services/docker.ts b/apps/dokploy/server/api/services/docker.ts index fea9a7e2..ba142ef8 100644 --- a/apps/dokploy/server/api/services/docker.ts +++ b/apps/dokploy/server/api/services/docker.ts @@ -126,12 +126,25 @@ export const getContainersByAppNameMatch = async ( return []; }; -export const getContainersByAppLabel = async (appName: string) => { +export const getContainersByAppLabel = async ( + appName: string, + serverId?: string, +) => { try { - const { stdout, stderr } = await execAsync( - `docker ps --filter "label=com.docker.swarm.service.name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`, - ); + let stdout = ""; + let stderr = ""; + const command = `docker ps --filter "label=com.docker.swarm.service.name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`; + console.log(command); + if (serverId) { + const result = await execAsyncRemote(serverId, command); + stdout = result.stdout; + stderr = result.stderr; + } else { + const result = await execAsync(command); + stdout = result.stdout; + stderr = result.stderr; + } if (stderr) { console.error(`Error: ${stderr}`); return; diff --git a/apps/dokploy/server/api/services/settings.ts b/apps/dokploy/server/api/services/settings.ts index a3f658d9..bdb5fc1f 100644 --- a/apps/dokploy/server/api/services/settings.ts +++ b/apps/dokploy/server/api/services/settings.ts @@ -3,6 +3,7 @@ import { join } from "node:path"; import { docker } from "@/server/constants"; import { getServiceContainer } from "@/server/utils/docker/utils"; import packageInfo from "../../../package.json"; +import { execAsyncRemote } from "@/server/utils/process/execAsync"; const updateIsAvailable = async () => { try { @@ -44,8 +45,66 @@ interface TreeDataItem { children?: TreeDataItem[]; } -export const readDirectory = (dirPath: string): TreeDataItem[] => { +export const readDirectory = async ( + dirPath: string, + serverId?: string, +): Promise => { + if (serverId) { + const { stdout } = await execAsyncRemote( + serverId, + ` +process_items() { + local parent_dir="$1" + local __resultvar=$2 + + local items_json="" + local first=true + for item in "$parent_dir"/*; do + [ -e "$item" ] || continue + process_item "$item" item_json + if [ "$first" = true ]; then + first=false + items_json="$item_json" + else + items_json="$items_json,$item_json" + fi + done + + eval $__resultvar="'[$items_json]'" +} + +process_item() { + local item_path="$1" + local __resultvar=$2 + + local item_name=$(basename "$item_path") + local escaped_name=$(echo "$item_name" | sed 's/"/\\"/g') + local escaped_path=$(echo "$item_path" | sed 's/"/\\"/g') + + if [ -d "$item_path" ]; then + # Is directory + process_items "$item_path" children_json + local json='{"id":"'"$escaped_path"'","name":"'"$escaped_name"'","type":"directory","children":'"$children_json"'}' + else + # Is file + local json='{"id":"'"$escaped_path"'","name":"'"$escaped_name"'","type":"file"}' + fi + + eval $__resultvar="'$json'" +} + +root_dir=${dirPath} + +process_items "$root_dir" json_output + +echo "$json_output" + `, + ); + const result = JSON.parse(stdout); + return result; + } const items = readdirSync(dirPath, { withFileTypes: true }); + return items.map((item) => { const fullPath = join(dirPath, item.name); if (item.isDirectory()) { @@ -61,5 +120,5 @@ export const readDirectory = (dirPath: string): TreeDataItem[] => { name: item.name, type: "file", }; - }); + }) as unknown as Promise; }; diff --git a/apps/dokploy/server/db/schema/admin.ts b/apps/dokploy/server/db/schema/admin.ts index 957db8a7..61ac0f20 100644 --- a/apps/dokploy/server/db/schema/admin.ts +++ b/apps/dokploy/server/db/schema/admin.ts @@ -72,15 +72,24 @@ export const apiTraefikConfig = z.object({ export const apiModifyTraefikConfig = z.object({ path: z.string().min(1), traefikConfig: z.string().min(1), + serverId: z.string().optional(), }); export const apiReadTraefikConfig = z.object({ path: z.string().min(1), + serverId: z.string().optional(), }); export const apiEnableDashboard = z.object({ enableDashboard: z.boolean().optional(), + serverId: z.string().optional(), }); +export const apiStorage = z + .object({ + serverId: z.string().optional(), + }) + .optional(); + export const apiReadStatsLogs = z.object({ page: z .object({ diff --git a/apps/dokploy/server/utils/docker/utils.ts b/apps/dokploy/server/utils/docker/utils.ts index e4e8ca17..3dcc27ac 100644 --- a/apps/dokploy/server/utils/docker/utils.ts +++ b/apps/dokploy/server/utils/docker/utils.ts @@ -149,27 +149,39 @@ export const getContainerByName = (name: string): Promise => { }); }); }; -export const cleanUpUnusedImages = async () => { +export const cleanUpUnusedImages = async (serverId?: string) => { try { - await execAsync("docker image prune --all --force"); + if (serverId) { + await execAsyncRemote(serverId, "docker image prune --all --force"); + } else { + await execAsync("docker image prune --all --force"); + } } catch (error) { console.error(error); throw error; } }; -export const cleanStoppedContainers = async () => { +export const cleanStoppedContainers = async (serverId?: string) => { try { - await execAsync("docker container prune --force"); + if (serverId) { + await execAsyncRemote(serverId, "docker container prune --force"); + } else { + await execAsync("docker container prune --force"); + } } catch (error) { console.error(error); throw error; } }; -export const cleanUpUnusedVolumes = async () => { +export const cleanUpUnusedVolumes = async (serverId?: string) => { try { - await execAsync("docker volume prune --all --force"); + if (serverId) { + await execAsyncRemote(serverId, "docker volume prune --all --force"); + } else { + await execAsync("docker volume prune --all --force"); + } } catch (error) { console.error(error); throw error; @@ -193,12 +205,23 @@ export const cleanUpInactiveContainers = async () => { } }; -export const cleanUpDockerBuilder = async () => { - await execAsync("docker builder prune --all --force"); +export const cleanUpDockerBuilder = async (serverId?: string) => { + if (serverId) { + await execAsyncRemote(serverId, "docker builder prune --all --force"); + } else { + await execAsync("docker builder prune --all --force"); + } }; -export const cleanUpSystemPrune = async () => { - await execAsync("docker system prune --all --force --volumes"); +export const cleanUpSystemPrune = async (serverId?: string) => { + if (serverId) { + await execAsyncRemote( + serverId, + "docker system prune --all --force --volumes", + ); + } else { + await execAsync("docker system prune --all --force --volumes"); + } }; export const startService = async (appName: string) => { diff --git a/apps/dokploy/server/utils/traefik/application.ts b/apps/dokploy/server/utils/traefik/application.ts index 86185dc6..d40f447d 100644 --- a/apps/dokploy/server/utils/traefik/application.ts +++ b/apps/dokploy/server/utils/traefik/application.ts @@ -5,6 +5,7 @@ import { paths } from "@/server/constants"; import { dump, load } from "js-yaml"; import { execAsyncRemote } from "../process/execAsync"; import type { FileConfig, HttpLoadBalancerService } from "./file-types"; +import { encodeBase64 } from "../docker/utils"; export const createTraefikConfig = (appName: string) => { const defaultPort = 3000; @@ -146,8 +147,14 @@ export const readMonitoringConfig = () => { return null; }; -export const readConfigInPath = (pathFile: string) => { +export const readConfigInPath = async (pathFile: string, serverId?: string) => { const configPath = path.join(pathFile); + + if (serverId) { + const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`); + if (!stdout) return null; + return stdout; + } if (fs.existsSync(configPath)) { const yamlStr = fs.readFileSync(configPath, "utf8"); return yamlStr; @@ -179,12 +186,22 @@ export const writeConfigRemote = async ( } }; -export const writeTraefikConfigInPath = ( +export const writeTraefikConfigInPath = async ( pathFile: string, traefikConfig: string, + serverId?: string, ) => { try { const configPath = path.join(pathFile); + if (serverId) { + const encoded = encodeBase64(traefikConfig); + await execAsyncRemote( + serverId, + `echo "${encoded}" | base64 -d > "${configPath}"`, + ); + } else { + fs.writeFileSync(configPath, traefikConfig, "utf8"); + } fs.writeFileSync(configPath, traefikConfig, "utf8"); } catch (e) { console.error("Error saving the YAML config file:", e); diff --git a/apps/dokploy/server/wss/docker-container-terminal.ts b/apps/dokploy/server/wss/docker-container-terminal.ts index 3af9ad2b..ad7c8137 100644 --- a/apps/dokploy/server/wss/docker-container-terminal.ts +++ b/apps/dokploy/server/wss/docker-container-terminal.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 { readSSHKey } from "../utils/filesystem/ssh"; +import { findServerById } from "../api/services/server"; export const setupDockerContainerTerminalWebSocketServer = ( server: http.Server, @@ -44,7 +47,123 @@ export const setupDockerContainerTerminalWebSocketServer = ( } try { if (serverId) { - // const server = await findServerById(serverId); + const server = await findServerById(serverId); + if (!server.sshKeyId) + throw new Error("No SSH key available for this server"); + + const keys = await readSSHKey(server.sshKeyId); + const conn = new Client(); + let stdout = ""; + let stderr = ""; + // conn + // .once("ready", () => { + // console.log("Client :: ready"); + // conn.exec( + // `docker run -i ${containerId} ${activeWay}`, + // (err, stream) => { + // if (err) throw err; + + // stream + // .on("close", (code: number, signal: string) => { + // console.log( + // `Stream :: close :: code: ${code}, signal: ${signal}`, + // ); + // conn.end(); + // }) + // .on("data", (data: string) => { + // stdout += data.toString(); + // ws.send(data.toString()); + // }) + // .stderr.on("data", (data) => { + // stderr += data.toString(); + // ws.send(data.toString()); + // console.error("Error: ", data.toString()); + // }); + + // // Maneja la entrada de comandos desde WebSocket + // ws.on("message", (message) => { + // try { + // let command: string | Buffer[] | Buffer | ArrayBuffer; + // if (Buffer.isBuffer(message)) { + // command = message.toString("utf8"); + // } else { + // command = message; + // } + // stream.write(command.toString()); + // } catch (error) { + // // @ts-ignore + // const errorMessage = error?.message as unknown as string; + // ws.send(errorMessage); + // } + // }); + + // // Cuando se cierra la conexión WebSocket + // ws.on("close", () => { + // stream.end(); + // }); + // }, + // ); + // })p + // .connect({ + // host: server.ipAddress, + // port: server.port, + // username: server.username, + // privateKey: keys.privateKey, + // timeout: 99999, + // }); + // conn + // .once("ready", () => { + // console.log("Client :: ready"); + // conn.shell((err, stream) => { + // if (err) throw err; + + // stream + // .on("close", (code: number, signal: string) => { + // console.log( + // `Stream :: close :: code: ${code}, signal: ${signal}`, + // ); + // conn.end(); + // }) + // .on("data", (data: string) => { + // stdout += data.toString(); + // ws.send(data.toString()); + // }) + // .stderr.on("data", (data) => { + // stderr += data.toString(); + // ws.send(data.toString()); + // console.error("Error: ", data.toString()); + // }); + // stream.write(`docker exec -it ${containerId} ${activeWay}\n`); + // // Maneja la entrada de comandos desde WebSocket + // ws.on("message", (message) => { + // try { + // let command: string | Buffer[] | Buffer | ArrayBuffer; + // if (Buffer.isBuffer(message)) { + // command = message.toString("utf8"); + // } else { + // command = message; + // } + // stream.write(command.toString()); + // } catch (error) { + // // @ts-ignore + // const errorMessage = error?.message as unknown as string; + // ws.send(errorMessage); + // } + // }); + + // // Cuando se cierra la conexión WebSocket + // ws.on("close", () => { + // stream.end(); + // }); + // }); + // }) + // .connect({ + // host: server.ipAddress, + // port: server.port, + // username: server.username, + // privateKey: keys.privateKey, + // timeout: 99999, + // }); } else { const shell = getShell(); const ptyProcess = spawn(