feat: config module avec structs et persistence JSON

This commit is contained in:
oussi
2026-04-07 11:29:43 +02:00
parent cfbc1c5606
commit 655f9e567a

View File

@@ -1 +1,249 @@
// Config module — Task 2 use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};
const MAX_ALERTS: usize = 500;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Thresholds {
pub cpu_percent: f64,
pub ram_percent: f64,
pub disk_percent: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessConfig {
pub name: String,
pub pattern: String,
pub memory_threshold_mb: f64,
pub enabled: bool,
pub alert_on_down: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SmtpConfig {
pub server: String,
pub port: u16,
pub use_tls: bool,
pub username: String,
pub password: String,
pub from_email: String,
pub to_emails: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserStatusThresholds {
pub active_minutes: u64,
pub inactive_minutes: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdminConfig {
pub username: String,
pub password_hash: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub secret_key: String,
pub port: u16,
pub check_interval_minutes: u64,
pub alert_cooldown_minutes: u64,
pub thresholds: Thresholds,
pub processes: Vec<ProcessConfig>,
pub smtp: SmtpConfig,
pub amadea_log_path: String,
pub user_status_thresholds: UserStatusThresholds,
pub admin: AdminConfig,
}
impl Default for Config {
fn default() -> Self {
Config {
secret_key: generate_secret_key(),
port: 5000,
check_interval_minutes: 1,
alert_cooldown_minutes: 30,
thresholds: Thresholds {
cpu_percent: 90.0,
ram_percent: 85.0,
disk_percent: 90.0,
},
processes: vec![
ProcessConfig {
name: "JVM".into(),
pattern: "java".into(),
memory_threshold_mb: 0.0,
enabled: true,
alert_on_down: true,
},
ProcessConfig {
name: "Nginx".into(),
pattern: "nginx".into(),
memory_threshold_mb: 0.0,
enabled: false,
alert_on_down: false,
},
ProcessConfig {
name: "Amadea Web 8 x64".into(),
pattern: "amadea".into(),
memory_threshold_mb: 0.0,
enabled: true,
alert_on_down: true,
},
],
smtp: SmtpConfig {
port: 587,
use_tls: true,
..Default::default()
},
amadea_log_path: r"C:\ProgramData\ISoft\Amadea Web 8 x64\data\logs".into(),
user_status_thresholds: UserStatusThresholds {
active_minutes: 5,
inactive_minutes: 30,
},
admin: AdminConfig {
username: "admin".into(),
password_hash: bcrypt::hash("admin", bcrypt::DEFAULT_COST)
.unwrap_or_default(),
},
}
}
}
fn generate_secret_key() -> String {
use rand::Rng;
let mut rng = rand::thread_rng();
(0..32).map(|_| format!("{:02x}", rng.gen::<u8>())).collect()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Alert {
pub timestamp: String,
#[serde(rename = "type")]
pub alert_type: String,
pub key: String,
pub message: String,
pub value: f64,
pub threshold: f64,
pub hostname: String,
}
pub struct ConfigManager {
config_file: PathBuf,
alerts_file: PathBuf,
pub config: Config,
}
impl ConfigManager {
pub fn new() -> Self {
let exe_dir = std::env::current_exe()
.unwrap_or_default()
.parent()
.unwrap_or(Path::new("."))
.to_path_buf();
Self::new_with_dir(exe_dir.join("data"))
}
pub fn new_with_dir(data_dir: PathBuf) -> Self {
fs::create_dir_all(&data_dir).ok();
let config_file = data_dir.join("config.json");
let alerts_file = data_dir.join("alerts.json");
let config = if config_file.exists() {
fs::read_to_string(&config_file)
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default()
} else {
let cfg = Config::default();
let json = serde_json::to_string_pretty(&cfg).unwrap_or_default();
fs::write(&config_file, &json).ok();
cfg
};
ConfigManager {
config_file,
alerts_file,
config,
}
}
pub fn save(&self) {
let json = serde_json::to_string_pretty(&self.config).unwrap_or_default();
fs::write(&self.config_file, json).ok();
}
pub fn update(&mut self, config: Config) {
self.config = config;
self.save();
}
pub fn load_alerts(&self) -> Vec<Alert> {
if !self.alerts_file.exists() {
return vec![];
}
fs::read_to_string(&self.alerts_file)
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default()
}
pub fn save_alert(&self, alert: Alert) {
let mut alerts = self.load_alerts();
alerts.insert(0, alert);
alerts.truncate(MAX_ALERTS);
let json = serde_json::to_string_pretty(&alerts).unwrap_or_default();
fs::write(&self.alerts_file, json).ok();
}
pub fn clear_alerts(&self) {
fs::write(&self.alerts_file, "[]").ok();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_has_expected_values() {
let cfg = Config::default();
assert_eq!(cfg.port, 5000);
assert_eq!(cfg.thresholds.cpu_percent, 90.0);
assert_eq!(cfg.thresholds.ram_percent, 85.0);
assert_eq!(cfg.thresholds.disk_percent, 90.0);
assert_eq!(cfg.check_interval_minutes, 1);
assert_eq!(cfg.alert_cooldown_minutes, 30);
assert_eq!(cfg.processes.len(), 3);
assert_eq!(cfg.admin.username, "admin");
}
#[test]
fn config_serializes_and_deserializes() {
let cfg = Config::default();
let json = serde_json::to_string(&cfg).unwrap();
let cfg2: Config = serde_json::from_str(&json).unwrap();
assert_eq!(cfg.port, cfg2.port);
assert_eq!(cfg.admin.username, cfg2.admin.username);
}
#[test]
fn save_alert_truncates_at_500() {
let dir = tempfile::tempdir().unwrap();
let cm = ConfigManager::new_with_dir(dir.path().to_path_buf());
for i in 0..510u32 {
cm.save_alert(Alert {
timestamp: format!("2026-01-01T00:00:{:02}", i % 60),
alert_type: "threshold".into(),
key: format!("cpu_{}", i),
message: format!("msg {}", i),
value: i as f64,
threshold: 90.0,
hostname: "test".into(),
});
}
let alerts = cm.load_alerts();
assert_eq!(alerts.len(), 500);
}
}