feat(cluster): configurable spot lifetime

Spots were bounded by COUNT alone, so on a quiet band a two-hour-old spot sat
on the band map looking like something to chase. Settings → DX Cluster now
takes a lifetime: presets at 5/10/15/30/60 minutes, or any value up to twelve
hours, and 0 keeps the old behaviour.

Spots are REMOVED from the shared list rather than hidden at render. The
cluster list, every band map and the counts all read that one array, so pruning
it once is what makes the setting mean the same thing in every view — the
lesson already paid for when the band map ignored the cluster's filters. It
also gives the memory back.

A spot whose timestamp will not parse is never dropped: an unreadable stamp is
a reason to distrust the clock, not to throw away the spot.

Swept every 30 seconds — close enough that a 5-minute setting is honoured, and
invisible next to the spot stream. Clamped in the backend as well as the UI.
This commit is contained in:
2026-08-13 10:38:08 +02:00
parent f8fb57210f
commit b2382a6135
7 changed files with 109 additions and 5 deletions
+33
View File
@@ -298,6 +298,7 @@ const (
keyClusterAutoConnect = "cluster.auto_connect" // open every enabled server at app start
keyClusterSelfSpot = "cluster.self_spot" // "1" → announce ourselves on the cluster as we log
keyClusterSelfSpotMin = "cluster.self_spot_minutes" // shortest gap between two self-spots
keyClusterSpotTTL = "cluster.spot_ttl_min" // drop spots older than this; 0 = keep them
keyScpEnabled = "scp.enabled" // Super Check Partial / N+1 suggestions on
@@ -17728,3 +17729,35 @@ func (a *App) SetKenwoodKeySpeed(wpm int) error {
}
return a.cat.KenwoodDo(func(k cat.KenwoodController) error { return k.SetKeySpeed(wpm) })
}
// spotTTLMax bounds the setting. Twelve hours is far past any operational use
// and exists only so a mistyped value cannot mean "never".
const spotTTLMax = 720
// GetSpotTTLMinutes returns how long a spot stays in the list, in minutes.
// 0 means spots are kept until the count cap pushes them out, which is what
// OpsLog always did.
func (a *App) GetSpotTTLMinutes() int {
n, _ := strconv.Atoi(a.settingOr(keyClusterSpotTTL, ""))
if n < 0 {
return 0
}
if n > spotTTLMax {
return spotTTLMax
}
return n
}
// SetSpotTTLMinutes sets it. Clamped here as well as in the UI: the value
// shapes what an operator sees on the band map, and a stale or bypassed
// frontend must not be able to widen it past the ceiling.
func (a *App) SetSpotTTLMinutes(min int) error {
if min < 0 {
min = 0
}
if min > spotTTLMax {
min = spotTTLMax
}
a.setSetting(keyClusterSpotTTL, strconv.Itoa(min))
return nil
}