import { useEffect, useState } from 'react' import { fetchHealth, fetchSources, fetchAlerts, fetchEnvStatus, type MeshHealth, type SourceHealth, type Alert, type EnvStatus, } from '@/lib/api' import { useWebSocket } from '@/hooks/useWebSocket' import { AlertTriangle, AlertCircle, Info, CheckCircle, Radio, Cpu, Activity, MapPin, } from 'lucide-react' function HealthGauge({ health }: { health: MeshHealth }) { const score = health.score const tier = health.tier // Color based on score const getColor = (s: number) => { if (s >= 80) return '#22c55e' if (s >= 60) return '#f59e0b' return '#ef4444' } const color = getColor(score) const circumference = 2 * Math.PI * 45 const progress = (score / 100) * circumference return (
{/* Background circle */} {/* Progress arc */} {/* Score text */} {score.toFixed(1)} {tier}
) } function PillarBar({ label, value, }: { label: string value: number }) { const getColor = (v: number) => { if (v >= 80) return 'bg-green-500' if (v >= 60) return 'bg-amber-500' return 'bg-red-500' } return (
{label}
{value.toFixed(1)}
) } function AlertItem({ alert }: { alert: Alert }) { const getSeverityStyles = (severity: string) => { switch (severity.toLowerCase()) { case 'critical': case 'emergency': return { bg: 'bg-red-500/10', border: 'border-red-500', icon: AlertCircle, iconColor: 'text-red-500', } case 'warning': return { bg: 'bg-amber-500/10', border: 'border-amber-500', icon: AlertTriangle, iconColor: 'text-amber-500', } default: return { bg: 'bg-green-500/10', border: 'border-green-500', icon: Info, iconColor: 'text-green-500', } } } const styles = getSeverityStyles(alert.severity) const Icon = styles.icon return (
{alert.message}
{alert.timestamp || 'Just now'}
) } function SourceCard({ source }: { source: SourceHealth }) { const getStatusColor = () => { if (!source.is_loaded) return 'bg-red-500' if (source.last_error) return 'bg-amber-500' return 'bg-green-500' } return (
{source.name}
{source.node_count} nodes • {source.type}
) } function StatCard({ icon: Icon, label, value, subvalue, }: { icon: typeof Radio label: string value: string | number subvalue?: string }) { return (
{label}
{value}
{subvalue && (
{subvalue}
)}
) } export default function Dashboard() { const [health, setHealth] = useState(null) const [sources, setSources] = useState([]) const [alerts, setAlerts] = useState([]) const [envStatus, setEnvStatus] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const { lastHealth } = useWebSocket() useEffect(() => { Promise.all([ fetchHealth(), fetchSources(), fetchAlerts(), fetchEnvStatus(), ]) .then(([h, src, a, e]) => { setHealth(h) setSources(src) setAlerts(a) setEnvStatus(e) setLoading(false) }) .catch((err) => { setError(err.message) setLoading(false) }) }, []) // Update health from WebSocket useEffect(() => { if (lastHealth) { setHealth(lastHealth) } }, [lastHealth]) if (loading) { return (
Loading...
) } if (error) { return (
Error: {error}
) } return (
{/* Mesh Health */}

Mesh Health

{health && ( <>
)}
{/* Alerts + Stats */}
{/* Active Alerts */}

Active Alerts

{alerts.length > 0 ? (
{alerts.map((alert, i) => ( ))}
) : (
No active alerts
)}
{/* Quick Stats */}
{/* Mesh Sources */}

Mesh Sources ({sources.length})

{sources.length > 0 ? (
{sources.map((source, i) => ( ))}
) : (
No sources configured
)}
{/* Environmental Feeds */}

Environmental Feeds

{envStatus?.enabled ? (
{envStatus.feeds.length} feeds active
) : (

Environmental feeds not enabled.

Enable in Config → Mesh Intelligence

)}
{/* HF Propagation placeholder */}

HF Propagation

Space weather data not enabled.

Coming in Phase 1

) }