Skip to content

Commit fe81438

Browse files
committed
feat: enhance log viewers
1 parent f8d9a6e commit fe81438

File tree

1 file changed

+38
-16
lines changed

1 file changed

+38
-16
lines changed

components/submissions/submission-log-viewer.tsx

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"use client";
2-
import { useState, useEffect, useRef } from 'react';
2+
import { useState, useEffect, useRef, useMemo } from 'react';
33
import useWebSocket, { ReadyState } from 'react-use-websocket';
44
import { useAuth } from '@/hooks/use-auth';
55
import { Problem, Submission, Container } from '@/lib/types';
@@ -8,25 +8,41 @@ import useSWR from 'swr';
88
import api from '@/lib/api';
99
import { Skeleton } from '../ui/skeleton';
1010

11+
interface LogMessage {
12+
stream: 'stdout' | 'stderr' | 'info' | 'error';
13+
data: string;
14+
}
15+
1116
// --- Sub-component for displaying static logs of finished containers ---
1217
const StaticLogViewer = ({ submissionId, containerId }: { submissionId: string, containerId: string }) => {
13-
// A simple text fetcher for SWR, as the log API returns plain text.
1418
const textFetcher = (url: string) => api.get(url, { responseType: 'text' }).then(res => res.data);
15-
1619
const { data: logText, error, isLoading } = useSWR(`/submissions/${submissionId}/containers/${containerId}/log`, textFetcher);
17-
1820
const logContainerRef = useRef<HTMLDivElement>(null);
1921

20-
// Scroll to bottom on initial load
22+
const messages: LogMessage[] = useMemo(() => {
23+
if (!logText) return [];
24+
return logText
25+
.split('\n')
26+
.filter((line: string) => line.trim() !== '')
27+
.map((line: string) => {
28+
try {
29+
return JSON.parse(line) as LogMessage;
30+
} catch (e) {
31+
console.error("Failed to parse log line as JSON:", line);
32+
return { stream: 'stdout', data: line };
33+
}
34+
});
35+
}, [logText]);
36+
2137
useEffect(() => {
2238
if (logContainerRef.current) {
2339
logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight;
2440
}
25-
}, [logText]);
41+
}, [messages]);
2642

2743
return (
2844
<div className="relative">
29-
<div className="absolute top-2 right-2 text-xs font-semibold flex items-center gap-2 z-10">
45+
<div className="absolute top-2 right-6 text-xs font-semibold flex items-center gap-2 z-10">
3046
<span className="h-2 w-2 rounded-full bg-gray-400"></span>
3147
Finished
3248
</div>
@@ -36,18 +52,24 @@ const StaticLogViewer = ({ submissionId, containerId }: { submissionId: string,
3652
>
3753
{isLoading && <Skeleton className="h-full w-full" />}
3854
{error && <p className="text-red-400">Failed to load log.</p>}
39-
{logText && <pre className="whitespace-pre-wrap break-all">{logText}</pre>}
55+
{messages.length > 0 && messages.map((msg, index) => (
56+
<span key={index} className="whitespace-pre-wrap break-all">
57+
{msg.stream === 'stderr' || msg.stream === 'error' ? (
58+
<span className="text-red-400">{msg.data}</span>
59+
) : msg.stream === 'info' ? (
60+
<span className="text-blue-400">{msg.data}</span>
61+
) : (
62+
<span className="text-foreground">{msg.data}</span>
63+
)}
64+
</span>
65+
))}
4066
</div>
4167
</div>
4268
);
4369
};
4470

45-
// --- Sub-component for streaming real-time logs via WebSocket ---
46-
interface LogMessage {
47-
stream: 'stdout' | 'stderr' | 'info' | 'error';
48-
data: string;
49-
}
5071

72+
// --- Sub-component for streaming real-time logs via WebSocket ---
5173
const RealtimeLogViewer = ({ wsUrl, onStatusUpdate }: { wsUrl: string | null, onStatusUpdate: () => void }) => {
5274
const [messages, setMessages] = useState<LogMessage[]>([]);
5375
const logContainerRef = useRef<HTMLDivElement>(null);
@@ -92,7 +114,7 @@ const RealtimeLogViewer = ({ wsUrl, onStatusUpdate }: { wsUrl: string | null, on
92114

93115
return (
94116
<div className="relative">
95-
<div className="absolute top-2 right-2 text-xs font-semibold flex items-center gap-2 z-10">
117+
<div className="absolute top-2 right-6 text-xs font-semibold flex items-center gap-2 z-10">
96118
<span className={`h-2 w-2 rounded-full ${connectionStatus.color}`}></span>
97119
{connectionStatus.text}
98120
</div>
@@ -102,15 +124,15 @@ const RealtimeLogViewer = ({ wsUrl, onStatusUpdate }: { wsUrl: string | null, on
102124
>
103125
{messages.length === 0 && <p className="text-muted-foreground">Waiting for judge output...</p>}
104126
{messages.map((msg, index) => (
105-
<div key={index} className="whitespace-pre-wrap break-all">
127+
<span key={index} className="whitespace-pre-wrap break-all">
106128
{msg.stream === 'stderr' || msg.stream === 'error' ? (
107129
<span className="text-red-400">{msg.data}</span>
108130
) : msg.stream === 'info' ? (
109131
<span className="text-blue-400">{msg.data}</span>
110132
) : (
111133
<span className="text-foreground">{msg.data}</span>
112134
)}
113-
</div>
135+
</span>
114136
))}
115137
</div>
116138
</div>

0 commit comments

Comments
 (0)