import * as React from "react"; const { useCallback, useEffect, useRef, useState } = React; export interface LogEntry { timestamp: string; level: string; message: string; } interface ServerLogsPanelProps { logs: LogEntry[]; } function levelColor(level: string): string { switch (level.toUpperCase()) { case "ERROR": return "#e06c75"; case "WARN": return "#e5c07b"; default: return "#98c379"; } } export function ServerLogsPanel({ logs }: ServerLogsPanelProps) { const [isOpen, setIsOpen] = useState(false); const [filter, setFilter] = useState(""); const [severityFilter, setSeverityFilter] = useState("ALL"); const scrollRef = useRef(null); const userScrolledUpRef = useRef(false); const lastScrollTopRef = useRef(0); const filteredLogs = logs.filter((entry) => { const matchesSeverity = severityFilter === "ALL" || entry.level.toUpperCase() === severityFilter; const matchesFilter = filter === "" || entry.message.toLowerCase().includes(filter.toLowerCase()) || entry.timestamp.includes(filter); return matchesSeverity && matchesFilter; }); const scrollToBottom = useCallback(() => { const el = scrollRef.current; if (el) { el.scrollTop = el.scrollHeight; lastScrollTopRef.current = el.scrollTop; } }, []); // Auto-scroll when new entries arrive (unless user scrolled up). useEffect(() => { if (!isOpen) return; if (!userScrolledUpRef.current) { scrollToBottom(); } }, [filteredLogs.length, isOpen, scrollToBottom]); const handleScroll = () => { const el = scrollRef.current; if (!el) return; const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 5; if (el.scrollTop < lastScrollTopRef.current) { userScrolledUpRef.current = true; } if (isAtBottom) { userScrolledUpRef.current = false; } lastScrollTopRef.current = el.scrollTop; }; const severityButtons = ["ALL", "INFO", "WARN", "ERROR"] as const; return (
{/* Header / toggle */} {isOpen && (
{/* Filter controls */}
setFilter(e.target.value)} placeholder="Filter logs..." style={{ flex: 1, minWidth: "80px", padding: "4px 8px", borderRadius: "4px", border: "1px solid #333", background: "#161b22", color: "#ccc", fontSize: "0.8em", outline: "none", }} /> {severityButtons.map((sev) => ( ))}
{/* Log entries */}
{filteredLogs.length === 0 ? (
No log entries
) : ( filteredLogs.map((entry, idx) => (
{entry.timestamp.replace("T", " ").replace("Z", "")} {entry.level} {entry.message}
)) )}
)}
); }