[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:
@@ -289,6 +289,10 @@ pub struct HttpFrontendConfig {
|
|||||||
pub spectrum_usable_span_ratio: f32,
|
pub spectrum_usable_span_ratio: f32,
|
||||||
/// Whether to expose the RF Gain control in the web UI.
|
/// Whether to expose the RF Gain control in the web UI.
|
||||||
pub show_sdr_gain_control: bool,
|
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.
|
/// Default decode history retention in minutes for the active rig.
|
||||||
pub decode_history_retention_min: u64,
|
pub decode_history_retention_min: u64,
|
||||||
/// Optional per-rig decode history retention overrides in minutes.
|
/// Optional per-rig decode history retention overrides in minutes.
|
||||||
@@ -308,6 +312,8 @@ impl Default for HttpFrontendConfig {
|
|||||||
spectrum_coverage_margin_hz: 50_000,
|
spectrum_coverage_margin_hz: 50_000,
|
||||||
spectrum_usable_span_ratio: 0.92,
|
spectrum_usable_span_ratio: 0.92,
|
||||||
show_sdr_gain_control: true,
|
show_sdr_gain_control: true,
|
||||||
|
bandplan_enabled: true,
|
||||||
|
bandplan_region: "iaru_r1".to_string(),
|
||||||
decode_history_retention_min: 24 * 60,
|
decode_history_retention_min: 24 * 60,
|
||||||
decode_history_retention_min_by_rig: HashMap::new(),
|
decode_history_retention_min_by_rig: HashMap::new(),
|
||||||
auth: HttpAuthConfig::default(),
|
auth: HttpAuthConfig::default(),
|
||||||
@@ -507,6 +513,15 @@ impl ClientConfig {
|
|||||||
"[frontends.http].spectrum_usable_span_ratio must be > 0.0 and <= 1.0".to_string(),
|
"[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 {
|
if self.frontends.http.decode_history_retention_min == 0 {
|
||||||
return Err("[frontends.http].decode_history_retention_min must be > 0".to_string());
|
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_coverage_margin_hz: 50_000,
|
||||||
spectrum_usable_span_ratio: 0.92,
|
spectrum_usable_span_ratio: 0.92,
|
||||||
show_sdr_gain_control: true,
|
show_sdr_gain_control: true,
|
||||||
|
bandplan_enabled: true,
|
||||||
|
bandplan_region: "iaru_r1".to_string(),
|
||||||
decode_history_retention_min: 24 * 60,
|
decode_history_retention_min: 24 * 60,
|
||||||
decode_history_retention_min_by_rig: HashMap::new(),
|
decode_history_retention_min_by_rig: HashMap::new(),
|
||||||
auth: HttpAuthConfig {
|
auth: HttpAuthConfig {
|
||||||
@@ -731,6 +748,8 @@ mod tests {
|
|||||||
assert_eq!(config.frontends.http.initial_map_zoom, 10);
|
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_coverage_margin_hz, 50_000);
|
||||||
assert_eq!(config.frontends.http.spectrum_usable_span_ratio, 0.92);
|
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_eq!(config.frontends.http.decode_history_retention_min, 1440);
|
||||||
assert!(config
|
assert!(config
|
||||||
.frontends
|
.frontends
|
||||||
@@ -807,6 +826,9 @@ uhf = 60
|
|||||||
assert_eq!(config.frontends.http.initial_map_zoom, 12);
|
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_coverage_margin_hz, 40_000);
|
||||||
assert_eq!(config.frontends.http.spectrum_usable_span_ratio, 0.9);
|
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.frontends.http.decode_history_retention_min, 720);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config
|
config
|
||||||
@@ -1155,4 +1177,32 @@ url = "remote.example.com:4530"
|
|||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.contains("poll_interval_ms must be > 0"));
|
.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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,6 +180,8 @@ async fn async_init() -> DynResult<AppState> {
|
|||||||
cfg.frontends.http.spectrum_coverage_margin_hz;
|
cfg.frontends.http.spectrum_coverage_margin_hz;
|
||||||
frontend_runtime.http_ui.spectrum_usable_span_ratio =
|
frontend_runtime.http_ui.spectrum_usable_span_ratio =
|
||||||
cfg.frontends.http.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 =
|
frontend_runtime.http_ui.decode_history_retention_min =
|
||||||
cfg.frontends.http.decode_history_retention_min;
|
cfg.frontends.http.decode_history_retention_min;
|
||||||
frontend_runtime.http_ui.decode_history_retention_min_by_rig = cfg
|
frontend_runtime.http_ui.decode_history_retention_min_by_rig = cfg
|
||||||
|
|||||||
@@ -282,6 +282,8 @@ pub struct HttpUiConfig {
|
|||||||
pub initial_map_zoom: u8,
|
pub initial_map_zoom: u8,
|
||||||
pub spectrum_coverage_margin_hz: u32,
|
pub spectrum_coverage_margin_hz: u32,
|
||||||
pub spectrum_usable_span_ratio: f32,
|
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: u64,
|
||||||
pub decode_history_retention_min_by_rig: HashMap<String, u64>,
|
pub decode_history_retention_min_by_rig: HashMap<String, u64>,
|
||||||
}
|
}
|
||||||
@@ -293,6 +295,8 @@ impl Default for HttpUiConfig {
|
|||||||
initial_map_zoom: 10,
|
initial_map_zoom: 10,
|
||||||
spectrum_coverage_margin_hz: 50_000,
|
spectrum_coverage_margin_hz: 50_000,
|
||||||
spectrum_usable_span_ratio: 0.92,
|
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: 24 * 60,
|
||||||
decode_history_retention_min_by_rig: HashMap::new(),
|
decode_history_retention_min_by_rig: HashMap::new(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3177,6 +3177,22 @@ function render(update) {
|
|||||||
if (typeof update.show_sdr_gain_control === "boolean") {
|
if (typeof update.show_sdr_gain_control === "boolean") {
|
||||||
if (sdrSettingsRowEl) sdrSettingsRowEl.style.display = update.show_sdr_gain_control ? "" : "none";
|
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") {
|
if (update.filter && sdrAgcEl && typeof update.filter.sdr_agc_enabled === "boolean") {
|
||||||
sdrAgcEl.checked = update.filter.sdr_agc_enabled;
|
sdrAgcEl.checked = update.filter.sdr_agc_enabled;
|
||||||
updateSdrGainInputState();
|
updateSdrGainInputState();
|
||||||
@@ -11279,6 +11295,7 @@ if (spectrumCenterRightBtn) {
|
|||||||
let bandplanData = null;
|
let bandplanData = null;
|
||||||
let bandplanRegion = loadSetting("bandplanRegion", "off");
|
let bandplanRegion = loadSetting("bandplanRegion", "off");
|
||||||
let bandplanShowLabels = loadSetting("bandplanLabels", true);
|
let bandplanShowLabels = loadSetting("bandplanLabels", true);
|
||||||
|
let _bandplanServerDefaultApplied = false;
|
||||||
let bandplanSegmentsCache = null;
|
let bandplanSegmentsCache = null;
|
||||||
let bandplanCacheKey = "";
|
let bandplanCacheKey = "";
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ struct FrontendMeta {
|
|||||||
initial_map_zoom: u8,
|
initial_map_zoom: u8,
|
||||||
spectrum_coverage_margin_hz: u32,
|
spectrum_coverage_margin_hz: u32,
|
||||||
spectrum_usable_span_ratio: f32,
|
spectrum_usable_span_ratio: f32,
|
||||||
|
bandplan_enabled: bool,
|
||||||
|
bandplan_region: String,
|
||||||
decode_history_retention_min: u64,
|
decode_history_retention_min: u64,
|
||||||
server_connected: bool,
|
server_connected: bool,
|
||||||
}
|
}
|
||||||
@@ -171,6 +173,8 @@ fn frontend_meta_from_context(
|
|||||||
initial_map_zoom: initial_map_zoom_from_context(context),
|
initial_map_zoom: initial_map_zoom_from_context(context),
|
||||||
spectrum_coverage_margin_hz: spectrum_coverage_margin_hz_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),
|
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),
|
decode_history_retention_min: decode_history_retention_min_from_context(context),
|
||||||
server_connected,
|
server_connected,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user