From 42f3105f6949184133ab9c085863eaf6c0abc6dd Mon Sep 17 00:00:00 2001 From: Nicholas Penree Date: Wed, 11 Dec 2024 19:13:53 -0500 Subject: [PATCH] feat(logs): improvements based on feedback --- .../dashboard/docker/logs/docker-logs-id.tsx | 546 +++++++++--------- .../docker/logs/show-docker-modal-logs.tsx | 5 +- .../dashboard/docker/logs/terminal-line.tsx | 172 +++--- .../components/dashboard/docker/logs/utils.ts | 266 ++++----- .../settings/web-server/show-modal-logs.tsx | 5 +- apps/dokploy/components/ui/badge.tsx | 6 +- apps/dokploy/styles/globals.css | 2 +- 7 files changed, 511 insertions(+), 491 deletions(-) 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 8cb9ff24..4d2dfe9c 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -1,272 +1,274 @@ -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import React, { useEffect, useRef } from "react"; -import { getLogType, LogLine, parseLogs } from "./utils"; -import { TerminalLine } from "./terminal-line"; -import { Download as DownloadIcon } from "lucide-react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Badge } from "@/components/ui/badge"; -import { api } from "@/utils/api"; - -interface Props { - containerId: string; - serverId?: string | null; -} - -type TimeFilter = "all" | "1h" | "6h" | "24h" | "168h" | "720h"; -type TypeFilter = "all" | "error" | "warning" | "success" | "info"; - -export const DockerLogsId: React.FC = ({ containerId, serverId }) => { - const { data } = api.docker.getConfig.useQuery( - { - containerId, - serverId, - }, - { - 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 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; - - 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) => { - // console.log("Received message:", e.data); - setRawLogs((prev) => prev + e.data); - }; - - ws.onerror = (error) => { - console.error("WebSocket error:", error); - }; - - ws.onclose = (e) => { - console.log("WebSocket closed:", e.reason); - }; - - 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"; - a.href = url; - a.download = `logs-${appName}-${new Date().toISOString()}.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) => ( - - )) - ) : ( -
- 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 } 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"; + +export const DockerLogsId: React.FC = ({ containerId, serverId }) => { + const { data } = api.docker.getConfig.useQuery( + { + containerId, + serverId, + }, + { + 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 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; + + 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) => { + // console.log("Received message:", e.data); + setRawLogs((prev) => prev + e.data); + }; + + ws.onerror = (error) => { + console.error("WebSocket error:", error); + }; + + ws.onclose = (e) => { + console.log("WebSocket closed:", e.reason); + }; + + 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"; + a.href = url; + a.download = `logs-${appName}-${new Date().toISOString()}.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) => ( + + )) + ) : ( +
+ No logs found +
+ )} +
+
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx b/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx index 4aea3c7d..f8531d77 100644 --- a/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx @@ -46,10 +46,7 @@ export const ShowDockerModalLogs = ({ View the logs for {containerId}
- +
diff --git a/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx b/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx index 6790e6d9..eb619277 100644 --- a/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx @@ -1,74 +1,98 @@ -import { Badge } from "@/components/ui/badge"; -import { cn } from "@/lib/utils"; -import { getLogType, type LogLine } from "./utils"; -import React from "react"; -import { escapeRegExp } from 'lodash'; - -interface LogLineProps { - log: LogLine; - searchTerm?: string; -} - -export function TerminalLine({ log, searchTerm }: LogLineProps) { - const { timestamp, message } = log; - const { type, variant, color } = getLogType(message); - - const formattedTime = timestamp - ? timestamp.toLocaleString("en-GB", { - day: "2-digit", - month: "short", - year: "numeric", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - hour12: false, - }) - : "--- No time found ---"; - - const highlightMessage = (text: string, term: string) => { - if (!term) return text; - - const parts = text.split(new RegExp(`(${escapeRegExp(term)})`, "gi")); - return parts.map((part, index) => - part.toLowerCase() === term.toLowerCase() ? ( - - {part} - - ) : ( - part - ) - ); - }; - - return ( -
- {" "} -
- {/* Icon to expand the log item maybe implement a colapsible later */} - {/* */} -
- - {formattedTime} - - - {type} - -
- - {searchTerm ? highlightMessage(message, searchTerm) : message} - -
- ); -} +import { Badge } from "@/components/ui/badge"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; +import { escapeRegExp } from "lodash"; +import React from "react"; +import { type LogLine, getLogType } from "./utils"; + +interface LogLineProps { + log: LogLine; + searchTerm?: string; +} + +export function TerminalLine({ log, searchTerm }: LogLineProps) { + const { timestamp, message, rawTimestamp } = log; + const { type, variant, color } = getLogType(message); + + const formattedTime = timestamp + ? timestamp.toLocaleString([], { + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + year: "2-digit", + second: "2-digit", + }) + : "--- No time found ---"; + + const highlightMessage = (text: string, term: string) => { + if (!term) return text; + + const parts = text.split(new RegExp(`(${escapeRegExp(term)})`, "gi")); + return parts.map((part, index) => + part.toLowerCase() === term.toLowerCase() ? ( + + {part} + + ) : ( + part + ), + ); + }; + + const tooltip = (color: string, timestamp: string) => { + return ( + + + +
+ + +

+ {timestamp} +

+
+ + + ); + }; + + return ( +
+ {" "} +
+ {/* Icon to expand the log item maybe implement a colapsible later */} + {/* */} + {rawTimestamp && tooltip(color, rawTimestamp)} + + {formattedTime} + + + {type} + +
+ + {searchTerm ? highlightMessage(message, searchTerm) : message} + +
+ ); +} diff --git a/apps/dokploy/components/dashboard/docker/logs/utils.ts b/apps/dokploy/components/dashboard/docker/logs/utils.ts index 1c010681..6e11fe0e 100644 --- a/apps/dokploy/components/dashboard/docker/logs/utils.ts +++ b/apps/dokploy/components/dashboard/docker/logs/utils.ts @@ -1,132 +1,134 @@ -export type LogType = "error" | "warning" | "success" | "info"; -export type LogVariant = "red" | "yellow" | "green" | "blue"; - -export interface LogLine { - timestamp: Date | null; - message: string; -} - -interface LogStyle { - type: LogType; - variant: LogVariant; - color: string; -} - -const LOG_STYLES: Record = { - error: { - type: "error", - variant: "red", - color: "bg-red-500/40", - }, - warning: { - type: "warning", - variant: "yellow", - color: "bg-yellow-500/40", - }, - success: { - type: "success", - variant: "green", - color: "bg-green-500/40", - }, - info: { - type: "info", - variant: "blue", - color: "bg-blue-600/40", - }, -} as const; - -export function parseLogs(logString: string): LogLine[] { - // Regex to match the log line format - // Exemple of return : - // 1 2024-12-10T10:00:00.000Z The server is running on port 8080 - // Should return : - // { timestamp: new Date("2024-12-10T10:00:00.000Z"), - // message: "The server is running on port 8080" } - const logRegex = - /^(?:(\d+)\s+)?(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z|\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} UTC)?\s*(.*)$/; - - return logString - .split("\n") - .map((line) => line.trim()) - .filter((line) => line !== "") - .map((line) => { - const match = line.match(logRegex); - if (!match) return null; - - const [, , timestamp, message] = match; - - if (!message?.trim()) return null; - - // Delete other timestamps and keep only the one from --timestamps - const cleanedMessage = message - ?.replace( - /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z|\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} UTC/g, - "" - ) - .trim(); - - return { - timestamp: timestamp ? new Date(timestamp.replace(" UTC", "Z")) : null, - message: cleanedMessage, - }; - }) - .filter((log) => log !== null); -} - -// Detect log type based on message content -export const getLogType = (message: string): LogStyle => { - const lowerMessage = message.toLowerCase(); - - if ( - /(?:^|\s)(?:error|err):?\s/i.test(lowerMessage) || - /\b(?:exception|failed|failure)\b/i.test(lowerMessage) || - /(?:stack\s?trace):\s*$/i.test(lowerMessage) || - /^\s*at\s+[\w.]+\s*\(?.+:\d+:\d+\)?/.test(lowerMessage) || - /\b(?:uncaught|unhandled)\s+(?:exception|error)\b/i.test(lowerMessage) || - /Error:\s.*(?:in|at)\s+.*:\d+(?::\d+)?/.test(lowerMessage) || - /\b(?:errno|code):\s*(?:\d+|[A-Z_]+)\b/i.test(lowerMessage) || - /\[(?:error|err|fatal)\]/i.test(lowerMessage) || - /\b(?:crash|critical|fatal)\b/i.test(lowerMessage) || - /\b(?:fail(?:ed|ure)?|broken|dead)\b/i.test(lowerMessage) - ) { - return LOG_STYLES.error; - } - - if ( - /(?:^|\s)(?:warning|warn):?\s/i.test(lowerMessage) || - /\[(?:warn(?:ing)?|attention)\]/i.test(lowerMessage) || - /(?:deprecated|obsolete)\s+(?:since|in|as\s+of)/i.test(lowerMessage) || - /\b(?:caution|attention|notice):\s/i.test(lowerMessage) || - /(?:might|may|could)\s+(?:not|cause|lead\s+to)/i.test(lowerMessage) || - /(?:!+\s*(?:warning|caution|attention)\s*!+)/i.test(lowerMessage) || - /\b(?:deprecated|obsolete)\b/i.test(lowerMessage) || - /\b(?:unstable|experimental)\b/i.test(lowerMessage) - ) { - return LOG_STYLES.warning; - } - - if ( - /(?:successfully|complete[d]?)\s+(?:initialized|started|completed|done)/i.test( - lowerMessage - ) || - /\[(?:success|ok|done)\]/i.test(lowerMessage) || - /(?:listening|running)\s+(?:on|at)\s+(?:port\s+)?\d+/i.test(lowerMessage) || - /(?:connected|established|ready)\s+(?:to|for|on)/i.test(lowerMessage) || - /\b(?:loaded|mounted|initialized)\s+successfully\b/i.test(lowerMessage) || - /✓|√|\[ok\]|done!/i.test(lowerMessage) || - /\b(?:success(?:ful)?|completed|ready)\b/i.test(lowerMessage) || - /\b(?:started|running|active)\b/i.test(lowerMessage) - ) { - return LOG_STYLES.success; - } - - if ( - /(?:^|\s)(?:info|inf):?\s/i.test(lowerMessage) || - /\[(info|log|debug|trace|server|db|api)\]/i.test(lowerMessage) || - /\b(?:version|config|start|import|load)\b:?/i.test(lowerMessage) - ) { - return LOG_STYLES.info; - } - - return LOG_STYLES.info; -}; +export type LogType = "error" | "warning" | "success" | "info"; +export type LogVariant = "red" | "yellow" | "green" | "blue"; + +export interface LogLine { + rawTimestamp: string | null; + timestamp: Date | null; + message: string; +} + +interface LogStyle { + type: LogType; + variant: LogVariant; + color: string; +} + +const LOG_STYLES: Record = { + error: { + type: "error", + variant: "red", + color: "bg-red-500/40", + }, + warning: { + type: "warning", + variant: "yellow", + color: "bg-yellow-500/40", + }, + success: { + type: "success", + variant: "green", + color: "bg-green-500/40", + }, + info: { + type: "info", + variant: "blue", + color: "bg-blue-600/40", + }, +} as const; + +export function parseLogs(logString: string): LogLine[] { + // Regex to match the log line format + // Exemple of return : + // 1 2024-12-10T10:00:00.000Z The server is running on port 8080 + // Should return : + // { timestamp: new Date("2024-12-10T10:00:00.000Z"), + // message: "The server is running on port 8080" } + const logRegex = + /^(?:(\d+)\s+)?(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z|\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} UTC)?\s*(.*)$/; + + return logString + .split("\n") + .map((line) => line.trim()) + .filter((line) => line !== "") + .map((line) => { + const match = line.match(logRegex); + if (!match) return null; + + const [, , timestamp, message] = match; + + if (!message?.trim()) return null; + + // Delete other timestamps and keep only the one from --timestamps + const cleanedMessage = message + ?.replace( + /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z|\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} UTC/g, + "", + ) + .trim(); + + return { + rawTimestamp: timestamp, + timestamp: timestamp ? new Date(timestamp.replace(" UTC", "Z")) : null, + message: cleanedMessage, + }; + }) + .filter((log) => log !== null); +} + +// Detect log type based on message content +export const getLogType = (message: string): LogStyle => { + const lowerMessage = message.toLowerCase(); + + if ( + /(?:^|\s)(?:error|err):?\s/i.test(lowerMessage) || + /\b(?:exception|failed|failure)\b/i.test(lowerMessage) || + /(?:stack\s?trace):\s*$/i.test(lowerMessage) || + /^\s*at\s+[\w.]+\s*\(?.+:\d+:\d+\)?/.test(lowerMessage) || + /\b(?:uncaught|unhandled)\s+(?:exception|error)\b/i.test(lowerMessage) || + /Error:\s.*(?:in|at)\s+.*:\d+(?::\d+)?/.test(lowerMessage) || + /\b(?:errno|code):\s*(?:\d+|[A-Z_]+)\b/i.test(lowerMessage) || + /\[(?:error|err|fatal)\]/i.test(lowerMessage) || + /\b(?:crash|critical|fatal)\b/i.test(lowerMessage) || + /\b(?:fail(?:ed|ure)?|broken|dead)\b/i.test(lowerMessage) + ) { + return LOG_STYLES.error; + } + + if ( + /(?:^|\s)(?:warning|warn):?\s/i.test(lowerMessage) || + /\[(?:warn(?:ing)?|attention)\]/i.test(lowerMessage) || + /(?:deprecated|obsolete)\s+(?:since|in|as\s+of)/i.test(lowerMessage) || + /\b(?:caution|attention|notice):\s/i.test(lowerMessage) || + /(?:might|may|could)\s+(?:not|cause|lead\s+to)/i.test(lowerMessage) || + /(?:!+\s*(?:warning|caution|attention)\s*!+)/i.test(lowerMessage) || + /\b(?:deprecated|obsolete)\b/i.test(lowerMessage) || + /\b(?:unstable|experimental)\b/i.test(lowerMessage) + ) { + return LOG_STYLES.warning; + } + + if ( + /(?:successfully|complete[d]?)\s+(?:initialized|started|completed|done)/i.test( + lowerMessage, + ) || + /\[(?:success|ok|done)\]/i.test(lowerMessage) || + /(?:listening|running)\s+(?:on|at)\s+(?:port\s+)?\d+/i.test(lowerMessage) || + /(?:connected|established|ready)\s+(?:to|for|on)/i.test(lowerMessage) || + /\b(?:loaded|mounted|initialized)\s+successfully\b/i.test(lowerMessage) || + /✓|√|\[ok\]|done!/i.test(lowerMessage) || + /\b(?:success(?:ful)?|completed|ready)\b/i.test(lowerMessage) || + /\b(?:started|running|active)\b/i.test(lowerMessage) + ) { + return LOG_STYLES.success; + } + + if ( + /(?:^|\s)(?:info|inf):?\s/i.test(lowerMessage) || + /\[(info|log|debug|trace|server|db|api)\]/i.test(lowerMessage) || + /\b(?:version|config|start|import|load)\b:?/i.test(lowerMessage) + ) { + return LOG_STYLES.info; + } + + return LOG_STYLES.info; +}; diff --git a/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx b/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx index f15e475c..92401dc3 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/show-modal-logs.tsx @@ -91,10 +91,7 @@ export const ShowModalLogs = ({ appName, children, serverId }: Props) => { - +
diff --git a/apps/dokploy/components/ui/badge.tsx b/apps/dokploy/components/ui/badge.tsx index fd190b8a..911b0071 100644 --- a/apps/dokploy/components/ui/badge.tsx +++ b/apps/dokploy/components/ui/badge.tsx @@ -14,16 +14,14 @@ const badgeVariants = cva( "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", - red: - "border-transparent select-none items-center whitespace-nowrap font-medium bg-red-500/15 text-destructive text-xs h-4 px-1 py-1 rounded-md", + red: "border-transparent select-none items-center whitespace-nowrap font-medium bg-red-500/15 text-destructive text-xs h-4 px-1 py-1 rounded-md", yellow: "border-transparent select-none items-center whitespace-nowrap font-medium bg-yellow-500/15 text-yellow-500 text-xs h-4 px-1 py-1 rounded-md", orange: "border-transparent select-none items-center whitespace-nowrap font-medium bg-orange-500/15 text-orange-500 text-xs h-4 px-1 py-1 rounded-md", green: "border-transparent select-none items-center whitespace-nowrap font-medium bg-emerald-500/15 text-emerald-500 text-xs h-4 px-1 py-1 rounded-md", - blue: - "border-transparent select-none items-center whitespace-nowrap font-medium bg-blue-500/15 text-blue-500 text-xs h-4 px-1 py-1 rounded-md", + blue: "border-transparent select-none items-center whitespace-nowrap font-medium bg-blue-500/15 text-blue-500 text-xs h-4 px-1 py-1 rounded-md", blank: "border-transparent select-none items-center whitespace-nowrap font-medium dark:bg-white/15 bg-black/15 text-foreground text-xs h-4 px-1 py-1 rounded-md", outline: "text-foreground", diff --git a/apps/dokploy/styles/globals.css b/apps/dokploy/styles/globals.css index a116d66b..ab6084e1 100644 --- a/apps/dokploy/styles/globals.css +++ b/apps/dokploy/styles/globals.css @@ -198,4 +198,4 @@ .custom-logs-scrollbar::-webkit-scrollbar-thumb:hover { background-color: hsl(var(--muted-foreground) / 0.5); } -} \ No newline at end of file +}