[feat](trx-client,trx-frontend-http): add default rig and hideable rf gain

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-02-28 23:45:17 +01:00
parent 9453c90dac
commit 420e98de31
6 changed files with 44 additions and 5 deletions
+13
View File
@@ -228,6 +228,10 @@ pub struct HttpFrontendConfig {
pub listen: IpAddr,
/// Listen port
pub port: u16,
/// Default rig selected in the web UI on startup.
pub default_rig_id: Option<String>,
/// Whether to expose the RF Gain control in the web UI.
pub show_sdr_gain_control: bool,
/// Authentication settings
pub auth: HttpAuthConfig,
}
@@ -238,6 +242,8 @@ impl Default for HttpFrontendConfig {
enabled: true,
listen: IpAddr::from([127, 0, 0, 1]),
port: 8080,
default_rig_id: None,
show_sdr_gain_control: true,
auth: HttpAuthConfig::default(),
}
}
@@ -329,6 +335,11 @@ impl ClientConfig {
if self.frontends.http.enabled && self.frontends.http.port == 0 {
return Err("[frontends.http].port must be > 0 when enabled".to_string());
}
if let Some(rig_id) = &self.frontends.http.default_rig_id {
if rig_id.trim().is_empty() {
return Err("[frontends.http].default_rig_id must not be empty when set".to_string());
}
}
if self.frontends.rigctl.enabled && self.frontends.rigctl.rig_ports.is_empty() {
return Err(
"[frontends.rigctl].rig_ports must contain at least one rig when enabled"
@@ -420,6 +431,8 @@ impl ClientConfig {
enabled: true,
listen: IpAddr::from([127, 0, 0, 1]),
port: 8080,
default_rig_id: Some("hf".to_string()),
show_sdr_gain_control: true,
auth: HttpAuthConfig {
enabled: false,
rx_passphrase: Some("rx-passphrase-example".to_string()),
+6 -1
View File
@@ -177,6 +177,7 @@ async fn async_init() -> DynResult<AppState> {
config::CookieSameSite::Lax => "Lax".to_string(),
config::CookieSameSite::None => "None".to_string(),
};
frontend_runtime.http_show_sdr_gain_control = cfg.frontends.http.show_sdr_gain_control;
// Resolve remote URL: CLI > config [remote] section > error
let remote_url = cli
@@ -189,7 +190,11 @@ async fn async_init() -> DynResult<AppState> {
parse_remote_url(&remote_url).map_err(|e| format!("Invalid remote URL: {}", e))?;
let remote_token = cli.token.clone().or_else(|| cfg.remote.auth.token.clone());
let remote_rig_id = cli.rig_id.clone().or_else(|| cfg.remote.rig_id.clone());
let remote_rig_id = cli
.rig_id
.clone()
.or_else(|| cfg.frontends.http.default_rig_id.clone())
.or_else(|| cfg.remote.rig_id.clone());
if let Ok(mut guard) = frontend_runtime.remote_active_rig_id.lock() {
*guard = remote_rig_id.clone();
}
+3
View File
@@ -166,6 +166,8 @@ pub struct FrontendRuntimeContext {
pub http_auth_cookie_secure: bool,
/// HTTP frontend auth cookie same-site policy
pub http_auth_cookie_same_site: String,
/// Whether the HTTP UI should expose the RF Gain control.
pub http_show_sdr_gain_control: bool,
/// Currently selected remote rig id (used by remote client routing).
pub remote_active_rig_id: Arc<Mutex<Option<String>>>,
/// Cached remote rig list from GetRigs polling.
@@ -199,6 +201,7 @@ impl FrontendRuntimeContext {
http_auth_session_ttl_secs: 480 * 60,
http_auth_cookie_secure: false,
http_auth_cookie_same_site: "Lax".to_string(),
http_show_sdr_gain_control: true,
remote_active_rig_id: Arc::new(Mutex::new(None)),
remote_rigs: Arc::new(Mutex::new(Vec::new())),
owner_callsign: None,
@@ -1281,6 +1281,9 @@ function render(update) {
wfmStFlagEl.classList.toggle("wfm-st-flag-mono", !detected);
}
}
if (sdrGainControlsEl && typeof update.show_sdr_gain_control === "boolean") {
sdrGainControlsEl.style.display = update.show_sdr_gain_control ? "" : "none";
}
if (update.status && update.status.freq && typeof update.status.freq.hz === "number") {
applyLocalTunedFrequency(update.status.freq.hz, true);
}
@@ -2574,6 +2577,7 @@ const audioRow = document.getElementById("audio-row");
const wfmControlsCol = document.getElementById("wfm-controls-col");
const wfmDeemphasisEl = document.getElementById("wfm-deemphasis");
const wfmAudioModeEl = document.getElementById("wfm-audio-mode");
const sdrGainControlsEl = document.getElementById("sdr-gain-controls");
const sdrGainEl = document.getElementById("sdr-gain-db");
const sdrGainSetBtn = document.getElementById("sdr-gain-set");
const wfmStFlagEl = document.getElementById("wfm-st-flag");
@@ -165,10 +165,12 @@
<option value="mono">Mono</option>
</select>
</label>
<label class="wfm-control">RF Gain
<input id="sdr-gain-db" class="status-input" type="number" min="0" max="60" step="1" inputmode="decimal">
</label>
<button id="sdr-gain-set" type="button" class="status-input">Set</button>
<div class="inline" id="sdr-gain-controls">
<label class="wfm-control">RF Gain
<input id="sdr-gain-db" class="status-input" type="number" min="0" max="60" step="1" inputmode="decimal">
</label>
<button id="sdr-gain-set" type="button" class="status-input">Set</button>
</div>
<label class="wfm-control wfm-st-flag-wrap" aria-label="Stereo pilot status">
<span id="wfm-st-flag" class="wfm-st-flag wfm-st-flag-mono">MO</span>
</label>
@@ -45,6 +45,7 @@ pub async fn status_api(
active_rig_id_from_context(context.get_ref().as_ref()),
rig_ids_from_context(context.get_ref().as_ref()),
owner_callsign_from_context(context.get_ref().as_ref()),
show_sdr_gain_control_from_context(context.get_ref().as_ref()),
);
Ok(HttpResponse::Ok()
.insert_header((header::CONTENT_TYPE, "application/json"))
@@ -60,6 +61,7 @@ fn inject_frontend_meta(
active_rig_id: Option<String>,
rig_ids: Vec<String>,
owner_callsign: Option<String>,
show_sdr_gain_control: bool,
) -> String {
let mut value: serde_json::Value = match serde_json::from_str(json) {
Ok(v) => v,
@@ -84,6 +86,10 @@ fn inject_frontend_meta(
if let Some(owner) = owner_callsign {
map.insert("owner_callsign".to_string(), serde_json::json!(owner));
}
map.insert(
"show_sdr_gain_control".to_string(),
serde_json::json!(show_sdr_gain_control),
);
serde_json::to_string(&value).unwrap_or_else(|_| json.to_string())
}
@@ -118,6 +124,10 @@ fn owner_callsign_from_context(context: &FrontendRuntimeContext) -> Option<Strin
context.owner_callsign.clone()
}
fn show_sdr_gain_control_from_context(context: &FrontendRuntimeContext) -> bool {
context.http_show_sdr_gain_control
}
#[get("/events")]
pub async fn events(
state: web::Data<watch::Receiver<RigState>>,
@@ -140,6 +150,7 @@ pub async fn events(
active_rig_id_from_context(context.get_ref().as_ref()),
rig_ids_from_context(context.get_ref().as_ref()),
owner_callsign_from_context(context.get_ref().as_ref()),
show_sdr_gain_control_from_context(context.get_ref().as_ref()),
);
let initial_stream =
once(async move { Ok::<Bytes, Error>(Bytes::from(format!("data: {initial_json}\n\n"))) });
@@ -160,6 +171,7 @@ pub async fn events(
active_rig_id_from_context(context.as_ref()),
rig_ids_from_context(context.as_ref()),
owner_callsign_from_context(context.as_ref()),
show_sdr_gain_control_from_context(context.as_ref()),
);
Ok::<Bytes, Error>(Bytes::from(format!("data: {json}\n\n")))
})