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>
  );
}