use serde::{Deserialize, Serialize}; use std::path::PathBuf; #[derive(Debug, Serialize, Deserialize)] pub struct HealthCheckResult { pub overall_score: u8, pub database_score: u8, pub service_score: u8, pub checks: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct HealthCheck { pub name: String, pub status: HealthStatus, pub score: u8, pub message: String, } #[derive(Debug, Serialize, Deserialize)] pub enum HealthStatus { Healthy, Warning, Critical, } #[tauri::command] pub async fn run_health_check() -> Result { let mut checks = vec![]; checks.push(check_database_connection().await); checks.push(check_file_system().await); checks.push(check_disk_space().await); checks.push(check_server_status().await); let database_score = calculate_database_score(&checks); let service_score = calculate_service_score(&checks); let overall_score = (database_score + service_score) / 2; Ok(HealthCheckResult { overall_score, database_score, service_score, checks, }) } async fn check_database_connection() -> HealthCheck { let db_path = PathBuf::from("data/users/demo.sqlite"); if db_path.exists() { let metadata = std::fs::metadata(&db_path); match metadata { Ok(meta) => { let size_mb = meta.len() as f64 / (1024.0 * 1024.0); if size_mb > 100.0 { HealthCheck { name: "Database Connection".to_string(), status: HealthStatus::Warning, score: 70, message: format!("Database size is {:.2} MB (consider cleanup)", size_mb), } } else { HealthCheck { name: "Database Connection".to_string(), status: HealthStatus::Healthy, score: 100, message: "Database connection is healthy".to_string(), } } } Err(e) => { HealthCheck { name: "Database Connection".to_string(), status: HealthStatus::Critical, score: 0, message: format!("Failed to read database metadata: {}", e), } } } } else { HealthCheck { name: "Database Connection".to_string(), status: HealthStatus::Critical, score: 0, message: "Database file not found".to_string(), } } } async fn check_file_system() -> HealthCheck { let data_dir = PathBuf::from("data"); if data_dir.exists() { let entries = std::fs::read_dir(&data_dir); match entries { Ok(entries) => { let count = entries.count(); HealthCheck { name: "File System".to_string(), status: HealthStatus::Healthy, score: 100, message: format!("File system is accessible ({} directories)", count), } } Err(e) => { HealthCheck { name: "File System".to_string(), status: HealthStatus::Critical, score: 0, message: format!("Failed to read file system: {}", e), } } } } else { HealthCheck { name: "File System".to_string(), status: HealthStatus::Critical, score: 0, message: "Data directory not found".to_string(), } } } async fn check_disk_space() -> HealthCheck { #[cfg(target_os = "macos")] { let output = std::process::Command::new("df") .args(&["-g", "/"]) .output(); match output { Ok(output) => { let stdout = String::from_utf8_lossy(&output.stdout); let lines: Vec<&str> = stdout.lines().collect(); if lines.len() > 1 { let fields: Vec<&str> = lines[1].split_whitespace().collect(); if fields.len() > 3 { let available_gb: u64 = fields[3].parse().unwrap_or(0); if available_gb > 50 { HealthCheck { name: "Disk Space".to_string(), status: HealthStatus::Healthy, score: 100, message: format!("Disk space is sufficient ({} GB available)", available_gb), } } else if available_gb > 10 { HealthCheck { name: "Disk Space".to_string(), status: HealthStatus::Warning, score: 70, message: format!("Disk space is low ({} GB available)", available_gb), } } else { HealthCheck { name: "Disk Space".to_string(), status: HealthStatus::Critical, score: 30, message: format!("Disk space is critical ({} GB available)", available_gb), } } } else { HealthCheck { name: "Disk Space".to_string(), status: HealthStatus::Warning, score: 50, message: "Failed to parse disk space".to_string(), } } } else { HealthCheck { name: "Disk Space".to_string(), status: HealthStatus::Warning, score: 50, message: "Failed to check disk space".to_string(), } } } Err(e) => { HealthCheck { name: "Disk Space".to_string(), status: HealthStatus::Warning, score: 50, message: format!("Failed to check disk space: {}", e), } } } } #[cfg(not(target_os = "macos"))] { HealthCheck { name: "Disk Space".to_string(), status: HealthStatus::Healthy, score: 100, message: "Disk space check not implemented for this platform".to_string(), } } } async fn check_server_status() -> HealthCheck { let output = std::process::Command::new("lsof") .args(&["-i", ":11438"]) .output(); match output { Ok(output) => { if output.status.success() && !output.stdout.is_empty() { HealthCheck { name: "Server Status".to_string(), status: HealthStatus::Healthy, score: 100, message: "Web server is running on port 11438".to_string(), } } else { HealthCheck { name: "Server Status".to_string(), status: HealthStatus::Warning, score: 50, message: "Web server is not running".to_string(), } } } Err(e) => { HealthCheck { name: "Server Status".to_string(), status: HealthStatus::Warning, score: 50, message: format!("Failed to check server status: {}", e), } } } } fn calculate_database_score(checks: &[HealthCheck]) -> u8 { let db_checks: Vec<&HealthCheck> = checks .iter() .filter(|c| c.name.contains("Database") || c.name.contains("File System")) .collect(); if db_checks.is_empty() { return 0; } let total_score: u8 = db_checks.iter().map(|c| c.score).sum(); total_score / db_checks.len() as u8 } fn calculate_service_score(checks: &[HealthCheck]) -> u8 { let service_checks: Vec<&HealthCheck> = checks .iter() .filter(|c| c.name.contains("Disk") || c.name.contains("Server")) .collect(); if service_checks.is_empty() { return 0; } let total_score: u8 = service_checks.iter().map(|c| c.score).sum(); total_score / service_checks.len() as u8 }