From 277ad6bb630064ff15e3662d90aa3225eb4ba17f Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Thu, 14 May 2026 21:00:57 -0500 Subject: [PATCH] =?UTF-8?q?feat(hostinger):=20VPS=20resource=20metrics=20m?= =?UTF-8?q?odal=20=E2=80=94=20CPU,=20RAM,=20disco,=20tr=C3=A1fico,=20uptim?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/services/hostinger_service.go | 6 +- resources/views/hostinger.html | 133 +++++++++++++++++++++++ rest/controllers/hostinger_controller.go | 8 +- 3 files changed, 144 insertions(+), 3 deletions(-) diff --git a/pkg/services/hostinger_service.go b/pkg/services/hostinger_service.go index 8ccd669..f94007c 100644 --- a/pkg/services/hostinger_service.go +++ b/pkg/services/hostinger_service.go @@ -382,8 +382,10 @@ func (c *HostingerClient) SetVPSHostname(vmID int, hostname string) error { } // GetVPSMetrics obtiene las métricas de uso de una VM. -func (c *HostingerClient) GetVPSMetrics(vmID int) (json.RawMessage, error) { - path := fmt.Sprintf("/vps/v1/virtual-machines/%d/metrics", vmID) +// dateFrom y dateTo deben estar en formato RFC3339, p. ej. "2025-01-01T00:00:00Z". +func (c *HostingerClient) GetVPSMetrics(vmID int, dateFrom, dateTo string) (json.RawMessage, error) { + path := fmt.Sprintf("/vps/v1/virtual-machines/%d/metrics?date_from=%s&date_to=%s", + vmID, dateFrom, dateTo) body, err := c.getRaw(context.Background(), path) if err != nil { return nil, err diff --git a/resources/views/hostinger.html b/resources/views/hostinger.html index cd2546c..c000758 100644 --- a/resources/views/hostinger.html +++ b/resources/views/hostinger.html @@ -109,6 +109,10 @@ class="flex-1 text-xs py-1.5 rounded border border-gray-300 text-gray-600 hover:bg-gray-50 transition"> ✏️ Hostname + @@ -313,6 +317,80 @@ + +
+
+
+

+ +
+
+
Cargando métricas...
+
+ +
+
+ CPU + +
+
+
+
+
+
+ +
+
+ RAM + +
+
+
+
+
+
+ +
+
+ Disco + +
+
+
+
+
+
+ +
+
+
↓ Entrante
+
+
+
+
↑ Saliente
+
+
+
+ +
+ Uptime + +
+
Promedio últimas 24 horas
+
+
+
+
+
@@ -505,6 +583,11 @@ document.addEventListener('alpine:init', () => { // Nameservers nsModal: false, nsForm: { domain: '', ns1: '', ns2: '', ns3: '', ns4: '' }, + // Métricas VPS + metricsModal: false, + metricsData: null, + metricsVPS: null, + metricsLoading: false, toast: { show: false, msg: '', type: 'ok' }, async init() { @@ -629,6 +712,56 @@ document.addEventListener('alpine:init', () => { this.loading = false; }, + async openMetrics(v) { + this.metricsVPS = v; + this.metricsData = null; + this.metricsModal = true; + this.metricsLoading = true; + try { + const res = await axios.get(`/app/hostinger/vps/${v.id}/metrics`); + this.metricsData = res.data; + } catch (e) { + this.showToast(e.response?.data?.error || 'Error al cargar métricas', 'error'); + this.metricsModal = false; + } + this.metricsLoading = false; + }, + + latestMetricVal(usage) { + if (!usage) return 0; + const keys = Object.keys(usage).sort(); + if (!keys.length) return 0; + return usage[keys[keys.length - 1]] || 0; + }, + + avgMetricVal(usage) { + if (!usage) return 0; + const vals = Object.values(usage); + if (!vals.length) return 0; + return vals.reduce((a, b) => a + b, 0) / vals.length; + }, + + sumMetricVal(usage) { + if (!usage) return 0; + return Object.values(usage).reduce((a, b) => a + b, 0); + }, + + formatBytesRaw(bytes) { + if (!bytes || bytes === 0) return '—'; + const gb = bytes / (1024 ** 3); + if (gb >= 1) return gb.toFixed(1) + ' GB'; + return (bytes / (1024 ** 2)).toFixed(0) + ' MB'; + }, + + formatUptime(ms) { + if (!ms) return '—'; + const s = Math.floor(ms / 1000); + const h = Math.floor(s / 3600); + const m = Math.floor((s % 3600) / 60); + if (h >= 24) return `${Math.floor(h / 24)}d ${h % 24}h`; + return `${h}h ${m}m`; + }, + async updateNameservers() { this.loading = true; try { diff --git a/rest/controllers/hostinger_controller.go b/rest/controllers/hostinger_controller.go index c856357..226fe57 100644 --- a/rest/controllers/hostinger_controller.go +++ b/rest/controllers/hostinger_controller.go @@ -1,6 +1,8 @@ package controllers import ( + "time" + "github.com/gofiber/fiber/v2" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" "github.com/sujit-baniya/fiber-boilerplate/pkg/services" @@ -317,6 +319,7 @@ func SetHostingerVPSHostname(c *fiber.Ctx) error { } // GetHostingerVPSMetrics retorna las métricas de una VM. +// Acepta query params date_from y date_to (RFC3339). Por defecto: últimas 24 h. func GetHostingerVPSMetrics(c *fiber.Ctx) error { id, err := c.ParamsInt("id") if err != nil { @@ -326,7 +329,10 @@ func GetHostingerVPSMetrics(c *fiber.Ctx) error { if errC != nil { return c.Status(fiber.StatusFailedDependency).JSON(fiber.Map{"error": "No se encontró configuración activa de Hostinger"}) } - data, err := client.GetVPSMetrics(id) + now := time.Now().UTC() + dateFrom := c.Query("date_from", now.Add(-24*time.Hour).Format(time.RFC3339)) + dateTo := c.Query("date_to", now.Format(time.RFC3339)) + data, err := client.GetVPSMetrics(id, dateFrom, dateTo) if err != nil { return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{"error": err.Error()}) }