fix: reactividad Alpine ping — estado en cx directamente
Alpine v3 NO puede rastrear accesos dinámicos pingCargando[cx.ID] dentro de x-for porque no sabe qué key observar por item. Solución: estado de ping embebido en cada objeto cx: - abrirModal inyecta _pingCargando:false y _pingResultado:null en cada cx - hacerPing muta cx._pingCargando / cx._pingResultado directamente - template usa cx._pingCargando y cx._pingResultado (proxy reactivo del x-for) Esto garantiza que el spinner, el botón deshabilitado y el resultado del ping se actualicen correctamente para cada card. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
parent
a484676392
commit
6cca8e6fcd
@@ -286,34 +286,34 @@
|
||||
</div>
|
||||
|
||||
<!-- Resultado del ping -->
|
||||
<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'"
|
||||
<div x-show="cx._pingResultado !== null" class="mt-1">
|
||||
<div :class="cx._pingResultado?.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 flex-col gap-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<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" class="font-medium">Sin conexión</span>
|
||||
<span x-text="cx._pingResultado?.exitoso ? '✓' : '✗'" class="font-bold text-base leading-none"></span>
|
||||
<span x-show="cx._pingResultado?.exitoso" x-text="'Conectado en ' + cx._pingResultado?.tiempo + ' ms'"></span>
|
||||
<span x-show="!cx._pingResultado?.exitoso" class="font-medium">Sin conexión</span>
|
||||
</div>
|
||||
<span x-show="pingResultado[cx.ID || cx.id]?.host" class="font-mono opacity-70"
|
||||
x-text="(pingResultado[cx.ID || cx.id]?.host || '') + ':' + (pingResultado[cx.ID || cx.id]?.puerto || '')"></span>
|
||||
<span x-show="!pingResultado[cx.ID || cx.id]?.exitoso && pingResultado[cx.ID || cx.id]?.error"
|
||||
x-text="pingResultado[cx.ID || cx.id]?.error" class="opacity-80 break-words"></span>
|
||||
<span x-show="cx._pingResultado?.host" class="font-mono opacity-70"
|
||||
x-text="(cx._pingResultado?.host || '') + ':' + (cx._pingResultado?.puerto || '')"></span>
|
||||
<span x-show="!cx._pingResultado?.exitoso && cx._pingResultado?.error"
|
||||
x-text="cx._pingResultado?.error" class="opacity-80 break-words"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ping + Query Runner buttons -->
|
||||
<div class="pt-3 border-t border-slate-100 flex items-center justify-between">
|
||||
<button type="button" @click.stop="hacerPing(cx)"
|
||||
:disabled="pingCargando[cx.ID || cx.id]"
|
||||
:disabled="cx._pingCargando"
|
||||
class="flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors"
|
||||
: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">
|
||||
:class="cx._pingCargando ? 'bg-slate-100 text-slate-400 cursor-wait' : 'bg-blue-50 text-blue-700 hover:bg-blue-100'">
|
||||
<svg x-show="!cx._pingCargando" 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 || cx.id]" class="w-3.5 h-3.5 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg x-show="cx._pingCargando" 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 || cx.id] ? 'Probando...' : 'Verificar'"></span>
|
||||
<span x-text="cx._pingCargando ? 'Probando...' : 'Verificar'"></span>
|
||||
</button>
|
||||
|
||||
<button type="button" @click="abrirQueryRunner(cx)"
|
||||
@@ -513,9 +513,16 @@
|
||||
try {
|
||||
const r = await fetch(`/app/servidor-dashboard/${servidor.ID}`);
|
||||
const data = await r.json();
|
||||
this.servidorSeleccionado._conexiones = data.conexiones || [];
|
||||
// Inyectar estado de ping directamente en cada conexión para que
|
||||
// Alpine v3 lo rastreé correctamente dentro del x-for
|
||||
const conexiones = (data.conexiones || []).map(c => ({
|
||||
...c,
|
||||
_pingCargando: false,
|
||||
_pingResultado: null,
|
||||
}));
|
||||
this.servidorSeleccionado._conexiones = conexiones;
|
||||
const idx = this.servidores.findIndex(s => s.ID === servidor.ID);
|
||||
if (idx >= 0) this.servidores[idx]._conexiones = data.conexiones || [];
|
||||
if (idx >= 0) this.servidores[idx]._conexiones = conexiones;
|
||||
} catch (e) {
|
||||
console.error('Error cargando conexiones:', e);
|
||||
this.servidorSeleccionado._conexiones = [];
|
||||
@@ -581,22 +588,21 @@
|
||||
async hacerPing(cx) {
|
||||
const id = cx.ID || cx.id;
|
||||
if (!id) return;
|
||||
// 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();
|
||||
// Mutar cx directamente — Alpine v3 rastrea propiedades del objeto
|
||||
// dentro de x-for como proxy reactivo, no keys dinámicas de otro objeto
|
||||
cx._pingCargando = true;
|
||||
cx._pingResultado = null;
|
||||
try {
|
||||
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, [id]: data };
|
||||
cx._pingResultado = await r.json();
|
||||
} catch (e) {
|
||||
this.pingResultado = { ...this.pingResultado, [id]: { exitoso: false, error: e.message || 'Sin conexión' } };
|
||||
cx._pingResultado = { exitoso: false, error: e.message || 'Sin conexión' };
|
||||
} finally {
|
||||
this.pingCargando = { ...this.pingCargando, [id]: false };
|
||||
cx._pingCargando = false;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user