feat: config module avec structs et persistence JSON
This commit is contained in:
250
src/config.rs
250
src/config.rs
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user