250 lines
7.1 KiB
Rust
250 lines
7.1 KiB
Rust
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);
|
|
}
|
|
}
|