diff --git a/src/api/handlers.rs b/src/api/handlers.rs index 3531b5c..ac7f217 100644 --- a/src/api/handlers.rs +++ b/src/api/handlers.rs @@ -60,6 +60,153 @@ pub async fn register_app_handler( } } +pub async fn update_app_handler( + State(state): State>, + Path(app_name): Path, + Json(payload): Json, +) -> Result>, StatusCode> { + use crate::config::get_config_manager; + use crate::systemd::{SystemCtl, ServiceGenerator}; + + let logger = crate::logger::get_logger(); + logger.info("API", &format!("✏️ Solicitud de actualización para: {}", app_name)); + + // Validar que el app_name coincida + if app_name != payload.app_name { + return Ok(Json(ApiResponse::error( + "El nombre de la app en la URL no coincide con el payload".to_string() + ))); + } + + // 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, + }; + + let service_name = format!("siax-app-{}.service", app_name); + + // 1. Detener el servicio + logger.info("API", &format!("🛑 Deteniendo servicio: {}", service_name)); + let _ = SystemCtl::stop(&service_name); + + // 2. Regenerar el archivo .service + logger.info("API", "📝 Regenerando archivo .service con nueva configuración"); + match ServiceGenerator::create_service(&config) { + Ok(service_content) => { + match ServiceGenerator::write_service_file(&config, &service_content) { + Ok(_) => { + logger.info("API", "✅ Archivo .service actualizado"); + } + Err(e) => { + return Ok(Json(ApiResponse::error( + format!("Error escribiendo archivo .service: {}", e) + ))); + } + } + } + Err(e) => { + return Ok(Json(ApiResponse::error( + format!("Error generando .service: {}", e) + ))); + } + } + + // 3. Recargar daemon + logger.info("API", "🔄 Ejecutando daemon-reload"); + let _ = SystemCtl::daemon_reload(); + + // 4. Actualizar monitored_apps.json + let config_manager = get_config_manager(); + let service_file_path = format!("/etc/systemd/system/{}", service_name); + let port = config.environment.get("PORT") + .and_then(|p| p.parse::().ok()) + .unwrap_or(8080); + + let entry_point = std::path::Path::new(&config.script_path) + .file_name() + .and_then(|f| f.to_str()) + .unwrap_or("server.js") + .to_string(); + + let node_bin = config.custom_executable.clone().unwrap_or_default(); + let mode = config.environment.get("NODE_ENV") + .cloned() + .unwrap_or_else(|| "production".to_string()); + + // Primero intentar hacer soft delete de la app anterior + let _ = config_manager.soft_delete_app(&app_name, Some("Actualizada - versión anterior".to_string())); + + // Luego agregar la nueva configuración + let monitored_app = crate::config::MonitoredApp { + name: config.app_name.clone(), + service_name: service_name.clone(), + path: config.working_directory.clone(), + port, + entry_point, + node_bin, + mode, + service_file_path, + registered_at: chrono::Local::now().to_rfc3339(), + deleted: false, + deleted_at: None, + deleted_reason: None, + systemd_service: None, + created_at: None, + }; + + match config_manager.add_app_full(monitored_app) { + Ok(_) => { + logger.info("API", "✅ JSON actualizado"); + } + Err(e) => { + logger.warning("API", &format!("No se pudo actualizar JSON: {}", e), None); + } + } + + // 5. Iniciar el servicio nuevamente + logger.info("API", &format!("▶️ Iniciando servicio: {}", service_name)); + match SystemCtl::start(&service_name) { + Ok(_) => { + logger.info("API", "✅ Servicio iniciado exitosamente"); + } + Err(e) => { + logger.warning("API", &format!("Error al iniciar servicio: {}", e), None); + } + } + + Ok(Json(ApiResponse::success(OperationResponse { + app_name: app_name.clone(), + operation: "update".to_string(), + success: true, + message: format!("Aplicación '{}' actualizada exitosamente", app_name), + }))) +} + pub async fn unregister_app_handler( State(state): State>, Path(app_name): Path, @@ -171,6 +318,30 @@ pub async fn restart_app_handler( } } +pub async fn get_app_details_handler( + Path(app_name): Path, +) -> Result, StatusCode> { + use crate::config::get_config_manager; + + let config_manager = get_config_manager(); + let apps = config_manager.get_apps(); + + match apps.iter().find(|a| a.name == app_name) { + Some(app) => { + Ok(Json(serde_json::json!({ + "success": true, + "data": app + }))) + } + None => { + Ok(Json(serde_json::json!({ + "success": false, + "error": format!("Aplicación '{}' no encontrada", app_name) + }))) + } + } +} + pub async fn get_app_status_handler( State(_state): State>, Path(app_name): Path, diff --git a/src/main.rs b/src/main.rs index 2a5dc42..2c60372 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,7 +71,7 @@ async fn main() { .route("/api/logs/errors", get(api::get_system_error_logs)) .route("/api/apps", get(api::list_apps_handler).post(api::register_app_handler)) .route("/api/apps/deleted", get(api::get_deleted_apps_handler)) - .route("/api/apps/:name", delete(api::unregister_app_handler)) + .route("/api/apps/:name", get(api::get_app_details_handler).delete(api::unregister_app_handler).put(api::update_app_handler)) .route("/api/apps/:name/status", get(api::get_app_status_handler)) .route("/api/apps/:name/start", post(api::start_app_handler)) .route("/api/apps/:name/stop", post(api::stop_app_handler)) diff --git a/web/edit.html b/web/edit.html new file mode 100644 index 0000000..1ab4eab --- /dev/null +++ b/web/edit.html @@ -0,0 +1,579 @@ + + + + + + Editar Aplicación - SIAX Monitor + + + + + + + + + +
+
+
+
+
+ Logo +
+

+ SIAX Monitor +

+
+
+
+ + +
+
+
+ +
+ +
+

+ Register New Application +

+

+ Register a Node.js or Python application to manage with + systemd. +

+
+ + + + + + + +
+ +
+

+ Basic Information +

+ +
+ + +

+ Solo letras, números, guiones y guiones bajos +

+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+
+ + +
+

+ Rutas y Usuario +

+ +
+ + +

+ Ruta completa al archivo principal (.js o .py) +

+
+ +
+ + +

+ Directorio desde el cual se ejecutará la aplicación +

+
+ +
+ + +

+ Usuario bajo el cual se ejecutará el proceso +

+
+
+ + +
+
+

+ Environment Variables +

+ +
+ +
+
+ + + +
+
+
+ + +
+ + +
+
+
+ + + + diff --git a/web/index.html b/web/index.html index 35e2cf1..91e89ff 100644 --- a/web/index.html +++ b/web/index.html @@ -792,6 +792,11 @@ ` } +