Skip to main content

max / makenotwork

3.1 KB · 103 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().await.ok()?.json::<PomTargetResponse>().await.ok()
73 }
74
75 /// Format an RFC3339 timestamp into a shorter display form (e.g. "14:30 UTC" or "Mar 11, 14:30").
76 pub(super) fn format_pom_timestamp(rfc3339: &str) -> String {
77 chrono::DateTime::parse_from_rfc3339(rfc3339)
78 .map(|dt| {
79 let utc = dt.with_timezone(&chrono::Utc);
80 let now = chrono::Utc::now();
81 if utc.date_naive() == now.date_naive() {
82 utc.format("%H:%M UTC").to_string()
83 } else {
84 utc.format("%b %d, %H:%M").to_string()
85 }
86 })
87 .unwrap_or_else(|_| rfc3339.to_string())
88 }
89
90 /// Format a duration in seconds as a human-readable string (e.g. "2h 15m", "45m", "3d 1h").
91 pub(super) fn format_incident_duration(secs: i64) -> String {
92 let days = secs / 86400;
93 let hours = (secs % 86400) / 3600;
94 let minutes = (secs % 3600) / 60;
95 if days > 0 {
96 format!("{}d {}h", days, hours)
97 } else if hours > 0 {
98 format!("{}h {}m", hours, minutes)
99 } else {
100 format!("{}m", minutes.max(1))
101 }
102 }
103