Files
soft_usite/rest/controllers/agent_controller.go
T
Lizandro GuarnizoandCopilot d9d3f8a5eb feat: sistema de agente ligero para monitoreo de servidores
Backend:
- Servidor model: AgentToken, AgentLastSeen, MetricasJson nuevos campos
- AgentHeartbeat: endpoint público POST /agent/heartbeat (auth por token)
- GenerateAgentToken: endpoint protegido POST /app/servidor/:id/agent-token
- Ruta pública /agent/install.sh sirve el script de instalación

Agente Go (agent/):
- agent/main.go: binario independiente con gopsutil
  - Recolecta RAM, CPU, Disco, Uptime, OS, Load average
  - Lee agent.yml o flags --api-url / --token
  - Envía POST a /agent/heartbeat cada N segundos
  - Instala como servicio systemd via install.sh
- agent/go.mod: módulo independiente (usite-agent)
- agent/agent.yml.sample: configuración de ejemplo
- agent/install.sh: instalador en 1 curl para Linux (systemd)

Frontend servidor_dashboard.html:
- Cards: badge ● En línea / ● Fuera / ○ Sin agente
- Barras de progreso live: RAM%, CPU%, Disco%
- Valores: GB usado/total, núcleos, uptime, OS real
- Tiempo desde último reporte
- Botón 'Agente' en cada card → modal de configuración
- Modal agente: estado, token con copy, comando curl 1 línea,
  instalación manual/avanzada con collapse, métricas actuales

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-21 11:29:24 -05:00

78 lines
2.4 KiB
Go

package controllers
import (
"crypto/rand"
"encoding/hex"
"time"
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/app"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
// AgentHeartbeat recibe métricas del agente instalado en un servidor.
// Ruta pública: POST /agent/heartbeat — autenticada por token en JSON body.
func AgentHeartbeat(c *fiber.Ctx) error {
type HeartbeatRequest struct {
Token string `json:"token"`
Metricas string `json:"metricas"` // JSON string ya serializado por el agente
}
var req HeartbeatRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"ok": false, "error": "body inválido"})
}
if req.Token == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"ok": false, "error": "token requerido"})
}
db := app.Http.Database.DB
var servidor models.Servidor
if err := db.Where("agent_token = ?", req.Token).First(&servidor).Error; err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"ok": false, "error": "token inválido"})
}
now := time.Now()
updates := map[string]interface{}{
"agent_last_seen": &now,
}
if req.Metricas != "" {
updates["metricas_json"] = req.Metricas
}
if err := db.Model(&servidor).Updates(updates).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"ok": false, "error": "no se pudo guardar"})
}
return c.JSON(fiber.Map{"ok": true, "servidor_id": servidor.ID})
}
// GenerateAgentToken genera o regenera el token de agente para un servidor.
// Ruta protegida: POST /app/servidor/:id/agent-token
func GenerateAgentToken(c *fiber.Ctx) error {
id := c.Params("id")
db := app.Http.Database.DB
var servidor models.Servidor
if err := db.First(&servidor, id).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "servidor no encontrado"})
}
// Generar token aleatorio de 32 bytes (64 hex chars)
tokenBytes := make([]byte, 32)
if _, err := rand.Read(tokenBytes); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "no se pudo generar token"})
}
token := hex.EncodeToString(tokenBytes)
if err := db.Model(&servidor).Update("agent_token", token).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "no se pudo guardar token"})
}
return c.JSON(fiber.Map{
"ok": true,
"agent_token": token,
"servidor_id": servidor.ID,
})
}