diff --git a/monitoring-web/src/db.rs b/monitoring-web/src/db.rs index 92f7db3b..af0126b1 100644 --- a/monitoring-web/src/db.rs +++ b/monitoring-web/src/db.rs @@ -5,6 +5,7 @@ pub struct ObsRow { pub temp_c: Option, #[allow(dead_code)] pub obs_time: Option, + pub created_at: Option, } /// Get recent temperature observations for an ICAO station. @@ -14,7 +15,7 @@ pub fn get_recent_obs(db_path: &str, icao: &str, minutes: i32, limit: usize) -> Err(_) => return vec![], }; let sql = format!( - "SELECT temp_c, obs_time FROM airport_obs_log \ + "SELECT temp_c, obs_time, created_at FROM airport_obs_log \ WHERE icao = ?1 AND created_at > datetime('now', '{} minutes') \ ORDER BY created_at DESC LIMIT ?2", -minutes @@ -28,6 +29,7 @@ pub fn get_recent_obs(db_path: &str, icao: &str, minutes: i32, limit: usize) -> Ok(ObsRow { temp_c: row.get(0)?, obs_time: row.get(1)?, + created_at: row.get(2)?, }) }) .ok() diff --git a/monitoring-web/src/main.rs b/monitoring-web/src/main.rs index 886914e3..b1550a3a 100644 --- a/monitoring-web/src/main.rs +++ b/monitoring-web/src/main.rs @@ -59,6 +59,18 @@ fn load_city_snapshot(db_path: &str, (key, display, icao, airport, tz, thresh): let temps: Vec = obs.iter().filter_map(|o| o.temp_c).collect(); let current_temp = temps.first().copied(); + // Age of most recent observation + let obs_age_min = obs.first().and_then(|o| { + o.created_at.as_ref().and_then(|ts| { + chrono::NaiveDateTime::parse_from_str(ts, "%Y-%m-%d %H:%M:%S") + .ok() + .map(|dt| { + let obs_utc = dt.and_utc(); + (now_utc - obs_utc).num_minutes().max(0) + }) + }) + }); + // Trend from last 6 points let trend_data: Vec = temps.iter().take(6).copied().collect(); let trend = trend::calc_trend(&trend_data); @@ -94,6 +106,7 @@ fn load_city_snapshot(db_path: &str, (key, display, icao, airport, tz, thresh): temp_ok: false, trend_ok: false, in_window: false, + obs_age_min, } } diff --git a/monitoring-web/src/model.rs b/monitoring-web/src/model.rs index 6d9df98d..63c2beeb 100644 --- a/monitoring-web/src/model.rs +++ b/monitoring-web/src/model.rs @@ -19,6 +19,7 @@ pub struct CitySnapshot { pub temp_ok: bool, pub trend_ok: bool, pub in_window: bool, + pub obs_age_min: Option, } #[derive(Debug, Clone, Copy, Serialize)] diff --git a/monitoring-web/static/style.css b/monitoring-web/static/style.css index 465d5896..1e2fa95d 100644 --- a/monitoring-web/static/style.css +++ b/monitoring-web/static/style.css @@ -172,6 +172,9 @@ body { .runway-label { color: #4a5160; } .runway-temp { color: #7a8290; font-variant-numeric: tabular-nums; } +/* ── obs age ── */ +.obs-age { font-size: 12px; color: #5a6170; } + /* ── HTMX indicator ── */ .htmx-indicator { text-align: center; diff --git a/monitoring-web/templates/monitor.html b/monitoring-web/templates/monitor.html index 798118c3..4eadf021 100644 --- a/monitoring-web/templates/monitor.html +++ b/monitoring-web/templates/monitor.html @@ -12,12 +12,14 @@

🔥 市场监控

- 更新 {{ generated_at }} + 更新中…
{% endif %}
+ + {{ generated_at }} {% for c in cities %}
@@ -52,6 +54,15 @@ 趋势 {{ c.trend.symbol() }}
+
+ 观测 + {% match c.obs_age_min %} + {% when Some with (m) %} + {{ m }} 分钟前 + {% when None %} + -- + {% endmatch %} +
{% if !c.runway_pairs.is_empty() %} @@ -73,6 +84,29 @@ {% if full_page %} +