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, }) }