From 2779c0a15daac2a37064c3036ba0af84f3429b35 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Thu, 21 May 2026 11:56:12 -0500 Subject: [PATCH] fix: ping real de BD + campo Host en ConxDb + reactividad Alpine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problema raíz: 1. El ping TCP usaba Servidor.IpServidor → fallaba si la BD solo escucha en localhost del servidor remoto (comportamiento normal en producción) 2. La reactividad Alpine v3.13 no detectaba keys nuevas en objeto vacío {} dentro de x-for loops, spinner nunca aparecía Cambios: - pkg/models/conx_db.go: nuevo campo Host (vacío = usa Servidor.IpServidor) + método HostEfectivo() como fuente única de verdad para host - pkg/services/query_runner_service.go: todos los puntos (openDynamicDB, openDynamicDBWithName, redisConnect, mongoURI) usan c.HostEfectivo() - rest/controllers/servidor_controller.go: PingConexion reescrito — primero prueba conexión real a la BD (no solo TCP), si falla prueba TCP, devuelve host+puerto testeado y mensajes de error descriptivos - servidor_dashboard.html: hacerPing usa spread-replace para forzar reactividad + $nextTick, resultado muestra host:puerto testeado y mensaje de error completo en múltiples líneas Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pkg/models/conx_db.go | 10 ++++ pkg/services/query_runner_service.go | 24 +++++----- resources/views/servidor_dashboard.html | 26 ++++++---- rest/controllers/servidor_controller.go | 64 +++++++++++++------------ 4 files changed, 73 insertions(+), 51 deletions(-) diff --git a/pkg/models/conx_db.go b/pkg/models/conx_db.go index 9acd1cc..bf01e27 100755 --- a/pkg/models/conx_db.go +++ b/pkg/models/conx_db.go @@ -10,6 +10,7 @@ import ( type ConxDb struct { gorm.Model Nombre string `json:"nombre" gorm:"column:nombre"` + Host string `json:"host" gorm:"column:host"` // vacío = usa Servidor.IpServidor Puerto string `json:"puerto" gorm:"column:puerto"` Usuario string `json:"usuario" gorm:"column:usuario"` Password string `json:"password" gorm:"column:password"` @@ -21,6 +22,15 @@ type ConxDb struct { Servidor Servidor `json:"servidor" gorm:"foreignKey:ServidorID"` } +// HostEfectivo devuelve el host de conexión: el campo Host si está definido, +// o el IP del servidor asociado como fallback. +func (c ConxDb) HostEfectivo() string { + if c.Host != "" { + return c.Host + } + return c.Servidor.IpServidor +} + func (ConxDb) TableName() string { return "conx_db" } diff --git a/pkg/services/query_runner_service.go b/pkg/services/query_runner_service.go index 126ce4a..e29b0d6 100644 --- a/pkg/services/query_runner_service.go +++ b/pkg/services/query_runner_service.go @@ -39,7 +39,7 @@ type QueryResult struct { // openDynamicDB abre una conexión a la base de datos indicada por ConxDb. func openDynamicDB(c models.ConxDb) (*sql.DB, error) { driver := strings.ToLower(c.TipoDb.Nombre) - host := c.Servidor.IpServidor + host := c.HostEfectivo() port := c.Puerto user := c.Usuario pass := c.Password @@ -319,7 +319,7 @@ func TestDBConnection(conx models.ConxDb) error { // ── helpers ────────────────────────────────────────────────────────────────── func openDynamicDBWithName(c models.ConxDb, dbName string) (*sql.DB, error) { - host := c.Servidor.IpServidor + host := c.HostEfectivo() port := c.Puerto user := c.Usuario pass := c.Password @@ -486,7 +486,7 @@ func isRedisDriver(driver string) bool { } func redisConnect(c models.ConxDb, dbIndex int) (*goredis.Client, error) { - host := c.Servidor.IpServidor + host := c.HostEfectivo() port := c.Puerto pass := c.Password if port == "" { @@ -637,10 +637,10 @@ func redisExecuteCommand(conx models.ConxDb, database, cmdText string) QueryResu } elapsed := time.Since(start).Milliseconds() result := QueryResult{ - Columns: []string{"command", "result", "error"}, - Rows: rows, - RowCount: len(rows), - IsSelect: true, + Columns: []string{"command", "result", "error"}, + Rows: rows, + RowCount: len(rows), + IsSelect: true, DurationMs: elapsed, } saveHistory(conx.ID, cmdText, "ok", "", int64(len(rows)), elapsed) @@ -700,11 +700,11 @@ func redisResultToQueryResult(val any, cmd string) QueryResult { label = "count" } return QueryResult{ - IsSelect: false, + IsSelect: false, AffectedRows: v, - Columns: []string{label}, - Rows: []map[string]any{{label: v}}, - RowCount: 1, + Columns: []string{label}, + Rows: []map[string]any{{label: v}}, + RowCount: 1, } case []any: // Lista o conjunto de valores @@ -797,7 +797,7 @@ func isMongoDriver(driver string) bool { } func mongoURI(c models.ConxDb) string { - host := c.Servidor.IpServidor + host := c.HostEfectivo() port := c.Puerto user := c.Usuario pass := c.Password diff --git a/resources/views/servidor_dashboard.html b/resources/views/servidor_dashboard.html index de521c3..a66ea08 100644 --- a/resources/views/servidor_dashboard.html +++ b/resources/views/servidor_dashboard.html @@ -288,10 +288,16 @@
- - - + class="rounded-lg px-3 py-2 border text-xs flex flex-col gap-1"> +
+ + + Sin conexión +
+ +
@@ -575,8 +581,10 @@ async hacerPing(cx) { const id = cx.ID || cx.id; if (!id) return; - this.pingCargando[id] = true; - this.pingResultado[id] = undefined; + // Spread-replace para forzar reactividad Alpine v3 con keys nuevas + this.pingCargando = { ...this.pingCargando, [id]: true }; + this.pingResultado = { ...this.pingResultado, [id]: null }; + await this.$nextTick(); try { const r = await fetch(`/app/conx-ping/${id}`); if (!r.ok) { @@ -584,11 +592,11 @@ throw new Error(`HTTP ${r.status}${txt ? ': ' + txt.substring(0, 80) : ''}`); } const data = await r.json(); - this.pingResultado[id] = data; + this.pingResultado = { ...this.pingResultado, [id]: data }; } catch (e) { - this.pingResultado[id] = { exitoso: false, error: e.message || 'Sin conexión' }; + this.pingResultado = { ...this.pingResultado, [id]: { exitoso: false, error: e.message || 'Sin conexión' } }; } finally { - this.pingCargando[id] = false; + this.pingCargando = { ...this.pingCargando, [id]: false }; } }, diff --git a/rest/controllers/servidor_controller.go b/rest/controllers/servidor_controller.go index 551419f..43383da 100755 --- a/rest/controllers/servidor_controller.go +++ b/rest/controllers/servidor_controller.go @@ -235,7 +235,6 @@ func PingConexion(c *fiber.Ctx) error { }) } - // Obtener conexión de BD var conexion models.ConxDb db := app.Http.Database.DB if err := db.Preload("TipoDb").Preload("Servidor").First(&conexion, uid).Error; err != nil { @@ -245,41 +244,46 @@ func PingConexion(c *fiber.Ctx) error { }) } - // Realizar ping a la conexión + host := conexion.HostEfectivo() + puerto := conexion.Puerto + + // Primero intentar conexión real a la BD (más fiable que solo TCP) inicio := time.Now() - exitoso := false - var tiempoMs int64 + testErr := services.TestDBConnection(conexion) + if testErr == nil { + return c.JSON(fiber.Map{ + "exitoso": true, + "tiempo": time.Since(inicio).Milliseconds(), + "host": host, + "puerto": puerto, + "tipo": conexion.TipoDb.Nombre, + "error": "", + }) + } - // Intentar conexión TCP - addr := fmt.Sprintf("%s:%s", conexion.Servidor.IpServidor, conexion.Puerto) - conn, err := net.DialTimeout("tcp", addr, 2*time.Second) - if err == nil { + // Si la BD no conecta, probar al menos TCP (puerto abierto) + tcpAddr := fmt.Sprintf("%s:%s", host, puerto) + inicioTCP := time.Now() + conn, errTCP := net.DialTimeout("tcp", tcpAddr, 3*time.Second) + if errTCP == nil { conn.Close() - exitoso = true - tiempoMs = time.Since(inicio).Milliseconds() - } - - // Si falla TCP, intentar con la BD específica - if !exitoso { - inicio = time.Now() - testErr := services.TestDBConnection(conexion) - if testErr == nil { - exitoso = true - tiempoMs = time.Since(inicio).Milliseconds() - } - } - - errorMsg := "" - if !exitoso { - errorMsg = fmt.Sprintf("No se pudo conectar a %s:%s", conexion.Servidor.IpServidor, conexion.Puerto) + return c.JSON(fiber.Map{ + "exitoso": false, + "tiempo": time.Since(inicioTCP).Milliseconds(), + "host": host, + "puerto": puerto, + "tipo": conexion.TipoDb.Nombre, + // Puerto abierto pero la BD rechaza la conexión + "error": fmt.Sprintf("Puerto %s abierto pero la BD no responde: %s", puerto, testErr.Error()), + }) } return c.JSON(fiber.Map{ - "exitoso": exitoso, - "tiempo": tiempoMs, - "host": conexion.Servidor.IpServidor, - "puerto": conexion.Puerto, + "exitoso": false, + "tiempo": int64(0), + "host": host, + "puerto": puerto, "tipo": conexion.TipoDb.Nombre, - "error": errorMsg, + "error": fmt.Sprintf("No se puede alcanzar %s:%s — verifica que el host sea accesible desde este servidor. Tip: si el DB solo escucha en localhost del servidor, configura el campo 'Host' de la conexión.", host, puerto), }) }