Spaces:
Running
Running
File size: 3,521 Bytes
8339370 6a4653e 8339370 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
import { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Badge } from '@/components/ui/badge';
import { getLogs, type LogEntry } from '@/services/api';
import { RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
export function SystemLogs() {
const [logs, setLogs] = useState<LogEntry[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchLogs = async () => {
setIsLoading(true);
setError(null);
try {
const data = await getLogs();
// Sort logs newest first if backend returns oldest first
setLogs(data.reverse());
} catch (err) {
console.error('Failed to fetch logs:', err);
setError('Failed to load logs.');
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchLogs();
// Optional: Auto-refresh every 5 seconds?
// const interval = setInterval(fetchLogs, 5000);
// return () => clearInterval(interval);
}, []);
const getLevelVariant = (level: string) => {
switch (level.toUpperCase()) {
case 'ERROR': return 'destructive';
case 'WARNING': return 'secondary'; // yellow-ish usually
case 'INFO': return 'default';
case 'DEBUG': return 'outline';
default: return 'secondary';
}
};
return (
<Card className="h-full flex flex-col">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div>
<CardTitle>System Logs</CardTitle>
<CardDescription>Backend operational logs (last 100 entries)</CardDescription>
</div>
<Button variant="ghost" size="sm" onClick={fetchLogs} disabled={isLoading}>
<RefreshCw className={`h-4 w-4 ${isLoading ? 'animate-spin' : ''}`} />
</Button>
</div>
</CardHeader>
<CardContent className="flex-1 overflow-hidden p-0">
<ScrollArea className="h-[400px] w-full p-4 pt-0">
{error && <div className="text-destructive text-sm p-2">{error}</div>}
{logs.length === 0 && !isLoading && !error && (
<div className="text-muted-foreground text-sm p-2">No logs available.</div>
)}
<div className="space-y-2">
{logs.map((log, i) => (
<div key={i} className="text-xs border-b border-border pb-2 last:border-0">
<div className="flex items-center gap-2 mb-1">
<span className="text-muted-foreground font-mono">{new Date(log.timestamp).toLocaleTimeString()}</span>
<Badge variant={getLevelVariant(log.level)} className="h-5 px-1.5 text-[10px]">
{log.level}
</Badge>
</div>
<div className="font-mono break-all whitespace-pre-wrap text-foreground/90">
{log.message}
</div>
{log.extra && Object.keys(log.extra).length > 0 && (
<div className="mt-1 pl-2 border-l-2 border-muted">
<pre className="text-[10px] text-muted-foreground">
{JSON.stringify(log.extra, null, 2)}
</pre>
</div>
)}
</div>
))}
</div>
</ScrollArea>
</CardContent>
</Card>
);
}
|