Skip to main content

max / makenotwork

3.1 KB · 109 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 let client = reqwest::Client::builder()
64 .timeout(std::time::Duration::from_secs(2))
65 .build()
66 .ok()?;
67
68 let mut req = client.get("http://127.0.0.1:9100/api/status/mnw");
69 if let Ok(token) = std::env::var("POM_API_TOKEN") {
70 req = req.bearer_auth(token);
71 }
72 req.send()
73 .await
74 .ok()?
75 .json::<PomTargetResponse>()
76 .await
77 .ok()
78 }
79
80 /// Format an RFC3339 timestamp into a shorter display form (e.g. "14:30 UTC" or "Mar 11, 14:30").
81 pub(super) fn format_pom_timestamp(rfc3339: &str) -> String {
82 chrono::DateTime::parse_from_rfc3339(rfc3339).map_or_else(
83 |_| rfc3339.to_string(),
84 |dt| {
85 let utc = dt.with_timezone(&chrono::Utc);
86 let now = chrono::Utc::now();
87 if utc.date_naive() == now.date_naive() {
88 utc.format("%H:%M UTC").to_string()
89 } else {
90 utc.format("%b %d, %H:%M").to_string()
91 }
92 },
93 )
94 }
95
96 /// Format a duration in seconds as a human-readable string (e.g. "2h 15m", "45m", "3d 1h").
97 pub(super) fn format_incident_duration(secs: i64) -> String {
98 let days = secs / 86400;
99 let hours = (secs % 86400) / 3600;
100 let minutes = (secs % 3600) / 60;
101 if days > 0 {
102 format!("{days}d {hours}h")
103 } else if hours > 0 {
104 format!("{hours}h {minutes}m")
105 } else {
106 format!("{}m", minutes.max(1))
107 }
108 }
109