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 afe18f40..60f567d7 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -1,289 +1,289 @@ -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { api } from "@/utils/api"; -import { Download as DownloadIcon, Loader2 } from "lucide-react"; -import React, { useEffect, useRef } from "react"; -import { TerminalLine } from "./terminal-line"; -import { type LogLine, getLogType, parseLogs } from "./utils"; - -interface Props { - containerId: string; - serverId?: string | null; -} - -type TimeFilter = "all" | "1h" | "6h" | "24h" | "168h" | "720h"; -type TypeFilter = "all" | "error" | "warning" | "success" | "info" | "debug"; - -export const DockerLogsId: React.FC = ({ containerId, serverId }) => { - const { data } = api.docker.getConfig.useQuery( - { - containerId, - serverId: serverId ?? undefined, - }, - { - enabled: !!containerId, - } - ); - - const [rawLogs, setRawLogs] = React.useState(""); - const [filteredLogs, setFilteredLogs] = React.useState([]); - const [autoScroll, setAutoScroll] = React.useState(true); - const [lines, setLines] = React.useState(100); - const [search, setSearch] = React.useState(""); - const [since, setSince] = React.useState("all"); - const [typeFilter, setTypeFilter] = React.useState("all"); - const scrollRef = useRef(null); - const [isLoading, setIsLoading] = React.useState(false); - - const scrollToBottom = () => { - if (autoScroll && scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - } - }; - - const handleScroll = () => { - if (!scrollRef.current) return; - - const { scrollTop, scrollHeight, clientHeight } = scrollRef.current; - const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10; - setAutoScroll(isAtBottom); - }; - - const handleSearch = (e: React.ChangeEvent) => { - setRawLogs(""); - setFilteredLogs([]); - setSearch(e.target.value || ""); - }; - - const handleLines = (e: React.ChangeEvent) => { - setRawLogs(""); - setFilteredLogs([]); - setLines(Number(e.target.value) || 1); - }; - - const handleSince = (value: TimeFilter) => { - setRawLogs(""); - setFilteredLogs([]); - setSince(value); - }; - - const handleTypeFilter = (value: TypeFilter) => { - setTypeFilter(value); - }; - - useEffect(() => { - setRawLogs(""); - setFilteredLogs([]); - }, [containerId]); - - useEffect(() => { - if (!containerId) return; - setIsLoading(true); - - const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; - const params = new globalThis.URLSearchParams({ - containerId, - tail: lines.toString(), - since, - search, - }); - - if (serverId) { - params.append("serverId", serverId); - } - - const wsUrl = `${protocol}//${ - window.location.host - }/docker-container-logs?${params.toString()}`; - console.log("Connecting to WebSocket:", wsUrl); - const ws = new WebSocket(wsUrl); - - ws.onopen = () => { - console.log("WebSocket connected"); - setIsLoading(false) - }; - - ws.onmessage = (e) => { - setRawLogs((prev) => prev + e.data); - setIsLoading(false); - }; - - ws.onerror = (error) => { - console.error("WebSocket error:", error); - setIsLoading(false); - }; - - ws.onclose = (e) => { - console.log("WebSocket closed:", e.reason); - setIsLoading(false); - }; - - return () => { - if (ws.readyState === WebSocket.OPEN) { - ws.close(); - } - }; - }, [containerId, serverId, lines, search, since]); - - const handleDownload = () => { - const logContent = filteredLogs - .map( - ({ timestamp, message }: { timestamp: Date | null; message: string }) => - `${timestamp?.toISOString() || "No timestamp"} ${message}` - ) - .join("\n"); - - const blob = new Blob([logContent], { type: "text/plain" }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - const appName = data.Name.replace("/", "") || "app"; - const isoDate = new Date().toISOString(); - a.href = url; - a.download = `${appName}-${isoDate.slice(0, 10).replace(/-/g, "")}_${isoDate - .slice(11, 19) - .replace(/:/g, "")}.log.txt`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - }; - - const handleFilter = (logs: LogLine[]) => { - return logs.filter((log) => { - const logType = getLogType(log.message).type; - - const matchesType = typeFilter === "all" || logType === typeFilter; - - return matchesType; - }); - }; - - useEffect(() => { - setRawLogs(""); - setFilteredLogs([]); - }, [containerId]); - - useEffect(() => { - const logs = parseLogs(rawLogs); - const filtered = handleFilter(logs); - setFilteredLogs(filtered); - }, [rawLogs, search, lines, since, typeFilter]); - - useEffect(() => { - scrollToBottom(); - - if (autoScroll && scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - } - }, [filteredLogs, autoScroll]); - - return ( -
-
-
-
-
- - - - - - - -
- - -
-
- {filteredLogs.length > 0 ? ( - filteredLogs.map((filteredLog: LogLine, index: number) => ( - - )) - ) : isLoading ? ( -
- -
- ) : ( -
- No logs found -
- )} -
-
-
-
- ); -}; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { api } from "@/utils/api"; +import { Download as DownloadIcon, Loader2 } from "lucide-react"; +import React, { useEffect, useRef } from "react"; +import { TerminalLine } from "./terminal-line"; +import { type LogLine, getLogType, parseLogs } from "./utils"; + +interface Props { + containerId: string; + serverId?: string | null; +} + +type TimeFilter = "all" | "1h" | "6h" | "24h" | "168h" | "720h"; +type TypeFilter = "all" | "error" | "warning" | "success" | "info" | "debug"; + +export const DockerLogsId: React.FC = ({ containerId, serverId }) => { + const { data } = api.docker.getConfig.useQuery( + { + containerId, + serverId: serverId ?? undefined, + }, + { + enabled: !!containerId, + } + ); + + const [rawLogs, setRawLogs] = React.useState(""); + const [filteredLogs, setFilteredLogs] = React.useState([]); + const [autoScroll, setAutoScroll] = React.useState(true); + const [lines, setLines] = React.useState(100); + const [search, setSearch] = React.useState(""); + + const [since, setSince] = React.useState("all"); + const [typeFilter, setTypeFilter] = React.useState("all"); + const scrollRef = useRef(null); + const [isLoading, setIsLoading] = React.useState(false); + + const scrollToBottom = () => { + if (autoScroll && scrollRef.current) { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + } + }; + + const handleScroll = () => { + if (!scrollRef.current) return; + + const { scrollTop, scrollHeight, clientHeight } = scrollRef.current; + const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10; + setAutoScroll(isAtBottom); + }; + + const handleSearch = (e: React.ChangeEvent) => { + setRawLogs(""); + setFilteredLogs([]); + setSearch(e.target.value || ""); + }; + + const handleLines = (e: React.ChangeEvent) => { + setRawLogs(""); + setFilteredLogs([]); + setLines(Number(e.target.value) || 1); + }; + + const handleSince = (value: TimeFilter) => { + setRawLogs(""); + setFilteredLogs([]); + setSince(value); + }; + + const handleTypeFilter = (value: TypeFilter) => { + setTypeFilter(value); + }; + + useEffect(() => { + setRawLogs(""); + setFilteredLogs([]); + }, [containerId]); + + useEffect(() => { + if (!containerId) return; + setIsLoading(true); + + const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + const params = new globalThis.URLSearchParams({ + containerId, + tail: lines.toString(), + since, + search, + }); + + if (serverId) { + params.append("serverId", serverId); + } + + const wsUrl = `${protocol}//${ + window.location.host + }/docker-container-logs?${params.toString()}`; + console.log("Connecting to WebSocket:", wsUrl); + const ws = new WebSocket(wsUrl); + + ws.onopen = () => { + console.log("WebSocket connected"); + }; + + ws.onmessage = (e) => { + setRawLogs((prev) => prev + e.data); + setIsLoading(false); + }; + + ws.onerror = (error) => { + console.error("WebSocket error:", error); + setIsLoading(false); + }; + + ws.onclose = (e) => { + console.log("WebSocket closed:", e.reason); + setIsLoading(false); + }; + + return () => { + if (ws.readyState === WebSocket.OPEN) { + ws.close(); + } + }; + }, [containerId, serverId, lines, search, since]); + + const handleDownload = () => { + const logContent = filteredLogs + .map( + ({ timestamp, message }: { timestamp: Date | null; message: string }) => + `${timestamp?.toISOString() || "No timestamp"} ${message}` + ) + .join("\n"); + + const blob = new Blob([logContent], { type: "text/plain" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + const appName = data.Name.replace("/", "") || "app"; + const isoDate = new Date().toISOString(); + a.href = url; + a.download = `${appName}-${isoDate.slice(0, 10).replace(/-/g, "")}_${isoDate + .slice(11, 19) + .replace(/:/g, "")}.log.txt`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + const handleFilter = (logs: LogLine[]) => { + return logs.filter((log) => { + const logType = getLogType(log.message).type; + + const matchesType = typeFilter === "all" || logType === typeFilter; + + return matchesType; + }); + }; + + useEffect(() => { + setRawLogs(""); + setFilteredLogs([]); + }, [containerId]); + + useEffect(() => { + const logs = parseLogs(rawLogs); + const filtered = handleFilter(logs); + setFilteredLogs(filtered); + }, [rawLogs, search, lines, since, typeFilter]); + + useEffect(() => { + scrollToBottom(); + + if (autoScroll && scrollRef.current) { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + } + }, [filteredLogs, autoScroll]); + + return ( +
+
+
+
+
+ + + + + + + +
+ + +
+
+ {filteredLogs.length > 0 ? ( + filteredLogs.map((filteredLog: LogLine, index: number) => ( + + )) + ) : isLoading ? ( +
+ +
+ ) : ( +
+ No logs found +
+ )} +
+
+
+
+ ); +}; \ No newline at end of file