167 lines
5.2 KiB
TypeScript
Raw Normal View History

'use client';
import { useEffect, useState, useRef } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { RefreshCw, Download, Search, Pause, Play } from 'lucide-react';
interface LogViewerProps {}
export function LogViewer() {
const [logs, setLogs] = useState<string[]>([]);
const [loading, setLoading] = useState(true);
const [autoRefresh, setAutoRefresh] = useState(true);
const [filter, setFilter] = useState('');
const filteredLogsRef = useRef<string[]>([]);
const fetchLogs = async () => {
try {
const response = await fetch('/api/logs?lines=200');
const data = await response.json();
setLogs(data.logs || []);
} catch (error) {
console.error('Failed to fetch logs:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchLogs();
let interval: NodeJS.Timeout;
if (autoRefresh) {
interval = setInterval(fetchLogs, 3000); // Refresh every 3s
}
return () => clearInterval(interval);
}, [autoRefresh]);
const toggleAutoRefresh = () => {
setAutoRefresh(!autoRefresh);
};
const handleDownload = () => {
const content = logs.join('\n');
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `openclaw-logs-${new Date().toISOString()}.log`;
a.click();
URL.revokeObjectURL(url);
};
// Filter logs
const filteredLogs = filter
? logs.filter((log) =>
log.toLowerCase().includes(filter.toLowerCase())
)
: logs;
filteredLogsRef.current = filteredLogs;
// Parse log level for styling
const getLogStyle = (log: string) => {
if (log.toLowerCase().includes('error') || log.toLowerCase().includes('failed'))
return 'text-red-400';
if (log.toLowerCase().includes('warn'))
return 'text-yellow-400';
if (log.toLowerCase().includes('info'))
return 'text-blue-400';
return 'text-zinc-300';
};
return (
<div className="space-y-4">
{/* Controls */}
<div className="flex flex-wrap gap-2 items-center">
<Button
variant="outline"
onClick={fetchLogs}
disabled={loading}
className="border-zinc-700 hover:bg-zinc-800"
>
<RefreshCw className={`w-4 h-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
Refresh
</Button>
<Button
variant={autoRefresh ? 'default' : 'outline'}
onClick={toggleAutoRefresh}
className={autoRefresh ? 'bg-orange-500 hover:bg-orange-600' : 'border-zinc-700 hover:bg-zinc-800'}
>
{autoRefresh ? (
<>
<Pause className="w-4 h-4 mr-2" />
Auto-refresh On
</>
) : (
<>
<Play className="w-4 h-4 mr-2" />
Auto-refresh Off
</>
)}
</Button>
<Button
variant="outline"
onClick={handleDownload}
className="border-zinc-700 hover:bg-zinc-800"
>
<Download className="w-4 h-4 mr-2" />
Export
</Button>
<div className="flex-1 min-w-[200px]">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-500" />
<Input
placeholder="Filter logs..."
value={filter}
onChange={(e) => setFilter(e.target.value)}
className="pl-10 bg-zinc-900/50 border-zinc-700 focus:border-orange-500"
/>
</div>
</div>
</div>
{/* Log Display */}
<Card className="bg-zinc-900/50 border-zinc-800">
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span>Gateway Logs</span>
<span className="text-sm font-normal text-zinc-400">
{filteredLogs.length} entries
</span>
</CardTitle>
</CardHeader>
<CardContent>
<div className="h-[600px] overflow-auto rounded-lg bg-black/50 p-4 font-mono text-xs">
{loading && logs.length === 0 ? (
<div className="flex items-center justify-center h-full">
<RefreshCw className="w-8 h-8 animate-spin text-orange-400" />
</div>
) : filteredLogs.length === 0 ? (
<div className="flex items-center justify-center h-full text-zinc-500">
{filter ? 'No logs match your filter' : 'No logs available'}
</div>
) : (
<div className="space-y-1">
{filteredLogs.map((log, index) => (
<div
key={index}
className={`whitespace-pre-wrap break-words ${getLogStyle(log)}`}
>
{log}
</div>
))}
</div>
)}
</div>
</CardContent>
</Card>
<div className="text-sm text-zinc-500">
<p> Logs refresh automatically every 3 seconds when auto-refresh is enabled.</p>
</div>
</div>
);
}