'use client' import { useEffect, useState, useCallback, useRef } from 'react' import { RefreshCw, Cpu, MemoryStick, HardDrive, Network, Activity, CheckCircle2, AlertTriangle, XCircle } from 'lucide-react' import { cn } from '@/lib/utils' // ============================================================================ // Types // ============================================================================ interface LiveStatsData { cpuPercent: number | null memoryPercent: number | null memoryUsedMb: number | null memoryTotalMb: number | null diskReadMbps: number | null diskWriteMbps: number | null networkInMbps: number | null networkOutMbps: number | null containersRunning: number | null containersStopped: number | null timestamp?: Date | string } interface LiveStatsPanelProps { data: LiveStatsData | null | undefined isRefreshing: boolean onRefresh: () => void autoRefreshInterval?: number // in milliseconds, default 15000 (15 seconds) } interface GaugeConfig { label: string value: number | null max: number unit: string icon: React.ReactNode thresholds: { warning: number // percentage at which to show warning (yellow) critical: number // percentage at which to show critical (red) } description?: string secondaryValue?: string } // ============================================================================ // Enhanced Circular Gauge Component // ============================================================================ function CircularGauge({ label, value, max, unit, icon, thresholds, description, secondaryValue, isRefreshing }: GaugeConfig & { isRefreshing: boolean }) { const displayValue = value ?? 0 const percentage = Math.min((displayValue / max) * 100, 100) // Determine status color based on thresholds const getStatusColor = () => { if (value === null) return { stroke: '#9ca3af', bg: '#f3f4f6', text: 'text-gray-500' } if (percentage >= thresholds.critical) return { stroke: '#ef4444', bg: '#fef2f2', text: 'text-red-600' } if (percentage >= thresholds.warning) return { stroke: '#f59e0b', bg: '#fffbeb', text: 'text-amber-600' } return { stroke: '#22c55e', bg: '#f0fdf4', text: 'text-green-600' } } const colors = getStatusColor() // SVG circle parameters const size = 120 const strokeWidth = 8 const radius = (size - strokeWidth) / 2 const circumference = 2 * Math.PI * radius const strokeDashoffset = circumference - (percentage / 100) * circumference // Get status icon const getStatusIcon = () => { if (value === null) return null if (percentage >= thresholds.critical) return if (percentage >= thresholds.warning) return return } return (
{/* Status indicator */}
{getStatusIcon()}
{/* Circular gauge */}
{/* Background circle */} {/* Progress circle */} {/* Center content */}
{icon}
{value !== null ? displayValue.toFixed(1) : '--'}
{unit}
{/* Label and description */}
{label}
{description && (
{description}
)} {secondaryValue && (
{secondaryValue}
)}
) } // ============================================================================ // Compact Stats Bar Component // ============================================================================ function CompactStatBar({ label, value, max, unit, color }: { label: string value: number | null max: number unit: string color: string }) { const displayValue = value ?? 0 const percentage = Math.min((displayValue / max) * 100, 100) return (
{label}
{value !== null ? `${displayValue.toFixed(1)} ${unit}` : '--'}
) } // ============================================================================ // Main Live Stats Panel // ============================================================================ export function LiveStatsPanel({ data, isRefreshing, onRefresh, autoRefreshInterval = 15000 }: LiveStatsPanelProps) { const [lastUpdated, setLastUpdated] = useState(null) const [countdown, setCountdown] = useState(autoRefreshInterval / 1000) const intervalRef = useRef(null) const countdownRef = useRef(null) // Update last updated time when data changes useEffect(() => { if (data?.timestamp) { setLastUpdated(new Date(data.timestamp)) } }, [data?.timestamp]) // Auto-refresh interval useEffect(() => { // Initial refresh on mount onRefresh() // Set up auto-refresh interval intervalRef.current = setInterval(() => { onRefresh() setCountdown(autoRefreshInterval / 1000) }, autoRefreshInterval) // Countdown timer countdownRef.current = setInterval(() => { setCountdown(prev => Math.max(0, prev - 1)) }, 1000) return () => { if (intervalRef.current) clearInterval(intervalRef.current) if (countdownRef.current) clearInterval(countdownRef.current) } }, [autoRefreshInterval, onRefresh]) // Reset countdown when manually refreshing const handleManualRefresh = useCallback(() => { onRefresh() setCountdown(autoRefreshInterval / 1000) }, [onRefresh, autoRefreshInterval]) // Format time ago const formatTimeAgo = (date: Date | null) => { if (!date) return 'Never' const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000) if (seconds < 60) return `${seconds}s ago` const minutes = Math.floor(seconds / 60) if (minutes < 60) return `${minutes}m ago` return `${Math.floor(minutes / 60)}h ago` } // Gauge configurations const gauges: GaugeConfig[] = [ { label: 'CPU Usage', value: data?.cpuPercent ?? null, max: 100, unit: '%', icon: , thresholds: { warning: 70, critical: 90 }, description: 'Processor utilization' }, { label: 'Memory', value: data?.memoryPercent ?? null, max: 100, unit: '%', icon: , thresholds: { warning: 75, critical: 90 }, description: 'RAM utilization', secondaryValue: data?.memoryUsedMb && data?.memoryTotalMb ? `${(data.memoryUsedMb / 1024).toFixed(1)} / ${(data.memoryTotalMb / 1024).toFixed(1)} GB` : undefined }, { label: 'Disk Read', value: data?.diskReadMbps ?? null, max: 100, unit: 'MB/s', icon: , thresholds: { warning: 50, critical: 80 }, description: 'Storage throughput' }, { label: 'Network In', value: data?.networkInMbps ?? null, max: 100, unit: 'Mbps', icon: , thresholds: { warning: 50, critical: 80 }, description: 'Inbound traffic' } ] return (
{/* Header */}

Live Stats

Last updated: {formatTimeAgo(lastUpdated)} • Next refresh in {countdown}s

{/* Main Gauges */}
{gauges.map((gauge) => ( ))}
{/* Additional Stats */}

Additional Metrics

{/* Container counts */} {(data?.containersRunning !== null || data?.containersStopped !== null) && (
{data?.containersRunning ?? 0} running
{data?.containersStopped ?? 0} stopped
{(data?.containersRunning ?? 0) + (data?.containersStopped ?? 0)} total containers
)}
) } export default LiveStatsPanel