use axum::{ body::Bytes, extract::{Form, State}, response::{IntoResponse, Redirect}, }; use serde::Deserialize; use tower_sessions::Session; use form_urlencoded; use crate::routes::{flash, get_and_clear_flash, render_html, AppState, AuthUser}; pub async fn settings_get( _auth: AuthUser, session: Session, State(state): State, ) -> impl IntoResponse { let flash_messages = get_and_clear_flash(&session).await; let (config, is_default_pw) = { let cm = state.config_manager.lock().await; let pw_default = bcrypt::verify("admin", &cm.config.admin.password_hash).unwrap_or(false); (cm.config.clone(), pw_default) }; let smtp_password_masked = if config.smtp.password.is_empty() { "".to_string() } else { "********".to_string() }; let mut ctx = tera::Context::new(); ctx.insert("flash_messages", &flash_messages); ctx.insert("is_authenticated", &true); ctx.insert("active_page", "settings"); ctx.insert("config", &config); ctx.insert("smtp", &config.smtp); ctx.insert("smtp_password_masked", &smtp_password_masked); ctx.insert("default_pw", &is_default_pw); render_html(&state.tera, "settings.html", ctx) } #[derive(Deserialize)] pub struct ThresholdsForm { pub cpu_percent: u32, pub ram_percent: u32, pub disk_percent: u32, } pub async fn update_thresholds( _auth: AuthUser, session: Session, State(state): State, Form(form): Form, ) -> impl IntoResponse { if !(1..=100).contains(&form.cpu_percent) || !(1..=100).contains(&form.ram_percent) || !(1..=100).contains(&form.disk_percent) { flash( &session, "danger", "Les seuils doivent être entre 1 et 100.", ) .await; return Redirect::to("/settings"); } { let mut cm = state.config_manager.lock().await; cm.config.thresholds.cpu_percent = form.cpu_percent as f64; cm.config.thresholds.ram_percent = form.ram_percent as f64; cm.config.thresholds.disk_percent = form.disk_percent as f64; cm.save(); } flash(&session, "success", "Seuils mis à jour.").await; Redirect::to("/settings") } #[derive(Deserialize)] pub struct MonitoringForm { pub check_interval_minutes: u64, pub alert_cooldown_minutes: u64, } pub async fn update_monitoring( _auth: AuthUser, session: Session, State(state): State, Form(form): Form, ) -> impl IntoResponse { if form.check_interval_minutes < 1 { flash( &session, "danger", "L'intervalle doit être d'au moins 1 minute.", ) .await; return Redirect::to("/settings"); } if form.alert_cooldown_minutes < 1 { flash( &session, "danger", "Le cooldown doit être d'au moins 1 minute.", ) .await; return Redirect::to("/settings"); } { let mut cm = state.config_manager.lock().await; cm.config.check_interval_minutes = form.check_interval_minutes; cm.config.alert_cooldown_minutes = form.alert_cooldown_minutes; cm.save(); } flash( &session, "success", "Paramètres de monitoring mis à jour.", ) .await; Redirect::to("/settings") } #[derive(Deserialize)] pub struct SmtpForm { pub smtp_server: String, pub smtp_port: u16, pub smtp_tls: Option, pub smtp_username: String, pub smtp_password: Option, pub smtp_from: String, pub smtp_to: String, } pub async fn update_smtp( _auth: AuthUser, session: Session, State(state): State, Form(form): Form, ) -> impl IntoResponse { let to_emails: Vec = form .smtp_to .split(',') .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()) .collect(); { let mut cm = state.config_manager.lock().await; let old_password = cm.config.smtp.password.clone(); cm.config.smtp.server = form.smtp_server.trim().to_string(); cm.config.smtp.port = form.smtp_port; cm.config.smtp.use_tls = form.smtp_tls.is_some(); cm.config.smtp.username = form.smtp_username.trim().to_string(); cm.config.smtp.from_email = form.smtp_from.trim().to_string(); cm.config.smtp.to_emails = to_emails; cm.config.smtp.password = match form.smtp_password { Some(pw) if !pw.is_empty() => pw, _ => old_password, }; cm.save(); } flash(&session, "success", "Configuration SMTP mise à jour.").await; Redirect::to("/settings") } pub async fn test_smtp( _auth: AuthUser, session: Session, State(state): State, ) -> impl IntoResponse { let smtp = { let cm = state.config_manager.lock().await; cm.config.smtp.clone() }; let (ok, msg) = state.alerter.send_test(&smtp).await; if ok { flash(&session, "success", &format!("Test réussi : {}", msg)).await; } else { flash(&session, "danger", &format!("Test échoué : {}", msg)).await; } Redirect::to("/settings") } pub async fn update_processes( _auth: AuthUser, session: Session, State(state): State, body: Bytes, ) -> impl IntoResponse { use crate::config::ProcessConfig; let mut names: Vec = Vec::new(); let mut patterns: Vec = Vec::new(); let mut mem_thresholds: Vec = Vec::new(); let mut enableds: Vec = Vec::new(); let mut alert_downs: Vec = Vec::new(); for (key, value) in form_urlencoded::parse(body.as_ref()) { match key.as_ref() { "proc_name[]" => names.push(value.into_owned()), "proc_pattern[]" => patterns.push(value.into_owned()), "proc_mem_threshold[]" => mem_thresholds.push(value.into_owned()), "proc_enabled[]" => enableds.push(value.into_owned()), "proc_alert_down[]" => alert_downs.push(value.into_owned()), _ => {} } } let mut processes = Vec::new(); for (i, name) in names.iter().enumerate() { let name = name.trim().to_string(); if name.is_empty() { continue; } processes.push(ProcessConfig { name, pattern: patterns .get(i) .map(|s| s.trim().to_lowercase()) .unwrap_or_default(), memory_threshold_mb: mem_thresholds .get(i) .and_then(|s| s.parse().ok()) .unwrap_or(0.0), enabled: enableds.contains(&i.to_string()), alert_on_down: alert_downs.contains(&i.to_string()), }); } { let mut cm = state.config_manager.lock().await; cm.config.processes = processes; cm.save(); } flash(&session, "success", "Processus surveillés mis à jour.").await; Redirect::to("/settings") } #[derive(Deserialize)] pub struct PasswordForm { pub current_password: String, pub new_password: String, pub confirm_password: String, } pub async fn update_password( _auth: AuthUser, session: Session, State(state): State, Form(form): Form, ) -> impl IntoResponse { let hash = { let cm = state.config_manager.lock().await; cm.config.admin.password_hash.clone() }; if !bcrypt::verify(&form.current_password, &hash).unwrap_or(false) { flash(&session, "danger", "Mot de passe actuel incorrect.").await; return Redirect::to("/settings"); } if form.new_password.len() < 8 { flash( &session, "danger", "Le nouveau mot de passe doit faire au moins 8 caractères.", ) .await; return Redirect::to("/settings"); } if form.new_password != form.confirm_password { flash( &session, "danger", "Les mots de passe ne correspondent pas.", ) .await; return Redirect::to("/settings"); } let new_hash = match bcrypt::hash(&form.new_password, bcrypt::DEFAULT_COST) { Ok(h) => h, Err(_) => { flash( &session, "danger", "Erreur lors du hachage du mot de passe.", ) .await; return Redirect::to("/settings"); } }; { let mut cm = state.config_manager.lock().await; cm.config.admin.password_hash = new_hash; cm.save(); } flash(&session, "success", "Mot de passe mis à jour.").await; Redirect::to("/settings") } #[derive(Deserialize)] pub struct PortForm { pub port: u16, } pub async fn update_port( _auth: AuthUser, session: Session, State(state): State, Form(form): Form, ) -> impl IntoResponse { if !(1024..=65535).contains(&form.port) { flash( &session, "danger", "Le port doit être entre 1024 et 65535.", ) .await; return Redirect::to("/settings"); } { let mut cm = state.config_manager.lock().await; cm.config.port = form.port; cm.save(); } flash( &session, "warning", &format!( "Port mis à jour à {}. Redémarrez l'application pour appliquer.", form.port ), ) .await; Redirect::to("/settings") } #[derive(Deserialize)] pub struct AmadeaLogPathForm { pub amadea_log_path: String, } pub async fn update_amadea_log_path( _auth: AuthUser, session: Session, State(state): State, Form(form): Form, ) -> impl IntoResponse { let path = form.amadea_log_path.trim().to_string(); if path.is_empty() { flash(&session, "danger", "Le chemin ne peut pas être vide.").await; return Redirect::to("/settings"); } { let mut cm = state.config_manager.lock().await; cm.config.amadea_log_path = path; cm.save(); } flash(&session, "success", "Chemin des logs Amadea mis à jour.").await; Redirect::to("/settings") } #[derive(Deserialize)] pub struct UserThresholdsForm { pub active_minutes: u64, pub inactive_minutes: u64, pub pause_threshold_minutes: u64, } pub async fn update_user_thresholds( _auth: AuthUser, session: Session, State(state): State, Form(form): Form, ) -> impl IntoResponse { if form.active_minutes < 1 || form.inactive_minutes < 1 || form.pause_threshold_minutes < 1 { flash( &session, "danger", "Les seuils doivent être d'au moins 1 minute.", ) .await; return Redirect::to("/settings"); } if form.active_minutes >= form.inactive_minutes { flash( &session, "danger", "Le seuil 'actif' doit être inférieur au seuil 'inactif'.", ) .await; return Redirect::to("/settings"); } { let mut cm = state.config_manager.lock().await; cm.config.user_status_thresholds.active_minutes = form.active_minutes; cm.config.user_status_thresholds.inactive_minutes = form.inactive_minutes; cm.config.user_status_thresholds.pause_threshold_minutes = form.pause_threshold_minutes; cm.save(); } flash(&session, "success", "Seuils utilisateurs mis à jour.").await; Redirect::to("/settings") }