feat: Sistema de monitoreo base con logging y configuración dinámica

- Implementado monitor de procesos Node.js con detección automática
- Sistema de logging con niveles (Info, Warning, Error, Critical)
- ConfigManager para gestión dinámica de apps monitoreadas
- Interfaz web básica con escaneo de procesos
- Integración con API central para reportar estados
- User-Agent tracking para identificación de agentes
- Persistencia de configuración en JSON
- Logs almacenados en archivo con rotación
- Sistema modular: monitor, interface, logger, config

Estructura:
- src/main.rs: Orquestador principal
- src/monitor.rs: Monitoreo de procesos y envío a API
- src/interface.rs: Servidor web Axum con endpoints
- src/logger.rs: Sistema de logging a archivo y consola
- src/config.rs: Gestión de configuración persistente
- web/: Templates HTML para interfaz web
- config/: Configuración de apps monitoreadas
- logs/: Archivos de log del sistema

Features implementadas:
 Detección automática de procesos Node.js
 Monitoreo de CPU y RAM por proceso
 Reportes periódicos a API central (cada 60s)
 Interfaz web en puerto 8080
 Logs estructurados con timestamps
 Configuración dinámica sin reinicio
 Script de despliegue automatizado

Próximos pasos:
- Integración con systemd para control de procesos
- Dashboard mejorado con cards de apps
- Logs en tiempo real vía WebSocket
- Start/Stop/Restart de aplicaciones
This commit is contained in:
2026-01-11 23:14:09 -05:00
parent bc1953fce1
commit 3595e55a1e
20 changed files with 3465 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
use serde::{Serialize, Deserialize};
use std::fs::{self, create_dir_all};
use std::path::Path;
use std::sync::{Arc, RwLock, OnceLock};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonitoredApp {
pub name: String,
pub port: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub apps: Vec<MonitoredApp>,
}
impl Default for AppConfig {
fn default() -> Self {
AppConfig {
apps: vec![
MonitoredApp { name: "app_tareas".to_string(), port: 3000 },
MonitoredApp { name: "fidelizacion".to_string(), port: 3001 },
]
}
}
}
pub struct ConfigManager {
config_path: String,
config: Arc<RwLock<AppConfig>>,
}
impl ConfigManager {
pub fn new(config_path: &str) -> Self {
// Crear directorio config si no existe
if let Some(parent) = Path::new(config_path).parent() {
let _ = create_dir_all(parent);
}
// Cargar o crear configuración
let config = Self::load_config(config_path);
ConfigManager {
config_path: config_path.to_string(),
config: Arc::new(RwLock::new(config)),
}
}
fn load_config(path: &str) -> AppConfig {
match fs::read_to_string(path) {
Ok(content) => {
match serde_json::from_str(&content) {
Ok(config) => {
println!("✅ Configuración cargada desde: {}", path);
config
}
Err(e) => {
eprintln!("⚠️ Error parseando config: {}. Usando default.", e);
AppConfig::default()
}
}
}
Err(_) => {
println!(" Archivo de config no encontrado. Creando uno nuevo...");
let default_config = AppConfig::default();
let _ = Self::save_config_to_file(path, &default_config);
default_config
}
}
}
fn save_config_to_file(path: &str, config: &AppConfig) -> std::io::Result<()> {
let json = serde_json::to_string_pretty(config)?;
fs::write(path, json)?;
println!("💾 Configuración guardada en: {}", path);
Ok(())
}
pub fn get_apps(&self) -> Vec<MonitoredApp> {
let config = self.config.read().unwrap();
config.apps.clone()
}
pub fn add_app(&self, name: String, port: i32) -> Result<(), String> {
let mut config = self.config.write().unwrap();
// Verificar si ya existe
if config.apps.iter().any(|app| app.name == name) {
return Err(format!("La app '{}' ya está siendo monitoreada", name));
}
config.apps.push(MonitoredApp { name, port });
// Guardar en disco
match Self::save_config_to_file(&self.config_path, &config) {
Ok(_) => Ok(()),
Err(e) => Err(format!("Error al guardar configuración: {}", e))
}
}
pub fn remove_app(&self, name: &str) -> Result<(), String> {
let mut config = self.config.write().unwrap();
let original_len = config.apps.len();
config.apps.retain(|app| app.name != name);
if config.apps.len() == original_len {
return Err(format!("La app '{}' no se encontró", name));
}
// Guardar en disco
match Self::save_config_to_file(&self.config_path, &config) {
Ok(_) => Ok(()),
Err(e) => Err(format!("Error al guardar configuración: {}", e))
}
}
pub fn get_arc(&self) -> Arc<RwLock<AppConfig>> {
Arc::clone(&self.config)
}
}
// Singleton global del ConfigManager
static CONFIG_MANAGER: OnceLock<ConfigManager> = OnceLock::new();
// ⚠️ IMPORTANTE: Esta función DEBE ser pública
pub fn get_config_manager() -> &'static ConfigManager {
CONFIG_MANAGER.get_or_init(|| ConfigManager::new("config/monitored_apps.json"))
}