up
This commit is contained in:
@@ -87,7 +87,7 @@
|
||||
</div>
|
||||
<span :class="statusClass(app.status)"
|
||||
class="text-xs px-2 py-0.5 rounded-full font-medium shrink-0"
|
||||
x-text="app.status || 'unknown'">
|
||||
x-text="statusLabel(app.status)">
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -107,42 +107,49 @@
|
||||
|
||||
<!-- Acciones -->
|
||||
<div class="flex flex-wrap gap-2 pt-1 border-t border-gray-100">
|
||||
<button @click="appAction(app.uuid, 'start')"
|
||||
<!-- Start: solo cuando está detenida / desconocida -->
|
||||
<button x-show="!isRunning(app.status) && !isTransitioning(app.status)"
|
||||
@click="appAction(app.uuid, 'start')"
|
||||
:disabled="actionLoading[app.uuid]"
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-green-50 text-green-700 hover:bg-green-100 transition disabled:opacity-50">
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-green-100 text-green-700 hover:bg-green-200 transition disabled:opacity-50 font-medium">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="currentColor" viewBox="0 0 16 16"><path d="M11.596 8.697l-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z"/></svg>
|
||||
Start
|
||||
</button>
|
||||
<button @click="appAction(app.uuid, 'stop')"
|
||||
:disabled="actionLoading[app.uuid]"
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-red-50 text-red-700 hover:bg-red-100 transition disabled:opacity-50">
|
||||
<!-- Stop: solo cuando está corriendo -->
|
||||
<button x-show="isRunning(app.status) || isTransitioning(app.status)"
|
||||
@click="appAction(app.uuid, 'stop')"
|
||||
:disabled="actionLoading[app.uuid] || isTransitioning(app.status)"
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-red-100 text-red-700 hover:bg-red-200 transition disabled:opacity-50 font-medium">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="currentColor" viewBox="0 0 16 16"><path d="M5 3.5h6A1.5 1.5 0 0 1 12.5 5v6a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 11V5A1.5 1.5 0 0 1 5 3.5z"/></svg>
|
||||
Stop
|
||||
</button>
|
||||
<button @click="appAction(app.uuid, 'restart')"
|
||||
<!-- Restart: solo cuando está corriendo -->
|
||||
<button x-show="isRunning(app.status)"
|
||||
@click="appAction(app.uuid, 'restart')"
|
||||
:disabled="actionLoading[app.uuid]"
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-yellow-50 text-yellow-700 hover:bg-yellow-100 transition disabled:opacity-50">
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-yellow-100 text-yellow-700 hover:bg-yellow-200 transition disabled:opacity-50 font-medium">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 0 0 4.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 0 1-15.357-2m15.357 2H15"/></svg>
|
||||
Restart
|
||||
</button>
|
||||
<!-- Deploy: siempre disponible -->
|
||||
<button @click="deployApp(app.uuid)"
|
||||
:disabled="actionLoading[app.uuid]"
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-indigo-50 text-indigo-700 hover:bg-indigo-100 transition disabled:opacity-50">
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-indigo-100 text-indigo-700 hover:bg-indigo-200 transition disabled:opacity-50 font-medium">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 17v-6l-2 2m0 0l2-2m-2 2h14M3 9l3-3 3 3"/></svg>
|
||||
Deploy
|
||||
</button>
|
||||
<button @click="openAppLogs(app)"
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-gray-50 text-gray-600 hover:bg-gray-100 transition">
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 transition">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5.586a1 1 0 0 1 .707.293l5.414 5.414a1 1 0 0 1 .293.707V19a2 2 0 0 1-2 2z"/></svg>
|
||||
Logs
|
||||
</button>
|
||||
<button @click="openAppEnvs(app)"
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-gray-50 text-gray-600 hover:bg-gray-100 transition">
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 transition">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 0 0-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 0 0-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 0 0-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 0 0-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 0 0 1.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/></svg>
|
||||
Envs
|
||||
</button>
|
||||
<button @click="openAppDeployments(app)"
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-gray-50 text-gray-600 hover:bg-gray-100 transition">
|
||||
class="flex items-center gap-1 text-xs px-2.5 py-1 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 transition">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/></svg>
|
||||
Historial
|
||||
</button>
|
||||
@@ -223,19 +230,22 @@
|
||||
</div>
|
||||
<span :class="statusClass(svc.status)"
|
||||
class="text-xs px-2 py-0.5 rounded-full font-medium shrink-0"
|
||||
x-text="svc.status || 'unknown'">
|
||||
x-text="statusLabel(svc.status)">
|
||||
</span>
|
||||
</div>
|
||||
<div x-show="svc.description" class="text-xs text-gray-500" x-text="svc.description"></div>
|
||||
<div class="flex flex-wrap gap-2 pt-1 border-t border-gray-100">
|
||||
<button @click="serviceAction(svc.uuid, 'start')" :disabled="actionLoading[svc.uuid]"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-green-50 text-green-700 hover:bg-green-100 transition disabled:opacity-50">Start</button>
|
||||
<button @click="serviceAction(svc.uuid, 'stop')" :disabled="actionLoading[svc.uuid]"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-red-50 text-red-700 hover:bg-red-100 transition disabled:opacity-50">Stop</button>
|
||||
<button @click="serviceAction(svc.uuid, 'restart')" :disabled="actionLoading[svc.uuid]"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-yellow-50 text-yellow-700 hover:bg-yellow-100 transition disabled:opacity-50">Restart</button>
|
||||
<button x-show="!isRunning(svc.status) && !isTransitioning(svc.status)"
|
||||
@click="serviceAction(svc.uuid, 'start')" :disabled="actionLoading[svc.uuid]"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-green-100 text-green-700 hover:bg-green-200 transition disabled:opacity-50 font-medium">Start</button>
|
||||
<button x-show="isRunning(svc.status) || isTransitioning(svc.status)"
|
||||
@click="serviceAction(svc.uuid, 'stop')" :disabled="actionLoading[svc.uuid] || isTransitioning(svc.status)"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-red-100 text-red-700 hover:bg-red-200 transition disabled:opacity-50 font-medium">Stop</button>
|
||||
<button x-show="isRunning(svc.status)"
|
||||
@click="serviceAction(svc.uuid, 'restart')" :disabled="actionLoading[svc.uuid]"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-yellow-100 text-yellow-700 hover:bg-yellow-200 transition disabled:opacity-50 font-medium">Restart</button>
|
||||
<button @click="openServiceEnvs(svc)"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-gray-50 text-gray-600 hover:bg-gray-100 transition">Envs</button>
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 transition">Envs</button>
|
||||
</div>
|
||||
<div x-show="actionLoading[svc.uuid]" class="text-xs text-indigo-500 flex items-center gap-1">
|
||||
<svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
|
||||
@@ -259,7 +269,7 @@
|
||||
</div>
|
||||
<span :class="statusClass(db.status)"
|
||||
class="text-xs px-2 py-0.5 rounded-full font-medium shrink-0"
|
||||
x-text="db.status || 'unknown'">
|
||||
x-text="statusLabel(db.status)">
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 space-y-0.5">
|
||||
@@ -267,12 +277,15 @@
|
||||
<div x-show="db.name">DB: <span class="font-medium text-gray-700" x-text="db.postgres_db || db.mysql_database || db.mongo_database || ''"></span></div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 pt-1 border-t border-gray-100">
|
||||
<button @click="dbAction(db.uuid, 'start')" :disabled="actionLoading[db.uuid]"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-green-50 text-green-700 hover:bg-green-100 transition disabled:opacity-50">Start</button>
|
||||
<button @click="dbAction(db.uuid, 'stop')" :disabled="actionLoading[db.uuid]"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-red-50 text-red-700 hover:bg-red-100 transition disabled:opacity-50">Stop</button>
|
||||
<button @click="dbAction(db.uuid, 'restart')" :disabled="actionLoading[db.uuid]"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-yellow-50 text-yellow-700 hover:bg-yellow-100 transition disabled:opacity-50">Restart</button>
|
||||
<button x-show="!isRunning(db.status) && !isTransitioning(db.status)"
|
||||
@click="dbAction(db.uuid, 'start')" :disabled="actionLoading[db.uuid]"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-green-100 text-green-700 hover:bg-green-200 transition disabled:opacity-50 font-medium">Start</button>
|
||||
<button x-show="isRunning(db.status) || isTransitioning(db.status)"
|
||||
@click="dbAction(db.uuid, 'stop')" :disabled="actionLoading[db.uuid] || isTransitioning(db.status)"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-red-100 text-red-700 hover:bg-red-200 transition disabled:opacity-50 font-medium">Stop</button>
|
||||
<button x-show="isRunning(db.status)"
|
||||
@click="dbAction(db.uuid, 'restart')" :disabled="actionLoading[db.uuid]"
|
||||
class="text-xs px-2.5 py-1 rounded-lg bg-yellow-100 text-yellow-700 hover:bg-yellow-200 transition disabled:opacity-50 font-medium">Restart</button>
|
||||
</div>
|
||||
<div x-show="actionLoading[db.uuid]" class="text-xs text-indigo-500 flex items-center gap-1">
|
||||
<svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
|
||||
@@ -442,17 +455,6 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Webhook URL para notificaciones -->
|
||||
<div class="px-6 pb-4 border-t pt-4">
|
||||
<p class="text-xs text-gray-500 mb-1 font-medium">URL Webhook (notificaciones)</p>
|
||||
<p class="text-xs text-gray-400 mb-2">Copia esta URL en <strong>Coolify → Settings → Notifications → Webhook</strong> para recibir eventos de deployment/restart.</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<code class="flex-1 text-xs bg-gray-50 border rounded px-3 py-1.5 truncate select-all"
|
||||
x-text="window.location.origin + '/webhooks/coolify'"></code>
|
||||
<button @click="navigator.clipboard.writeText(window.location.origin + '/webhooks/coolify').then(() => flash('URL copiada'))"
|
||||
class="text-xs px-3 py-1.5 rounded-lg border text-gray-600 hover:bg-gray-50 shrink-0">Copiar</button>
|
||||
</div>
|
||||
</div>
|
||||
<div x-show="cfgMsg" class="px-6 pb-4 text-sm" :class="cfgOk ? 'text-green-600' : 'text-red-600'" x-text="cfgMsg"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -743,18 +745,53 @@ document.addEventListener('alpine:init', () => {
|
||||
catch { return dt; }
|
||||
},
|
||||
|
||||
// Coolify puede devolver "running:healthy", "exited:0", etc. — extrae la parte base.
|
||||
normalizeStatus(status) {
|
||||
if (!status) return '';
|
||||
return status.toLowerCase().split(':')[0].trim();
|
||||
},
|
||||
|
||||
isRunning(status) {
|
||||
const s = this.normalizeStatus(status);
|
||||
return s === 'running';
|
||||
},
|
||||
|
||||
isStopped(status) {
|
||||
const s = this.normalizeStatus(status);
|
||||
return s === 'stopped' || s === 'exited' || s === 'idle';
|
||||
},
|
||||
|
||||
isTransitioning(status) {
|
||||
const s = this.normalizeStatus(status);
|
||||
return s === 'starting' || s === 'stopping' || s === 'restarting';
|
||||
},
|
||||
|
||||
statusClass(status) {
|
||||
if (!status) return 'bg-gray-100 text-gray-500';
|
||||
const s = status.toLowerCase();
|
||||
if (s === 'running') return 'bg-green-100 text-green-700';
|
||||
if (s === 'stopped') return 'bg-red-100 text-red-600';
|
||||
if (s === 'exited') return 'bg-red-100 text-red-600';
|
||||
if (s === 'starting') return 'bg-yellow-100 text-yellow-700';
|
||||
const s = this.normalizeStatus(status);
|
||||
if (!s) return 'bg-gray-100 text-gray-500';
|
||||
if (s === 'running') return 'bg-green-100 text-green-700';
|
||||
if (s === 'stopped') return 'bg-red-100 text-red-600';
|
||||
if (s === 'exited') return 'bg-red-100 text-red-600';
|
||||
if (s === 'idle') return 'bg-gray-100 text-gray-500';
|
||||
if (s === 'starting') return 'bg-yellow-100 text-yellow-700';
|
||||
if (s === 'stopping') return 'bg-yellow-100 text-yellow-700';
|
||||
if (s === 'restarting') return 'bg-yellow-100 text-yellow-700';
|
||||
if (s === 'degraded') return 'bg-orange-100 text-orange-700';
|
||||
if (s === 'degraded') return 'bg-orange-100 text-orange-700';
|
||||
if (s === 'unhealthy') return 'bg-orange-100 text-orange-700';
|
||||
return 'bg-gray-100 text-gray-500';
|
||||
},
|
||||
|
||||
statusLabel(status) {
|
||||
if (!status) return 'desconocido';
|
||||
// Muestra el status original sin el sufijo de health check
|
||||
const [base, extra] = status.toLowerCase().split(':');
|
||||
const labels = { running:'corriendo', stopped:'detenido', exited:'detenido', idle:'inactivo',
|
||||
starting:'iniciando', stopping:'deteniendo', restarting:'reiniciando',
|
||||
degraded:'degradado', unhealthy:'no saludable' };
|
||||
const baseLabel = labels[base] || base;
|
||||
return extra ? `${baseLabel} (${extra})` : baseLabel;
|
||||
},
|
||||
|
||||
deployStatusClass(status) {
|
||||
if (!status) return 'bg-gray-100 text-gray-500';
|
||||
const s = status.toLowerCase();
|
||||
@@ -861,6 +898,8 @@ document.addEventListener('alpine:init', () => {
|
||||
const r = await fetch('/app/coolify/apps' + qs);
|
||||
const j = await r.json();
|
||||
this.apps = Array.isArray(j) ? j : (j.data || []);
|
||||
// Pre-init actionLoading para reactividad garantizada en Alpine.js
|
||||
const al = {}; this.apps.forEach(a => { al[a.uuid] = false; }); this.actionLoading = al;
|
||||
this.lastFetch = this.fmtDate(new Date().toISOString());
|
||||
} catch(e) { this.flash('Error cargando apps: ' + e.message, false); }
|
||||
finally { this.loading = false; }
|
||||
@@ -883,6 +922,7 @@ document.addEventListener('alpine:init', () => {
|
||||
const r = await fetch('/app/coolify/services');
|
||||
const j = await r.json();
|
||||
this.services = Array.isArray(j) ? j : (j.data || []);
|
||||
const sl = { ...this.actionLoading }; this.services.forEach(s => { if (!(s.uuid in sl)) sl[s.uuid] = false; }); this.actionLoading = sl;
|
||||
this.lastFetch = this.fmtDate(new Date().toISOString());
|
||||
} catch(e) { this.flash('Error cargando servicios: ' + e.message, false); }
|
||||
finally { this.loading = false; }
|
||||
@@ -894,6 +934,7 @@ document.addEventListener('alpine:init', () => {
|
||||
const r = await fetch('/app/coolify/databases');
|
||||
const j = await r.json();
|
||||
this.databases = Array.isArray(j) ? j : (j.data || []);
|
||||
const dl = { ...this.actionLoading }; this.databases.forEach(d => { if (!(d.uuid in dl)) dl[d.uuid] = false; }); this.actionLoading = dl;
|
||||
this.lastFetch = this.fmtDate(new Date().toISOString());
|
||||
} catch(e) { this.flash('Error cargando bases de datos: ' + e.message, false); }
|
||||
finally { this.loading = false; }
|
||||
|
||||
Reference in New Issue
Block a user