280 líneas
8.7 KiB
JavaScript
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;
|