Files
supervision/src/main.rs
2026-04-20 16:47:15 +02:00

276 lines
9.3 KiB
Rust

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<String> = 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<OsString>) {
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");
}
}