feat: Implementación completa Fase 4 - Sistema de monitoreo con API REST y WebSocket
✨ Nuevas funcionalidades: - API REST unificada en puerto 8080 (eliminado CORS) - WebSocket para logs en tiempo real desde journalctl - Integración completa con systemd para gestión de servicios - Escaneo automático de procesos Node.js y Python - Rate limiting (1 operación/segundo por app) - Interface web moderna con Tailwind CSS (tema oscuro) - Documentación API estilo Swagger completamente en español 🎨 Interface Web (todas las páginas en español): - Dashboard con estadísticas en tiempo real - Visor de escaneo de procesos con filtros - Formulario de registro de aplicaciones con variables de entorno - Visor de logs en tiempo real con WebSocket y sidebar - Página de selección de apps detectadas - Documentación completa de API REST 🏗️ Arquitectura: - Módulo models: ServiceConfig, ManagedApp, AppStatus - Módulo systemd: wrapper de systemctl, generador de .service, parser - Módulo orchestrator: AppManager, LifecycleManager con validaciones - Módulo api: handlers REST, WebSocket manager, DTOs - Servidor unificado en puerto 8080 (Web + API + WS) 🔧 Mejoras técnicas: - Eliminación de CORS mediante servidor unificado - Separación clara frontend/backend con carga dinámica - Thread-safe con Arc<DashMap> para estado compartido - Reconciliación de estados: sysinfo vs systemd - Validaciones de paths, usuarios y configuraciones - Manejo robusto de errores con thiserror 📝 Documentación: - README.md actualizado con arquitectura completa - EJEMPLOS.md con casos de uso detallados - ESTADO_PROYECTO.md con progreso de Fase 4 - API docs interactiva en /api-docs - Script de despliegue mejorado con health checks 🚀 Producción: - Deployment script con validaciones - Health checks y rollback capability - Configuración de sudoers para systemctl - Hardening de seguridad en servicios systemd
This commit is contained in:
@@ -4,6 +4,8 @@ use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};
|
||||
use std::time::Duration;
|
||||
use crate::logger::get_logger;
|
||||
use crate::config::get_config_manager;
|
||||
use crate::systemd::SystemCtl;
|
||||
use crate::models::{AppStatus, ServiceStatus};
|
||||
|
||||
// User-Agent dinámico
|
||||
fn generate_user_agent() -> String {
|
||||
@@ -24,6 +26,9 @@ struct AppStatusUpdate {
|
||||
memory_usage: String,
|
||||
cpu_usage: String,
|
||||
last_check: String,
|
||||
systemd_status: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
discrepancy: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn run_monitoring(server_name: String, api_key: String, cloud_url: String) {
|
||||
@@ -47,7 +52,16 @@ pub async fn run_monitoring(server_name: String, api_key: String, cloud_url: Str
|
||||
}
|
||||
|
||||
for app in apps_to_monitor {
|
||||
let data = collect_metrics(&sys, &app.name, app.port, &server_name);
|
||||
let data = collect_metrics_with_systemd(&sys, &app.name, app.port, &server_name);
|
||||
|
||||
// Reportar discrepancias
|
||||
if let Some(ref disc) = data.discrepancy {
|
||||
logger.warning(
|
||||
"Monitor",
|
||||
&format!("Discrepancia detectada en {}", app.name),
|
||||
Some(disc)
|
||||
);
|
||||
}
|
||||
|
||||
match send_to_cloud(data, &api_key, &cloud_url, &user_agent).await {
|
||||
Ok(_) => {},
|
||||
@@ -66,41 +80,63 @@ pub async fn run_monitoring(server_name: String, api_key: String, cloud_url: Str
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_metrics(sys: &System, name: &str, port: i32, server: &str) -> AppStatusUpdate {
|
||||
fn collect_metrics_with_systemd(sys: &System, name: &str, port: i32, server: &str) -> AppStatusUpdate {
|
||||
let mut pid_encontrado = 0;
|
||||
let mut cpu = 0.0;
|
||||
let mut mem = 0.0;
|
||||
let mut status = "stopped".to_string();
|
||||
let mut process_detected = false;
|
||||
|
||||
// 1. Detección por proceso (método original)
|
||||
for (pid, process) in sys.processes() {
|
||||
let process_name = process.name();
|
||||
|
||||
if process_name.contains("node") {
|
||||
// Soportar node y python
|
||||
if process_name.contains("node") || process_name.contains("python") {
|
||||
if let Some(cwd) = process.cwd() {
|
||||
let cwd_str = cwd.to_string_lossy();
|
||||
if cwd_str.contains(name) {
|
||||
pid_encontrado = pid.as_u32() as i32;
|
||||
cpu = process.cpu_usage();
|
||||
mem = process.memory() as f64 / 1024.0 / 1024.0;
|
||||
status = "running".to_string();
|
||||
process_detected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Consultar systemd
|
||||
let service_name = format!("{}.service", name);
|
||||
let systemd_status = SystemCtl::status(&service_name);
|
||||
|
||||
// 3. Reconciliar estados
|
||||
let final_status = AppStatus::reconcile(process_detected, &systemd_status);
|
||||
|
||||
// 4. Detectar discrepancias
|
||||
let discrepancy = match (&final_status, process_detected, &systemd_status) {
|
||||
(AppStatus::Crashed, false, ServiceStatus::Active) => {
|
||||
Some(format!("Systemd reporta activo pero proceso no detectado"))
|
||||
}
|
||||
(AppStatus::Zombie, true, ServiceStatus::Inactive) => {
|
||||
Some(format!("Proceso detectado pero systemd reporta inactivo"))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let now = chrono::Local::now();
|
||||
let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
|
||||
AppStatusUpdate {
|
||||
app_name: name.to_string(),
|
||||
server: server.to_string(),
|
||||
status,
|
||||
status: final_status.as_str().to_string(),
|
||||
port,
|
||||
pid: pid_encontrado,
|
||||
pid: if pid_encontrado > 0 { pid_encontrado } else { 0 },
|
||||
memory_usage: format!("{:.2}MB", mem),
|
||||
cpu_usage: format!("{:.2}%", cpu),
|
||||
last_check: timestamp,
|
||||
systemd_status: systemd_status.as_str().to_string(),
|
||||
discrepancy,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,4 +193,4 @@ async fn send_to_cloud(
|
||||
eprintln!("⚠️ Error HTTP {}: {}", status, error_text);
|
||||
Err(format!("HTTP {}: {}", status, error_text).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user