47
README.md
47
README.md
@@ -50,23 +50,29 @@ npm install
|
|||||||
|
|
||||||
### Running the Network
|
### Running the Network
|
||||||
|
|
||||||
1. **Start the first node (Oracle) as bootstrap:**
|
1. **Start the Dashboard (Optional):**
|
||||||
```bash
|
```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
|
```bash
|
||||||
# Regular node
|
# 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
|
# 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
|
```bash
|
||||||
npm run start:node -- --port 8083 --bootstrap localhost:8080
|
npm run start:node -- --port 8083 --bootstrap localhost:8080 --dashboard http://localhost:3000
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📖 Usage
|
## 📖 Usage
|
||||||
@@ -77,6 +83,7 @@ npm run start:node -- --port 8083 --bootstrap localhost:8080
|
|||||||
--port <port> # Port to listen on (default: random)
|
--port <port> # Port to listen on (default: random)
|
||||||
--id <id> # Node ID (default: auto-generated UUID)
|
--id <id> # Node ID (default: auto-generated UUID)
|
||||||
--bootstrap <addr> # Bootstrap node address (host:port)
|
--bootstrap <addr> # Bootstrap node address (host:port)
|
||||||
|
--dashboard <url> # Dashboard server URL (default: http://localhost:3000)
|
||||||
--ice-servers <json> # ICE servers configuration (JSON array)
|
--ice-servers <json> # ICE servers configuration (JSON array)
|
||||||
--config <file> # Load configuration from JSON file
|
--config <file> # Load configuration from JSON file
|
||||||
--help # Show help message
|
--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.
|
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
|
## 🧪 Testing
|
||||||
|
|
||||||
Run the test suite to verify network functionality:
|
Run the test suite to verify network functionality:
|
||||||
|
|||||||
62
demo-dashboard.js
Archivo normal
62
demo-dashboard.js
Archivo normal
@@ -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!
|
||||||
|
`));
|
||||||
4
node.js
4
node.js
@@ -19,6 +19,9 @@ for (let i = 0; i < args.length; i++) {
|
|||||||
case '--bootstrap':
|
case '--bootstrap':
|
||||||
options.bootstrap = args[++i];
|
options.bootstrap = args[++i];
|
||||||
break;
|
break;
|
||||||
|
case '--dashboard':
|
||||||
|
options.dashboardUrl = args[++i];
|
||||||
|
break;
|
||||||
case '--ice-servers':
|
case '--ice-servers':
|
||||||
try {
|
try {
|
||||||
options.iceServers = JSON.parse(args[++i]);
|
options.iceServers = JSON.parse(args[++i]);
|
||||||
@@ -49,6 +52,7 @@ Options:
|
|||||||
--port <port> Port to listen on (default: random)
|
--port <port> Port to listen on (default: random)
|
||||||
--id <id> Node ID (default: auto-generated)
|
--id <id> Node ID (default: auto-generated)
|
||||||
--bootstrap <addr> Bootstrap node address (host:port)
|
--bootstrap <addr> Bootstrap node address (host:port)
|
||||||
|
--dashboard <url> Dashboard server URL (default: http://localhost:3000)
|
||||||
--ice-servers <json> ICE servers configuration (JSON array)
|
--ice-servers <json> ICE servers configuration (JSON array)
|
||||||
--config <file> Load configuration from JSON file
|
--config <file> Load configuration from JSON file
|
||||||
--help Show this help message
|
--help Show this help message
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ for (let i = 0; i < args.length; i++) {
|
|||||||
case '--bootstrap':
|
case '--bootstrap':
|
||||||
options.bootstrap = args[++i];
|
options.bootstrap = args[++i];
|
||||||
break;
|
break;
|
||||||
|
case '--dashboard':
|
||||||
|
options.dashboardUrl = args[++i];
|
||||||
|
break;
|
||||||
case '--ice-servers':
|
case '--ice-servers':
|
||||||
try {
|
try {
|
||||||
options.iceServers = JSON.parse(args[++i]);
|
options.iceServers = JSON.parse(args[++i]);
|
||||||
@@ -49,6 +52,7 @@ Options:
|
|||||||
--port <port> Port to listen on (default: random)
|
--port <port> Port to listen on (default: random)
|
||||||
--id <id> Node ID (default: auto-generated)
|
--id <id> Node ID (default: auto-generated)
|
||||||
--bootstrap <addr> Bootstrap node address (host:port)
|
--bootstrap <addr> Bootstrap node address (host:port)
|
||||||
|
--dashboard <url> Dashboard server URL (default: http://localhost:3000)
|
||||||
--ice-servers <json> ICE servers configuration (JSON array)
|
--ice-servers <json> ICE servers configuration (JSON array)
|
||||||
--config <file> Load configuration from JSON file
|
--config <file> Load configuration from JSON file
|
||||||
--help Show this help message
|
--help Show this help message
|
||||||
|
|||||||
@@ -10,7 +10,10 @@
|
|||||||
"start": "node index.js",
|
"start": "node index.js",
|
||||||
"start:oracle": "node oracle.js",
|
"start:oracle": "node oracle.js",
|
||||||
"start:node": "node node.js",
|
"start:node": "node node.js",
|
||||||
|
"start:dashboard": "cd server && npm start",
|
||||||
|
"dashboard:dev": "cd server && npm run dev",
|
||||||
"demo": "node demo.js",
|
"demo": "node demo.js",
|
||||||
|
"demo:dashboard": "node demo-dashboard.js",
|
||||||
"example": "node examples/basic-example.js",
|
"example": "node examples/basic-example.js",
|
||||||
"test": "node test/test-network.js"
|
"test": "node test/test-network.js"
|
||||||
},
|
},
|
||||||
@@ -18,6 +21,7 @@
|
|||||||
"@koush/wrtc": "^0.5.3",
|
"@koush/wrtc": "^0.5.3",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"node-fetch": "^3.3.2",
|
||||||
"simple-peer": "^9.11.1",
|
"simple-peer": "^9.11.1",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"ws": "^8.14.2"
|
"ws": "^8.14.2"
|
||||||
|
|||||||
159
server/README.md
Archivo normal
159
server/README.md
Archivo normal
@@ -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
20
server/package.json
Archivo normal
@@ -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
414
server/public/index.html
Archivo normal
@@ -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
279
server/server.js
Archivo normal
@@ -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;
|
||||||
@@ -34,6 +34,8 @@ export class RingNode extends EventEmitter {
|
|||||||
this.activeSignalingConnections = new Map(); // Store active WebSocket connections for signaling
|
this.activeSignalingConnections = new Map(); // Store active WebSocket connections for signaling
|
||||||
this.nodePositions = new Map(); // Track all node positions in the ring
|
this.nodePositions = new Map(); // Track all node positions in the ring
|
||||||
this.ringSize = 1000; // Virtual ring size for position calculation
|
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
|
// Assign initial position for this node
|
||||||
this.assignPosition(this.id);
|
this.assignPosition(this.id);
|
||||||
@@ -44,6 +46,7 @@ export class RingNode extends EventEmitter {
|
|||||||
// Start connection management
|
// Start connection management
|
||||||
this.startConnectionManager();
|
this.startConnectionManager();
|
||||||
this.startHeartbeat();
|
this.startHeartbeat();
|
||||||
|
this.startDashboardReporting();
|
||||||
}
|
}
|
||||||
|
|
||||||
getRandomPort() {
|
getRandomPort() {
|
||||||
@@ -812,6 +815,12 @@ export class RingNode extends EventEmitter {
|
|||||||
if (this.heartbeatInterval) {
|
if (this.heartbeatInterval) {
|
||||||
clearInterval(this.heartbeatInterval);
|
clearInterval(this.heartbeatInterval);
|
||||||
}
|
}
|
||||||
|
if (this.dashboardReportInterval) {
|
||||||
|
clearInterval(this.dashboardReportInterval);
|
||||||
|
}
|
||||||
|
if (this.dashboardReportInterval) {
|
||||||
|
clearInterval(this.dashboardReportInterval);
|
||||||
|
}
|
||||||
|
|
||||||
// Notify peers of shutdown
|
// Notify peers of shutdown
|
||||||
this.webrtc.broadcast({
|
this.webrtc.broadcast({
|
||||||
@@ -891,6 +900,59 @@ export class RingNode extends EventEmitter {
|
|||||||
}, 30000);
|
}, 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() {
|
getPersistentConnections() {
|
||||||
const persistentConnections = [];
|
const persistentConnections = [];
|
||||||
for (const [nodeId, nodeInfo] of this.knownNodes.entries()) {
|
for (const [nodeId, nodeInfo] of this.knownNodes.entries()) {
|
||||||
|
|||||||
Referencia en una nueva incidencia
Block a user