kukuy 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ctagsd/ctagsd.json +954 -0
- package/.ctagsd/file_list.txt +100 -0
- package/.ctagsd/tags.db +0 -0
- package/CHANGELOG.md +101 -0
- package/LICENSE +680 -0
- package/README.md +251 -0
- package/captura.png +0 -0
- package/kukuy.js +23 -0
- package/kukuy.workspace +11 -0
- package/package.json +26 -0
- package/restart-balancer.sh +10 -0
- package/routes.json +14 -0
- package/scripts/load_test.py +151 -0
- package/servers.json +19 -0
- package/servers_real.json +19 -0
- package/src/algorithms/AlgorithmManager.js +85 -0
- package/src/algorithms/IPHashAlgorithm.js +131 -0
- package/src/algorithms/LoadBalancingAlgorithm.js +23 -0
- package/src/algorithms/RoundRobinAlgorithm.js +67 -0
- package/src/config/ConfigManager.js +37 -0
- package/src/config/RouteLoader.js +36 -0
- package/src/core/Balancer.js +353 -0
- package/src/core/RoundRobinAlgorithm.js +60 -0
- package/src/core/ServerPool.js +77 -0
- package/src/dashboard/WebDashboard.js +150 -0
- package/src/dashboard/WebSocketServer.js +114 -0
- package/src/extensibility/CachingFilter.js +134 -0
- package/src/extensibility/FilterChain.js +93 -0
- package/src/extensibility/HookManager.js +48 -0
- package/src/protocol/HttpBalancer.js +37 -0
- package/src/protocol/HttpsBalancer.js +47 -0
- package/src/utils/BalancerLogger.js +102 -0
- package/src/utils/HealthChecker.js +51 -0
- package/src/utils/Logger.js +39 -0
- package/src/utils/MetricsCollector.js +82 -0
- package/src/utils/ProfessionalMetrics.js +501 -0
- package/start-iphash.sh +5 -0
- package/start-roundrobin.sh +5 -0
- package/stress-test.js +190 -0
- package/webpage/README.md +17 -0
- package/webpage/index.html +549 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Página Web de KUKUY
|
|
2
|
+
|
|
3
|
+
Interfaz web para monitorear y gestionar KUKUY.
|
|
4
|
+
|
|
5
|
+
## Características
|
|
6
|
+
|
|
7
|
+
- Panel de control en tiempo real con métricas del sistema
|
|
8
|
+
- Visualización del estado de los servidores backend
|
|
9
|
+
- Indicadores de rendimiento (RPS, tiempo de respuesta promedio)
|
|
10
|
+
- Controles para gestionar el estado del balanceador
|
|
11
|
+
- Representación gráfica del uso de los servidores
|
|
12
|
+
|
|
13
|
+
## Uso
|
|
14
|
+
|
|
15
|
+
La página web es una aplicación HTML estática que se puede abrir directamente en cualquier navegador web moderno. Simplemente abre el archivo `index.html` en tu navegador.
|
|
16
|
+
|
|
17
|
+
Para integrar con el balanceador real, se necesitaría implementar una API que proporcione datos en tiempo real al frontend.
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="es">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Panel de Control - KUKUY</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
body {
|
|
16
|
+
background: linear-gradient(135deg, #000, #000, #1a3997a3);
|
|
17
|
+
color: #f4f4f4;
|
|
18
|
+
min-height: 100vh;
|
|
19
|
+
padding: 20px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.container {
|
|
23
|
+
max-width: 1200px;
|
|
24
|
+
margin: 0 auto;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
header {
|
|
28
|
+
text-align: center;
|
|
29
|
+
padding: 30px 0;
|
|
30
|
+
margin-bottom: 30px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
h1 {
|
|
34
|
+
font-size: 2.5rem;
|
|
35
|
+
margin-bottom: 10px;
|
|
36
|
+
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.subtitle {
|
|
40
|
+
font-size: 1.2rem;
|
|
41
|
+
opacity: 0.9;
|
|
42
|
+
max-width: 600px;
|
|
43
|
+
margin: 0 auto;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.dashboard {
|
|
47
|
+
display: grid;
|
|
48
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
49
|
+
gap: 20px;
|
|
50
|
+
margin-bottom: 30px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.card {
|
|
54
|
+
background: rgba(255, 255, 255, 0.1);
|
|
55
|
+
backdrop-filter: blur(10px);
|
|
56
|
+
border-radius: 15px;
|
|
57
|
+
padding: 20px;
|
|
58
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
|
59
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.card h2 {
|
|
63
|
+
font-size: 1.3rem;
|
|
64
|
+
margin-bottom: 15px;
|
|
65
|
+
color: #4fc3f7;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.stats-grid {
|
|
69
|
+
display: grid;
|
|
70
|
+
grid-template-columns: repeat(2, 1fr);
|
|
71
|
+
gap: 15px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.stat-item {
|
|
75
|
+
background: rgba(0, 0, 0, 0.2);
|
|
76
|
+
padding: 15px;
|
|
77
|
+
border-radius: 10px;
|
|
78
|
+
text-align: center;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.stat-value {
|
|
82
|
+
font-size: 1.8rem;
|
|
83
|
+
font-weight: bold;
|
|
84
|
+
color: #4fc3f7;
|
|
85
|
+
margin: 10px 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.stat-label {
|
|
89
|
+
font-size: 0.9rem;
|
|
90
|
+
opacity: 0.8;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.servers-status {
|
|
94
|
+
margin-top: 20px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.server-item {
|
|
98
|
+
display: flex;
|
|
99
|
+
justify-content: space-between;
|
|
100
|
+
padding: 10px 0;
|
|
101
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.server-name {
|
|
105
|
+
font-weight: bold;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.server-status {
|
|
109
|
+
display: inline-block;
|
|
110
|
+
width: 12px;
|
|
111
|
+
height: 12px;
|
|
112
|
+
border-radius: 50%;
|
|
113
|
+
margin-left: 10px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.status-online {
|
|
117
|
+
background-color: #4caf50;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.status-offline {
|
|
121
|
+
background-color: #f44336;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.controls {
|
|
125
|
+
display: flex;
|
|
126
|
+
gap: 15px;
|
|
127
|
+
margin-top: 20px;
|
|
128
|
+
flex-wrap: wrap;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
button {
|
|
132
|
+
flex: 1;
|
|
133
|
+
min-width: 120px;
|
|
134
|
+
padding: 12px 20px;
|
|
135
|
+
border: none;
|
|
136
|
+
border-radius: 8px;
|
|
137
|
+
background: #2196f3;
|
|
138
|
+
color: white;
|
|
139
|
+
font-weight: bold;
|
|
140
|
+
cursor: pointer;
|
|
141
|
+
transition: all 0.3s ease;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
button:hover {
|
|
145
|
+
background: #0d8bf2;
|
|
146
|
+
transform: translateY(-2px);
|
|
147
|
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
button:active {
|
|
151
|
+
transform: translateY(0);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.metrics {
|
|
155
|
+
margin-top: 30px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.chart-container {
|
|
159
|
+
height: 200px;
|
|
160
|
+
background: rgba(0, 0, 0, 0.2);
|
|
161
|
+
border-radius: 10px;
|
|
162
|
+
margin-top: 15px;
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: flex-end;
|
|
165
|
+
padding: 10px;
|
|
166
|
+
gap: 5px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.bar {
|
|
170
|
+
flex: 1;
|
|
171
|
+
background: #4fc3f7;
|
|
172
|
+
border-radius: 5px 5px 0 0;
|
|
173
|
+
min-width: 20px;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
footer {
|
|
177
|
+
text-align: center;
|
|
178
|
+
margin-top: 40px;
|
|
179
|
+
padding: 20px;
|
|
180
|
+
opacity: 0.7;
|
|
181
|
+
font-size: 0.9rem;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.loading {
|
|
185
|
+
text-align: center;
|
|
186
|
+
padding: 20px;
|
|
187
|
+
color: #ccc;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@media (max-width: 768px) {
|
|
191
|
+
.dashboard {
|
|
192
|
+
grid-template-columns: 1fr;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
h1 {
|
|
196
|
+
font-size: 2rem;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
</style>
|
|
200
|
+
</head>
|
|
201
|
+
<body>
|
|
202
|
+
<div class="container">
|
|
203
|
+
<header>
|
|
204
|
+
<h1>KUKUY</h1>
|
|
205
|
+
<p class="subtitle">Sistema de distribución de carga entre servidores backend con soporte para hooks y filtros</p>
|
|
206
|
+
</header>
|
|
207
|
+
|
|
208
|
+
<div class="dashboard">
|
|
209
|
+
<div class="card">
|
|
210
|
+
<h2>Estado del Sistema</h2>
|
|
211
|
+
<div class="stats-grid">
|
|
212
|
+
<div class="stat-item">
|
|
213
|
+
<div class="stat-value" id="requests-count">0</div>
|
|
214
|
+
<div class="stat-label">Solicitudes</div>
|
|
215
|
+
</div>
|
|
216
|
+
<div class="stat-item">
|
|
217
|
+
<div class="stat-value" id="active-servers">0</div>
|
|
218
|
+
<div class="stat-label">Servidores Activos</div>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="stat-item">
|
|
221
|
+
<div class="stat-value" id="rps-value">0</div>
|
|
222
|
+
<div class="stat-label">RPS Acumulado</div>
|
|
223
|
+
</div>
|
|
224
|
+
<div class="stat-item">
|
|
225
|
+
<div class="stat-value" id="instantaneous-rps">0</div>
|
|
226
|
+
<div class="stat-label">RPS Instantáneo</div>
|
|
227
|
+
</div>
|
|
228
|
+
<div class="stat-item">
|
|
229
|
+
<div class="stat-value" id="avg-response">0ms</div>
|
|
230
|
+
<div class="stat-label">Respuesta Promedio</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<div class="controls">
|
|
235
|
+
<button id="refresh-btn">Actualizar</button>
|
|
236
|
+
<button id="start-btn">Iniciar</button>
|
|
237
|
+
<button id="stop-btn">Detener</button>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div class="card">
|
|
242
|
+
<h2>Configuración</h2>
|
|
243
|
+
<div class="stat-item" style="text-align: left;">
|
|
244
|
+
<div><strong>Algoritmo Balanceo:</strong> <span id="algorithm">-</span></div>
|
|
245
|
+
<div><strong>Puerto HTTP:</strong> <span id="http-port">-</span></div>
|
|
246
|
+
<div><strong>Puerto HTTPS:</strong> <span id="https-port">-</span></div>
|
|
247
|
+
<div><strong>Filtros Activos:</strong> <span id="filters-count">-</span></div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<h2 style="margin-top: 20px;">Métricas del Sistema</h2>
|
|
251
|
+
<div class="stat-item" style="text-align: left;" id="system-metrics">
|
|
252
|
+
<div><strong>Uso de CPU:</strong> <span id="cpu-usage">-</span>%</div>
|
|
253
|
+
<div><strong>Uso de Memoria:</strong> <span id="memory-usage">-</span>%</div>
|
|
254
|
+
<div><strong>Carga Promedio:</strong> <span id="load-average">-</span></div>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<h2 style="margin-top: 20px;">Hooks Registrados</h2>
|
|
258
|
+
<div class="stat-item" style="text-align: left;" id="hooks-list">
|
|
259
|
+
<!-- Los hooks se cargarán dinámicamente -->
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<div class="card servers-status">
|
|
264
|
+
<h2>Servidores Backend</h2>
|
|
265
|
+
<div id="servers-list">
|
|
266
|
+
<div class="loading">Cargando servidores...</div>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
<div class="metrics">
|
|
270
|
+
<h2>Uso Reciente</h2>
|
|
271
|
+
<div class="chart-container" id="usage-chart">
|
|
272
|
+
<!-- Barras de uso se generarán dinámicamente -->
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<footer>
|
|
279
|
+
<p>KUKUY v1.2.1 | Sistema de Distribución de Carga con Hooks y Filtros</p>
|
|
280
|
+
</footer>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
<script>
|
|
284
|
+
// URLs de la API
|
|
285
|
+
const API_BASE = window.location.origin;
|
|
286
|
+
const STATUS_API = `${API_BASE}/api/status`;
|
|
287
|
+
const METRICS_API = `${API_BASE}/api/metrics`;
|
|
288
|
+
const SERVERS_API = `${API_BASE}/api/servers`;
|
|
289
|
+
const SERVER_STATS_API = `${API_BASE}/api/server-stats`;
|
|
290
|
+
|
|
291
|
+
// Elementos del DOM
|
|
292
|
+
const elements = {
|
|
293
|
+
requestsCount: document.getElementById('requests-count'),
|
|
294
|
+
activeServers: document.getElementById('active-servers'),
|
|
295
|
+
rpsValue: document.getElementById('rps-value'),
|
|
296
|
+
instantaneousRps: document.getElementById('instantaneous-rps'),
|
|
297
|
+
avgResponse: document.getElementById('avg-response'),
|
|
298
|
+
algorithm: document.getElementById('algorithm'),
|
|
299
|
+
httpPort: document.getElementById('http-port'),
|
|
300
|
+
httpsPort: document.getElementById('https-port'),
|
|
301
|
+
filtersCount: document.getElementById('filters-count'),
|
|
302
|
+
serversList: document.getElementById('servers-list'),
|
|
303
|
+
refreshBtn: document.getElementById('refresh-btn'),
|
|
304
|
+
startBtn: document.getElementById('start-btn'),
|
|
305
|
+
stopBtn: document.getElementById('stop-btn'),
|
|
306
|
+
hooksList: document.getElementById('hooks-list'),
|
|
307
|
+
usageChart: document.getElementById('usage-chart'),
|
|
308
|
+
cpuUsage: document.getElementById('cpu-usage'),
|
|
309
|
+
memoryUsage: document.getElementById('memory-usage'),
|
|
310
|
+
loadAverage: document.getElementById('load-average')
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Variables para la configuración
|
|
314
|
+
let ws = null;
|
|
315
|
+
let reconnectInterval = null;
|
|
316
|
+
let websocketPort = 8083; // Puerto por defecto
|
|
317
|
+
|
|
318
|
+
// Cargar la configuración del servidor
|
|
319
|
+
async function loadServerConfig() {
|
|
320
|
+
try {
|
|
321
|
+
const response = await fetch('/api/config');
|
|
322
|
+
if (response.ok) {
|
|
323
|
+
const config = await response.json();
|
|
324
|
+
websocketPort = config.websocketPort || 8083;
|
|
325
|
+
}
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.error('Error cargando configuración del servidor:', error);
|
|
328
|
+
// Usar puerto por defecto si falla
|
|
329
|
+
websocketPort = 8083;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function connectWebSocket() {
|
|
334
|
+
// Cargar la configuración antes de conectar
|
|
335
|
+
await loadServerConfig();
|
|
336
|
+
|
|
337
|
+
// Determinar la URL del WebSocket según el protocolo de la página y la configuración
|
|
338
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
339
|
+
const wsUrl = `${protocol}//${window.location.hostname}:${websocketPort}`;
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
ws = new WebSocket(wsUrl);
|
|
343
|
+
|
|
344
|
+
ws.onopen = function(event) {
|
|
345
|
+
console.log(`Conectado al servidor WebSocket en puerto: ${websocketPort}`);
|
|
346
|
+
clearInterval(reconnectInterval);
|
|
347
|
+
reconnectInterval = null;
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
ws.onmessage = function(event) {
|
|
351
|
+
try {
|
|
352
|
+
const data = JSON.parse(event.data);
|
|
353
|
+
if (data.type === 'metrics_update') {
|
|
354
|
+
updateUIWithMetrics(data);
|
|
355
|
+
}
|
|
356
|
+
} catch (error) {
|
|
357
|
+
console.error('Error procesando mensaje WebSocket:', error);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
ws.onclose = function(event) {
|
|
362
|
+
console.log('Conexión WebSocket cerrada. Reconectando...');
|
|
363
|
+
scheduleReconnect();
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
ws.onerror = function(error) {
|
|
367
|
+
console.error('Error en WebSocket:', error);
|
|
368
|
+
};
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.error('Error creando WebSocket:', error);
|
|
371
|
+
scheduleReconnect();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function scheduleReconnect() {
|
|
376
|
+
if (reconnectInterval) return;
|
|
377
|
+
reconnectInterval = setInterval(() => {
|
|
378
|
+
console.log('Intentando reconectar al WebSocket...');
|
|
379
|
+
connectWebSocket();
|
|
380
|
+
}, 5000); // Reintentar cada 5 segundos
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function updateUIWithMetrics(data) {
|
|
384
|
+
const { metrics, status } = data;
|
|
385
|
+
|
|
386
|
+
// Actualizar métricas principales
|
|
387
|
+
elements.requestsCount.textContent = metrics.requests?.total || 0;
|
|
388
|
+
elements.rpsValue.textContent = metrics.calculated?.cumulativeRps ? metrics.calculated.cumulativeRps.toFixed(2) : '0.00';
|
|
389
|
+
elements.instantaneousRps.textContent = metrics.calculated?.instantaneousRps ? metrics.calculated.instantaneousRps.toFixed(2) : '0.00';
|
|
390
|
+
elements.avgResponse.textContent = metrics.performance?.avgResponseTime ? Math.round(metrics.performance.avgResponseTime) + 'ms' : '0ms';
|
|
391
|
+
|
|
392
|
+
// Actualizar el conteo de servidores activos basado en métricas
|
|
393
|
+
elements.activeServers.textContent = metrics.servers?.healthy || 0;
|
|
394
|
+
|
|
395
|
+
// Actualizar algoritmo
|
|
396
|
+
elements.algorithm.textContent = status.algorithm || '-';
|
|
397
|
+
|
|
398
|
+
// Actualizar información adicional si está disponible
|
|
399
|
+
if (metrics.calculated?.successRate !== undefined) {
|
|
400
|
+
const successRateElement = document.createElement('div');
|
|
401
|
+
successRateElement.className = 'stat-item';
|
|
402
|
+
successRateElement.innerHTML = `
|
|
403
|
+
<div class="stat-value">${metrics.calculated.successRate}%</div>
|
|
404
|
+
<div class="stat-label">Tasa de Éxito</div>
|
|
405
|
+
`;
|
|
406
|
+
|
|
407
|
+
// Insertar después del elemento de servidores activos si no existe ya
|
|
408
|
+
const statsGrid = document.querySelector('.stats-grid');
|
|
409
|
+
const existingSuccessRate = statsGrid.querySelector('.success-rate');
|
|
410
|
+
if (!existingSuccessRate) {
|
|
411
|
+
successRateElement.classList.add('success-rate');
|
|
412
|
+
statsGrid.appendChild(successRateElement);
|
|
413
|
+
} else {
|
|
414
|
+
existingSuccessRate.querySelector('.stat-value').textContent = metrics.calculated.successRate + '%';
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Actualizar métricas del sistema
|
|
419
|
+
if (metrics.system) {
|
|
420
|
+
elements.cpuUsage.textContent = metrics.system.cpuUsage ? metrics.system.cpuUsage.toFixed(2) : '-';
|
|
421
|
+
elements.memoryUsage.textContent = metrics.system.memoryUsage ? metrics.system.memoryUsage.toFixed(2) : '-';
|
|
422
|
+
|
|
423
|
+
// Mostrar la carga promedio (puede ser un array o un valor único)
|
|
424
|
+
if (Array.isArray(metrics.system.loadAverage)) {
|
|
425
|
+
elements.loadAverage.textContent = metrics.system.loadAverage.join(', ');
|
|
426
|
+
} else {
|
|
427
|
+
elements.loadAverage.textContent = metrics.system.loadAverage || '-';
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Actualizar gráfico de uso reciente
|
|
432
|
+
updateUsageChart(metrics);
|
|
433
|
+
|
|
434
|
+
// Actualizar lista de servidores
|
|
435
|
+
updateServersListFromMetrics(metrics);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Función para actualizar el gráfico de uso reciente
|
|
439
|
+
function updateUsageChart(data) {
|
|
440
|
+
elements.usageChart.innerHTML = '';
|
|
441
|
+
|
|
442
|
+
// Obtener los últimos tiempos de respuesta para mostrar en el gráfico
|
|
443
|
+
const responseTimes = data.responseTimes || [];
|
|
444
|
+
const maxToShow = 10; // Mostrar solo los últimos 10 tiempos
|
|
445
|
+
|
|
446
|
+
// Tomar los últimos tiempos de respuesta
|
|
447
|
+
const timesToShow = responseTimes.slice(-maxToShow);
|
|
448
|
+
|
|
449
|
+
if (timesToShow.length === 0) {
|
|
450
|
+
elements.usageChart.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;width:100%;">Sin datos</div>';
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Calcular el valor máximo para escalar las barras
|
|
455
|
+
const maxValue = Math.max(...timesToShow, 100); // Valor mínimo para visualización
|
|
456
|
+
|
|
457
|
+
// Crear barras para cada tiempo de respuesta
|
|
458
|
+
timesToShow.forEach(time => {
|
|
459
|
+
const barHeight = (time / maxValue) * 100; // Porcentaje basado en el valor máximo
|
|
460
|
+
const bar = document.createElement('div');
|
|
461
|
+
bar.className = 'bar';
|
|
462
|
+
bar.style.height = `${Math.max(barHeight, 5)}%`; // Altura mínima para visibilidad
|
|
463
|
+
bar.title = `${time}ms`;
|
|
464
|
+
elements.usageChart.appendChild(bar);
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Función para actualizar la lista de servidores desde las métricas
|
|
469
|
+
function updateServersListFromMetrics(metrics) {
|
|
470
|
+
// Si tenemos serverStats en las métricas, usarlos para actualizar la lista
|
|
471
|
+
if (metrics.serverStats) {
|
|
472
|
+
const servers = Object.values(metrics.serverStats).map(stat => ({
|
|
473
|
+
id: stat.id,
|
|
474
|
+
host: stat.id, // En este caso usamos el ID como host para simplificar
|
|
475
|
+
url: `Servidor ${stat.id}`,
|
|
476
|
+
status: stat.totalRequests > 0 ? 'online' : 'offline'
|
|
477
|
+
}));
|
|
478
|
+
|
|
479
|
+
elements.serversList.innerHTML = '';
|
|
480
|
+
|
|
481
|
+
if (!servers || servers.length === 0) {
|
|
482
|
+
elements.serversList.innerHTML = '<div class="loading">No hay servidores disponibles</div>';
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
servers.forEach(server => {
|
|
487
|
+
const serverItem = document.createElement('div');
|
|
488
|
+
serverItem.className = 'server-item';
|
|
489
|
+
|
|
490
|
+
serverItem.innerHTML = `
|
|
491
|
+
<div>
|
|
492
|
+
<span class="server-name">${server.id}. ${server.host}</span>
|
|
493
|
+
<small>${server.url}</small>
|
|
494
|
+
</div>
|
|
495
|
+
<div>
|
|
496
|
+
<span class="server-status status-${server.status}"></span>
|
|
497
|
+
<span>${server.status}</span>
|
|
498
|
+
</div>
|
|
499
|
+
`;
|
|
500
|
+
|
|
501
|
+
elements.serversList.appendChild(serverItem);
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Función para cargar hooks registrados
|
|
507
|
+
async function loadHooks() {
|
|
508
|
+
try {
|
|
509
|
+
// Para esta implementación, mostraremos hooks genéricos
|
|
510
|
+
// En una implementación real, esto vendría de una API específica
|
|
511
|
+
const hooks = ['onRequestReceived', 'onServerSelected', 'onResponseSent', 'onServerError'];
|
|
512
|
+
|
|
513
|
+
elements.hooksList.innerHTML = '';
|
|
514
|
+
|
|
515
|
+
hooks.forEach(hook => {
|
|
516
|
+
const hookElement = document.createElement('div');
|
|
517
|
+
hookElement.textContent = `• ${hook}`;
|
|
518
|
+
elements.hooksList.appendChild(hookElement);
|
|
519
|
+
});
|
|
520
|
+
} catch (error) {
|
|
521
|
+
console.error('Error al cargar hooks:', error);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Función para actualizar todos los datos (método de respaldo si WebSocket falla)
|
|
526
|
+
async function updateAllData() {
|
|
527
|
+
// Intentar cargar hooks
|
|
528
|
+
await loadHooks();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Conectar al WebSocket
|
|
532
|
+
connectWebSocket();
|
|
533
|
+
|
|
534
|
+
// Cargar datos iniciales
|
|
535
|
+
updateAllData();
|
|
536
|
+
|
|
537
|
+
// Event listeners para botones
|
|
538
|
+
elements.refreshBtn.addEventListener('click', updateAllData);
|
|
539
|
+
|
|
540
|
+
elements.startBtn.addEventListener('click', () => {
|
|
541
|
+
alert('Funcionalidad de inicio no implementada en el panel web. Usa el script de inicio correspondiente.');
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
elements.stopBtn.addEventListener('click', () => {
|
|
545
|
+
alert('Funcionalidad de detención no implementada en el panel web. Usa pkill -f "node index.js" o el script de reinicio.');
|
|
546
|
+
});
|
|
547
|
+
</script>
|
|
548
|
+
</body>
|
|
549
|
+
</html>
|