From e9548e13aea74bd93677130be0271088f37a4395 Mon Sep 17 00:00:00 2001
From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com>
Date: Tue, 26 May 2026 11:42:41 -0500
Subject: [PATCH] up
---
main.go | 11 ++
resources/views/coolify.html | 137 ++++++++++---------------
resources/views/vcard_api.html | 29 +++++-
rest/controllers/coolify_controller.go | 97 ++++++-----------
4 files changed, 118 insertions(+), 156 deletions(-)
diff --git a/main.go b/main.go
index e2034de..7d2ca3f 100755
--- a/main.go
+++ b/main.go
@@ -49,6 +49,10 @@ func main() {
migrations.MigratePortal()
// Crear tablas de integraciones y pasarelas si no existen
app.Http.Database.DB.AutoMigrate(
+ // Tablas base del sistema (columnas nuevas)
+ &models.Users{},
+ &models.Roles{},
+ &models.Submodules{},
&models.QueryHistory{},
&models.HostingerConfig{},
&models.CloudflareConfig{},
@@ -66,6 +70,7 @@ func main() {
// Notificaciones
&models.NotifEventoConfig{},
&models.SistemaNotificacion{},
+ &models.ServidorAlertaUmbral{},
// Shield
&models.ShieldConfig{},
// Partner
@@ -75,6 +80,12 @@ func main() {
&models.PortalPasswordResetToken{},
// Integración VCard API Admin
&models.VcardApiConfig{},
+ // Integración Coolify
+ &models.CoolifyConfig{},
+ // Servidores: nuevos campos de agente + tabla join de integraciones
+ &models.Servidor{},
+ &models.ConxDb{},
+ &models.ProvServidor{},
)
// Seed automático (idempotente) de módulos del sistema
migrations.SeedRenovaciones()
diff --git a/resources/views/coolify.html b/resources/views/coolify.html
index 80726c9..2323040 100644
--- a/resources/views/coolify.html
+++ b/resources/views/coolify.html
@@ -87,7 +87,11 @@
+=======
+ x-text="app.status || 'unknown'">
+>>>>>>> parent of 71fcf98 (up)
@@ -107,49 +111,42 @@
-
-
-
-
+=======
+ x-text="svc.status || 'unknown'">
+>>>>>>> parent of 71fcf98 (up)
- Start
- Stop
- Restart
+ Start
+ Stop
+ Restart
Envs
+ class="text-xs px-2.5 py-1 rounded-lg bg-gray-50 text-gray-600 hover:bg-gray-100 transition">Envs
+=======
+ x-text="db.status || 'unknown'">
+>>>>>>> parent of 71fcf98 (up)
@@ -277,15 +279,12 @@
DB:
- Start
- Stop
- Restart
+ Start
+ Stop
+ Restart
+
+
+
URL Webhook (notificaciones)
+
Copia esta URL en Coolify → Settings → Notifications → Webhook para recibir eventos de deployment/restart.
+
+
+ flash('URL copiada'))"
+ class="text-xs px-3 py-1.5 rounded-lg border text-gray-600 hover:bg-gray-50 shrink-0">Copiar
+
+
@@ -785,53 +795,18 @@ 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) {
- 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 (!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';
if (s === 'restarting') return 'bg-yellow-100 text-yellow-700';
- if (s === 'degraded') return 'bg-orange-100 text-orange-700';
- if (s === 'unhealthy') return 'bg-orange-100 text-orange-700';
+ if (s === 'degraded') 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();
@@ -938,8 +913,6 @@ 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; }
@@ -962,7 +935,6 @@ 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; }
@@ -974,7 +946,6 @@ 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; }
diff --git a/resources/views/vcard_api.html b/resources/views/vcard_api.html
index 1cdfbed..0c760e7 100644
--- a/resources/views/vcard_api.html
+++ b/resources/views/vcard_api.html
@@ -446,12 +446,31 @@
- |
- |
- |
-
+
+
+ |
+
+
+
+ |
+
+
+
+ |
+
+
+
+
+
+
+
|
diff --git a/rest/controllers/coolify_controller.go b/rest/controllers/coolify_controller.go
index e8f9130..f61aaeb 100644
--- a/rest/controllers/coolify_controller.go
+++ b/rest/controllers/coolify_controller.go
@@ -12,13 +12,9 @@ import (
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
-// ─── helpers internos ────────────────────────────────────────────────────────
-
-// coolifyDo ejecuta una petición a la API de Coolify y devuelve el body crudo.
func coolifyDo(method, endpoint string, reqBody io.Reader, contentType string, cfg *models.CoolifyConfig) ([]byte, int, error) {
base := strings.TrimRight(cfg.BaseURL, "/")
url := fmt.Sprintf("%s/api/v1%s", base, endpoint)
-
client := &http.Client{Timeout: 30 * time.Second}
req, err := http.NewRequest(method, url, reqBody)
if err != nil {
@@ -29,7 +25,6 @@ func coolifyDo(method, endpoint string, reqBody io.Reader, contentType string, c
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
-
resp, err := client.Do(req)
if err != nil {
return nil, 0, err
@@ -39,7 +34,6 @@ func coolifyDo(method, endpoint string, reqBody io.Reader, contentType string, c
return body, resp.StatusCode, nil
}
-// coolifyProxy resuelve config, aplica qs de la request y responde al frontend.
func coolifyProxy(c *fiber.Ctx, method, endpoint string) error {
cfg, err := models.GetCoolifyConfig()
if err != nil {
@@ -48,34 +42,33 @@ func coolifyProxy(c *fiber.Ctx, method, endpoint string) error {
if !cfg.Activo {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "La integración Coolify está inactiva"})
}
-
ep := endpoint
if qs := string(c.Request().URI().QueryString()); qs != "" {
ep = endpoint + "?" + qs
}
-
var bodyReader io.Reader
ct := ""
if raw := c.Body(); len(raw) > 0 {
bodyReader = strings.NewReader(string(raw))
ct = c.Get("Content-Type", "application/json")
}
-
body, status, err := coolifyDo(method, ep, bodyReader, ct, cfg)
if err != nil {
- return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
+ // No devolver 502: Cloudflare lo intercepta y oculta el error al frontend
+ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "No se pudo conectar a Coolify: " + err.Error()})
}
-
var result json.RawMessage
if err := json.Unmarshal(body, &result); err != nil {
result = json.RawMessage(fmt.Sprintf(`{"raw":%q}`, string(body)))
}
+ // Si Coolify devuelve 5xx, bajar a 400 para que Cloudflare no lo intercepte
+ if status >= 500 {
+ status = fiber.StatusBadRequest
+ }
c.Status(status)
return c.JSON(result)
}
-// ─── Página principal ─────────────────────────────────────────────────────────
-
func CoolifyIndex(c *fiber.Ctx) error {
cfg, _ := models.GetCoolifyConfig()
return c.Render("coolify", fiber.Map{
@@ -85,8 +78,6 @@ func CoolifyIndex(c *fiber.Ctx) error {
}, "layouts/main")
}
-// ─── Configuración ────────────────────────────────────────────────────────────
-
func CoolifyGetConfig(c *fiber.Ctx) error {
cfg, err := models.GetCoolifyConfig()
if err != nil {
@@ -127,7 +118,6 @@ func CoolifySaveConfig(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"ok": true, "config": cfg})
}
-// CoolifyTestConnection verifica conectividad con /health (no requiere auth).
func CoolifyTestConnection(c *fiber.Ctx) error {
cfg, err := models.GetCoolifyConfig()
if err != nil {
@@ -137,7 +127,7 @@ func CoolifyTestConnection(c *fiber.Ctx) error {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get(base + "/api/health")
if err != nil {
- return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
+ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "No se pudo conectar a Coolify: " + err.Error()})
}
defer resp.Body.Close()
body, _ := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
@@ -149,171 +139,142 @@ func CoolifyTestConnection(c *fiber.Ctx) error {
return c.JSON(result)
}
-// ─── Applications ─────────────────────────────────────────────────────────────
-
func CoolifyListApplications(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/applications")
}
-
func CoolifyGetApplication(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid"))
}
-
func CoolifyApplicationLogs(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/logs")
}
-
func CoolifyApplicationEnvs(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/envs")
}
-
func CoolifyApplicationEnvCreate(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodPost, "/applications/"+c.Params("uuid")+"/envs")
}
-
func CoolifyApplicationEnvUpdateBulk(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodPatch, "/applications/"+c.Params("uuid")+"/envs")
}
-
func CoolifyApplicationEnvDelete(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodDelete, "/applications/"+c.Params("uuid")+"/envs/"+c.Params("env_id"))
}
-
func CoolifyApplicationStart(c *fiber.Ctx) error {
- return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/start")
+ return coolifyProxy(c, http.MethodPost, "/applications/"+c.Params("uuid")+"/start")
}
-
func CoolifyApplicationStop(c *fiber.Ctx) error {
- return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/stop")
+ return coolifyProxy(c, http.MethodPost, "/applications/"+c.Params("uuid")+"/stop")
}
-
func CoolifyApplicationRestart(c *fiber.Ctx) error {
- return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/restart")
+ return coolifyProxy(c, http.MethodPost, "/applications/"+c.Params("uuid")+"/restart")
}
-
func CoolifyApplicationDeployments(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/deployments")
}
-
func CoolifyApplicationDeploy(c *fiber.Ctx) error {
- // POST /api/v1/deploy?uuid={uuid}&force=false
return coolifyProxy(c, http.MethodPost, "/deploy")
}
-// ─── Servers ──────────────────────────────────────────────────────────────────
-
func CoolifyListServers(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/servers")
}
-
func CoolifyGetServer(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/servers/"+c.Params("uuid"))
}
-
func CoolifyServerResources(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/servers/"+c.Params("uuid")+"/resources")
}
-
func CoolifyServerDomains(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/servers/"+c.Params("uuid")+"/domains")
}
-
func CoolifyServerValidate(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/servers/"+c.Params("uuid")+"/validate")
}
-// ─── Services ─────────────────────────────────────────────────────────────────
-
func CoolifyListServices(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/services")
}
-
func CoolifyGetService(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid"))
}
-
func CoolifyServiceStart(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid")+"/start")
}
-
func CoolifyServiceStop(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid")+"/stop")
}
-
func CoolifyServiceRestart(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid")+"/restart")
}
-
func CoolifyServiceEnvs(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid")+"/envs")
}
-
func CoolifyServiceEnvUpdateBulk(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodPatch, "/services/"+c.Params("uuid")+"/envs")
}
-// ─── Databases ────────────────────────────────────────────────────────────────
-
func CoolifyListDatabases(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/databases")
}
-
func CoolifyGetDatabase(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/databases/"+c.Params("uuid"))
}
-
func CoolifyDatabaseStart(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/databases/"+c.Params("uuid")+"/start")
}
-
func CoolifyDatabaseStop(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/databases/"+c.Params("uuid")+"/stop")
}
-
func CoolifyDatabaseRestart(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/databases/"+c.Params("uuid")+"/restart")
}
-// ─── Projects ─────────────────────────────────────────────────────────────────
-
func CoolifyListProjects(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/projects")
}
-
func CoolifyGetProject(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/projects/"+c.Params("uuid"))
}
-
func CoolifyProjectEnvironments(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/projects/"+c.Params("uuid")+"/environments")
}
-// ─── Deployments ──────────────────────────────────────────────────────────────
-
func CoolifyListDeployments(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/deployments")
}
-
func CoolifyGetDeployment(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/deployments/"+c.Params("uuid"))
}
-// ─── Teams ────────────────────────────────────────────────────────────────────
-
func CoolifyListTeams(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/teams")
}
-
func CoolifyCurrentTeam(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/teams/current")
}
-
func CoolifyTeamMembers(c *fiber.Ctx) error {
return coolifyProxy(c, http.MethodGet, "/teams/current/members")
}
-// CoolifyWebhook recibe notificaciones push de Coolify (deployments, etc.)
-// Acepta el payload y devuelve 200; el procesamiento puede extenderse aquí.
+// CoolifyWebhook recibe notificaciones push de Coolify (deployment events, etc.)
+// Configura en Coolify UI → Settings → Notifications → Webhook → URL: https://admin.u-site.app/app/coolify/webhook
func CoolifyWebhook(c *fiber.Ctx) error {
- return c.SendStatus(fiber.StatusOK)
+ var payload map[string]interface{}
+ if err := c.BodyParser(&payload); err != nil {
+ // acepta también body vacío o texto plano
+ payload = map[string]interface{}{"raw": string(c.Body())}
+ }
+
+ // Log del evento para debug
+ eventType := ""
+ if t, ok := payload["type"].(string); ok {
+ eventType = t
+ } else if t, ok := payload["event"].(string); ok {
+ eventType = t
+ }
+
+ _ = eventType // en el futuro: guardar en DB, enviar push, etc.
+
+ return c.JSON(fiber.Map{"ok": true})
}