use axum::{ extract::{Path, State}, http::StatusCode, response::Json, }; use std::sync::Arc; use sysinfo::System; use crate::orchestrator::{AppManager, LifecycleManager}; use crate::models::{ServiceConfig, RestartPolicy, AppType}; use super::dto::*; pub struct ApiState { pub app_manager: Arc, pub lifecycle_manager: Arc, } pub async fn register_app_handler( State(state): State>, Json(payload): Json, ) -> Result>, StatusCode> { // Parsear tipo de aplicación let app_type = match payload.app_type.to_lowercase().as_str() { "nodejs" | "node" => AppType::NodeJs, "python" | "py" => AppType::Python, _ => return Ok(Json(ApiResponse::error( "Tipo de aplicación inválido. Use 'nodejs' o 'python'".to_string() ))), }; // Parsear política de reinicio let restart_policy = match payload.restart_policy.to_lowercase().as_str() { "always" => RestartPolicy::Always, "on-failure" | "onfailure" => RestartPolicy::OnFailure, "no" | "never" => RestartPolicy::No, _ => RestartPolicy::Always, }; let config = ServiceConfig { app_name: payload.app_name.clone(), script_path: payload.script_path, working_directory: payload.working_directory, user: payload.user, environment: payload.environment, restart_policy, app_type, description: payload.description, custom_executable: payload.custom_executable, use_npm_start: payload.use_npm_start, }; match state.app_manager.register_app(config) { Ok(_) => Ok(Json(ApiResponse::success(OperationResponse { app_name: payload.app_name, operation: "register".to_string(), success: true, message: "Aplicación registrada exitosamente".to_string(), }))), Err(e) => Ok(Json(ApiResponse::error(e.to_string()))), } } pub async fn unregister_app_handler( State(state): State>, Path(app_name): Path, ) -> Result>, StatusCode> { match state.app_manager.unregister_app(&app_name) { Ok(_) => Ok(Json(ApiResponse::success(OperationResponse { app_name: app_name.clone(), operation: "unregister".to_string(), success: true, message: "Aplicación eliminada exitosamente".to_string(), }))), Err(e) => Ok(Json(ApiResponse::error(e.to_string()))), } } pub async fn start_app_handler( State(state): State>, Path(app_name): Path, ) -> Result>, StatusCode> { match state.lifecycle_manager.start_app(&app_name) { Ok(_) => Ok(Json(ApiResponse::success(OperationResponse { app_name: app_name.clone(), operation: "start".to_string(), success: true, message: "Aplicación iniciada exitosamente".to_string(), }))), Err(e) => Ok(Json(ApiResponse::error(e.to_string()))), } } pub async fn stop_app_handler( State(state): State>, Path(app_name): Path, ) -> Result>, StatusCode> { match state.lifecycle_manager.stop_app(&app_name) { Ok(_) => Ok(Json(ApiResponse::success(OperationResponse { app_name: app_name.clone(), operation: "stop".to_string(), success: true, message: "Aplicación detenida exitosamente".to_string(), }))), Err(e) => Ok(Json(ApiResponse::error(e.to_string()))), } } pub async fn restart_app_handler( State(state): State>, Path(app_name): Path, ) -> Result>, StatusCode> { match state.lifecycle_manager.restart_app(&app_name) { Ok(_) => Ok(Json(ApiResponse::success(OperationResponse { app_name: app_name.clone(), operation: "restart".to_string(), success: true, message: "Aplicación reiniciada exitosamente".to_string(), }))), Err(e) => Ok(Json(ApiResponse::error(e.to_string()))), } } pub async fn get_app_status_handler( State(_state): State>, Path(app_name): Path, ) -> Result>, StatusCode> { use crate::config::get_config_manager; use crate::systemd::SystemCtl; use crate::models::{AppStatus, ServiceStatus}; let config_manager = get_config_manager(); let apps = config_manager.get_apps(); // Buscar la app en monitored_apps.json let app = apps.iter().find(|a| a.name == app_name); match app { Some(app) => { let service_name = format!("siax-app-{}.service", app.name); let systemd_status = SystemCtl::status(&service_name); // Obtener métricas del proceso let mut sys = System::new_all(); sys.refresh_all(); let mut pid = None; let mut cpu_usage = 0.0; let mut memory_mb = 0.0; // Buscar proceso por nombre de app for (process_pid, process) in sys.processes() { let cmd = process.cmd().join(" "); if cmd.contains(&app.name) || cmd.contains(&app.entry_point) { pid = Some(process_pid.as_u32() as i32); cpu_usage = process.cpu_usage(); memory_mb = process.memory() as f64 / 1024.0 / 1024.0; break; } } let status = match systemd_status { ServiceStatus::Active => "Running", ServiceStatus::Inactive => "Stopped", ServiceStatus::Failed => "Failed", ServiceStatus::Activating => "Starting", ServiceStatus::Deactivating => "Stopping", ServiceStatus::Unknown => "Unknown", }; let response = AppStatusResponse { name: app.name.clone(), status: status.to_string(), pid, cpu_usage, memory_usage: format!("{:.2} MB", memory_mb), systemd_status: systemd_status.as_str().to_string(), last_updated: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(), }; Ok(Json(ApiResponse::success(response))) } None => Ok(Json(ApiResponse::error( format!("Aplicación '{}' no encontrada", app_name) ))), } } pub async fn list_apps_handler( State(_state): State>, ) -> Result, StatusCode> { use crate::config::get_config_manager; use crate::systemd::SystemCtl; // Leer apps desde monitored_apps.json (apps descubiertas + registradas) let config_manager = get_config_manager(); let monitored_apps = config_manager.get_apps(); // Crear respuesta con información de cada app let mut apps_with_status = Vec::new(); for app in monitored_apps { // Verificar estado en systemd let service_name = format!("siax-app-{}.service", app.name); let systemd_status = SystemCtl::status(&service_name); let status = match systemd_status { crate::models::ServiceStatus::Active => "Running", crate::models::ServiceStatus::Inactive => "Stopped", crate::models::ServiceStatus::Failed => "Failed", crate::models::ServiceStatus::Activating => "Starting", crate::models::ServiceStatus::Deactivating => "Stopping", crate::models::ServiceStatus::Unknown => "Unknown", }; apps_with_status.push(serde_json::json!({ "name": app.name, "status": status, "port": app.port, "service_name": app.service_name, })); } let total = apps_with_status.len(); Ok(Json(serde_json::json!({ "success": true, "data": { "apps": apps_with_status, "total": total } }))) } pub async fn scan_processes_handler() -> Result>, StatusCode> { let mut sys = System::new_all(); sys.refresh_all(); let mut detected_processes = Vec::new(); for (pid, process) in sys.processes() { let process_name = process.name().to_lowercase(); let cmd = process.cmd().join(" ").to_lowercase(); let process_type = if process_name.contains("node") || cmd.contains("node") { Some("nodejs") } else if process_name.contains("python") || cmd.contains("python") { Some("python") } else { None }; if let Some(ptype) = process_type { detected_processes.push(DetectedProcess { pid: pid.as_u32() as i32, name: process.name().to_string(), user: process.user_id().map(|u| u.to_string()), cpu_usage: process.cpu_usage() as f64, memory_mb: process.memory() as f64 / 1024.0 / 1024.0, process_type: ptype.to_string(), }); } } let total = detected_processes.len(); Ok(Json(ApiResponse::success(ProcessScanResponse { processes: detected_processes, total, }))) } pub async fn health_handler( State(state): State>, ) -> Result>, StatusCode> { use std::path::Path; let config_path = "config/monitored_apps.json"; let config_exists = Path::new(config_path).exists(); let apps = state.app_manager.list_apps(); let apps_count = apps.len(); let mut systemd_services = Vec::new(); for app_name in &apps { let service_name = format!("siax-app-{}.service", app_name); systemd_services.push(service_name); } Ok(Json(ApiResponse::success(HealthResponse { status: "ok".to_string(), config_loaded: config_exists, config_path: config_path.to_string(), apps_count, systemd_services, version: env!("CARGO_PKG_VERSION").to_string(), }))) } /// Endpoint para ver las apps monitoreadas desde el JSON pub async fn get_monitored_apps_handler() -> Result, StatusCode> { use crate::config::get_config_manager; let config_manager = get_config_manager(); let apps = config_manager.get_apps(); let response = serde_json::json!({ "success": true, "count": apps.len(), "apps": apps }); Ok(Json(response)) } /// Endpoint para obtener los logs de errores del sistema pub async fn get_system_error_logs() -> Result, StatusCode> { use std::fs; use std::path::Path; let log_path = "logs/errors.log"; // Verificar si el archivo existe if !Path::new(log_path).exists() { return Ok(Json(serde_json::json!({ "success": true, "logs": [], "message": "Archivo de logs no encontrado" }))); } // Leer el archivo match fs::read_to_string(log_path) { Ok(content) => { // Dividir en líneas y tomar las últimas 500 let lines: Vec<&str> = content.lines().collect(); let total = lines.len(); let recent_lines: Vec<&str> = if lines.len() > 500 { lines[lines.len() - 500..].to_vec() } else { lines }; Ok(Json(serde_json::json!({ "success": true, "logs": recent_lines, "total_lines": total }))) } Err(e) => { Ok(Json(serde_json::json!({ "success": false, "error": format!("Error leyendo archivo: {}", e) }))) } } }