Files
soft_usite/rest/controllers/agent_controller.go
T
2026-06-03 20:10:22 -05:00

123 lines
3.9 KiB
Go

package controllers
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"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,
"ultimo_ping": now.Format(time.RFC3339),
}
if req.Metricas != "" {
updates["metricas_json"] = req.Metricas
// Actualizar campos planos desde las métricas del agente (la verdad viene del agente)
type agentRAM struct {
TotalGB float64 `json:"total_gb"`
}
type agentCPU struct {
Nucleos int `json:"nucleos"`
}
var rawMap map[string]any
if jsonErr := json.Unmarshal([]byte(req.Metricas), &rawMap); jsonErr == nil {
if os, _ := rawMap["os"].(string); os != "" {
updates["so"] = os
}
if ram, _ := rawMap["ram"].(map[string]any); ram != nil {
if total, _ := ram["total_gb"].(float64); total > 0 {
updates["ram"] = fmt.Sprintf("%.0f GB", total)
}
}
if cpu, _ := rawMap["cpu"].(map[string]any); cpu != nil {
if n, _ := cpu["nucleos"].(float64); n > 0 {
updates["nucleos"] = fmt.Sprintf("%d", int(n))
}
}
// Compatibilidad: nuevo formato (discos array) y viejo (disco objeto)
discos, hasArray := rawMap["discos"].([]any)
if hasArray {
var maxTotal float64
for _, item := range discos {
if d, _ := item.(map[string]any); d != nil {
if t, _ := d["total_gb"].(float64); t > maxTotal {
maxTotal = t
}
}
}
if maxTotal > 0 {
updates["disco"] = fmt.Sprintf("%.0f GB", maxTotal)
}
} else if disco, _ := rawMap["disco"].(map[string]any); disco != nil {
if total, _ := disco["total_gb"].(float64); total > 0 {
updates["disco"] = fmt.Sprintf("%.0f GB", total)
}
}
}
}
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,
})
}