Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
ale
2025-06-17 00:29:01 +02:00
padre b5e4b9a8ac
commit d59980046b
Se han modificado 10 ficheros con 1048 adiciones y 7 borrados

Ver fichero

@@ -50,23 +50,29 @@ npm install
### Running the Network
1. **Start the first node (Oracle) as bootstrap:**
1. **Start the Dashboard (Optional):**
```bash
npm run start:oracle -- --port 8080
npm run start:dashboard
```
Dashboard available at: http://localhost:3000
2. **Start the first node (Oracle) as bootstrap:**
```bash
npm run start:oracle -- --port 8080 --dashboard http://localhost:3000
```
2. **Start additional nodes:**
3. **Start additional nodes:**
```bash
# Regular node
npm run start:node -- --port 8081 --bootstrap localhost:8080
npm run start:node -- --port 8081 --bootstrap localhost:8080 --dashboard http://localhost:3000
# Another Oracle node
npm run start:oracle -- --port 8082 --bootstrap localhost:8080
npm run start:oracle -- --port 8082 --bootstrap localhost:8080 --dashboard http://localhost:3000
```
3. **Start more nodes:**
4. **Start more nodes:**
```bash
npm run start:node -- --port 8083 --bootstrap localhost:8080
npm run start:node -- --port 8083 --bootstrap localhost:8080 --dashboard http://localhost:3000
```
## 📖 Usage
@@ -77,6 +83,7 @@ npm run start:node -- --port 8083 --bootstrap localhost:8080
--port <port> # Port to listen on (default: random)
--id <id> # Node ID (default: auto-generated UUID)
--bootstrap <addr> # Bootstrap node address (host:port)
--dashboard <url> # Dashboard server URL (default: http://localhost:3000)
--ice-servers <json> # ICE servers configuration (JSON array)
--config <file> # Load configuration from JSON file
--help # Show help message
@@ -211,6 +218,32 @@ The Ring Network now features **automatic node positioning** that optimizes netw
Use the `topology` command in any node to view the current ring structure and positions.
## 📊 Dashboard & Monitoring
The RingNet includes a web-based dashboard for real-time network monitoring:
### Features
- **Real-time Network Statistics**: Total nodes, connections, Oracle nodes
- **Interactive Ring Topology**: Visual representation of the ring structure
- **Node Status Monitoring**: Active/inactive status, connection health
- **REST API**: Programmatic access to network data
### Quick Start
```bash
# Start the dashboard server
npm run start:dashboard
# Dashboard available at: http://localhost:3000
```
### API Endpoints
- `GET /api/nodes` - List all nodes
- `GET /api/network/stats` - Network statistics
- `GET /api/network/topology` - Ring topology data
- `GET /api/nodes/:nodeId` - Node details
See `server/README.md` for complete API documentation.
## 🧪 Testing
Run the test suite to verify network functionality:

62
demo-dashboard.js Archivo normal
Ver fichero

@@ -0,0 +1,62 @@
#!/usr/bin/env node
import chalk from 'chalk';
console.log(chalk.blue(`
📊 RingNet Dashboard Demo
This demonstration shows how to use the RingNet dashboard
to monitor your network in real-time.
🚀 Step 1: Start the Dashboard Server
${chalk.green('npm run start:dashboard')}
The dashboard will be available at: ${chalk.cyan('http://localhost:3000')}
🔗 Step 2: Start Nodes with Dashboard Integration
${chalk.green('npm run start:oracle -- --port 8080 --dashboard http://localhost:3000')}
${chalk.green('npm run start:node -- --port 8081 --bootstrap localhost:8080 --dashboard http://localhost:3000')}
${chalk.green('npm run start:node -- --port 8082 --bootstrap localhost:8080 --dashboard http://localhost:3000')}
📈 Step 3: Monitor Your Network
Open ${chalk.cyan('http://localhost:3000')} in your browser to see:
✅ Real-time Network Statistics
- Total active nodes
- Oracle nodes count
- Total connections
- Last update time
✅ Live Node List
- Node status indicators
- Node types (Oracle/Regular)
- Ring positions
- Connection health
✅ Interactive Ring Topology
- Visual ring structure
- Node positioning
- Click nodes for details
- Color-coded by type
✅ REST API Access
- GET /api/nodes - List all nodes
- GET /api/network/stats - Network statistics
- GET /api/network/topology - Ring topology
- GET /api/nodes/:id - Node details
📡 API Examples:
${chalk.cyan('curl http://localhost:3000/api/network/stats')}
${chalk.cyan('curl http://localhost:3000/api/nodes')}
${chalk.cyan('curl http://localhost:3000/api/network/topology')}
🔄 Features:
- Auto-refresh every 5 seconds
- Inactive node detection (5 minute timeout)
- Responsive design for mobile/desktop
- Real-time topology updates
- Connection health monitoring
The dashboard provides a comprehensive view of your RingNet
network without affecting node performance!
`));

Ver fichero

@@ -19,6 +19,9 @@ for (let i = 0; i < args.length; i++) {
case '--bootstrap':
options.bootstrap = args[++i];
break;
case '--dashboard':
options.dashboardUrl = args[++i];
break;
case '--ice-servers':
try {
options.iceServers = JSON.parse(args[++i]);
@@ -49,6 +52,7 @@ Options:
--port <port> Port to listen on (default: random)
--id <id> Node ID (default: auto-generated)
--bootstrap <addr> Bootstrap node address (host:port)
--dashboard <url> Dashboard server URL (default: http://localhost:3000)
--ice-servers <json> ICE servers configuration (JSON array)
--config <file> Load configuration from JSON file
--help Show this help message

Ver fichero

@@ -19,6 +19,9 @@ for (let i = 0; i < args.length; i++) {
case '--bootstrap':
options.bootstrap = args[++i];
break;
case '--dashboard':
options.dashboardUrl = args[++i];
break;
case '--ice-servers':
try {
options.iceServers = JSON.parse(args[++i]);
@@ -49,6 +52,7 @@ Options:
--port <port> Port to listen on (default: random)
--id <id> Node ID (default: auto-generated)
--bootstrap <addr> Bootstrap node address (host:port)
--dashboard <url> Dashboard server URL (default: http://localhost:3000)
--ice-servers <json> ICE servers configuration (JSON array)
--config <file> Load configuration from JSON file
--help Show this help message

Ver fichero

@@ -10,7 +10,10 @@
"start": "node index.js",
"start:oracle": "node oracle.js",
"start:node": "node node.js",
"start:dashboard": "cd server && npm start",
"dashboard:dev": "cd server && npm run dev",
"demo": "node demo.js",
"demo:dashboard": "node demo-dashboard.js",
"example": "node examples/basic-example.js",
"test": "node test/test-network.js"
},
@@ -18,6 +21,7 @@
"@koush/wrtc": "^0.5.3",
"chalk": "^5.3.0",
"express": "^4.18.2",
"node-fetch": "^3.3.2",
"simple-peer": "^9.11.1",
"uuid": "^9.0.1",
"ws": "^8.14.2"

159
server/README.md Archivo normal
Ver fichero

@@ -0,0 +1,159 @@
# RingNet Dashboard Server
A REST API and web dashboard for monitoring RingNet network topology, nodes, and connections in real-time.
## Features
### 🚀 REST API
- **GET /api/nodes** - List all registered nodes
- **GET /api/nodes/:nodeId** - Get specific node details
- **GET /api/nodes/:nodeId/connections** - Get node connections
- **GET /api/network/stats** - Network statistics
- **GET /api/network/topology** - Ring topology data
- **POST /api/nodes/register** - Register/update node status
- **PUT /api/nodes/:nodeId** - Update node information
### 📊 Web Dashboard
- Real-time network statistics
- Interactive ring topology visualization
- Node status monitoring
- Connection health tracking
- Auto-refresh every 5 seconds
## Quick Start
### 1. Start the Dashboard Server
```bash
# From the ringnet root directory
npm run start:dashboard
# Or with development mode (auto-restart)
npm run dashboard:dev
# Or directly from server directory
cd server
npm start
```
The dashboard will be available at: **http://localhost:3000**
### 2. Start Nodes with Dashboard Reporting
```bash
# Start Oracle node with dashboard reporting
npm run start:oracle -- --port 8080 --dashboard http://localhost:3000
# Start regular nodes
npm run start:node -- --port 8081 --bootstrap localhost:8080 --dashboard http://localhost:3000
npm run start:node -- --port 8082 --bootstrap localhost:8080 --dashboard http://localhost:3000
```
### 3. View the Dashboard
Open your browser to **http://localhost:3000** to see:
- Network statistics (total nodes, connections, oracle nodes)
- Live list of active nodes
- Interactive ring topology visualization
- Real-time status updates
## API Examples
### Get Network Statistics
```bash
curl http://localhost:3000/api/network/stats
```
### Get All Nodes
```bash
curl http://localhost:3000/api/nodes
```
### Get Ring Topology
```bash
curl http://localhost:3000/api/network/topology
```
### Register a Node (used automatically by nodes)
```bash
curl -X POST http://localhost:3000/api/nodes/register \
-H "Content-Type: application/json" \
-d '{
"nodeId": "abc123...",
"port": 8080,
"isOracle": true,
"ringPosition": 0
}'
```
## Configuration
### Environment Variables
- `PORT` - Server port (default: 3000)
### Node Configuration
Nodes automatically report to the dashboard when started with the `--dashboard` parameter:
```bash
--dashboard http://localhost:3000
```
## Dashboard Features
### Network Statistics
- **Total Nodes**: Number of active nodes in the network
- **Oracle Nodes**: Number of Oracle nodes providing enhanced services
- **Total Connections**: Sum of all peer connections
- **Last Updated**: Timestamp of the most recent update
### Node List
- Real-time status indicators (active/inactive)
- Node type identification (Oracle vs Regular)
- Ring position and port information
- Last seen timestamps
### Ring Topology Visualization
- Interactive circular visualization of the ring
- Node positioning based on ring coordinates
- Color-coded Oracle nodes (orange) vs regular nodes (blue)
- Click nodes for detailed information
- Visual representation of the double-ring structure
### Auto-Monitoring
- Nodes automatically register and update their status
- Inactive nodes are removed after 5 minutes of no updates
- Real-time updates every 5 seconds
- Graceful handling of network disconnections
## Dependencies
- **express** - Web server framework
- **cors** - Cross-origin resource sharing
- **ws** - WebSocket support (for future real-time features)
- **chalk** - Colored terminal output
## Development
### Running in Development Mode
```bash
npm run dashboard:dev
```
### API Testing
The dashboard includes a health check endpoint:
```bash
curl http://localhost:3000/health
```
### Custom Styling
Modify `public/index.html` to customize the dashboard appearance. The dashboard uses:
- CSS Grid for responsive layout
- CSS backdrop-filter for glassmorphism effects
- Vanilla JavaScript for API interactions
- SVG-based ring visualization
## Integration
Nodes automatically report their status when the `--dashboard` parameter is provided. The reporting includes:
- Node identification and type
- Ring position and topology
- Connection status and peer information
- Network statistics and health data
The dashboard is designed to be lightweight and can run alongside the ring network without affecting performance.

20
server/package.json Archivo normal
Ver fichero

@@ -0,0 +1,20 @@
{
"name": "ringnet-dashboard-server",
"version": "1.0.0",
"description": "REST API and Dashboard for RingNet Network Monitoring",
"main": "server.js",
"type": "module",
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"ws": "^8.16.0",
"chalk": "^5.3.0"
},
"keywords": ["ringnet", "dashboard", "monitoring", "api"],
"author": "",
"license": "MIT"
}

414
server/public/index.html Archivo normal
Ver fichero

@@ -0,0 +1,414 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RingNet Dashboard</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #333;
min-height: 100vh;
}
.header {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
padding: 1rem 2rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.header h1 {
color: white;
display: flex;
align-items: center;
gap: 0.5rem;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: rgba(255, 255, 255, 0.9);
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.stat-card h3 {
color: #555;
margin-bottom: 0.5rem;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.stat-value {
font-size: 2rem;
font-weight: bold;
color: #667eea;
}
.content-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
}
.panel {
background: rgba(255, 255, 255, 0.9);
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.panel h2 {
margin-bottom: 1rem;
color: #333;
border-bottom: 2px solid #667eea;
padding-bottom: 0.5rem;
}
.node-list {
max-height: 400px;
overflow-y: auto;
}
.node-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
margin-bottom: 0.5rem;
background: rgba(255, 255, 255, 0.5);
border-radius: 8px;
border-left: 4px solid #667eea;
}
.node-item.oracle {
border-left-color: #f39c12;
}
.node-info {
display: flex;
flex-direction: column;
}
.node-id {
font-weight: bold;
color: #333;
}
.node-meta {
font-size: 0.8rem;
color: #666;
}
.node-status {
display: flex;
align-items: center;
gap: 0.5rem;
}
.status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background: #27ae60;
}
.status-indicator.inactive {
background: #e74c3c;
}
.topology-container {
position: relative;
height: 400px;
background: rgba(255, 255, 255, 0.3);
border-radius: 8px;
overflow: hidden;
}
.ring-visualization {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.ring-circle {
position: relative;
width: 300px;
height: 300px;
border: 3px solid #667eea;
border-radius: 50%;
}
.node-dot {
position: absolute;
width: 20px;
height: 20px;
border-radius: 50%;
background: #667eea;
border: 3px solid white;
cursor: pointer;
transition: all 0.3s ease;
}
.node-dot.oracle {
background: #f39c12;
}
.node-dot:hover {
transform: scale(1.2);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.node-label {
position: absolute;
top: -30px;
left: 50%;
transform: translateX(-50%);
font-size: 0.7rem;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 2px 6px;
border-radius: 4px;
white-space: nowrap;
}
.refresh-btn {
background: #667eea;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
transition: background 0.3s ease;
}
.refresh-btn:hover {
background: #5a6fd8;
}
.loading {
text-align: center;
color: #666;
padding: 2rem;
}
@media (max-width: 768px) {
.content-grid {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
</head>
<body>
<div class="header">
<h1>🔗 RingNet Dashboard</h1>
</div>
<div class="container">
<div class="stats-grid">
<div class="stat-card">
<h3>Total Nodes</h3>
<div class="stat-value" id="total-nodes">-</div>
</div>
<div class="stat-card">
<h3>Oracle Nodes</h3>
<div class="stat-value" id="oracle-nodes">-</div>
</div>
<div class="stat-card">
<h3>Total Connections</h3>
<div class="stat-value" id="total-connections">-</div>
</div>
<div class="stat-card">
<h3>Last Updated</h3>
<div class="stat-value" id="last-updated" style="font-size: 1rem;">-</div>
</div>
</div>
<div class="content-grid">
<div class="panel">
<h2>📋 Active Nodes</h2>
<button class="refresh-btn" onclick="refreshData()">🔄 Refresh</button>
<div id="nodes-list" class="node-list loading">
Loading nodes...
</div>
</div>
<div class="panel">
<h2>🔄 Ring Topology</h2>
<div class="topology-container">
<div class="ring-visualization">
<div class="ring-circle" id="ring-circle">
<div id="topology-loading" class="loading">Loading topology...</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let nodesData = [];
let topologyData = {};
async function fetchData() {
try {
// Fetch network stats
const statsResponse = await fetch('/api/network/stats');
const stats = await statsResponse.json();
updateStats(stats);
// Fetch nodes
const nodesResponse = await fetch('/api/nodes');
nodesData = await nodesResponse.json();
updateNodesList(nodesData);
// Fetch topology
const topologyResponse = await fetch('/api/network/topology');
topologyData = await topologyResponse.json();
updateTopology(topologyData);
} catch (error) {
console.error('Error fetching data:', error);
}
}
function updateStats(stats) {
document.getElementById('total-nodes').textContent = stats.totalNodes;
document.getElementById('oracle-nodes').textContent = stats.oracleNodes;
document.getElementById('total-connections').textContent = stats.totalConnections;
const lastUpdated = new Date(stats.lastUpdated);
document.getElementById('last-updated').textContent = lastUpdated.toLocaleTimeString();
}
function updateNodesList(nodes) {
const nodesList = document.getElementById('nodes-list');
if (nodes.length === 0) {
nodesList.innerHTML = '<div class="loading">No nodes connected</div>';
return;
}
nodesList.innerHTML = nodes.map(node => {
const lastSeen = new Date(node.lastSeen);
const isRecent = Date.now() - lastSeen.getTime() < 2 * 60 * 1000; // 2 minutes
return `
<div class="node-item ${node.isOracle ? 'oracle' : ''}">
<div class="node-info">
<div class="node-id">${node.nodeId.substring(0, 12)}...</div>
<div class="node-meta">
${node.isOracle ? '🔮 Oracle' : '💾 Node'} |
Position: ${node.ringPosition || 'N/A'} |
Port: ${node.port || 'N/A'}
</div>
</div>
<div class="node-status">
<div class="status-indicator ${isRecent ? '' : 'inactive'}"></div>
<span>${isRecent ? 'Active' : 'Inactive'}</span>
</div>
</div>
`;
}).join('');
}
function updateTopology(topology) {
const ringCircle = document.getElementById('ring-circle');
const loadingDiv = document.getElementById('topology-loading');
if (loadingDiv) {
loadingDiv.remove();
}
// Clear existing nodes
ringCircle.querySelectorAll('.node-dot').forEach(dot => dot.remove());
if (!topology.nodes || topology.nodes.length === 0) {
ringCircle.innerHTML = '<div class="loading">No topology data</div>';
return;
}
const ringRadius = 140; // Radius for node positioning
const centerX = 150;
const centerY = 150;
topology.nodes.forEach(node => {
// Calculate position on circle
const angle = (node.position / topology.ringSize) * 2 * Math.PI - Math.PI / 2;
const x = centerX + ringRadius * Math.cos(angle);
const y = centerY + ringRadius * Math.sin(angle);
// Create node dot
const nodeDot = document.createElement('div');
nodeDot.className = `node-dot ${node.isOracle ? 'oracle' : ''}`;
nodeDot.style.left = `${x - 10}px`;
nodeDot.style.top = `${y - 10}px`;
// Add label
const label = document.createElement('div');
label.className = 'node-label';
label.textContent = node.shortId;
nodeDot.appendChild(label);
// Add click handler for node details
nodeDot.addEventListener('click', () => {
showNodeDetails(node);
});
ringCircle.appendChild(nodeDot);
});
}
function showNodeDetails(node) {
alert(`Node Details:
ID: ${node.nodeId}
Position: ${node.position}
Type: ${node.isOracle ? 'Oracle' : 'Regular'}
Status: ${node.status}
Connections: ${node.connections.length}`);
}
function refreshData() {
fetchData();
}
// Initial load
fetchData();
// Auto-refresh every 5 seconds
setInterval(fetchData, 5000);
</script>
</body>
</html>

279
server/server.js Archivo normal
Ver fichero

@@ -0,0 +1,279 @@
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;

Ver fichero

@@ -34,6 +34,8 @@ export class RingNode extends EventEmitter {
this.activeSignalingConnections = new Map(); // Store active WebSocket connections for signaling
this.nodePositions = new Map(); // Track all node positions in the ring
this.ringSize = 1000; // Virtual ring size for position calculation
this.dashboardUrl = options.dashboardUrl || 'http://localhost:3000'; // Dashboard server URL
this.dashboardReportInterval = null;
// Assign initial position for this node
this.assignPosition(this.id);
@@ -44,6 +46,7 @@ export class RingNode extends EventEmitter {
// Start connection management
this.startConnectionManager();
this.startHeartbeat();
this.startDashboardReporting();
}
getRandomPort() {
@@ -812,6 +815,12 @@ export class RingNode extends EventEmitter {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
}
if (this.dashboardReportInterval) {
clearInterval(this.dashboardReportInterval);
}
if (this.dashboardReportInterval) {
clearInterval(this.dashboardReportInterval);
}
// Notify peers of shutdown
this.webrtc.broadcast({
@@ -891,6 +900,59 @@ export class RingNode extends EventEmitter {
}, 30000);
}
// Dashboard reporting methods
startDashboardReporting() {
// Initial registration
this.reportToDashboard();
// Report every 30 seconds
this.dashboardReportInterval = setInterval(() => {
this.reportToDashboard();
}, 30000);
}
async reportToDashboard() {
try {
const nodeInfo = {
nodeId: this.id,
port: this.port,
isOracle: this.isOracle,
ringPosition: this.ringPosition,
rings: this.rings,
connectedPeers: this.webrtc.getConnectedPeers(),
knownNodes: Array.from(this.knownNodes.keys()),
oracleNodes: Array.from(this.oracleNodes),
nodePositions: Object.fromEntries(this.nodePositions),
status: 'active',
connections: this.getPersistentConnections()
};
// Use dynamic import to avoid adding fetch as a dependency
const { default: fetch } = await import('node-fetch').catch(() => ({ default: null }));
if (!fetch) {
// Fallback: just log that we would report
console.log(chalk.blue(`📊 Would report to dashboard: ${this.id.substring(0, 8)}...`));
return;
}
const response = await fetch(`${this.dashboardUrl}/api/nodes/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(nodeInfo),
timeout: 5000
});
if (response.ok) {
// Successfully reported
}
} catch (error) {
// Silently handle dashboard reporting errors - dashboard might not be running
}
}
getPersistentConnections() {
const persistentConnections = [];
for (const [nodeId, nodeInfo] of this.knownNodes.entries()) {