/** * Network Page - Відображення всіх нод у мережі DAGI */ import React, { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; interface RegisteredNode { id: string; node_id: string; node_name: string; node_role: string; node_type: string; ip_address?: string; local_ip?: string; hostname?: string; status: 'online' | 'offline' | 'maintenance' | 'degraded'; last_heartbeat?: string; registered_at: string; updated_at: string; metadata: { capabilities?: any; first_registration?: string; last_registration?: string; }; } interface NetworkStats { service: string; uptime_seconds: number; total_nodes: number; online_nodes: number; offline_nodes: number; uptime_percentage: number; timestamp: string; } export default function NetworkPage() { const [selectedRole, setSelectedRole] = useState('all'); const [selectedStatus, setSelectedStatus] = useState('all'); const [nodes, setNodes] = useState([]); const [stats, setStats] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); // Fetch nodes and stats const fetchData = async () => { console.log('[NetworkPage] Fetching data...'); try { setIsLoading(true); setError(null); // Fetch nodes console.log('[NetworkPage] Fetching nodes from /node-registry/api/v1/nodes'); const nodesResponse = await fetch('/node-registry/api/v1/nodes'); console.log('[NetworkPage] Nodes response status:', nodesResponse.status); if (!nodesResponse.ok) { throw new Error(`Failed to fetch nodes: ${nodesResponse.status} ${nodesResponse.statusText}`); } const nodesData = await nodesResponse.json(); console.log('[NetworkPage] Nodes data:', nodesData); setNodes(nodesData.nodes || []); // Fetch stats console.log('[NetworkPage] Fetching stats from /node-registry/metrics'); const statsResponse = await fetch('/node-registry/metrics'); console.log('[NetworkPage] Stats response status:', statsResponse.status); if (!statsResponse.ok) { throw new Error(`Failed to fetch stats: ${statsResponse.status} ${statsResponse.statusText}`); } const statsData = await statsResponse.json(); console.log('[NetworkPage] Stats data:', statsData); setStats(statsData); console.log('[NetworkPage] Data fetched successfully'); } catch (err) { console.error('[NetworkPage] Error fetching data:', err); setError(err instanceof Error ? err.message : 'Unknown error'); } finally { setIsLoading(false); } }; useEffect(() => { console.log('[NetworkPage] Component mounted'); fetchData(); const interval = setInterval(fetchData, 10000); // Оновлювати кожні 10 секунд return () => { console.log('[NetworkPage] Component unmounting'); clearInterval(interval); }; }, []); const nodesLoading = isLoading; const statsLoading = isLoading; const nodesError = error; return (
{/* Header */}

🌐 DAGI Network

Децентралізована мережа AI нод

{/* Network Stats */} {!statsLoading && stats && (
Всього нод
{stats.total_nodes}
Online
{stats.online_nodes}
Offline
{stats.offline_nodes}
Uptime
{stats.uptime_percentage}%
)} {/* Filters */}
➕ Підключити ноду
{/* Nodes List */} {nodesLoading && (
⚙️
Завантаження нод...
)} {nodesError && (
❌ Помилка завантаження
{nodesError}
Переконайтесь що Node Registry запущено на порту 9205
)} {!nodesLoading && !nodesError && nodes.length === 0 && (
🌐
Ноди не знайдено
Запустіть Bootstrap Agent на нодах для реєстрації
)} {!nodesLoading && !nodesError && nodes.length > 0 && (
{nodes.map((node) => ( ))}
)}
); } // Node Card Component function NodeCard({ node }: { node: RegisteredNode }) { const [expanded, setExpanded] = useState(false); const statusColors = { online: 'border-green-600 bg-green-900/20', offline: 'border-red-600 bg-red-900/20', maintenance: 'border-yellow-600 bg-yellow-900/20', degraded: 'border-orange-600 bg-orange-900/20', }; const statusIcons = { online: '🟢', offline: '🔴', maintenance: '🟡', degraded: '🟠', }; const roleIcons = { production: '🏭', development: '🔬', backup: '💾', worker: '⚙️', router: '🌐', }; const capabilities = node.metadata?.capabilities; const systemInfo = capabilities?.system; const ollamaInfo = capabilities?.ollama; return (
{roleIcons[node.node_role as keyof typeof roleIcons] || '📡'}

{node.node_name}

{statusIcons[node.status]} {node.status.toUpperCase()}
🆔 {node.node_id}
{node.hostname &&
🖥️ {node.hostname}
} {node.ip_address &&
🌍 Public IP: {node.ip_address}
} {node.local_ip &&
🏠 Local IP: {node.local_ip}
}
Last heartbeat
{node.last_heartbeat ? new Date(node.last_heartbeat).toLocaleString('uk-UA') : 'Never'}
{/* Quick Stats */} {systemInfo && (
CPU
{systemInfo.cpu_count} cores
RAM
{systemInfo.memory_total_gb} GB
Disk
{systemInfo.disk_total_gb} GB
Platform
{systemInfo.platform}
)} {/* Services & Features */} {capabilities && (
{capabilities.services?.map((service) => ( 🔧 {service} ))} {capabilities.features?.map((feature) => ( ⚡ {feature} ))} {capabilities.gpu?.available && ( 🎮 GPU ({capabilities.gpu.count}) )}
)} {/* Ollama Models */} {ollamaInfo?.available && ollamaInfo.models && ollamaInfo.models.length > 0 && (
🤖 Ollama Models ({ollamaInfo.models.length})
{ollamaInfo.models.slice(0, expanded ? undefined : 5).map((model) => ( {model} ))} {!expanded && ollamaInfo.models.length > 5 && ( )}
)} {/* Expand button */} {/* Expanded Details */} {expanded && systemInfo && (
Architecture: {systemInfo.architecture}
Python: {systemInfo.python_version}
Platform Version:
{systemInfo.platform_version}
Registered: {new Date(node.registered_at).toLocaleString('uk-UA')}
)}
); }