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.
Files changed (41) hide show
  1. package/.ctagsd/ctagsd.json +954 -0
  2. package/.ctagsd/file_list.txt +100 -0
  3. package/.ctagsd/tags.db +0 -0
  4. package/CHANGELOG.md +101 -0
  5. package/LICENSE +680 -0
  6. package/README.md +251 -0
  7. package/captura.png +0 -0
  8. package/kukuy.js +23 -0
  9. package/kukuy.workspace +11 -0
  10. package/package.json +26 -0
  11. package/restart-balancer.sh +10 -0
  12. package/routes.json +14 -0
  13. package/scripts/load_test.py +151 -0
  14. package/servers.json +19 -0
  15. package/servers_real.json +19 -0
  16. package/src/algorithms/AlgorithmManager.js +85 -0
  17. package/src/algorithms/IPHashAlgorithm.js +131 -0
  18. package/src/algorithms/LoadBalancingAlgorithm.js +23 -0
  19. package/src/algorithms/RoundRobinAlgorithm.js +67 -0
  20. package/src/config/ConfigManager.js +37 -0
  21. package/src/config/RouteLoader.js +36 -0
  22. package/src/core/Balancer.js +353 -0
  23. package/src/core/RoundRobinAlgorithm.js +60 -0
  24. package/src/core/ServerPool.js +77 -0
  25. package/src/dashboard/WebDashboard.js +150 -0
  26. package/src/dashboard/WebSocketServer.js +114 -0
  27. package/src/extensibility/CachingFilter.js +134 -0
  28. package/src/extensibility/FilterChain.js +93 -0
  29. package/src/extensibility/HookManager.js +48 -0
  30. package/src/protocol/HttpBalancer.js +37 -0
  31. package/src/protocol/HttpsBalancer.js +47 -0
  32. package/src/utils/BalancerLogger.js +102 -0
  33. package/src/utils/HealthChecker.js +51 -0
  34. package/src/utils/Logger.js +39 -0
  35. package/src/utils/MetricsCollector.js +82 -0
  36. package/src/utils/ProfessionalMetrics.js +501 -0
  37. package/start-iphash.sh +5 -0
  38. package/start-roundrobin.sh +5 -0
  39. package/stress-test.js +190 -0
  40. package/webpage/README.md +17 -0
  41. 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>