mirror of
https://github.com/KeymonSoft/jRDC-Multi.git
synced 2026-04-18 05:09:32 +00:00
-feat: Implementación robusta de monitoreo de pool de conexiones y peticiones activas -Este commit resuelve problemas críticos en el monitoreo del pool de conexiones (C3P0) y el conteo de peticiones activas por base de datos, mejorando significativamente la visibilidad y fiabilidad del rendimiento del servidor jRDC2-Multi. -Problemas Identificados y Resueltos: -1. **Métricas de `BusyConnections` y `TotalConnections` inconsistentes o siempre en `0` en el `Manager` y `query_logs`:** * **Problema**: Anteriormente, la métrica `busy_connections` en `query_logs` a menudo reportaba `0` o no reflejaba el estado real. De manera similar, el panel de `Manager?command=totalcon` consistentemente mostraba `BusyConnections: 0` y `TotalConnections` estancadas en `InitialPoolSize`, a pesar de que Oracle sí reportaba conexiones activas. Esto generaba confusión sobre el uso real y la expansión del pool. * **Solución**: Se modificó la lógica en los *handlers* (`DBHandlerJSON.bas` y `DBHandlerB4X.bas`) para capturar la métrica `BusyConnections` directamente del pool de C3P0 **inmediatamente después de que el *handler* adquiere una conexión** (`con = Connector.GetConnection(finalDbKey)`). Este valor se pasa explícitamente a la subrutina `Main.LogQueryPerformance` para su registro en `query_logs` y para ser consumido por `Manager.bas` a través de `RDCConnector.GetPoolStats`. Esto garantiza que el valor registrado y reportado refleje con precisión el número de conexiones activas en el instante de su adquisición. Pruebas exhaustivas confirmaron que C3P0 sí reporta conexiones ocupadas y sí expande `TotalConnections` hasta `MaxPoolSize` cuando la demanda lo exige. -2. **Contador `handler_active_requests` no decrementaba correctamente:** * **Problema**: El contador de peticiones activas por base de datos (`GlobalParameters.ActiveRequestsCountByDB`) no mostraba un decremento consistente, resultando en un conteo que solo aumentaba o mostraba valores erráticos en los logs. * **Solución**: * Se aseguró la declaración `Public ActiveRequestsCountByDB As Map` en `GlobalParameters.bas`. * Se garantizó su inicialización como un `srvr.CreateThreadSafeMap` en `Main.AppStart` para un manejo concurrente seguro de los contadores. * En `DBHandlerJSON.bas`, la `dbKey` (obtenida del parámetro `dbx` del JSON) ahora se resuelve *antes* de incrementar el contador, asegurando que el incremento y el decremento se apliquen siempre a la misma clave de base de datos correcta. * Se implementó una coerción explícita a `Int` (`.As(Int)`) para todas las operaciones de lectura y escritura (`GetDefault`, `Put`) en `GlobalParameters.ActiveRequestsCountByDB`, resolviendo problemas de tipo que causaban inconsistencias y el fallo en el decremento. * La lógica de decremento en `Private Sub CleanupAndLog` (presente en ambos *handlers*) se hizo más robusta, verificando que el contador sea mayor que cero antes de decrementar para evitar valores negativos. -Beneficios de estos Cambios: * **Monitoreo Preciso y Fiable**: Las métricas `busy_connections` y `handler_active_requests` en `query_logs` y el panel `Manager` ahora son totalmente fiables, proporcionando una visión clara y en tiempo real del uso del pool de conexiones y la carga de peticiones activas por base de datos. * **Diagnóstico Mejorado**: La visibilidad interna del estado del pool de C3P0 durante las pruebas confirma que la configuración de `RDCConnector` es correcta y que el pool se expande y contrae según lo esperado por la demanda. * **Robustez del Código**: La gestión de contadores de peticiones activas es ahora consistente, thread-safe y a prueba de fallos de tipo, mejorando la estabilidad general del servidor bajo carga.
467 lines
23 KiB
QBasic
467 lines
23 KiB
QBasic
B4J=true
|
|
Group=Default Group
|
|
ModulesStructureVersion=1
|
|
Type=Class
|
|
Version=8.8
|
|
@EndOfDesignText@
|
|
'Handler class
|
|
Sub Class_Globals
|
|
Dim j As JSONGenerator
|
|
' Dim rdcc As RDCConnector
|
|
End Sub
|
|
|
|
Public Sub Initialize
|
|
|
|
End Sub
|
|
|
|
Sub Handle(req As ServletRequest, resp As ServletResponse)
|
|
' 1. --- Bloque de Seguridad ---
|
|
If req.GetSession.GetAttribute2("user_is_authorized", False) = False Then
|
|
resp.SendRedirect("/login")
|
|
Return
|
|
End If
|
|
|
|
Dim Command As String = req.GetParameter("command")
|
|
If Command = "" Then Command = "ping"
|
|
Log($"Command: ${Command}"$)
|
|
|
|
' --- MANEJO ESPECIAL PARA SNAPSHOT ---
|
|
' El comando "snapshot" no devuelve HTML, sino una imagen. Lo manejamos por separado al principio.
|
|
If Command = "snapshot" Then
|
|
' Try
|
|
' resp.ContentType = "image/png"
|
|
' Dim robot, toolkit As JavaObject
|
|
' robot.InitializeNewInstance("java.awt.Robot", Null)
|
|
' toolkit.InitializeStatic("java.awt.Toolkit")
|
|
' Dim screenRect As JavaObject
|
|
' screenRect.InitializeNewInstance("java.awt.Rectangle", Array As Object( _
|
|
' toolkit.RunMethodJO("getDefaultToolkit", Null).RunMethod("getScreenSize", Null)))
|
|
' Dim image As JavaObject = robot.RunMethod("createScreenCapture", Array As Object(screenRect))
|
|
' Dim ImageIO As JavaObject
|
|
' ImageIO.InitializeStatic("javax.imageio.ImageIO").RunMethod("write", Array As Object(image, "png", resp.OutputStream))
|
|
' Catch
|
|
' resp.SendError(500, LastException.Message)
|
|
' End Try
|
|
' Return ' Detenemos la ejecución aquí para no enviar más HTML.
|
|
End If
|
|
' --- FIN DE MANEJO ESPECIAL ---
|
|
|
|
' Para todos los demás comandos, construimos la página HTML
|
|
resp.ContentType = "text/html"
|
|
Dim sb As StringBuilder
|
|
sb.Initialize
|
|
|
|
' --- Estilos y JavaScript (igual que antes) ---
|
|
sb.Append("<html><head><style>")
|
|
sb.Append("body {font-family: sans-serif; margin: 2em; background-color: #f9f9f9;} ")
|
|
sb.Append("h1, h2 {color: #333;} hr {margin: 2em 0; border: 0; border-top: 1px solid #ddd;} ")
|
|
sb.Append("input {display: block; width: 95%; padding: 8px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px;} ")
|
|
sb.Append("button {padding: 10px 15px; border: none; background-color: #007bff; color: white; cursor: pointer; border-radius: 4px; margin-right: 1em;} ")
|
|
sb.Append(".nav a, .logout a {text-decoration: none; margin-right: 10px; color: #007bff;} ")
|
|
sb.Append(".output {background: #fff; padding: 1em; border: 1px solid #eee; border-radius: 8px; font-family: monospace; white-space: pre-wrap; word-wrap: break-word;} ")
|
|
sb.Append("#changePassForm {background: #f0f0f0; padding: 1.5em; border-radius: 8px; max-width: 400px; margin-top: 1em;} ")
|
|
sb.Append("</style>")
|
|
sb.Append("<script>function toggleForm() {var form = document.getElementById('changePassForm'); if (form.style.display === 'none') {form.style.display = 'block';} else {form.style.display = 'none';}}</script>")
|
|
sb.Append("</head><body>")
|
|
|
|
' --- Cabecera, Botón y Formulario Oculto (igual que antes) ---
|
|
sb.Append("<h1>Panel de Administración jRDC</h1>")
|
|
sb.Append($"Bienvenido, <b>${req.GetSession.GetAttribute("username")}</b><br>"$)
|
|
' sb.Append("<p class='nav'><a href='/manager?command=test'>Test</a> | <a href='/manager?command=ping'>Ping</a> | <a href='/manager?command=reload'>Reload</a> | <a href='/manager?command=rpm2'>Reiniciar (pm2)</a> | <a href='/manager?command=reviveBow'>Revive Bow</a></p><hr>")
|
|
sb.Append("<p class='nav'><a href='/manager?command=test'>Test</a> | <a href='/manager?command=ping'>Ping</a> | <a href='/manager?command=reload'>Reload</a> | <a href='/manager?command=slowqueries'>Queries Lentos</a> | <a href='/manager?command=totalcon'>Estadísticas Pool</a> | <a href='/manager?command=rpm2'>Reiniciar (pm2)</a> | <a href='/manager?command=reviveBow'>Revive Bow</a></p><hr>")
|
|
' sb.Append("<button onclick='toggleForm()'>Cambiar Contraseña</button>")
|
|
sb.Append("<div id='changePassForm' style='display:none;'>")
|
|
sb.Append("<h2>Cambiar Contraseña</h2><form action='/changepass' method='post'>")
|
|
sb.Append("Contraseña Actual: <input type='password' name='current_password' required><br>")
|
|
sb.Append("Nueva Contraseña: <input type='password' name='new_password' required><br>")
|
|
sb.Append("Confirmar Nueva Contraseña: <input type='password' name='confirm_password' required><br>")
|
|
sb.Append("<button type='submit'>Actualizar Contraseña</button> <button onclick='toggleForm()'>Cancelar</button></form></div>")
|
|
|
|
' --- Resultado del Comando ---
|
|
sb.Append("<h2>Resultado del Comando: '" & Command & "'</h2>")
|
|
sb.Append("<div class='output'>")
|
|
|
|
' =========================================================================
|
|
' ### INICIO DE TU LÓGICA DE COMANDOS INTEGRADA ###
|
|
' =========================================================================
|
|
If Command = "reload" Then
|
|
' Usamos un StringBuilder temporal para acumular los logs de la recarga antes de añadirlos al StringBuilder principal.
|
|
Dim sbTemp As StringBuilder
|
|
sbTemp.Initialize
|
|
sbTemp.Append($"Iniciando recarga de configuración (Hot-Swap) ($DateTime{DateTime.Now})"$).Append("<br>" & CRLF)
|
|
|
|
' 1. Crear un nuevo mapa temporal para almacenar los conectores recién inicializados.
|
|
Dim newConnectors As Map
|
|
newConnectors.Initialize
|
|
|
|
' Guardamos una referencia al mapa de conectores actualmente activos.
|
|
Dim oldConnectors As Map
|
|
|
|
Dim reloadSuccessful As Boolean = True
|
|
|
|
' *** INICIO DEL BLOQUE CRÍTICO 1: Obtener oldConnectors con ReentrantLock ***
|
|
Dim lock1Acquired As Boolean = False ' Bandera para saber si el bloqueo fue adquirido.
|
|
Try
|
|
Main.MainConnectorsLock.RunMethod("lock", Null) ' Adquirimos el bloqueo.
|
|
lock1Acquired = True ' Marcamos que el bloqueo fue adquirido.
|
|
oldConnectors = Main.Connectors ' Obtenemos la referencia al mapa actual de conectores.
|
|
Catch
|
|
sbTemp.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para leer conectores antiguos: ${LastException.Message}"$).Append("<br>" & CRLF)
|
|
reloadSuccessful = False ' Si falla aquí, la recarga no puede continuar.
|
|
End Try
|
|
If lock1Acquired Then
|
|
Main.MainConnectorsLock.RunMethod("unlock", Null) ' Liberamos el bloqueo si fue adquirido.
|
|
End If
|
|
' *** FIN DEL BLOQUE CRÍTICO 1 ***
|
|
|
|
If Not(reloadSuccessful) Then ' Si el primer bloqueo falló o la asignación, salimos temprano.
|
|
sb.Append(sbTemp.ToString) ' Añadimos los logs acumulados al StringBuilder principal.
|
|
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de bloqueo inicial! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
|
|
Return ' Salir del Handle ya que ocurrió un error crítico irrecuperable.
|
|
End If
|
|
|
|
' 2. Iterar sobre las bases de datos configuradas y crear *nuevas* instancias de RDCConnector.
|
|
For Each dbKey As String In Main.listaDeCP
|
|
Try
|
|
Dim newRDC As RDCConnector
|
|
newRDC.Initialize(dbKey) ' Inicializa la nueva instancia con la configuración fresca.
|
|
newConnectors.Put(dbKey, newRDC)
|
|
|
|
Dim newPoolStats As Map = newRDC.GetPoolStats
|
|
sbTemp.Append($" -> ${dbKey}: Nuevo conector inicializado. Conexiones: ${newPoolStats.Get("TotalConnections")}"$).Append("<br>" & CRLF)
|
|
|
|
Catch
|
|
sbTemp.Append($" -> ERROR CRÍTICO al inicializar nuevo conector para ${dbKey}: ${LastException.Message}"$).Append("<br>" & CRLF)
|
|
reloadSuccessful = False
|
|
Exit ' Si uno falla, abortamos la recarga completa para evitar un estado inconsistente.
|
|
End Try
|
|
Next
|
|
|
|
sb.Append(sbTemp.ToString) ' Añadimos los logs acumulados de la inicialización al StringBuilder principal.
|
|
|
|
If reloadSuccessful Then
|
|
' 3. Si todos los nuevos conectores se inicializaron con éxito,
|
|
' realizamos el "cambio de cartel" (hot-swap) de forma atómica.
|
|
' *** INICIO DEL BLOQUE CRÍTICO 2: Reemplazar Main.Connectors con ReentrantLock ***
|
|
Dim lock2Acquired As Boolean = False ' Bandera para saber si el bloqueo fue adquirido.
|
|
Try
|
|
Main.MainConnectorsLock.RunMethod("lock", Null) ' Adquirimos el bloqueo.
|
|
lock2Acquired = True ' Marcamos que el bloqueo fue adquirido.
|
|
Main.Connectors = newConnectors ' Reemplazamos el mapa de conectores completo por el nuevo.
|
|
Catch
|
|
sb.Append($" -> ERROR CRÍTICO: No se pudo adquirir el bloqueo para reemplazar conectores: ${LastException.Message}"$).Append("<br>" & CRLF)
|
|
reloadSuccessful = False ' Si falla aquí, la recarga no se completó con éxito.
|
|
End Try
|
|
If lock2Acquired Then
|
|
Main.MainConnectorsLock.RunMethod("unlock", Null) ' Liberamos el bloqueo si fue adquirido.
|
|
End If
|
|
' *** FIN DEL BLOQUE CRÍTICO 2 ***
|
|
|
|
If reloadSuccessful Then ' Si el segundo bloqueo y swap fue exitoso
|
|
sb.Append($"¡Recarga de configuración completada con éxito (Hot-Swap)!"$).Append("<br>" & CRLF)
|
|
sb.Append($"Nuevos conectores activos. Verificando estado final..."$).Append("<br>" & CRLF)
|
|
|
|
' Mostrar el estado de los *nuevos* conectores después del swap.
|
|
If Main.Connectors.IsInitialized Then
|
|
Dim liveStats As Map
|
|
liveStats.Initialize
|
|
For Each dbKey As String In Main.Connectors.Keys
|
|
Dim currentConnector As RDCConnector = Main.Connectors.Get(dbKey).As(RDCConnector)
|
|
liveStats.Put(dbKey, currentConnector.GetPoolStats) ' Obtiene las estadísticas en tiempo real.
|
|
Next
|
|
j.Initialize(liveStats)
|
|
sb.Append($"Estado actual de los pools: ${j.ToString}"$).Append(CRLF) ' No <br> para JSON puro
|
|
End If
|
|
|
|
' 4. Cerrar explícitamente los pools de conexión de las instancias antiguas.
|
|
If oldConnectors.IsInitialized Then
|
|
sb.Append("Cerrando conectores antiguos...").Append("<br>" & CRLF)
|
|
For Each dbKey As String In oldConnectors.Keys
|
|
Dim oldRDC As RDCConnector = oldConnectors.Get(dbKey).As(RDCConnector)
|
|
If oldRDC <> Null And oldRDC.IsInitialized Then
|
|
oldRDC.Close ' Llama al método Close que hemos añadido al RDCConnector.
|
|
sb.Append($" -> Pool antiguo de ${dbKey} cerrado."$).Append("<br>" & CRLF)
|
|
End If
|
|
Next
|
|
End If
|
|
Else
|
|
sb.Append($"¡ERROR: La recarga de configuración falló en la fase de reemplazo de conectores! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
|
|
End If
|
|
Else
|
|
sb.Append($"¡ERROR: La recarga de configuración falló durante la inicialización de nuevos conectores! Los conectores antiguos siguen activos."$).Append("<br>" & CRLF)
|
|
' Si la recarga falló, los conectores antiguos (oldConnectors) se mantienen activos
|
|
' y siguen sirviendo para evitar un paro del servicio.
|
|
End If
|
|
Else If Command = "slowqueries" Then ' <<< INICIO: NUEVA Lógica para mostrar las queries lentas
|
|
sb.Append("<h2>Consultas Lentas Recientes</h2>")
|
|
Try
|
|
' Ajusta la consulta SQL para obtener las 20 queries más lentas.
|
|
' Utilizamos datetime con 'unixepoch' y 'localtime' para una visualización legible del timestamp.
|
|
Dim rs As ResultSet = Main.SQL1.ExecQuery("SELECT query_name, duration_ms, datetime(timestamp / 1000, 'unixepoch', 'localtime') as timestamp_local, db_key, client_ip, busy_connections, handler_active_requests FROM query_logs ORDER BY duration_ms DESC LIMIT 20")
|
|
|
|
sb.Append("<table border='1' style='width:100%; text-align:left; border-collapse: collapse;'>")
|
|
sb.Append("<thead><tr><th>Query</th><th>Duración (ms)</th><th>Fecha/Hora Local</th><th>DB Key</th><th>Cliente IP</th><th>Conex. Ocupadas</th><th>Peticiones Activas</th></tr></thead>")
|
|
sb.Append("<tbody>")
|
|
|
|
Do While rs.NextRow
|
|
sb.Append("<tr>")
|
|
sb.Append($"<td>${rs.GetString("query_name")}</td>"$)
|
|
sb.Append($"<td>${rs.GetLong("duration_ms")}</td>"$)
|
|
sb.Append($"<td>${rs.GetString("timestamp_local")}</td>"$)
|
|
sb.Append($"<td>${rs.GetString("db_key")}</td>"$)
|
|
sb.Append($"<td>${rs.GetString("client_ip")}</td>"$)
|
|
sb.Append($"<td>${rs.GetInt("busy_connections")}</td>"$)
|
|
sb.Append($"<td>${rs.GetInt("handler_active_requests")}</td>"$)
|
|
sb.Append("</tr>")
|
|
Loop
|
|
sb.Append("</tbody>")
|
|
sb.Append("</table>")
|
|
rs.Close
|
|
Catch
|
|
Log("Error al obtener queries lentas en Manager: " & LastException.Message)
|
|
sb.Append($"<p style='color:red;'>Error al cargar queries lentas: ${LastException.Message}</p>"$)
|
|
End Try
|
|
Else If Command = "test" Then
|
|
Try
|
|
Dim con As SQL = Main.Connectors.Get("DB1").As(RDCConnector).GetConnection("")
|
|
sb.Append("Connection successful.</br></br>")
|
|
Private estaDB As String = ""
|
|
Log(Main.listaDeCP)
|
|
For i = 0 To Main.listaDeCP.Size - 1
|
|
If Main.listaDeCP.get(i) <> "" Then estaDB = "." & Main.listaDeCP.get(i)
|
|
sb.Append($"Using config${estaDB}.properties<br/>"$)
|
|
Next
|
|
con.Close
|
|
Catch
|
|
resp.Write("Error fetching connection.")
|
|
End Try
|
|
Else If Command = "stop" Then
|
|
' Public shl As Shell...
|
|
Else If Command = "rsx" Then
|
|
Log($"Ejecutamos ${File.DirApp}\start.bat"$)
|
|
sb.Append($"Ejecutamos ${File.DirApp}\start.bat"$)
|
|
Public shl As Shell
|
|
shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\start.bat " & Main.srvr.Port))
|
|
shl.WorkingDirectory = File.DirApp
|
|
shl.Run(-1)
|
|
Else If Command = "rpm2" Then
|
|
Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
|
|
sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
|
|
Public shl As Shell
|
|
shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\reiniciaProcesoPM2.bat " & Main.srvr.Port))
|
|
shl.WorkingDirectory = File.DirApp
|
|
shl.Run(-1)
|
|
Else If Command = "reviveBow" Then
|
|
Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat"$)
|
|
sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat<br><br>"$)
|
|
sb.Append($"!!!BOW REINICIANDO!!!"$)
|
|
Public shl As Shell
|
|
shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\reiniciaProcesoBow.bat " & Main.srvr.Port))
|
|
shl.WorkingDirectory = File.DirApp
|
|
shl.Run(-1)
|
|
Else If Command = "paused" Then
|
|
GlobalParameters.IsPaused = 1
|
|
sb.Append("Servidor pausado.")
|
|
Else If Command = "continue" Then
|
|
GlobalParameters.IsPaused = 0
|
|
sb.Append("Servidor reanudado.")
|
|
Else If Command = "logs" Then
|
|
If GlobalParameters.mpLogs.IsInitialized Then
|
|
j.Initialize(GlobalParameters.mpLogs)
|
|
sb.Append(j.ToString)
|
|
End If
|
|
Else If Command = "block" Then
|
|
Dim BlockedConIP As String = req.GetParameter("IP")
|
|
If GlobalParameters.mpBlockConnection.IsInitialized Then
|
|
GlobalParameters.mpBlockConnection.Put(BlockedConIP, BlockedConIP)
|
|
sb.Append("IP bloqueada: " & BlockedConIP)
|
|
End If
|
|
Else If Command = "unblock" Then
|
|
Dim UnBlockedConIP As String = req.GetParameter("IP")
|
|
If GlobalParameters.mpBlockConnection.IsInitialized Then
|
|
GlobalParameters.mpBlockConnection.Remove(UnBlockedConIP)
|
|
sb.Append("IP desbloqueada: " & UnBlockedConIP)
|
|
End If
|
|
Else If Command = "restartserver" Then
|
|
Log($"Ejecutamos ${File.DirApp}/restarServer.bat"$)
|
|
sb.Append("Reiniciando servidor...")
|
|
Else If Command = "runatstartup" Then
|
|
File.Copy("C:\jrdcinterface", "startup.bat", "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp", "startup.bat")
|
|
sb.Append("Script de inicio añadido.")
|
|
Else If Command = "stoprunatstartup" Then
|
|
File.Delete("C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp", "startup.bat")
|
|
sb.Append("Script de inicio eliminado.")
|
|
Else If Command = "totalrequests" Then
|
|
If GlobalParameters.mpTotalRequests.IsInitialized Then
|
|
j.Initialize(GlobalParameters.mpTotalRequests)
|
|
sb.Append(j.ToString)
|
|
End If
|
|
Else If Command = "totalblocked" Then
|
|
If GlobalParameters.mpBlockConnection.IsInitialized Then
|
|
' j.Initialize(Global.mpBlockConnection)
|
|
sb.Append(j.ToString)
|
|
End If
|
|
Else If Command = "ping" Then
|
|
sb.Append($"Pong ($DateTime{DateTime.Now})"$)
|
|
Else If Command = "totalcon" Then ' <<< Modificado: Ahora usa GetPoolStats para cada pool
|
|
' Verificamos que el mapa global de conexiones esté inicializado.
|
|
' Aunque no lo poblamos directamente, es un buen chequeo de estado.
|
|
If GlobalParameters.mpTotalConnections.IsInitialized Then
|
|
sb.Append("<h2>Estadísticas del Pool de Conexiones por DB:</h2>")
|
|
|
|
' Creamos un mapa LOCAL para almacenar las estadísticas de TODOS los pools de conexiones.
|
|
Dim allPoolStats As Map
|
|
allPoolStats.Initialize
|
|
|
|
' Iteramos sobre cada clave de base de datos que tenemos configurada (DB1, DB2, etc.).
|
|
For Each dbKey As String In Main.listaDeCP
|
|
' Obtenemos el conector RDC para la base de datos actual.
|
|
Dim connector As RDCConnector = Main.Connectors.Get(dbKey).As(RDCConnector)
|
|
|
|
' Si el conector no está inicializado (lo cual no debería ocurrir si Main.AppStart funcionó),
|
|
' registramos un error y pasamos al siguiente.
|
|
If connector.IsInitialized = False Then
|
|
Log($"Manager: ADVERTENCIA: El conector para ${dbKey} no está inicializado."$)
|
|
Dim errorMap As Map = CreateMap("Error": "Conector no inicializado o no cargado correctamente")
|
|
allPoolStats.Put(dbKey, errorMap)
|
|
Continue ' Salta a la siguiente iteración del bucle.
|
|
End If
|
|
|
|
' Llamamos al método GetPoolStats del conector para obtener las métricas de su pool.
|
|
Dim poolStats As Map = connector.GetPoolStats
|
|
|
|
' Añadimos las estadísticas de este pool (poolStats) al mapa general (allPoolStats),
|
|
' usando la clave de la base de datos (dbKey) como su identificador.
|
|
allPoolStats.Put(dbKey, poolStats)
|
|
Next
|
|
|
|
' Inicializamos el generador JSON con el mapa 'allPoolStats' (que ahora sí debería contener datos).
|
|
' (La variable 'j' ya está declarada en Class_Globals de Manager.bas, no la declares de nuevo aquí).
|
|
j.Initialize(allPoolStats)
|
|
|
|
' Añadimos la representación JSON de las estadísticas al StringBuilder para la respuesta HTML.
|
|
sb.Append(j.ToString)
|
|
Else
|
|
sb.Append("El mapa de conexiones GlobalParameters.mpTotalConnections no está inicializado.")
|
|
End If
|
|
End If
|
|
' =========================================================================
|
|
' ### FIN DE TU LÓGICA DE COMANDOS ###
|
|
' =========================================================================
|
|
|
|
' --- Cerramos la página y la enviamos ---
|
|
sb.Append("</div><p class='logout'><a href='/logout'>Cerrar Sesión</a> | <a href=# onclick='toggleForm()'>Cambiar Contraseña</a></p></body></html>")
|
|
resp.Write(sb.ToString)
|
|
|
|
If GlobalParameters.mpLogs.IsInitialized Then GlobalParameters.mpLogs.Put(Command, "Manager : " & Command & " - Time : " & DateTime.Time(DateTime.Now))
|
|
End Sub
|
|
|
|
Sub Handle0(req As ServletRequest, resp As ServletResponse)
|
|
' 1. --- Bloque de Seguridad (se mantiene igual) ---
|
|
If req.GetSession.GetAttribute2("user_is_authorized", False) = False Then
|
|
resp.SendRedirect("/login")
|
|
Return
|
|
End If
|
|
|
|
Dim Command As String = req.GetParameter("command")
|
|
If Command = "" Then Command = "ping"
|
|
Log($"Command: ${Command}"$)
|
|
resp.ContentType = "text/html"
|
|
|
|
' 2. --- Construimos la ESTRUCTURA de la página ---
|
|
Dim sb As StringBuilder
|
|
sb.Initialize
|
|
|
|
' Estilos para la página
|
|
sb.Append("<html><head><style>")
|
|
sb.Append("body {font-family: sans-serif; margin: 2em; background-color: #f9f9f9;} ")
|
|
sb.Append("h1, h2 {color: #333;} hr {margin: 2em 0; border: 0; border-top: 1px solid #ddd;} ")
|
|
sb.Append("form {background: #f0f0f0; padding: 1.5em; border-radius: 8px; max-width: 400px; margin-bottom: 2em;} ")
|
|
sb.Append("input {display: block; width: 95%; padding: 8px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px;} ")
|
|
sb.Append("button {padding: 10px 15px; border: none; background-color: #007bff; color: white; cursor: pointer; border-radius: 4px;} ")
|
|
sb.Append(".nav a, .logout a {text-decoration: none; margin-right: 10px; color: #007bff;} ")
|
|
sb.Append(".output {background: #fff; padding: 1em; border: 1px solid #eee; border-radius: 8px;} ")
|
|
sb.Append("</style></head><body>")
|
|
|
|
' Cabecera y bienvenida
|
|
sb.Append("<h1>Panel de Administración jRDC</h1>")
|
|
sb.Append($"Bienvenido, <b>${req.GetSession.GetAttribute("username")}</b><br>"$)
|
|
|
|
' Menú de navegación (se define una sola vez)
|
|
sb.Append("<p class='nav'>")
|
|
sb.Append($"<a href="/test">Test</a> | <a href="/manager?command=ping">Ping</a> | <a href="/manager?command=reload">Reload</a> | <a href="/manager?command=rpm2">Reiniciar (pm2)</a> | <a href="/manager?command=reviveBow">Revive Bow</a>"$)
|
|
sb.Append("</p>")
|
|
|
|
' Formulario para cambiar contraseña
|
|
sb.Append("<hr>")
|
|
sb.Append("<h2>Cambiar Contraseña</h2>")
|
|
sb.Append("<form action='/changepass' method='post'>")
|
|
sb.Append("Contraseña Actual: <input type='password' name='current_password' required><br>")
|
|
sb.Append("Nueva Contraseña: <input type='password' name='new_password' required><br>")
|
|
sb.Append("Confirmar Nueva Contraseña: <input type='password' name='confirm_password' required><br>")
|
|
sb.Append("<button type='submit'>Actualizar Contraseña</button>")
|
|
sb.Append("</form>")
|
|
|
|
' Sección para el resultado del comando
|
|
sb.Append("<hr><h2>Resultado del Comando: '" & Command & "'</h2>")
|
|
sb.Append("<div class='output'>")
|
|
|
|
' 3. --- Lógica de TUS COMANDOS (modificada para usar sb.Append) ---
|
|
If Command = "reload" Then
|
|
Private estaDB As String = ""
|
|
For i = 0 To Main.listaDeCP.Size - 1
|
|
Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).Initialize(Main.listaDeCP.get(i))
|
|
If Main.listaDeCP.get(i) <> "DB1" Then estaDB = "." & Main.listaDeCP.get(i) Else estaDB = ""
|
|
sb.Append($"Recargando config${estaDB}.properties ($DateTime{DateTime.Now})<br/>"$)
|
|
sb.Append($"Queries en config.properties: <b>${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).commands.Size}</b><br/>"$)
|
|
sb.Append($"<b>JdbcUrl:</b> ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).config.Get("JdbcUrl")}</b><br/>"$)
|
|
sb.Append($"<b>User:</b> ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).config.Get("User")}</b><br/>"$)
|
|
sb.Append($"<b>ServerPort:</b> ${Main.srvr.Port}</b><br/><br/>"$)
|
|
Next
|
|
else If Command = "stop" Then
|
|
' Tu código para "stop"
|
|
else If Command = "rsx" Then
|
|
Log($"Ejecutamos ${File.DirApp}\start.bat"$)
|
|
sb.Append($"Ejecutamos ${File.DirApp}\start.bat"$)
|
|
Public shl As Shell
|
|
shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\start.bat " & Main.srvr.Port))
|
|
shl.WorkingDirectory = File.DirApp
|
|
shl.Run(-1)
|
|
else If Command = "rpm2" Then
|
|
Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
|
|
sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoPM2.bat"$)
|
|
Public shl As Shell
|
|
shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\reiniciaProcesoPM2.bat " & Main.srvr.Port))
|
|
shl.WorkingDirectory = File.DirApp
|
|
shl.Run(-1)
|
|
else If Command = "reviveBow" Then
|
|
Log($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat"$)
|
|
sb.Append($"Ejecutamos ${File.DirApp}\reiniciaProcesoBow.bat<br><br>"$)
|
|
sb.Append($"!!!BOW REINICIANDO!!!"$)
|
|
Public shl As Shell
|
|
shl.Initialize("shl","cmd",Array("/c",File.DirApp & "\reiniciaProcesoBow.bat " & Main.srvr.Port))
|
|
shl.WorkingDirectory = File.DirApp
|
|
shl.Run(-1)
|
|
else if Command = "totalrequests" Then
|
|
If GlobalParameters.mpTotalRequests.IsInitialized Then
|
|
j.Initialize(GlobalParameters.mpTotalRequests)
|
|
sb.Append(j.ToString)
|
|
End If
|
|
else if Command = "ping" Then
|
|
sb.Append($"Pong ($DateTime{DateTime.Now})"$)
|
|
End If
|
|
'...(aquí continuaría el resto de tus Else If)...
|
|
|
|
|
|
' 4. --- Cerramos la página y la enviamos TODA JUNTA ---
|
|
sb.Append("</div>") ' Cierre de div.output
|
|
sb.Append("<p class='logout'><a href='/logout'>Cerrar Sesión</a></p>")
|
|
sb.Append("</body></html>")
|
|
|
|
resp.Write(sb.ToString) ' Se envía toda la página de una vez
|
|
|
|
' Lógica final de logs (se mantiene igual)
|
|
If GlobalParameters.mpLogs.IsInitialized Then GlobalParameters.mpLogs.Put(Command, "Manager : " & Command & " - Time : " & DateTime.Time(DateTime.Now))
|
|
End Sub
|
|
|