'use client'; import { useEffect, useRef, useState, useCallback } from 'react'; import { useWebSocket } from '@/lib/websocket/context'; import type { WebSocketMessage } from '@/lib/schemas/websocket'; import { getServerLogAction } from '@/lib/actions/serverLog'; const MAX_BUFFER = 5000; const LINE_OPTIONS = [100, 500, 1000, 5000] as const; interface ServerLogViewerProps { serverId: string; } export function ServerLogViewer({ serverId }: ServerLogViewerProps) { const [lines, setLines] = useState([]); const [initialLines, setInitialLines] = useState(100); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [isPaused, setIsPaused] = useState(false); const { associateWithServer, addMessageHandler, removeMessageHandler } = useWebSocket(); const scrollRef = useRef(null); const isPausedRef = useRef(false); const appendLines = useCallback((newLines: string[]) => { setLines((prev) => { const combined = [...prev, ...newLines]; return combined.length > MAX_BUFFER ? combined.slice(combined.length - MAX_BUFFER) : combined; }); }, []); const fetchInitial = useCallback( async (count: number) => { setIsLoading(true); setError(null); const result = await getServerLogAction(serverId, count); setIsLoading(false); if (!result.success) { setError(result.message); return; } setLines(result.lines); }, [serverId] ); useEffect(() => { associateWithServer(serverId); fetchInitial(initialLines); }, [serverId, associateWithServer, fetchInitial, initialLines]); const handleMessage = useCallback( (message: WebSocketMessage) => { if (message.type !== 'log_line') return; if (message.server_id !== serverId) return; appendLines([message.data.line]); }, [serverId, appendLines] ); useEffect(() => { addMessageHandler(handleMessage); return () => { removeMessageHandler(handleMessage); }; }, [addMessageHandler, removeMessageHandler, handleMessage]); // Auto-scroll to bottom when new lines arrive, unless paused useEffect(() => { if (!isPausedRef.current && scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [lines]); const handleScroll = () => { if (!scrollRef.current) return; const { scrollTop, scrollHeight, clientHeight } = scrollRef.current; const isAtBottom = scrollHeight - scrollTop - clientHeight < 40; const shouldPause = !isAtBottom; isPausedRef.current = shouldPause; setIsPaused(shouldPause); }; const jumpToBottom = () => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } isPausedRef.current = false; setIsPaused(false); }; const handleLineLimitChange = (e: React.ChangeEvent) => { const count = Number(e.target.value); setInitialLines(count); }; return (
{lines.length} lines buffered
{error &&

{error}

}
{isLoading ? ( Loading… ) : lines.length === 0 ? ( No log output yet. ) : ( lines.map((line, i) => (
{line}
)) )}
{isPaused && (
Paused
)}
); }