import express from 'express'; import cors from 'cors'; import WebSocket from 'ws'; import chalk from 'chalk'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); class RingNetDashboardServer { constructor(port = 3000) { this.port = port; this.app = express(); this.nodes = new Map(); // Store connected nodes information this.networkStats = { totalNodes: 0, totalConnections: 0, oracleNodes: 0, lastUpdated: new Date() }; this.setupMiddleware(); this.setupRoutes(); this.setupWebSocketConnections(); } setupMiddleware() { this.app.use(cors()); this.app.use(express.json()); this.app.use(express.static(join(__dirname, 'public'))); } setupRoutes() { // API Routes this.app.get('/api/nodes', (req, res) => { const nodes = Array.from(this.nodes.values()).map(node => ({ ...node, lastSeen: new Date(node.lastSeen).toISOString() })); res.json(nodes); }); this.app.get('/api/network/stats', (req, res) => { res.json({ ...this.networkStats, lastUpdated: this.networkStats.lastUpdated.toISOString() }); }); this.app.get('/api/network/topology', (req, res) => { const topology = this.generateNetworkTopology(); res.json(topology); }); this.app.get('/api/nodes/:nodeId', (req, res) => { const node = this.nodes.get(req.params.nodeId); if (!node) { return res.status(404).json({ error: 'Node not found' }); } res.json({ ...node, lastSeen: new Date(node.lastSeen).toISOString() }); }); this.app.get('/api/nodes/:nodeId/connections', (req, res) => { const node = this.nodes.get(req.params.nodeId); if (!node) { return res.status(404).json({ error: 'Node not found' }); } res.json(node.connections || []); }); // Dashboard route this.app.get('/', (req, res) => { res.sendFile(join(__dirname, 'public', 'index.html')); }); // Health check this.app.get('/health', (req, res) => { res.json({ status: 'healthy', uptime: process.uptime(), timestamp: new Date().toISOString() }); }); } setupWebSocketConnections() { // This could be used to connect to ring nodes and receive real-time updates // For now, we'll simulate or wait for nodes to report their status } generateNetworkTopology() { const nodes = Array.from(this.nodes.values()); const ringPositions = nodes .filter(node => node.ringPosition !== undefined) .sort((a, b) => a.ringPosition - b.ringPosition); return { totalNodes: nodes.length, ringSize: 1000, // Default ring size nodes: ringPositions.map(node => ({ nodeId: node.nodeId, shortId: node.nodeId.substring(0, 8), position: node.ringPosition, isOracle: node.isOracle || false, status: node.status || 'unknown', connections: node.connectedPeers || [], lastSeen: node.lastSeen })), connections: this.generateConnectionMap(nodes) }; } generateConnectionMap(nodes) { const connections = []; nodes.forEach(node => { if (node.rings) { // Inner ring connections if (node.rings.inner) { if (node.rings.inner.left) { connections.push({ from: node.nodeId, to: node.rings.inner.left, type: 'inner-left' }); } if (node.rings.inner.right) { connections.push({ from: node.nodeId, to: node.rings.inner.right, type: 'inner-right' }); } } // Outer ring connections if (node.rings.outer) { if (node.rings.outer.left) { connections.push({ from: node.nodeId, to: node.rings.outer.left, type: 'outer-left' }); } if (node.rings.outer.right) { connections.push({ from: node.nodeId, to: node.rings.outer.right, type: 'outer-right' }); } } } }); return connections; } // Method for nodes to register themselves registerNode(nodeInfo) { const nodeId = nodeInfo.nodeId; this.nodes.set(nodeId, { ...nodeInfo, lastSeen: Date.now(), status: 'active' }); this.updateNetworkStats(); console.log(chalk.green(`šŸ“Š Node registered: ${nodeId.substring(0, 8)}...`)); } // Method for nodes to update their status updateNode(nodeId, updates) { const node = this.nodes.get(nodeId); if (node) { this.nodes.set(nodeId, { ...node, ...updates, lastSeen: Date.now() }); this.updateNetworkStats(); } } // Method to remove inactive nodes removeInactiveNodes() { const now = Date.now(); const timeout = 5 * 60 * 1000; // 5 minutes timeout for (const [nodeId, node] of this.nodes.entries()) { if (now - node.lastSeen > timeout) { this.nodes.delete(nodeId); console.log(chalk.yellow(`šŸ• Removed inactive node: ${nodeId.substring(0, 8)}...`)); } } this.updateNetworkStats(); } updateNetworkStats() { const nodes = Array.from(this.nodes.values()); this.networkStats = { totalNodes: nodes.length, totalConnections: nodes.reduce((sum, node) => sum + (node.connectedPeers?.length || 0), 0), oracleNodes: nodes.filter(node => node.isOracle).length, lastUpdated: new Date() }; } start() { // Start cleanup interval for inactive nodes setInterval(() => { this.removeInactiveNodes(); }, 60000); // Check every minute this.server = this.app.listen(this.port, () => { console.log(chalk.blue(` šŸš€ RingNet Dashboard Server Started! šŸ“Š Dashboard: http://localhost:${this.port} šŸ”— API: http://localhost:${this.port}/api šŸ“ˆ Health: http://localhost:${this.port}/health Monitoring ${this.networkStats.totalNodes} nodes... `)); }); return this.server; } stop() { if (this.server) { this.server.close(); console.log(chalk.yellow('šŸ›‘ Dashboard server stopped')); } } } // API endpoint for nodes to report their status const dashboardServer = new RingNetDashboardServer(process.env.PORT || 3000); // Add POST endpoint for nodes to register/update dashboardServer.app.post('/api/nodes/register', (req, res) => { try { const nodeInfo = req.body; dashboardServer.registerNode(nodeInfo); res.json({ success: true, message: 'Node registered successfully' }); } catch (error) { res.status(400).json({ error: error.message }); } }); dashboardServer.app.put('/api/nodes/:nodeId', (req, res) => { try { const nodeId = req.params.nodeId; const updates = req.body; dashboardServer.updateNode(nodeId, updates); res.json({ success: true, message: 'Node updated successfully' }); } catch (error) { res.status(400).json({ error: error.message }); } }); // Start the server dashboardServer.start(); // Graceful shutdown process.on('SIGINT', () => { console.log(chalk.yellow('\nšŸ›‘ Shutting down dashboard server...')); dashboardServer.stop(); process.exit(0); }); process.on('SIGTERM', () => { console.log(chalk.yellow('\nšŸ›‘ Shutting down dashboard server...')); dashboardServer.stop(); process.exit(0); }); export default RingNetDashboardServer;