use chrono::{DateTime, Utc}; use ratatui::layout::Rect; use ratatui::Frame; use crate::app::{HostDisplayData, ConnectionStatus}; // Removed: evaluate_performance and PerfSeverity no longer needed use crate::ui::widget::{render_widget_data, WidgetData, WidgetStatus, StatusLevel}; pub fn render(frame: &mut Frame, hosts: &[HostDisplayData], area: Rect) { let (severity, _ok_count, _warn_count, _fail_count) = classify_hosts(hosts); let title = "Hosts".to_string(); let widget_status = match severity { HostSeverity::Critical => StatusLevel::Error, HostSeverity::Warning => StatusLevel::Warning, HostSeverity::Healthy => StatusLevel::Ok, HostSeverity::Unknown => StatusLevel::Unknown, }; let mut data = WidgetData::new( title, Some(WidgetStatus::new(widget_status)), vec!["Host".to_string(), "Status".to_string(), "Timestamp".to_string()] ); if hosts.is_empty() { data.add_row( None, vec![], vec![ "No hosts configured".to_string(), "".to_string(), "".to_string(), ], ); } else { for host in hosts { let (status_text, severity, _emphasize) = host_status(host); let status_level = match severity { HostSeverity::Critical => StatusLevel::Error, HostSeverity::Warning => StatusLevel::Warning, HostSeverity::Healthy => StatusLevel::Ok, HostSeverity::Unknown => StatusLevel::Unknown, }; let update = latest_timestamp(host) .map(|ts| ts.format("%Y-%m-%d %H:%M:%S").to_string()) .unwrap_or_else(|| "—".to_string()); data.add_row( Some(WidgetStatus::new(status_level)), vec![], vec![ host.name.clone(), status_text, update, ], ); } } render_widget_data(frame, area, data); } #[derive(Copy, Clone, Eq, PartialEq)] enum HostSeverity { Healthy, Warning, Critical, Unknown, } fn classify_hosts(hosts: &[HostDisplayData]) -> (HostSeverity, usize, usize, usize) { let mut ok = 0; let mut warn = 0; let mut fail = 0; for host in hosts { let severity = host_severity(host); match severity { HostSeverity::Healthy => ok += 1, HostSeverity::Warning => warn += 1, HostSeverity::Critical => fail += 1, HostSeverity::Unknown => warn += 1, } } let highest = if fail > 0 { HostSeverity::Critical } else if warn > 0 { HostSeverity::Warning } else if ok > 0 { HostSeverity::Healthy } else { HostSeverity::Unknown }; (highest, ok, warn, fail) } fn host_severity(host: &HostDisplayData) -> HostSeverity { // Check connection status first match host.connection_status { ConnectionStatus::Error => return HostSeverity::Critical, ConnectionStatus::Timeout => return HostSeverity::Warning, ConnectionStatus::Unknown => return HostSeverity::Unknown, ConnectionStatus::Connected => {}, // Continue with other checks } if host.last_error.is_some() { return HostSeverity::Critical; } if let Some(smart) = host.smart.as_ref() { if smart.summary.critical > 0 { return HostSeverity::Critical; } if smart.summary.warning > 0 || !smart.issues.is_empty() { return HostSeverity::Warning; } } if let Some(services) = host.services.as_ref() { if services.summary.failed > 0 { return HostSeverity::Critical; } if services.summary.degraded > 0 { return HostSeverity::Warning; } // TODO: Update to use agent-provided system statuses instead of evaluate_performance // let (perf_severity, _) = evaluate_performance(&services.summary); // match perf_severity { // PerfSeverity::Critical => return HostSeverity::Critical, // PerfSeverity::Warning => return HostSeverity::Warning, // PerfSeverity::Ok => {} // } } if let Some(backup) = host.backup.as_ref() { match backup.overall_status.as_str() { "critical" => return HostSeverity::Critical, "warning" => return HostSeverity::Warning, _ => {} } } if host.smart.is_none() && host.services.is_none() && host.backup.is_none() { HostSeverity::Unknown } else { HostSeverity::Healthy } } fn host_status(host: &HostDisplayData) -> (String, HostSeverity, bool) { // Check connection status first match host.connection_status { ConnectionStatus::Error => { let msg = if let Some(error) = &host.last_error { format!("Connection error: {}", error) } else { "Connection error".to_string() }; return (msg, HostSeverity::Critical, true); }, ConnectionStatus::Timeout => { let msg = if let Some(error) = &host.last_error { format!("Keep-alive timeout: {}", error) } else { "Keep-alive timeout".to_string() }; return (msg, HostSeverity::Warning, true); }, ConnectionStatus::Unknown => { return ("No data received".to_string(), HostSeverity::Unknown, true); }, ConnectionStatus::Connected => {}, // Continue with other checks } if let Some(error) = &host.last_error { return (format!("error: {}", error), HostSeverity::Critical, true); } if let Some(smart) = host.smart.as_ref() { if smart.summary.critical > 0 { return ( "critical: SMART critical".to_string(), HostSeverity::Critical, true, ); } if let Some(issue) = smart.issues.first() { return (format!("warning: {}", issue), HostSeverity::Warning, true); } } if let Some(services) = host.services.as_ref() { if services.summary.failed > 0 { return ( format!("critical: {} failed svc", services.summary.failed), HostSeverity::Critical, true, ); } if services.summary.degraded > 0 { return ( format!("warning: {} degraded svc", services.summary.degraded), HostSeverity::Warning, true, ); } // TODO: Update to use agent-provided system statuses instead of evaluate_performance // let (perf_severity, reason) = evaluate_performance(&services.summary); // if let Some(reason_text) = reason { // match perf_severity { // PerfSeverity::Critical => { // return ( // format!("critical: {}", reason_text), // HostSeverity::Critical, // true, // ); // } // PerfSeverity::Warning => { // return ( // format!("warning: {}", reason_text), // HostSeverity::Warning, // true, // ); // } // PerfSeverity::Ok => {} // } // } } if let Some(backup) = host.backup.as_ref() { match backup.overall_status.as_str() { "critical" => { return ( "critical: backup failed".to_string(), HostSeverity::Critical, true, ); } "warning" => { return ( "warning: backup warning".to_string(), HostSeverity::Warning, true, ); } _ => {} } } if host.smart.is_none() && host.services.is_none() && host.backup.is_none() { let status = if host.last_success.is_none() { "pending: awaiting metrics" } else { "pending: no recent data" }; return (status.to_string(), HostSeverity::Warning, false); } ("ok".to_string(), HostSeverity::Healthy, false) } fn latest_timestamp(host: &HostDisplayData) -> Option> { let mut latest = host.last_success; if let Some(smart) = host.smart.as_ref() { latest = Some(match latest { Some(current) => current.max(smart.timestamp), None => smart.timestamp, }); } if let Some(services) = host.services.as_ref() { latest = Some(match latest { Some(current) => current.max(services.timestamp), None => services.timestamp, }); } if let Some(backup) = host.backup.as_ref() { latest = Some(match latest { Some(current) => current.max(backup.timestamp), None => backup.timestamp, }); } latest }