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>
78 lines
2.4 KiB
Go
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,
|
|
})
|
|
}
|