fix: corregir ping - mutación directa y manejo de cx.ID || cx.id

- Cambia spread+reassign por mutación directa (this.pingCargando[id] = true)
  para garantizar reactividad correcta en Alpine.js 3.x
- Usa (cx.ID || cx.id) en toda la plantilla para compatibilidad con
  ambos formatos de serialización JSON de GORM
- Agrega type="button" y @click.stop en el botón de verificar
- Maneja respuestas HTTP no-OK con mensaje de error explícito
- Muestra mensaje de error real del catch en la UI

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Lizandro Guarnizo
2026-05-21 11:07:39 -05:00
co-authored by Copilot
parent d35398875d
commit f52e266527
+27 -19
View File
@@ -195,7 +195,7 @@
<!-- Grid de conexiones -->
<div x-show="!cargandoConexiones && servidorSeleccionado?._conexiones?.length > 0"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<template x-for="cx in (servidorSeleccionado?._conexiones || [])" :key="cx.ID">
<template x-for="cx in (servidorSeleccionado?._conexiones || [])" :key="(cx.ID || cx.id)">
<div class="bg-white border-2 border-slate-200 rounded-xl overflow-hidden hover:shadow-md transition-all">
<!-- DB header with type color -->
@@ -203,7 +203,7 @@
<div class="flex items-center gap-2">
<span class="text-white font-bold text-sm px-2 py-0.5 rounded bg-black/20"
x-text="cx.tipo_db?.nombre || 'DB'"></span>
<span class="text-white/80 text-xs font-mono" x-text="(cx.nombre ? cx.nombre + ' · ' : '') + '#' + cx.ID"></span>
<span class="text-white/80 text-xs font-mono" x-text="(cx.nombre ? cx.nombre + ' · ' : '') + '#' + (cx.ID || cx.id)"></span>
</div>
</div>
@@ -223,31 +223,31 @@
</div>
<!-- Resultado del ping -->
<div x-show="pingResultado[cx.ID] !== undefined" class="mt-1">
<div :class="pingResultado[cx.ID]?.exitoso ? 'bg-green-50 border-green-200 text-green-800' : 'bg-red-50 border-red-200 text-red-800'"
<div x-show="pingResultado[cx.ID || cx.id] !== undefined && pingResultado[cx.ID || cx.id] !== null" class="mt-1">
<div :class="pingResultado[cx.ID || cx.id]?.exitoso ? 'bg-green-50 border-green-200 text-green-800' : 'bg-red-50 border-red-200 text-red-800'"
class="rounded-lg px-3 py-2 border text-xs flex items-center gap-2">
<span x-text="pingResultado[cx.ID]?.exitoso ? '✓' : '✗'" class="font-bold text-base leading-none"></span>
<span x-show="pingResultado[cx.ID]?.exitoso" x-text="'Conectado en ' + pingResultado[cx.ID]?.tiempo + ' ms'"></span>
<span x-show="!pingResultado[cx.ID]?.exitoso" x-text="pingResultado[cx.ID]?.error || 'Sin conexión'"></span>
<span x-text="pingResultado[cx.ID || cx.id]?.exitoso ? '✓' : '✗'" class="font-bold text-base leading-none"></span>
<span x-show="pingResultado[cx.ID || cx.id]?.exitoso" x-text="'Conectado en ' + pingResultado[cx.ID || cx.id]?.tiempo + ' ms'"></span>
<span x-show="!pingResultado[cx.ID || cx.id]?.exitoso" x-text="pingResultado[cx.ID || cx.id]?.error || 'Sin conexión'"></span>
</div>
</div>
<!-- Ping + Query Runner buttons -->
<div class="pt-3 border-t border-slate-100 flex items-center justify-between">
<button @click="hacerPing(cx)"
:disabled="pingCargando[cx.ID]"
<button type="button" @click.stop="hacerPing(cx)"
:disabled="pingCargando[cx.ID || cx.id]"
class="flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors"
:class="pingCargando[cx.ID] ? 'bg-slate-100 text-slate-400 cursor-wait' : 'bg-blue-50 text-blue-700 hover:bg-blue-100'">
<svg x-show="!pingCargando[cx.ID]" class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
:class="pingCargando[cx.ID || cx.id] ? 'bg-slate-100 text-slate-400 cursor-wait' : 'bg-blue-50 text-blue-700 hover:bg-blue-100'">
<svg x-show="!pingCargando[cx.ID || cx.id]" class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0"/>
</svg>
<svg x-show="pingCargando[cx.ID]" class="w-3.5 h-3.5 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg x-show="pingCargando[cx.ID || cx.id]" class="w-3.5 h-3.5 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
<span x-text="pingCargando[cx.ID] ? 'Probando...' : 'Verificar'"></span>
<span x-text="pingCargando[cx.ID || cx.id] ? 'Probando...' : 'Verificar'"></span>
</button>
<button @click="abrirQueryRunner(cx)"
<button type="button" @click="abrirQueryRunner(cx)"
class="flex items-center gap-1 px-3 py-1.5 bg-slate-50 text-slate-600 rounded-lg text-xs font-medium hover:bg-slate-100 transition-colors">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
@@ -327,15 +327,23 @@
},
async hacerPing(cx) {
this.pingCargando = { ...this.pingCargando, [cx.ID]: true };
const id = cx.ID || cx.id;
if (!id) return;
// Usar mutación directa para garantizar reactividad en Alpine.js
this.pingCargando[id] = true;
this.pingResultado[id] = undefined; // resetear resultado previo
try {
const r = await fetch(`/app/conx-ping/${cx.ID}`);
const r = await fetch(`/app/conx-ping/${id}`);
if (!r.ok) {
const txt = await r.text().catch(() => '');
throw new Error(`HTTP ${r.status}${txt ? ': ' + txt.substring(0, 80) : ''}`);
}
const data = await r.json();
this.pingResultado = { ...this.pingResultado, [cx.ID]: data };
this.pingResultado[id] = data;
} catch (e) {
this.pingResultado = { ...this.pingResultado, [cx.ID]: { exitoso: false } };
this.pingResultado[id] = { exitoso: false, error: e.message || 'Sin conexión' };
} finally {
this.pingCargando = { ...this.pingCargando, [cx.ID]: false };
this.pingCargando[id] = false;
}
},