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...
+
+
+
+
+
+
+
+
+
+
+
+ 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()})
}