use pom::checks::http; use pom::config::Config; use pom::db; use pom::display; use pom::error::Result; use pom::types::{LatencyStats, TestStaleness}; pub(crate) async fn cmd_status( pool: &sqlx::SqlitePool, config: &Config, json: bool, ) -> Result<()> { let mut target_statuses = Vec::new(); for name in config.target_names() { let target = config.get_target(&name).unwrap(); let health = db::get_latest_health(pool, &name).await?; let tls_check = db::get_latest_tls_check(pool, &name).await?; let route_checks = db::get_latest_route_checks(pool, &name).await?; let dns_checks = db::get_latest_dns_checks(pool, &name).await?; let whois_check = db::get_latest_whois_check(pool, &name).await?; let test = db::get_latest_test_run(pool, &name).await?; let incident = db::get_open_incident(pool, &name).await?; // Compute 24h latency stats let latency_24h = { let cutoff = (chrono::Utc::now() - chrono::Duration::hours(24)).to_rfc3339(); let times = db::get_response_times(pool, &name, &cutoff).await.unwrap_or_default(); let operational_times: Vec = times.iter() .filter(|(_, ms)| *ms > 0) .map(|(_, ms)| *ms) .collect(); LatencyStats::from_times(&operational_times) }; // Compute test staleness let staleness: Option = if let Some(tests_config) = &target.tests { let current_version = health.as_ref() .and_then(|h| h.details.as_ref()) .and_then(|d| d.version.clone()); let tested_version = if let Some(ref t) = test { db::get_version_at_time(pool, &name, &t.started_at).await.unwrap_or(None) } else { None }; Some(http::compute_test_staleness( current_version.as_deref(), tested_version.as_deref(), test.as_ref().map(|t| t.started_at.as_str()), tests_config.staleness_days, )) } else { None }; // Compute test duration trend let test_durations = if target.tests.is_some() { db::get_test_durations(pool, &name, 13).await.unwrap_or_default() } else { vec![] }; let duration_drift = if !test_durations.is_empty() { http::detect_test_duration_drift(&test_durations, 10, 3, 1.5) } else { None }; if json { target_statuses.push(serde_json::json!({ "target": name, "label": target.label, "health": health, "tls": tls_check, "latency_24h": latency_24h, "dns": dns_checks, "whois": whois_check, "last_test": test.map(|t| serde_json::json!({ "passed": t.passed, "exit_code": t.exit_code, "duration_secs": t.duration_secs, "started_at": t.started_at, "summary": t.summary, })), "test_staleness": staleness, "test_duration_drift": duration_drift, "incident": incident, })); } else { let route_slice = if route_checks.is_empty() { None } else { Some(route_checks.as_slice()) }; let dns_slice = if dns_checks.is_empty() { None } else { Some(dns_checks.as_slice()) }; print!( "{}", display::format_status_target( &name, &target.label, health.as_ref(), latency_24h.as_ref(), tls_check.as_ref(), route_slice, dns_slice, whois_check.as_ref(), test.as_ref(), staleness.as_ref(), incident.as_ref(), ) ); if !test_durations.is_empty() { let recent_5: Vec<(String, i64)> = test_durations.iter().take(5).cloned().collect(); print!("{}", display::format_test_duration_trend(&recent_5, duration_drift.as_deref())); } } } if json { println!("{}", serde_json::to_string_pretty(&target_statuses)?); } Ok(()) }