From 1a96caeb8300c8fe1462bbfdf8750bc654877d2d Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Thu, 21 May 2026 23:49:37 -0500 Subject: [PATCH] ip --- migrations/migrate.go | 1 + pkg/models/notif_config.go | 56 +++++++++ pkg/services/cron_service.go | 108 +++++++++++++++++- resources/views/notif_config.html | 102 ++++++++++++++++- resources/views/servidor_dashboard.html | 43 +++++-- .../controllers/servidor_alerta_controller.go | 24 ++++ rest/routes/user.go | 3 + 7 files changed, 324 insertions(+), 13 deletions(-) create mode 100644 rest/controllers/servidor_alerta_controller.go diff --git a/migrations/migrate.go b/migrations/migrate.go index ea7e121..84bcf5f 100755 --- a/migrations/migrate.go +++ b/migrations/migrate.go @@ -87,6 +87,7 @@ func Migrate() { // Sistema de notificaciones por evento &models.NotifEventoConfig{}, &models.SistemaNotificacion{}, + &models.ServidorAlertaUmbral{}, // Submódulo Partner &models.PartnerRecurso{}, &models.PartnerComunicado{}, diff --git a/pkg/models/notif_config.go b/pkg/models/notif_config.go index daa9a5d..cc135e3 100644 --- a/pkg/models/notif_config.go +++ b/pkg/models/notif_config.go @@ -1,6 +1,8 @@ package models import ( + "time" + "github.com/sujit-baniya/fiber-boilerplate/app" "gorm.io/gorm" ) @@ -130,3 +132,57 @@ func MarcarNotifsFacturasLeidas(usuarioID uint) error { Where("tipo_usuario = 'portal_user' AND usuario_id = ? AND icono = '🧾'", usuarioID). Update("leida", true).Error } + +// ─── ServidorAlertaUmbral ───────────────────────────────────────────────────── +// Configuración global de umbrales para alertas de servidores. +// Solo existe un registro (singleton, ID = 1). + +type ServidorAlertaUmbral struct { + gorm.Model + Activo bool `json:"activo" gorm:"column:activo;default:true"` + MinutosSinPing int `json:"minutos_sin_ping" gorm:"column:minutos_sin_ping;default:10"` + UmbralCPU int `json:"umbral_cpu" gorm:"column:umbral_cpu;default:90"` + UmbralRAM int `json:"umbral_ram" gorm:"column:umbral_ram;default:90"` + UmbralDisco int `json:"umbral_disco" gorm:"column:umbral_disco;default:90"` + DiasAnteVencimiento int `json:"dias_ante_vencimiento" gorm:"column:dias_ante_vencimiento;default:7"` +} + +func (ServidorAlertaUmbral) TableName() string { return "servidor_alerta_umbral" } + +// GetServidorAlertaUmbral obtiene el singleton de umbrales; crea uno con defaults si no existe. +func GetServidorAlertaUmbral() *ServidorAlertaUmbral { + var cfg ServidorAlertaUmbral + db := app.Http.Database.DB + if err := db.First(&cfg).Error; err != nil { + cfg = ServidorAlertaUmbral{Activo: true, MinutosSinPing: 10, UmbralCPU: 90, UmbralRAM: 90, UmbralDisco: 90, DiasAnteVencimiento: 7} + db.Create(&cfg) + } + return &cfg +} + +// SaveServidorAlertaUmbral guarda el singleton. +func SaveServidorAlertaUmbral(cfg *ServidorAlertaUmbral) error { + db := app.Http.Database.DB + var existing ServidorAlertaUmbral + if err := db.First(&existing).Error; err != nil { + return db.Create(cfg).Error + } + return db.Model(&existing).Updates(map[string]interface{}{ + "activo": cfg.Activo, + "minutos_sin_ping": cfg.MinutosSinPing, + "umbral_cpu": cfg.UmbralCPU, + "umbral_ram": cfg.UmbralRAM, + "umbral_disco": cfg.UmbralDisco, + "dias_ante_vencimiento": cfg.DiasAnteVencimiento, + }).Error +} + +// YaExisteAlertaServidor evita spam: true si ya se creó una notificación con el +// mismo título para este servidor en las últimas `horas` horas. +func YaExisteAlertaServidor(titulo string, horas int) bool { + var count int64 + app.Http.Database.DB.Model(&SistemaNotificacion{}). + Where("titulo = ? AND created_at > ?", titulo, time.Now().Add(-time.Duration(horas)*time.Hour)). + Count(&count) + return count > 0 +} diff --git a/pkg/services/cron_service.go b/pkg/services/cron_service.go index a564acd..16ec560 100644 --- a/pkg/services/cron_service.go +++ b/pkg/services/cron_service.go @@ -1,8 +1,11 @@ package services import ( + "encoding/json" "fmt" "log" + "strconv" + "time" "github.com/robfig/cron/v3" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" @@ -32,8 +35,14 @@ func IniciarCron() { return } + // Salud de servidores — cada 5 minutos + if _, err := cronScheduler.AddFunc("*/5 * * * *", VerificarSaludServidores); err != nil { + log.Printf("[CRON] Error registrando tarea salud_servidores: %v", err) + return + } + cronScheduler.Start() - log.Println("[CRON] Scheduler iniciado — vencimientos próximos 8AM, ya vencidos 9AM, Bold polling cada 15min") + log.Println("[CRON] Scheduler iniciado — vencimientos próximos 8AM, ya vencidos 9AM, Bold polling cada 15min, salud servidores cada 5min") } // DetenerCron para graceful shutdown @@ -208,3 +217,100 @@ func VerificarPagosBoldPendientes() { log.Println("[BOLD-POLL] Verificación completada") } + +// ─── Salud de Servidores ───────────────────────────────────────────────────── + +// VerificarSaludServidores corre cada 5 min y genera notificaciones del sistema +// cuando detecta: agente caído, recurso al límite o VPS próximo a vencer. +func VerificarSaludServidores() { + cfg := models.GetServidorAlertaUmbral() + if !cfg.Activo { + return + } + + servidores, _, err := models.GetAllServidores(1000, 0, "") + if err != nil { + log.Printf("[CRON-SRV] Error obteniendo servidores: %v", err) + return + } + + for _, srv := range servidores { + // 1. Agente caído (sin ping en X minutos) + if srv.AgentToken != "" && srv.AgentLastSeen != nil { + minutos := time.Since(*srv.AgentLastSeen).Minutes() + if minutos > float64(cfg.MinutosSinPing) { + titulo := fmt.Sprintf("🔴 Sin señal: %s", srv.Nombre) + if !models.YaExisteAlertaServidor(titulo, 1) { + crearNotifServidor(titulo, + fmt.Sprintf("El agente no reporta hace %.0f minutos (umbral: %d min)", minutos, cfg.MinutosSinPing), + "servidor_caido") + } + } + } + + // 2. Recursos (CPU / RAM / Disco) al límite + if srv.MetricasJson != "" { + var m struct { + CPU struct{ Porcentaje float64 `json:"porcentaje"` } `json:"cpu"` + RAM struct{ Porcentaje float64 `json:"porcentaje"` } `json:"ram"` + Disco struct{ Porcentaje float64 `json:"porcentaje"` } `json:"disco"` + } + if jsonErr := json.Unmarshal([]byte(srv.MetricasJson), &m); jsonErr == nil { + checkRecurso(srv.Nombre, "CPU", m.CPU.Porcentaje, cfg.UmbralCPU) + checkRecurso(srv.Nombre, "RAM", m.RAM.Porcentaje, cfg.UmbralRAM) + checkRecurso(srv.Nombre, "Disco", m.Disco.Porcentaje, cfg.UmbralDisco) + } + } + + // 3. Vencimiento próximo + if srv.Vencimiento != "" && cfg.DiasAnteVencimiento > 0 { + vence, parseErr := time.Parse("2006-01-02", srv.Vencimiento) + if parseErr == nil { + dias := int(time.Until(vence).Hours() / 24) + if dias >= 0 && dias <= cfg.DiasAnteVencimiento { + titulo := fmt.Sprintf("📅 %s vence en %s", srv.Nombre, strconv.Itoa(dias)+" días") + if dias == 0 { + titulo = fmt.Sprintf("📅 %s vence HOY", srv.Nombre) + } + if !models.YaExisteAlertaServidor(titulo, 12) { + crearNotifServidor(titulo, + fmt.Sprintf("Fecha de vencimiento: %s. Renueva a tiempo.", srv.Vencimiento), + "servidor_vence_pronto") + } + } + } + } + } +} + +func checkRecurso(nombre, recurso string, valor float64, umbral int) { + if valor < float64(umbral) { + return + } + titulo := fmt.Sprintf("⚠️ %s alto en %s (%.0f%%)", recurso, nombre, valor) + if !models.YaExisteAlertaServidor(titulo, 1) { + crearNotifServidor(titulo, + fmt.Sprintf("%s al %.0f%% (umbral: %d%%)", recurso, valor, umbral), + "servidor_recurso_alto") + } +} + +func crearNotifServidor(titulo, cuerpo, evento string) { + // Verificar si la notificación del sistema está habilitada para este evento + evtCfg := models.GetNotifConfig(evento, "admin") + // Por defecto, si no hay config, crear notificación de sistema igualmente + if evtCfg == nil || evtCfg.CanalSistema { + _ = models.CreateSistemaNotif(&models.SistemaNotificacion{ + TipoUsuario: "admin", + Titulo: titulo, + Cuerpo: cuerpo, + Url: "/app/servidor-dashboard", + Icono: "🖥️", + }) + } + // Telegram (si está configurado para este evento) + if evtCfg != nil && evtCfg.CanalTelegram { + msg := fmt.Sprintf("%s\n%s", titulo, cuerpo) + sendTelegramAdmin(msg) + } +} diff --git a/resources/views/notif_config.html b/resources/views/notif_config.html index 5cbe9cd..87a6413 100644 --- a/resources/views/notif_config.html +++ b/resources/views/notif_config.html @@ -91,7 +91,81 @@
- + +Alerta si el agente no reporta en X minutos.
+ +Alerta si la CPU supera este porcentaje.
+ +Alerta si la RAM supera este porcentaje.
+ +Alerta si el disco supera este porcentaje.
+ +Alerta X días antes de que venza el VPS.
+ +