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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user