From ce2ffff3e8cf63cd15b63256f3bf858ca28ef196 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Mon, 25 May 2026 21:01:58 -0500 Subject: [PATCH] feat: add missing Coolify controller, view and routes - rest/controllers/coolify_controller.go: proxy completo hacia API Coolify - resources/views/coolify.html: interfaz Alpine.js con tabs apps/servers/services/dbs/projects/deployments/team - rest/routes/user.go: registra todas las rutas /app/coolify/* Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- resources/views/coolify.html | 1186 ++++++++++++++++++++++++ rest/controllers/coolify_controller.go | 253 +++++ rest/routes/user.go | 50 + 3 files changed, 1489 insertions(+) create mode 100644 resources/views/coolify.html create mode 100644 rest/controllers/coolify_controller.go diff --git a/resources/views/coolify.html b/resources/views/coolify.html new file mode 100644 index 0000000..6e4c915 --- /dev/null +++ b/resources/views/coolify.html @@ -0,0 +1,1186 @@ +
+ + +
+ Cargando... +
+ +
+ + +
+
+

Coolify

+

Gestión completa de aplicaciones, servicios, bases de datos, servidores y despliegues a través de la API de Coolify.

+
+ +
+ + +
+ Sin configuración activa. Haz clic en "Configurar API" para ingresar la URL base y el token de tu instancia de Coolify. + El token lo generas en Coolify → Keys & Tokens → API tokens. +
+ + +
+ + +
+ + +
+ +
+ + +
+ + + +
+ + +
+ + +
+ + +
+
Sin aplicaciones. Haz clic en "Actualizar".
+
+ +
+
+ + +
+
Sin servidores. Haz clic en "Actualizar".
+
+ +
+
+ + +
+
Sin servicios. Haz clic en "Actualizar".
+
+ +
+
+ + +
+
Sin bases de datos. Haz clic en "Actualizar".
+
+ +
+
+ + +
+
Sin proyectos. Haz clic en "Actualizar".
+
+ +
+
+ + +
+
Sin despliegues. Haz clic en "Actualizar".
+
+ + + + + + + + + + + + + +
AplicaciónStatusCommitIniciadoLogs
+
+
+ + +
+ +
+

+

+
+ ID: + Equipo personal +
+
+ +
Sin miembros cargados.
+
+

Miembros del equipo

+
+ + + + + + + + + + + +
NombreEmailRol
+
+
+
+ +
+ + + +
+
+
+

Configurar Coolify API

+

Ingresa la URL base de tu instancia y el token de API.

+
+
+
+ + +
+
+ + +

Ej: http://10.0.0.1:8000 o https://app.coolify.io

+
+
+ + +

Genera el token en Coolify → Keys & Tokens → API tokens. Deja en blanco para no cambiar el token guardado.

+
+
+ + +
+
+
+ +
+ + +
+
+
+
+
+ + +
+
+
+

+
+ + +
+
+
+ Cargando logs... + Sin logs disponibles. + +
+
+
+ + +
+
+
+

+ +
+
+
Cargando variables...
+
Sin variables de entorno.
+ +
+ +
+

Agregar variable

+
+ + + +
+
+
+
+ + +
+
+
+

+ +
+
+
Cargando...
+
Sin despliegues registrados.
+ + + + + + + + + + + + + +
UUIDStatusCommitFechaLogs
+
+
+
+ + +
+
+
+

Log de despliegue

+ +
+
+ Sin log disponible. + +
+
+
+ + +
+
+
+

+ +
+
+
Cargando recursos...
+
Sin recursos.
+
+ +
+
+
+
+ + +
+
+
+

+ +
+
+
Cargando dominios...
+
Sin dominios.
+ +
+
+
+ + +
+
+
+

+ +
+
+
Cargando entornos...
+
Sin entornos.
+ +
+
+
+ +
+ + diff --git a/rest/controllers/coolify_controller.go b/rest/controllers/coolify_controller.go new file mode 100644 index 0000000..1cf4dfe --- /dev/null +++ b/rest/controllers/coolify_controller.go @@ -0,0 +1,253 @@ +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/sujit-baniya/fiber-boilerplate/pkg/models" +) + +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 { + return nil, 0, err + } + req.Header.Set("Authorization", "Bearer "+cfg.ApiToken) + req.Header.Set("Accept", "application/json") + if contentType != "" { + req.Header.Set("Content-Type", contentType) + } + resp, err := client.Do(req) + if err != nil { + return nil, 0, err + } + defer resp.Body.Close() + body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024*1024)) + return body, resp.StatusCode, nil +} + +func coolifyProxy(c *fiber.Ctx, method, endpoint string) error { + cfg, err := models.GetCoolifyConfig() + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Configura primero la integración Coolify"}) + } + 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()}) + } + var result json.RawMessage + if err := json.Unmarshal(body, &result); err != nil { + result = json.RawMessage(fmt.Sprintf(`{"raw":%q}`, string(body))) + } + c.Status(status) + return c.JSON(result) +} + +func CoolifyIndex(c *fiber.Ctx) error { + cfg, _ := models.GetCoolifyConfig() + return c.Render("coolify", fiber.Map{ + "user": c.Locals("user").(map[string]interface{}), + "modules": c.Locals("userModules"), + "config": cfg, + }, "layouts/main") +} + +func CoolifyGetConfig(c *fiber.Ctx) error { + cfg, err := models.GetCoolifyConfig() + if err != nil { + return c.JSON(fiber.Map{"config": nil}) + } + masked := "••••••••" + if cfg.ApiToken == "" { + masked = "" + } + return c.JSON(fiber.Map{"config": fiber.Map{ + "ID": cfg.ID, + "nombre": cfg.Nombre, + "base_url": cfg.BaseURL, + "api_token": masked, + "activo": cfg.Activo, + }}) +} + +func CoolifySaveConfig(c *fiber.Ctx) error { + type Req struct { + Nombre string `json:"nombre"` + BaseURL string `json:"base_url"` + ApiToken string `json:"api_token"` + Activo bool `json:"activo"` + } + var req Req + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"}) + } + req.BaseURL = strings.TrimRight(strings.TrimSpace(req.BaseURL), "/") + if req.BaseURL == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "base_url es requerido"}) + } + cfg, err := models.UpsertCoolifyConfig(req.Nombre, req.BaseURL, req.ApiToken, req.Activo) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) + } + return c.JSON(fiber.Map{"ok": true, "config": cfg}) +} + +func CoolifyTestConnection(c *fiber.Ctx) error { + cfg, err := models.GetCoolifyConfig() + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Sin config"}) + } + base := strings.TrimRight(cfg.BaseURL, "/") + 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()}) + } + defer resp.Body.Close() + body, _ := io.ReadAll(io.LimitReader(resp.Body, 64*1024)) + var result json.RawMessage + if jsonErr := json.Unmarshal(body, &result); jsonErr != nil { + result = json.RawMessage(fmt.Sprintf(`{"raw":%q}`, string(body))) + } + c.Status(resp.StatusCode) + return c.JSON(result) +} + +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") +} +func CoolifyApplicationStop(c *fiber.Ctx) error { + return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/stop") +} +func CoolifyApplicationRestart(c *fiber.Ctx) error { + return coolifyProxy(c, http.MethodGet, "/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 { + return coolifyProxy(c, http.MethodPost, "/deploy") +} + +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") +} + +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") +} + +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") +} + +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") +} + +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")) +} + +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") +} diff --git a/rest/routes/user.go b/rest/routes/user.go index 99bb5d0..72615cc 100755 --- a/rest/routes/user.go +++ b/rest/routes/user.go @@ -162,6 +162,56 @@ func UserRoutes(app fiber.Router) { protected.Get("/cloudflare/zones/:zone_id/ssl", controllers.GetCloudflareSSL) protected.Get("/cloudflare/zones/:zone_id/firewall", controllers.GetCloudflareFirewall) + // ─── Coolify API ────────────────────────────────────────────────── + protected.Get("/coolify", middlewares.MenuMiddleware, controllers.CoolifyIndex) + protected.Get("/coolify/config", controllers.CoolifyGetConfig) + protected.Post("/coolify/config", controllers.CoolifySaveConfig) + protected.Get("/coolify/health", controllers.CoolifyTestConnection) + // Applications + protected.Get("/coolify/apps", controllers.CoolifyListApplications) + protected.Get("/coolify/apps/:uuid", controllers.CoolifyGetApplication) + protected.Get("/coolify/apps/:uuid/logs", controllers.CoolifyApplicationLogs) + protected.Get("/coolify/apps/:uuid/envs", controllers.CoolifyApplicationEnvs) + protected.Post("/coolify/apps/:uuid/envs", controllers.CoolifyApplicationEnvCreate) + protected.Patch("/coolify/apps/:uuid/envs", controllers.CoolifyApplicationEnvUpdateBulk) + protected.Delete("/coolify/apps/:uuid/envs/:env_id", controllers.CoolifyApplicationEnvDelete) + protected.Get("/coolify/apps/:uuid/start", controllers.CoolifyApplicationStart) + protected.Get("/coolify/apps/:uuid/stop", controllers.CoolifyApplicationStop) + protected.Get("/coolify/apps/:uuid/restart", controllers.CoolifyApplicationRestart) + protected.Get("/coolify/apps/:uuid/deployments", controllers.CoolifyApplicationDeployments) + protected.Post("/coolify/apps/:uuid/deploy", controllers.CoolifyApplicationDeploy) + // Servers + protected.Get("/coolify/servers", controllers.CoolifyListServers) + protected.Get("/coolify/servers/:uuid", controllers.CoolifyGetServer) + protected.Get("/coolify/servers/:uuid/resources", controllers.CoolifyServerResources) + protected.Get("/coolify/servers/:uuid/domains", controllers.CoolifyServerDomains) + protected.Get("/coolify/servers/:uuid/validate", controllers.CoolifyServerValidate) + // Services + protected.Get("/coolify/services", controllers.CoolifyListServices) + protected.Get("/coolify/services/:uuid", controllers.CoolifyGetService) + protected.Get("/coolify/services/:uuid/start", controllers.CoolifyServiceStart) + protected.Get("/coolify/services/:uuid/stop", controllers.CoolifyServiceStop) + protected.Get("/coolify/services/:uuid/restart", controllers.CoolifyServiceRestart) + protected.Get("/coolify/services/:uuid/envs", controllers.CoolifyServiceEnvs) + protected.Patch("/coolify/services/:uuid/envs", controllers.CoolifyServiceEnvUpdateBulk) + // Databases + protected.Get("/coolify/databases", controllers.CoolifyListDatabases) + protected.Get("/coolify/databases/:uuid", controllers.CoolifyGetDatabase) + protected.Get("/coolify/databases/:uuid/start", controllers.CoolifyDatabaseStart) + protected.Get("/coolify/databases/:uuid/stop", controllers.CoolifyDatabaseStop) + protected.Get("/coolify/databases/:uuid/restart", controllers.CoolifyDatabaseRestart) + // Projects + protected.Get("/coolify/projects", controllers.CoolifyListProjects) + protected.Get("/coolify/projects/:uuid", controllers.CoolifyGetProject) + protected.Get("/coolify/projects/:uuid/environments", controllers.CoolifyProjectEnvironments) + // Deployments + protected.Get("/coolify/deployments", controllers.CoolifyListDeployments) + protected.Get("/coolify/deployments/:uuid", controllers.CoolifyGetDeployment) + // Team + protected.Get("/coolify/team", controllers.CoolifyCurrentTeam) + protected.Get("/coolify/team/members", controllers.CoolifyTeamMembers) + protected.Get("/coolify/teams", controllers.CoolifyListTeams) + // ─── Pasarelas de Pago ──────────────────────────────────────────── protected.Get("/pasarelas-pago", middlewares.MenuMiddleware, controllers.PasarelasPage) // Bold