feat: routes modules (auth, dashboard, settings, alerts, users)
This commit is contained in:
@@ -1 +1,398 @@
|
||||
// Settings routes — Task 9
|
||||
use axum::{
|
||||
extract::{Form, State},
|
||||
response::{IntoResponse, Redirect},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tower_sessions::Session;
|
||||
|
||||
use crate::routes::{flash, get_and_clear_flash, render_html, AppState, AuthUser};
|
||||
|
||||
pub async fn settings_get(
|
||||
_auth: AuthUser,
|
||||
session: Session,
|
||||
State(state): State<AppState>,
|
||||
) -> 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<AppState>,
|
||||
Form(form): Form<ThresholdsForm>,
|
||||
) -> 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<AppState>,
|
||||
Form(form): Form<MonitoringForm>,
|
||||
) -> 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<String>,
|
||||
pub smtp_username: String,
|
||||
pub smtp_password: Option<String>,
|
||||
pub smtp_from: String,
|
||||
pub smtp_to: String,
|
||||
}
|
||||
|
||||
pub async fn update_smtp(
|
||||
_auth: AuthUser,
|
||||
session: Session,
|
||||
State(state): State<AppState>,
|
||||
Form(form): Form<SmtpForm>,
|
||||
) -> impl IntoResponse {
|
||||
let to_emails: Vec<String> = 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<AppState>,
|
||||
) -> 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")
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ProcessesForm {
|
||||
#[serde(rename = "proc_name[]")]
|
||||
pub proc_name: Option<Vec<String>>,
|
||||
#[serde(rename = "proc_pattern[]")]
|
||||
pub proc_pattern: Option<Vec<String>>,
|
||||
#[serde(rename = "proc_mem_threshold[]")]
|
||||
pub proc_mem_threshold: Option<Vec<String>>,
|
||||
#[serde(rename = "proc_enabled[]")]
|
||||
pub proc_enabled: Option<Vec<String>>,
|
||||
#[serde(rename = "proc_alert_down[]")]
|
||||
pub proc_alert_down: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
pub async fn update_processes(
|
||||
_auth: AuthUser,
|
||||
session: Session,
|
||||
State(state): State<AppState>,
|
||||
Form(form): Form<ProcessesForm>,
|
||||
) -> impl IntoResponse {
|
||||
use crate::config::ProcessConfig;
|
||||
let names = form.proc_name.unwrap_or_default();
|
||||
let patterns = form.proc_pattern.unwrap_or_default();
|
||||
let mem_thresholds = form.proc_mem_threshold.unwrap_or_default();
|
||||
let enableds = form.proc_enabled.unwrap_or_default();
|
||||
let alert_downs = form.proc_alert_down.unwrap_or_default();
|
||||
|
||||
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<AppState>,
|
||||
Form(form): Form<PasswordForm>,
|
||||
) -> 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<AppState>,
|
||||
Form(form): Form<PortForm>,
|
||||
) -> 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<AppState>,
|
||||
Form(form): Form<AmadeaLogPathForm>,
|
||||
) -> 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 async fn update_user_thresholds(
|
||||
_auth: AuthUser,
|
||||
session: Session,
|
||||
State(state): State<AppState>,
|
||||
Form(form): Form<UserThresholdsForm>,
|
||||
) -> impl IntoResponse {
|
||||
if form.active_minutes < 1 || form.inactive_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.save();
|
||||
}
|
||||
flash(&session, "success", "Seuils utilisateurs mis à jour.").await;
|
||||
Redirect::to("/settings")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user