[feat](trx-client): add bandplan display config to client settings

Add bandplan_enabled (default: true) and bandplan_region (default:
"iaru_r1") fields to [frontends.http] config section, allowing the
operator to control the initial bandplan display setting from the
server config rather than requiring each browser session to configure
it manually. The server-provided default is applied on first connect
only when the user has no existing localStorage override.

https://claude.ai/code/session_01H7427hzbJepJzkoUJzoDmH
Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-03-29 22:27:21 +00:00
committed by Stan Grams
parent 6000c30d9c
commit 8c5706f6c3
5 changed files with 77 additions and 0 deletions
+50
View File
@@ -289,6 +289,10 @@ pub struct HttpFrontendConfig {
pub spectrum_usable_span_ratio: f32,
/// Whether to expose the RF Gain control in the web UI.
pub show_sdr_gain_control: bool,
/// Whether the bandplan strip is shown by default in the spectrum display.
pub bandplan_enabled: bool,
/// Default bandplan region: "iaru_r1", "iaru_r2", or "iaru_r3".
pub bandplan_region: String,
/// Default decode history retention in minutes for the active rig.
pub decode_history_retention_min: u64,
/// Optional per-rig decode history retention overrides in minutes.
@@ -308,6 +312,8 @@ impl Default for HttpFrontendConfig {
spectrum_coverage_margin_hz: 50_000,
spectrum_usable_span_ratio: 0.92,
show_sdr_gain_control: true,
bandplan_enabled: true,
bandplan_region: "iaru_r1".to_string(),
decode_history_retention_min: 24 * 60,
decode_history_retention_min_by_rig: HashMap::new(),
auth: HttpAuthConfig::default(),
@@ -507,6 +513,15 @@ impl ClientConfig {
"[frontends.http].spectrum_usable_span_ratio must be > 0.0 and <= 1.0".to_string(),
);
}
match self.frontends.http.bandplan_region.as_str() {
"iaru_r1" | "iaru_r2" | "iaru_r3" => {}
other => {
return Err(format!(
"[frontends.http].bandplan_region must be \"iaru_r1\", \"iaru_r2\", or \"iaru_r3\", got \"{}\"",
other
));
}
}
if self.frontends.http.decode_history_retention_min == 0 {
return Err("[frontends.http].decode_history_retention_min must be > 0".to_string());
}
@@ -650,6 +665,8 @@ impl ClientConfig {
spectrum_coverage_margin_hz: 50_000,
spectrum_usable_span_ratio: 0.92,
show_sdr_gain_control: true,
bandplan_enabled: true,
bandplan_region: "iaru_r1".to_string(),
decode_history_retention_min: 24 * 60,
decode_history_retention_min_by_rig: HashMap::new(),
auth: HttpAuthConfig {
@@ -731,6 +748,8 @@ mod tests {
assert_eq!(config.frontends.http.initial_map_zoom, 10);
assert_eq!(config.frontends.http.spectrum_coverage_margin_hz, 50_000);
assert_eq!(config.frontends.http.spectrum_usable_span_ratio, 0.92);
assert!(config.frontends.http.bandplan_enabled);
assert_eq!(config.frontends.http.bandplan_region, "iaru_r1");
assert_eq!(config.frontends.http.decode_history_retention_min, 1440);
assert!(config
.frontends
@@ -807,6 +826,9 @@ uhf = 60
assert_eq!(config.frontends.http.initial_map_zoom, 12);
assert_eq!(config.frontends.http.spectrum_coverage_margin_hz, 40_000);
assert_eq!(config.frontends.http.spectrum_usable_span_ratio, 0.9);
// bandplan fields not set in TOML → defaults
assert!(config.frontends.http.bandplan_enabled);
assert_eq!(config.frontends.http.bandplan_region, "iaru_r1");
assert_eq!(config.frontends.http.decode_history_retention_min, 720);
assert_eq!(
config
@@ -1155,4 +1177,32 @@ url = "remote.example.com:4530"
.unwrap_err()
.contains("poll_interval_ms must be > 0"));
}
#[test]
fn test_validate_rejects_invalid_bandplan_region() {
let mut config = ClientConfig::default();
config.frontends.http.bandplan_region = "invalid".to_string();
assert!(config.validate().is_err());
}
#[test]
fn test_validate_accepts_all_bandplan_regions() {
for region in &["iaru_r1", "iaru_r2", "iaru_r3"] {
let mut config = ClientConfig::default();
config.frontends.http.bandplan_region = region.to_string();
assert!(config.validate().is_ok(), "region {} should be valid", region);
}
}
#[test]
fn test_parse_bandplan_config_from_toml() {
let toml_str = r#"
[frontends.http]
bandplan_enabled = false
bandplan_region = "iaru_r2"
"#;
let config: ClientConfig = toml::from_str(toml_str).unwrap();
assert!(!config.frontends.http.bandplan_enabled);
assert_eq!(config.frontends.http.bandplan_region, "iaru_r2");
}
}
+2
View File
@@ -180,6 +180,8 @@ async fn async_init() -> DynResult<AppState> {
cfg.frontends.http.spectrum_coverage_margin_hz;
frontend_runtime.http_ui.spectrum_usable_span_ratio =
cfg.frontends.http.spectrum_usable_span_ratio;
frontend_runtime.http_ui.bandplan_enabled = cfg.frontends.http.bandplan_enabled;
frontend_runtime.http_ui.bandplan_region = cfg.frontends.http.bandplan_region.clone();
frontend_runtime.http_ui.decode_history_retention_min =
cfg.frontends.http.decode_history_retention_min;
frontend_runtime.http_ui.decode_history_retention_min_by_rig = cfg
+4
View File
@@ -282,6 +282,8 @@ pub struct HttpUiConfig {
pub initial_map_zoom: u8,
pub spectrum_coverage_margin_hz: u32,
pub spectrum_usable_span_ratio: f32,
pub bandplan_enabled: bool,
pub bandplan_region: String,
pub decode_history_retention_min: u64,
pub decode_history_retention_min_by_rig: HashMap<String, u64>,
}
@@ -293,6 +295,8 @@ impl Default for HttpUiConfig {
initial_map_zoom: 10,
spectrum_coverage_margin_hz: 50_000,
spectrum_usable_span_ratio: 0.92,
bandplan_enabled: true,
bandplan_region: "iaru_r1".to_string(),
decode_history_retention_min: 24 * 60,
decode_history_retention_min_by_rig: HashMap::new(),
}
@@ -3177,6 +3177,22 @@ function render(update) {
if (typeof update.show_sdr_gain_control === "boolean") {
if (sdrSettingsRowEl) sdrSettingsRowEl.style.display = update.show_sdr_gain_control ? "" : "none";
}
// Apply server-configured bandplan defaults once, only when the user has not
// previously overridden the setting via the UI (localStorage).
if (!_bandplanServerDefaultApplied && typeof update.bandplan_enabled === "boolean"
&& typeof update.bandplan_region === "string") {
_bandplanServerDefaultApplied = true;
const hasUserOverride = localStorage.getItem(STORAGE_PREFIX + "bandplanRegion") !== null;
if (!hasUserOverride) {
const region = update.bandplan_enabled ? update.bandplan_region : "off";
bandplanRegion = region;
saveSetting("bandplanRegion", region);
if (bandplanRegionSelect) bandplanRegionSelect.value = region;
bandplanSegmentsCache = null;
bandplanCacheKey = "";
if (lastSpectrumData) scheduleSpectrumDraw();
}
}
if (update.filter && sdrAgcEl && typeof update.filter.sdr_agc_enabled === "boolean") {
sdrAgcEl.checked = update.filter.sdr_agc_enabled;
updateSdrGainInputState();
@@ -11279,6 +11295,7 @@ if (spectrumCenterRightBtn) {
let bandplanData = null;
let bandplanRegion = loadSetting("bandplanRegion", "off");
let bandplanShowLabels = loadSetting("bandplanLabels", true);
let _bandplanServerDefaultApplied = false;
let bandplanSegmentsCache = null;
let bandplanCacheKey = "";
@@ -82,6 +82,8 @@ struct FrontendMeta {
initial_map_zoom: u8,
spectrum_coverage_margin_hz: u32,
spectrum_usable_span_ratio: f32,
bandplan_enabled: bool,
bandplan_region: String,
decode_history_retention_min: u64,
server_connected: bool,
}
@@ -171,6 +173,8 @@ fn frontend_meta_from_context(
initial_map_zoom: initial_map_zoom_from_context(context),
spectrum_coverage_margin_hz: spectrum_coverage_margin_hz_from_context(context),
spectrum_usable_span_ratio: spectrum_usable_span_ratio_from_context(context),
bandplan_enabled: context.http_ui.bandplan_enabled,
bandplan_region: context.http_ui.bandplan_region.clone(),
decode_history_retention_min: decode_history_retention_min_from_context(context),
server_connected,
}