diff --git a/resources/views/coolify.html b/resources/views/coolify.html
index 36e4b83..80726c9 100644
--- a/resources/views/coolify.html
+++ b/resources/views/coolify.html
@@ -87,7 +87,7 @@
+ x-text="actionStatusText(app.uuid, app.status)">
@@ -158,7 +158,7 @@
@@ -230,7 +230,7 @@
+ x-text="actionStatusText(svc.uuid, svc.status)">
@@ -249,7 +249,7 @@
@@ -269,7 +269,7 @@
+ x-text="actionStatusText(db.uuid, db.status)">
@@ -289,7 +289,7 @@
@@ -489,10 +489,10 @@
Cargando variables...
Sin variables de entorno.
-
+
-
-
+
+
Preview
@@ -677,6 +677,7 @@ document.addEventListener('alpine:init', () => {
// Action loading by uuid
actionLoading: {},
+ actionPhase: {},
// ─── Config modal ─────────────────────────────
configModal: false,
@@ -744,6 +745,34 @@ document.addEventListener('alpine:init', () => {
return env.description || env.branch || env.fqdn || env.url || '';
},
+ actionTextFor(action) {
+ const map = {
+ start: 'iniciando',
+ stop: 'deteniendo',
+ restart: 'reiniciando',
+ deploy: 'desplegando',
+ };
+ return map[action] || 'procesando';
+ },
+
+ actionStatusText(uuid, status) {
+ return this.actionPhase[uuid] || this.statusLabel(status);
+ },
+
+ actionSpinnerText(uuid) {
+ return this.actionPhase[uuid] || 'procesando';
+ },
+
+ clearActionState(uuid) {
+ const nextLoading = { ...this.actionLoading };
+ delete nextLoading[uuid];
+ this.actionLoading = nextLoading;
+
+ const nextPhase = { ...this.actionPhase };
+ delete nextPhase[uuid];
+ this.actionPhase = nextPhase;
+ },
+
flash(msg, ok = true) {
this.flashMsg = msg;
this.flashOk = ok;
@@ -967,7 +996,8 @@ document.addEventListener('alpine:init', () => {
try {
const r = await fetch('/app/coolify/deployments');
const j = await r.json();
- this.deployments = Array.isArray(j) ? j : (j.data || []);
+ this.deployments = Array.isArray(j) ? j
+ : (j.deployments || j.items || j.data || j.result || j.deployments_list || []);
this.lastFetch = this.fmtDate(new Date().toISOString());
} catch(e) { this.flash('Error cargando despliegues: ' + e.message, false); }
finally { this.loading = false; }
@@ -994,20 +1024,25 @@ document.addEventListener('alpine:init', () => {
const prev = { ...this.actionLoading };
prev[uuid] = true;
this.actionLoading = prev;
+ const phase = this.actionTextFor(action);
+ this.actionPhase = { ...this.actionPhase, [uuid]: phase };
+ let clearNow = true;
try {
const r = await fetch(`/app/coolify/apps/${uuid}/${action}`);
const j = await r.json();
if (r.ok) {
- this.flash(`App ${action} iniciado correctamente.`);
- setTimeout(() => this.loadApps(), 2000);
+ clearNow = false;
+ this.flash(`App ${phase} correctamente.`);
+ setTimeout(async () => {
+ await this.loadApps();
+ this.clearActionState(uuid);
+ }, 2000);
} else {
this.flash((j.message || j.error || `Error en ${action}`), false);
}
} catch(e) { this.flash('Error: ' + e.message, false); }
finally {
- const next = { ...this.actionLoading };
- next[uuid] = false;
- this.actionLoading = next;
+ if (clearNow) this.clearActionState(uuid);
}
},
@@ -1015,19 +1050,21 @@ document.addEventListener('alpine:init', () => {
const prev = { ...this.actionLoading };
prev[uuid] = true;
this.actionLoading = prev;
+ this.actionPhase = { ...this.actionPhase, [uuid]: this.actionTextFor('deploy') };
+ let clearNow = true;
try {
const r = await fetch(`/app/coolify/apps/${uuid}/deploy`, { method: 'POST' });
const j = await r.json();
if (r.ok) {
+ clearNow = false;
this.flash('Deploy disparado correctamente.');
+ setTimeout(() => this.clearActionState(uuid), 3000);
} else {
this.flash((j.message || j.error || 'Error al hacer deploy'), false);
}
} catch(e) { this.flash('Error: ' + e.message, false); }
finally {
- const next = { ...this.actionLoading };
- next[uuid] = false;
- this.actionLoading = next;
+ if (clearNow) this.clearActionState(uuid);
}
},
@@ -1070,7 +1107,7 @@ document.addEventListener('alpine:init', () => {
try {
const r = await fetch(`/app/coolify/apps/${app.uuid}/envs`);
const j = await r.json();
- this.envsList = Array.isArray(j) ? j : (j.data || []);
+ this.envsList = Array.isArray(j) ? j : (j.data || j.variables || j.envs || j.items || j.environment_variables || []);
} catch(e) { this.flash('Error: ' + e.message, false); }
finally { this.envsLoading = false; }
},
@@ -1085,7 +1122,7 @@ document.addEventListener('alpine:init', () => {
try {
const r = await fetch(`/app/coolify/services/${svc.uuid}/envs`);
const j = await r.json();
- this.envsList = Array.isArray(j) ? j : (j.data || []);
+ this.envsList = Array.isArray(j) ? j : (j.data || j.variables || j.envs || j.items || j.environment_variables || []);
} catch(e) { this.flash('Error: ' + e.message, false); }
finally { this.envsLoading = false; }
},
@@ -1110,7 +1147,7 @@ document.addEventListener('alpine:init', () => {
// Recargar lista
const r2 = await fetch(endpoint.replace(/\/envs$/, '/envs'));
const j2 = await r2.json();
- this.envsList = Array.isArray(j2) ? j2 : (j2.data || []);
+ this.envsList = Array.isArray(j2) ? j2 : (j2.data || j2.variables || j2.envs || j2.items || j2.environment_variables || []);
} else {
this.flash(j.message || j.error || 'Error al crear variable.', false);
}
@@ -1127,10 +1164,16 @@ document.addEventListener('alpine:init', () => {
try {
const r = await fetch(`/app/coolify/apps/${app.uuid}/deployments`);
const j = await r.json();
- this.appDeploymentsList = Array.isArray(j) ? j : (j.data || []);
+ let list = Array.isArray(j) ? j
+ : (j.deployments || j.items || j.data || j.result || j.deployments_list || []);
+ // Si la respuesta es vacía pero tiene data a nivel superior, prueba parsear como objeto wrapper
+ if (!Array.isArray(list) && typeof list === 'object' && list !== null) {
+ list = Object.values(list).filter(v => Array.isArray(v))[0] || [];
+ }
+ this.appDeploymentsList = Array.isArray(list) ? list : [];
} catch(e) { this.flash('Error: ' + e.message, false); }
finally { this.appDeploymentsLoading = false; }
- },
+ }
// ─── Deployment log ──────────────────────────────
openDeploymentLog(dep) {
@@ -1143,20 +1186,25 @@ document.addEventListener('alpine:init', () => {
const prev = { ...this.actionLoading };
prev[uuid] = true;
this.actionLoading = prev;
+ const phase = this.actionTextFor(action);
+ this.actionPhase = { ...this.actionPhase, [uuid]: phase };
+ let clearNow = true;
try {
const r = await fetch(`/app/coolify/services/${uuid}/${action}`);
const j = await r.json();
if (r.ok) {
- this.flash(`Servicio ${action} iniciado.`);
- setTimeout(() => this.loadServices(), 2000);
+ clearNow = false;
+ this.flash(`Servicio ${phase} correctamente.`);
+ setTimeout(async () => {
+ await this.loadServices();
+ this.clearActionState(uuid);
+ }, 2000);
} else {
this.flash(j.message || j.error || `Error en ${action}`, false);
}
} catch(e) { this.flash('Error: ' + e.message, false); }
finally {
- const next = { ...this.actionLoading };
- next[uuid] = false;
- this.actionLoading = next;
+ if (clearNow) this.clearActionState(uuid);
}
},
@@ -1165,20 +1213,25 @@ document.addEventListener('alpine:init', () => {
const prev = { ...this.actionLoading };
prev[uuid] = true;
this.actionLoading = prev;
+ const phase = this.actionTextFor(action);
+ this.actionPhase = { ...this.actionPhase, [uuid]: phase };
+ let clearNow = true;
try {
const r = await fetch(`/app/coolify/databases/${uuid}/${action}`);
const j = await r.json();
if (r.ok) {
- this.flash(`Base de datos ${action} iniciado.`);
- setTimeout(() => this.loadDatabases(), 2000);
+ clearNow = false;
+ this.flash(`Base de datos ${phase} correctamente.`);
+ setTimeout(async () => {
+ await this.loadDatabases();
+ this.clearActionState(uuid);
+ }, 2000);
} else {
this.flash(j.message || j.error || `Error en ${action}`, false);
}
} catch(e) { this.flash('Error: ' + e.message, false); }
finally {
- const next = { ...this.actionLoading };
- next[uuid] = false;
- this.actionLoading = next;
+ if (clearNow) this.clearActionState(uuid);
}
},