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 bf01739564..fd51efbe27 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -6,10 +6,12 @@ import { Loader2, Pause, Play, + SendIcon, } from "lucide-react"; import React, { useEffect, useRef } from "react"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; +import { ButtonGroup } from "@/components/ui/button-group"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { LineCountFilter } from "./line-count-filter"; @@ -76,6 +78,7 @@ export const DockerLogsId: React.FC = ({ const scrollRef = useRef(null); const [isLoading, setIsLoading] = React.useState(false); const [copied, setCopied] = React.useState(false); + const [ws, setWs] = React.useState(null); const scrollToBottom = () => { if (autoScroll && scrollRef.current) { @@ -160,6 +163,7 @@ export const DockerLogsId: React.FC = ({ window.location.host }/docker-container-logs?${params.toString()}`; const ws = new WebSocket(wsUrl); + setWs(ws); const resetNoDataTimeout = () => { if (noDataTimeout) clearTimeout(noDataTimeout); @@ -394,29 +398,64 @@ export const DockerLogsId: React.FC = ({ )} -
- {filteredLogs.length > 0 ? ( - filteredLogs.map((filteredLog: LogLine, index: number) => ( - +
+ {filteredLogs.length > 0 ? ( + filteredLogs.map((filteredLog: LogLine, index: number) => ( + + )) + ) : isLoading ? ( +
+ +
+ ) : ( +
+ No logs found +
+ )} +
+
) => { + e.preventDefault(); + const input = e.currentTarget.command as HTMLInputElement; + + const command = input.value; + + if (!ws || ws.readyState !== WebSocket.OPEN) return; + if (isPaused) return; + if (!command.trim()) return; + + ws.send(command); + input.value = ""; + }} + > + + - )) - ) : isLoading ? ( -
- -
- ) : ( -
- No logs found -
- )} + +
+
diff --git a/apps/dokploy/components/ui/button-group.tsx b/apps/dokploy/components/ui/button-group.tsx new file mode 100644 index 0000000000..d6e78014fc --- /dev/null +++ b/apps/dokploy/components/ui/button-group.tsx @@ -0,0 +1,83 @@ +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" +import { Separator } from "@/components/ui/separator" + +const buttonGroupVariants = cva( + "flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1", + { + variants: { + orientation: { + horizontal: + "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none", + vertical: + "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none", + }, + }, + defaultVariants: { + orientation: "horizontal", + }, + } +) + +function ButtonGroup({ + className, + orientation, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function ButtonGroupText({ + className, + asChild = false, + ...props +}: React.ComponentProps<"div"> & { + asChild?: boolean +}) { + const Comp = asChild ? Slot : "div" + + return ( + + ) +} + +function ButtonGroupSeparator({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + ButtonGroup, + ButtonGroupSeparator, + ButtonGroupText, + buttonGroupVariants, +} diff --git a/apps/dokploy/server/wss/docker-container-logs.ts b/apps/dokploy/server/wss/docker-container-logs.ts index eaefa21f14..9b3565f738 100644 --- a/apps/dokploy/server/wss/docker-container-logs.ts +++ b/apps/dokploy/server/wss/docker-container-logs.ts @@ -129,6 +129,17 @@ export const setupDockerContainerLogsWebSocketServer = ( rows: 30, }); + const attachCommand = `docker attach ${containerId}`; + + const attachPty = spawn(shell, ["-c", attachCommand], { + name: "xterm-256color", + cwd: process.env.HOME, + env: process.env, + encoding: "utf8", + cols: 80, + rows: 30, + }); + ptyProcess.onData((data) => { ws.send(data); }); @@ -144,7 +155,7 @@ export const setupDockerContainerLogsWebSocketServer = ( } else { command = message; } - ptyProcess.write(command.toString()); + attachPty.write(`${command.toString()}\n`); } catch (error) { // @ts-ignore const errorMessage = error?.message as unknown as string;