import { useEffect, useState, useMemo } from 'react'; import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'; import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Button } from '@/components/ui/button'; import { RefreshCw, Activity, Terminal } from 'lucide-react'; import { ScrollArea } from '@/components/ui/scroll-area'; interface Metric { id: number; container_id: string; stack_name: string; cpu_percent: number; memory_mb: number; net_rx_mb: number; net_tx_mb: number; timestamp: number; } interface LogEntry { stackName: string; containerName: string; level: string; message: string; timestamp: number; } export function GlobalObservabilityView() { const [metrics, setMetrics] = useState([]); const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); // Filters const [selectedStack, setSelectedStack] = useState('all'); const [selectedLevel, setSelectedLevel] = useState('all'); const fetchData = async () => { setLoading(true); try { const [metricsRes, logsRes] = await Promise.all([ fetch('/api/metrics/historical'), fetch('/api/logs/global') ]); if (metricsRes.ok) { setMetrics(await metricsRes.json()); } if (logsRes.ok) { setLogs(await logsRes.json()); } } catch (error) { console.error('Failed to fetch observability data:', error); } finally { setLoading(false); } }; useEffect(() => { fetchData(); const interval = setInterval(fetchData, 30000); // 30s auto-refresh return () => clearInterval(interval); }, []); // Aggregate metrics by timestamp (minute buckets) for the chart const chartData = useMemo(() => { const buckets: Record = {}; metrics.forEach(m => { // Bucket by minute (ignoring seconds) const date = new Date(m.timestamp); date.setSeconds(0, 0); const key = date.getTime() + ''; if (!buckets[key]) { buckets[key] = { time: date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), timestamp: date.getTime(), cpu: 0, ram: 0 }; } buckets[key].cpu += m.cpu_percent; buckets[key].ram += m.memory_mb; }); return Object.values(buckets).sort((a, b) => a.timestamp - b.timestamp); }, [metrics]); const chartConfig = { cpu: { label: 'CPU Usage (%)', color: 'var(--chart-1)' }, ram: { label: 'RAM Usage (MB)', color: 'var(--chart-2)' }, }; // Filter logs const filteredLogs = useMemo(() => { return logs.filter(log => { if (selectedStack !== 'all' && log.stackName !== selectedStack) return false; if (selectedLevel !== 'all' && log.level !== selectedLevel) return false; return true; }); }, [logs, selectedStack, selectedLevel]); const uniqueStacks = useMemo(() => { const stacks = new Set(logs.map(l => l.stackName)); return Array.from(stacks).sort(); }, [logs]); return (

Global Observability

24-hour historical metrics and centralized logging.

{/* Charts Section */}
Aggregate CPU Usage Total CPU percentage across all containers. {chartData.length > 0 ? ( `${val}%`} /> } /> ) : (
No historical CPU data available.
)}
Aggregate Memory Usage Total Memory (MB) allocated across all containers. {chartData.length > 0 ? ( `${val}MB`} /> } /> ) : (
No historical Memory data available.
)}
{/* Global Logs Section */}
Unified Global Logs
{filteredLogs.length > 0 ? ( filteredLogs.map((log, idx) => (
[{new Date(log.timestamp).toLocaleTimeString()}] [{log.stackName}/{log.containerName}] {log.level}: {log.message}
)) ) : (
No logs found matching criteria.
)}
); }