Files
ringnet/server/server.js
ale d59980046b dashboard
Signed-off-by: ale <ale@manalejandro.com>
2025-06-17 00:29:01 +02:00

280 líneas
8.7 KiB
JavaScript

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;