[fix](trx-rs): limit aprs live bar to one hour

Attach replay timestamps to APRS history and filter the APRS live bar
so it only shows the last hour of valid entries.

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-01 18:08:38 +01:00
parent 63fd35802e
commit 5899592b6c
5 changed files with 30 additions and 5 deletions
+1
View File
@@ -416,6 +416,7 @@ fn parse_aprs(ax25: &Ax25Frame) -> AprsPacket {
}
AprsPacket {
ts_ms: None,
src_call,
dest_call,
path,
@@ -4,6 +4,7 @@ const aprsPacketsEl = document.getElementById("aprs-packets");
const aprsFilterInput = document.getElementById("aprs-filter");
const aprsBarOverlay = document.getElementById("aprs-bar-overlay");
const APRS_MAX_PACKETS = 100;
const APRS_BAR_WINDOW_MS = 60 * 60 * 1000;
let aprsFilterText = "";
let aprsPacketHistory = [];
@@ -105,7 +106,8 @@ function applyAprsFilterToAll() {
function updateAprsBar() {
if (!aprsBarOverlay) return;
const isPkt = (document.getElementById("mode")?.value || "").toUpperCase() === "PKT";
const okFrames = aprsPacketHistory.filter((p) => p.crcOk);
const cutoffMs = Date.now() - APRS_BAR_WINDOW_MS;
const okFrames = aprsPacketHistory.filter((p) => p.crcOk && p._tsMs >= cutoffMs);
if (!isPkt || okFrames.length === 0) {
aprsBarOverlay.style.display = "none";
return;
@@ -143,8 +145,9 @@ function addAprsPacket(pkt) {
console.log(tag, `${pkt.srcCall}>${pkt.destCall}${pkt.path ? "," + pkt.path : ""}: ${pkt.info}`, pkt);
// Stamp timestamp for persistence
pkt._ts = new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
pkt._tsMs = Date.now();
const tsMs = Number.isFinite(pkt.ts_ms) ? Number(pkt.ts_ms) : Date.now();
pkt._tsMs = tsMs;
pkt._ts = new Date(tsMs).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
aprsPacketHistory.unshift(pkt);
if (aprsPacketHistory.length > APRS_MAX_PACKETS) aprsPacketHistory.length = APRS_MAX_PACKETS;
@@ -12,7 +12,7 @@
use std::collections::VecDeque;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use actix_web::{get, web, Error, HttpRequest, HttpResponse};
use actix_ws::Message;
@@ -103,7 +103,14 @@ pub fn snapshot_aprs_history(context: &FrontendRuntimeContext) -> Vec<AprsPacket
.lock()
.expect("aprs history mutex poisoned");
prune_aprs_history(&mut history);
history.iter().map(|(_, pkt)| pkt.clone()).collect()
history
.iter()
.map(|(ts, pkt)| {
let mut pkt = pkt.clone();
pkt.ts_ms = Some(timestamp_ms_for_elapsed(ts.elapsed()));
pkt
})
.collect()
}
pub fn snapshot_cw_history(context: &FrontendRuntimeContext) -> Vec<CwEvent> {
@@ -141,6 +148,17 @@ pub fn clear_aprs_history(context: &FrontendRuntimeContext) {
history.clear();
}
fn timestamp_ms_for_elapsed(elapsed: Duration) -> i64 {
let wall_clock = SystemTime::now()
.checked_sub(elapsed)
.unwrap_or(UNIX_EPOCH);
let millis = wall_clock
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis();
i64::try_from(millis).unwrap_or(i64::MAX)
}
pub fn clear_cw_history(context: &FrontendRuntimeContext) {
let mut history = context
.cw_history
+2
View File
@@ -22,6 +22,8 @@ pub enum DecodedMessage {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AprsPacket {
#[serde(skip_serializing_if = "Option::is_none")]
pub ts_ms: Option<i64>,
pub src_call: String,
pub dest_call: String,
pub path: String,
+1
View File
@@ -250,6 +250,7 @@ mod tests {
fn make_pkt(src: &str, dest: &str, path: &str, info: &str, crc_ok: bool) -> AprsPacket {
AprsPacket {
ts_ms: None,
src_call: src.to_string(),
dest_call: dest.to_string(),
path: path.to_string(),