mod config; mod monitor; mod alerter; mod user_monitor; mod routes; use std::sync::Arc; use tokio::sync::Mutex as AsyncMutex; use tower_sessions::{MemoryStore, SessionManagerLayer}; use tower_http::services::ServeDir; use axum::{ Router, routing::{get, post}, http::HeaderValue, }; use config::ConfigManager; use monitor::SystemMonitor; use alerter::Alerter; use user_monitor::UserMonitor; use routes::{ AppState, auth::{login_get, login_post, logout}, dashboard::{dashboard, api_metrics, toggle_monitoring}, settings::{ settings_get, update_thresholds, update_monitoring, update_smtp, test_smtp, update_processes, update_password, update_port, update_amadea_log_path, update_user_thresholds, }, alerts::{alerts_get, clear_alerts}, users::{users_get, api_users, api_users_weekly, api_users_day}, }; pub async fn run_server() { // Init config let config_manager = Arc::new(AsyncMutex::new(ConfigManager::new())); // Init services let alerter = Arc::new(Alerter); let monitor = Arc::new(SystemMonitor::new( config_manager.clone(), alerter.clone(), )); let user_monitor = Arc::new(UserMonitor::new(config_manager.clone())); // Démarrer monitoring et user monitor monitor.clone().start().await; { let m = monitor.clone(); let _ = m.collect().await; } user_monitor.clone().start().await; // App state let state = AppState::new( config_manager.clone(), monitor, alerter, user_monitor, ); // Sessions let session_store = MemoryStore::default(); let session_layer = SessionManagerLayer::new(session_store) .with_secure(false) .with_name("supervision_session"); let app = Router::new() .route("/login", get(login_get)) .route("/login", post(login_post)) .route("/logout", get(logout)) .route("/", get(dashboard)) .route("/api/metrics", get(api_metrics)) .route("/api/monitoring/toggle", post(toggle_monitoring)) .route("/settings", get(settings_get)) .route("/settings/thresholds", post(update_thresholds)) .route("/settings/monitoring", post(update_monitoring)) .route("/settings/smtp", post(update_smtp)) .route("/settings/smtp/test", post(test_smtp)) .route("/settings/processes", post(update_processes)) .route("/settings/password", post(update_password)) .route("/settings/port", post(update_port)) .route("/settings/amadea-log-path", post(update_amadea_log_path)) .route("/settings/user-thresholds", post(update_user_thresholds)) .route("/alerts", get(alerts_get)) .route("/alerts/clear", post(clear_alerts)) .route("/users", get(users_get)) .route("/api/users", get(api_users)) .route("/api/users/activity/weekly", get(api_users_weekly)) .route("/api/users/day/:date", get(api_users_day)) .nest_service("/static", ServeDir::new("static")) .layer(session_layer) .with_state(state.clone()) .layer(axum::middleware::map_response(|mut response: axum::response::Response| async move { let headers = response.headers_mut(); headers.insert("X-Content-Type-Options", HeaderValue::from_static("nosniff")); headers.insert("X-Frame-Options", HeaderValue::from_static("DENY")); headers.insert("X-XSS-Protection", HeaderValue::from_static("1; mode=block")); response })); let port = { let cm = state.config_manager.lock().await; cm.config.port }; let addr = format!("0.0.0.0:{}", port); tracing::info!("Supervision démarré sur http://localhost:{}", port); let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); axum::serve(listener, app).await.unwrap(); } #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); let args: Vec = std::env::args().collect(); #[cfg(windows)] { if args.get(1).map(|s| s.as_str()) == Some("install") { service::install_service(); return; } if args.get(1).map(|s| s.as_str()) == Some("uninstall") { service::uninstall_service(); return; } if service::is_running_as_service() { service::run_service(); return; } } #[cfg(not(windows))] let _ = args; run_server().await; } #[cfg(windows)] mod service { use std::ffi::OsString; use std::time::Duration; use windows_service::{ define_windows_service, service::{ ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl, ServiceExitCode, ServiceInfo, ServiceStartType, ServiceState, ServiceStatus, ServiceType, }, service_control_handler::{self, ServiceControlHandlerResult}, service_dispatcher, service_manager::{ServiceManager, ServiceManagerAccess}, }; const SERVICE_NAME: &str = "Supervision"; const SERVICE_DISPLAY: &str = "Supervision - Monitoring Système"; const SERVICE_DESCRIPTION: &str = "Surveille CPU, RAM, disques et processus. Interface web sur http://localhost:5000"; pub fn install_service() { let manager = ServiceManager::local_computer( None::<&str>, ServiceManagerAccess::CREATE_SERVICE, ) .expect("Impossible d'ouvrir le Service Manager (lancer en administrateur)"); let exe_path = std::env::current_exe().unwrap(); let service_info = ServiceInfo { name: OsString::from(SERVICE_NAME), display_name: OsString::from(SERVICE_DISPLAY), service_type: ServiceType::OWN_PROCESS, start_type: ServiceStartType::AutoStart, error_control: ServiceErrorControl::Normal, executable_path: exe_path, launch_arguments: vec![], dependencies: vec![], account_name: None, account_password: None, }; let service = manager .create_service(&service_info, ServiceAccess::CHANGE_CONFIG) .expect("Impossible de créer le service"); service .set_description(SERVICE_DESCRIPTION) .expect("Impossible de définir la description"); println!("Service '{}' installé avec succès.", SERVICE_NAME); println!("Démarrer avec: sc start {}", SERVICE_NAME); } pub fn uninstall_service() { let manager = ServiceManager::local_computer( None::<&str>, ServiceManagerAccess::CONNECT, ) .expect("Impossible d'ouvrir le Service Manager"); let service = manager .open_service(SERVICE_NAME, ServiceAccess::DELETE) .expect("Service introuvable"); service.delete().expect("Impossible de supprimer le service"); println!("Service '{}' désinstallé.", SERVICE_NAME); } pub fn is_running_as_service() -> bool { // Heuristique : variable SESSIONNAME absente = pas de session interactive std::env::var("SESSIONNAME").is_err() } define_windows_service!(ffi_service_main, service_main); fn service_main(_arguments: Vec) { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>(); let mut shutdown_tx = Some(shutdown_tx); let status_handle = service_control_handler::register( SERVICE_NAME, move |control| match control { ServiceControl::Stop => { if let Some(tx) = shutdown_tx.take() { let _ = tx.send(()); } ServiceControlHandlerResult::NoError } ServiceControl::Interrogate => ServiceControlHandlerResult::NoError, _ => ServiceControlHandlerResult::NotImplemented, }, ) .unwrap(); status_handle .set_service_status(ServiceStatus { service_type: ServiceType::OWN_PROCESS, current_state: ServiceState::Running, controls_accepted: ServiceControlAccept::STOP, exit_code: ServiceExitCode::Win32(0), checkpoint: 0, wait_hint: Duration::default(), process_id: None, }) .unwrap(); tokio::select! { _ = crate::run_server() => {}, _ = shutdown_rx => {}, } status_handle .set_service_status(ServiceStatus { service_type: ServiceType::OWN_PROCESS, current_state: ServiceState::Stopped, controls_accepted: ServiceControlAccept::empty(), exit_code: ServiceExitCode::Win32(0), checkpoint: 0, wait_hint: Duration::default(), process_id: None, }) .unwrap(); }); } pub fn run_service() { service_dispatcher::start(SERVICE_NAME, ffi_service_main) .expect("Impossible de démarrer le service dispatcher"); } }