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("") sb.Append("") sb.Append("") ' --- Cabecera, Botón y Formulario Oculto (igual que antes) --- sb.Append("

Panel de Administración jRDC

") sb.Append($"Bienvenido, ${req.GetSession.GetAttribute("username")}
"$) ' sb.Append("
") sb.Append("
") ' sb.Append("") sb.Append("") ' --- Resultado del Comando --- sb.Append("

Resultado del Comando: '" & Command & "'

") sb.Append("
") ' ========================================================================= ' ### 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("
" & 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("
" & 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("
" & 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("
" & CRLF) Catch sbTemp.Append($" -> ERROR CRÍTICO al inicializar nuevo conector para ${dbKey}: ${LastException.Message}"$).Append("
" & 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("
" & 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("
" & CRLF) sb.Append($"Nuevos conectores activos. Verificando estado final..."$).Append("
" & 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
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("
" & 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("
" & 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("
" & 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("
" & 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("

Consultas Lentas Recientes

") 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("") sb.Append("") sb.Append("") Do While rs.NextRow sb.Append("") sb.Append($""$) sb.Append($""$) sb.Append($""$) sb.Append($""$) sb.Append($""$) sb.Append($""$) sb.Append($""$) sb.Append("") Loop sb.Append("") sb.Append("
QueryDuración (ms)Fecha/Hora LocalDB KeyCliente IPConex. OcupadasPeticiones Activas
${rs.GetString("query_name")}${rs.GetLong("duration_ms")}${rs.GetString("timestamp_local")}${rs.GetString("db_key")}${rs.GetString("client_ip")}${rs.GetInt("busy_connections")}${rs.GetInt("handler_active_requests")}
") rs.Close Catch Log("Error al obtener queries lentas en Manager: " & LastException.Message) sb.Append($"

Error al cargar queries lentas: ${LastException.Message}

"$) End Try Else If Command = "test" Then Try Dim con As SQL = Main.Connectors.Get("DB1").As(RDCConnector).GetConnection("") sb.Append("Connection successful.

") 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
"$) 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

"$) 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("

Estadísticas del Pool de Conexiones por DB:

") ' 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("

Cerrar Sesión | Cambiar Contraseña

") 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("") ' Cabecera y bienvenida sb.Append("

Panel de Administración jRDC

") sb.Append($"Bienvenido, ${req.GetSession.GetAttribute("username")}
"$) ' Menú de navegación (se define una sola vez) sb.Append("") ' Formulario para cambiar contraseña sb.Append("
") sb.Append("

Cambiar Contraseña

") sb.Append("
") sb.Append("Contraseña Actual:
") sb.Append("Nueva Contraseña:
") sb.Append("Confirmar Nueva Contraseña:
") sb.Append("") sb.Append("
") ' Sección para el resultado del comando sb.Append("

Resultado del Comando: '" & Command & "'

") sb.Append("
") ' 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})
"$) sb.Append($"Queries en config.properties: ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).commands.Size}
"$) sb.Append($"JdbcUrl: ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).config.Get("JdbcUrl")}
"$) sb.Append($"User: ${Main.Connectors.Get(Main.listaDeCP.get(i)).As(RDCConnector).config.Get("User")}
"$) sb.Append($"ServerPort: ${Main.srvr.Port}

"$) 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

"$) 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("
") ' Cierre de div.output sb.Append("

Cerrar Sesión

") sb.Append("") 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