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:
128
src/orchestrator/app_manager.rs
Normal file
128
src/orchestrator/app_manager.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
use super::{Result, OrchestratorError};
|
||||
use crate::models::{ServiceConfig, ManagedApp, AppStatus};
|
||||
use crate::systemd::{ServiceGenerator, SystemCtl};
|
||||
use crate::logger::get_logger;
|
||||
use dashmap::DashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct AppManager {
|
||||
apps: Arc<DashMap<String, ServiceConfig>>,
|
||||
}
|
||||
|
||||
impl AppManager {
|
||||
pub fn new() -> Self {
|
||||
AppManager {
|
||||
apps: Arc::new(DashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_app(&self, config: ServiceConfig) -> Result<()> {
|
||||
let logger = get_logger();
|
||||
|
||||
// Validar configuración
|
||||
config.validate()
|
||||
.map_err(|e| OrchestratorError::ValidationError(e))?;
|
||||
|
||||
// Verificar si ya existe
|
||||
if self.apps.contains_key(&config.app_name) {
|
||||
logger.warning("AppManager", "Aplicación ya registrada", Some(&config.app_name));
|
||||
return Err(OrchestratorError::AppAlreadyExists(config.app_name.clone()));
|
||||
}
|
||||
|
||||
// Verificar si el servicio ya existe en systemd
|
||||
if SystemCtl::is_service_exists(&config.service_name()) {
|
||||
logger.warning("AppManager", "Servicio systemd ya existe", Some(&config.service_name()));
|
||||
return Err(OrchestratorError::AppAlreadyExists(
|
||||
format!("El servicio {} ya existe en systemd", config.service_name())
|
||||
));
|
||||
}
|
||||
|
||||
logger.info("AppManager", &format!("Registrando aplicación: {}", config.app_name));
|
||||
|
||||
// Generar archivo de servicio
|
||||
let service_content = ServiceGenerator::create_service(&config)?;
|
||||
ServiceGenerator::write_service_file(&config, &service_content)?;
|
||||
|
||||
// Recargar daemon de systemd
|
||||
SystemCtl::daemon_reload()?;
|
||||
|
||||
// Habilitar el servicio
|
||||
SystemCtl::enable(&config.service_name())?;
|
||||
|
||||
// Guardar en memoria
|
||||
self.apps.insert(config.app_name.clone(), config.clone());
|
||||
|
||||
logger.info("AppManager", &format!("Aplicación {} registrada exitosamente", config.app_name));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unregister_app(&self, app_name: &str) -> Result<()> {
|
||||
let logger = get_logger();
|
||||
|
||||
logger.info("AppManager", &format!("Desregistrando aplicación: {}", app_name));
|
||||
|
||||
// Obtener configuración
|
||||
let config = self.apps.get(app_name)
|
||||
.ok_or_else(|| OrchestratorError::AppNotFound(app_name.to_string()))?;
|
||||
|
||||
let service_name = config.service_name();
|
||||
drop(config); // Liberar el lock
|
||||
|
||||
// Detener el servicio si está corriendo
|
||||
let _ = SystemCtl::stop(&service_name);
|
||||
|
||||
// Deshabilitar el servicio
|
||||
let _ = SystemCtl::disable(&service_name);
|
||||
|
||||
// Eliminar archivo de servicio
|
||||
ServiceGenerator::delete_service_file(&service_name)?;
|
||||
|
||||
// Recargar daemon
|
||||
SystemCtl::daemon_reload()?;
|
||||
|
||||
// Eliminar de memoria
|
||||
self.apps.remove(app_name);
|
||||
|
||||
logger.info("AppManager", &format!("Aplicación {} desregistrada exitosamente", app_name));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_apps(&self) -> Vec<String> {
|
||||
self.apps.iter()
|
||||
.map(|entry| entry.key().clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_app(&self, app_name: &str) -> Option<ServiceConfig> {
|
||||
self.apps.get(app_name).map(|entry| entry.clone())
|
||||
}
|
||||
|
||||
pub fn app_exists(&self, app_name: &str) -> bool {
|
||||
self.apps.contains_key(app_name)
|
||||
}
|
||||
|
||||
pub fn get_app_status(&self, app_name: &str) -> Option<ManagedApp> {
|
||||
let config = self.get_app(app_name)?;
|
||||
let systemd_status = SystemCtl::status(&config.service_name());
|
||||
|
||||
// Por ahora retornamos información básica
|
||||
// El monitor.rs se encargará de enriquecer con PID, CPU, RAM
|
||||
Some(ManagedApp {
|
||||
name: app_name.to_string(),
|
||||
status: AppStatus::reconcile(false, &systemd_status),
|
||||
pid: None,
|
||||
cpu_usage: 0.0,
|
||||
memory_usage: 0,
|
||||
systemd_status,
|
||||
last_updated: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AppManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
126
src/orchestrator/lifecycle.rs
Normal file
126
src/orchestrator/lifecycle.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
use super::{Result, OrchestratorError};
|
||||
use crate::systemd::SystemCtl;
|
||||
use crate::logger::get_logger;
|
||||
use dashmap::DashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub struct LifecycleManager {
|
||||
rate_limiter: Arc<DashMap<String, Instant>>,
|
||||
rate_limit_duration: Duration,
|
||||
}
|
||||
|
||||
impl LifecycleManager {
|
||||
pub fn new() -> Self {
|
||||
LifecycleManager {
|
||||
rate_limiter: Arc::new(DashMap::new()),
|
||||
rate_limit_duration: Duration::from_secs(1),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_app(&self, app_name: &str) -> Result<()> {
|
||||
let logger = get_logger();
|
||||
|
||||
// Verificar rate limit
|
||||
self.check_rate_limit(app_name)?;
|
||||
|
||||
logger.info("Lifecycle", &format!("Iniciando aplicación: {}", app_name));
|
||||
|
||||
let service_name = format!("{}.service", app_name);
|
||||
SystemCtl::start(&service_name)?;
|
||||
|
||||
// Actualizar rate limiter
|
||||
self.update_rate_limiter(app_name);
|
||||
|
||||
logger.info("Lifecycle", &format!("Aplicación {} iniciada", app_name));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop_app(&self, app_name: &str) -> Result<()> {
|
||||
let logger = get_logger();
|
||||
|
||||
// Verificar rate limit
|
||||
self.check_rate_limit(app_name)?;
|
||||
|
||||
logger.info("Lifecycle", &format!("Deteniendo aplicación: {}", app_name));
|
||||
|
||||
let service_name = format!("{}.service", app_name);
|
||||
SystemCtl::stop(&service_name)?;
|
||||
|
||||
// Actualizar rate limiter
|
||||
self.update_rate_limiter(app_name);
|
||||
|
||||
logger.info("Lifecycle", &format!("Aplicación {} detenida", app_name));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn restart_app(&self, app_name: &str) -> Result<()> {
|
||||
let logger = get_logger();
|
||||
|
||||
// Verificar rate limit
|
||||
self.check_rate_limit(app_name)?;
|
||||
|
||||
logger.info("Lifecycle", &format!("Reiniciando aplicación: {}", app_name));
|
||||
|
||||
let service_name = format!("{}.service", app_name);
|
||||
SystemCtl::restart(&service_name)?;
|
||||
|
||||
// Actualizar rate limiter
|
||||
self.update_rate_limiter(app_name);
|
||||
|
||||
logger.info("Lifecycle", &format!("Aplicación {} reiniciada", app_name));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_rate_limit(&self, app_name: &str) -> Result<()> {
|
||||
if let Some(last_action) = self.rate_limiter.get(app_name) {
|
||||
let elapsed = last_action.elapsed();
|
||||
if elapsed < self.rate_limit_duration {
|
||||
let logger = get_logger();
|
||||
logger.warning(
|
||||
"Lifecycle",
|
||||
"Rate limit excedido",
|
||||
Some(&format!("App: {}, Espera: {:?}", app_name, self.rate_limit_duration - elapsed))
|
||||
);
|
||||
return Err(OrchestratorError::RateLimitExceeded(app_name.to_string()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_rate_limiter(&self, app_name: &str) {
|
||||
self.rate_limiter.insert(app_name.to_string(), Instant::now());
|
||||
}
|
||||
|
||||
pub fn recover_inconsistent_state(&self, app_name: &str, expected_running: bool) -> Result<()> {
|
||||
let logger = get_logger();
|
||||
logger.warning(
|
||||
"Lifecycle",
|
||||
"Intentando recuperar estado inconsistente",
|
||||
Some(app_name)
|
||||
);
|
||||
|
||||
let service_name = format!("{}.service", app_name);
|
||||
|
||||
if expected_running {
|
||||
// Se espera que esté corriendo pero no está
|
||||
logger.info("Lifecycle", &format!("Intentando reiniciar {}", app_name));
|
||||
SystemCtl::start(&service_name)?;
|
||||
} else {
|
||||
// Se espera que esté detenido pero está corriendo
|
||||
logger.info("Lifecycle", &format!("Intentando detener {}", app_name));
|
||||
SystemCtl::stop(&service_name)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LifecycleManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
27
src/orchestrator/mod.rs
Normal file
27
src/orchestrator/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
pub mod app_manager;
|
||||
pub mod lifecycle;
|
||||
|
||||
pub use app_manager::*;
|
||||
pub use lifecycle::*;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum OrchestratorError {
|
||||
#[error("Error de systemd: {0}")]
|
||||
SystemdError(#[from] crate::systemd::SystemdError),
|
||||
|
||||
#[error("Aplicación ya existe: {0}")]
|
||||
AppAlreadyExists(String),
|
||||
|
||||
#[error("Aplicación no encontrada: {0}")]
|
||||
AppNotFound(String),
|
||||
|
||||
#[error("Rate limit excedido para: {0}")]
|
||||
RateLimitExceeded(String),
|
||||
|
||||
#[error("Error de validación: {0}")]
|
||||
ValidationError(String),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, OrchestratorError>;
|
||||
Reference in New Issue
Block a user