Skip to main content

max / makenotwork

3.2 KB · 110 lines History Blame Raw
1 //! PoM (Proof of Monitoring) types and helpers for the health dashboard.
2
3 use serde::Deserialize;
4
5 /// Deserialized snapshot from PoM's API response.
6 #[derive(Deserialize, Clone)]
7 pub(super) struct PomSnapshotJson {
8 pub status: String,
9 pub checked_at: String,
10 pub response_time_ms: i64,
11 }
12
13 /// Deserialized incident from PoM's API response.
14 #[derive(Deserialize, Clone)]
15 #[allow(dead_code)]
16 pub(super) struct PomIncidentJson {
17 pub started_at: String,
18 pub ended_at: Option<String>,
19 pub duration_secs: Option<i64>,
20 pub from_status: String,
21 pub to_status: String,
22 }
23
24 /// Deserialized latency stats from PoM's API response.
25 #[derive(Deserialize, Clone)]
26 pub(super) struct PomLatencyJson {
27 pub avg_ms: f64,
28 pub p95_ms: i64,
29 }
30
31 /// Deserialized route status from PoM's API response.
32 #[derive(Deserialize, Clone)]
33 pub(super) struct PomRouteStatusJson {
34 pub path: String,
35 #[allow(dead_code)]
36 pub status_code: i64,
37 pub ok: bool,
38 #[allow(dead_code)]
39 pub checked_at: String,
40 #[allow(dead_code)]
41 pub response_time_ms: i64,
42 }
43
44 /// Response from PoM's `GET /api/status/{target}` endpoint.
45 #[derive(Deserialize)]
46 pub(super) struct PomTargetResponse {
47 pub latest: Option<PomSnapshotJson>,
48 pub recent: Vec<PomSnapshotJson>,
49 pub uptime_24h: Option<f64>,
50 pub uptime_7d: Option<f64>,
51 #[serde(default)]
52 pub latency_24h: Option<PomLatencyJson>,
53 #[serde(default)]
54 pub current_incident: Option<PomIncidentJson>,
55 #[serde(default)]
56 pub incidents: Vec<PomIncidentJson>,
57 #[serde(default)]
58 pub route_status: Vec<PomRouteStatusJson>,
59 }
60
61 /// Fetch external monitoring data from the local PoM API.
62 pub(super) async fn fetch_pom_status() -> Option<PomTargetResponse> {
63 crate::crypto::install_default_crypto_provider();
64 let client = reqwest::Client::builder()
65 .timeout(std::time::Duration::from_secs(2))
66 .build()
67 .ok()?;
68
69 let mut req = client.get("http://127.0.0.1:9100/api/status/mnw");
70 if let Ok(token) = std::env::var("POM_API_TOKEN") {
71 req = req.bearer_auth(token);
72 }
73 req.send()
74 .await
75 .ok()?
76 .json::<PomTargetResponse>()
77 .await
78 .ok()
79 }
80
81 /// Format an RFC3339 timestamp into a shorter display form (e.g. "14:30 UTC" or "Mar 11, 14:30").
82 pub(super) fn format_pom_timestamp(rfc3339: &str) -> String {
83 chrono::DateTime::parse_from_rfc3339(rfc3339).map_or_else(
84 |_| rfc3339.to_string(),
85 |dt| {
86 let utc = dt.with_timezone(&chrono::Utc);
87 let now = chrono::Utc::now();
88 if utc.date_naive() == now.date_naive() {
89 utc.format("%H:%M UTC").to_string()
90 } else {
91 utc.format("%b %d, %H:%M").to_string()
92 }
93 },
94 )
95 }
96
97 /// Format a duration in seconds as a human-readable string (e.g. "2h 15m", "45m", "3d 1h").
98 pub(super) fn format_incident_duration(secs: i64) -> String {
99 let days = secs / 86400;
100 let hours = (secs % 86400) / 3600;
101 let minutes = (secs % 3600) / 60;
102 if days > 0 {
103 format!("{days}d {hours}h")
104 } else if hours > 0 {
105 format!("{hours}h {minutes}m")
106 } else {
107 format!("{}m", minutes.max(1))
108 }
109 }
110