feat(http-frontend): add rig and owner header lines

This commit is contained in:
2026-02-27 01:14:30 +01:00
parent 600257a7c4
commit 223c01392a
5 changed files with 55 additions and 16 deletions
+1
View File
@@ -238,6 +238,7 @@ async fn async_init() -> DynResult<AppState> {
.callsign .callsign
.clone() .clone()
.or_else(|| cfg.general.callsign.clone()); .or_else(|| cfg.general.callsign.clone());
frontend_runtime.owner_callsign = callsign.clone();
info!( info!(
"Starting trx-client (remote: {}, frontends: {})", "Starting trx-client (remote: {}, frontends: {})",
+3
View File
@@ -153,6 +153,8 @@ pub struct FrontendRuntimeContext {
pub remote_active_rig_id: Arc<Mutex<Option<String>>>, pub remote_active_rig_id: Arc<Mutex<Option<String>>>,
/// Cached remote rig list from GetRigs polling. /// Cached remote rig list from GetRigs polling.
pub remote_rigs: Arc<Mutex<Vec<RemoteRigEntry>>>, pub remote_rigs: Arc<Mutex<Vec<RemoteRigEntry>>>,
/// Owner callsign from trx-client config/CLI for frontend display.
pub owner_callsign: Option<String>,
} }
impl FrontendRuntimeContext { impl FrontendRuntimeContext {
@@ -180,6 +182,7 @@ impl FrontendRuntimeContext {
http_auth_cookie_same_site: "Lax".to_string(), http_auth_cookie_same_site: "Lax".to_string(),
remote_active_rig_id: Arc::new(Mutex::new(None)), remote_active_rig_id: Arc::new(Mutex::new(None)),
remote_rigs: Arc::new(Mutex::new(Vec::new())), remote_rigs: Arc::new(Mutex::new(Vec::new())),
owner_callsign: None,
} }
} }
} }
@@ -285,6 +285,8 @@ const swrValue = document.getElementById("swr-value");
const loadingEl = document.getElementById("loading"); const loadingEl = document.getElementById("loading");
const contentEl = document.getElementById("content"); const contentEl = document.getElementById("content");
const serverSubtitle = document.getElementById("server-subtitle"); const serverSubtitle = document.getElementById("server-subtitle");
const rigSubtitle = document.getElementById("rig-subtitle");
const ownerSubtitle = document.getElementById("owner-subtitle");
const loadingTitle = document.getElementById("loading-title"); const loadingTitle = document.getElementById("loading-title");
const loadingSub = document.getElementById("loading-sub"); const loadingSub = document.getElementById("loading-sub");
const headerSigCanvas = document.getElementById("header-sig-canvas"); const headerSigCanvas = document.getElementById("header-sig-canvas");
@@ -297,7 +299,6 @@ const headerRigSwitchBtn = document.getElementById("header-rig-switch-btn");
let lastControl; let lastControl;
let lastTxEn = null; let lastTxEn = null;
let lastRendered = null; let lastRendered = null;
let rigName = "Rig";
let hintTimer = null; let hintTimer = null;
let sigMeasuring = false; let sigMeasuring = false;
let sigLastSUnits = null; let sigLastSUnits = null;
@@ -729,6 +730,8 @@ function setDisabled(disabled) {
let serverVersion = null; let serverVersion = null;
let serverBuildDate = null; let serverBuildDate = null;
let serverCallsign = null; let serverCallsign = null;
let ownerCallsign = null;
let rigDisplayName = null;
let serverLat = null; let serverLat = null;
let serverLon = null; let serverLon = null;
@@ -741,17 +744,20 @@ function updateFooterBuildInfo() {
} }
function updateTitle() { function updateTitle() {
let title = rigName || "Rig"; document.getElementById("rig-title").textContent = originalTitle;
if (serverCallsign) title = `${serverCallsign}'s ${title}`;
document.getElementById("rig-title").textContent = title;
} }
function render(update) { function render(update) {
if (!update) return; if (!update) return;
if (update.info && update.info.model) rigName = update.info.model; if (update.info && typeof update.info.model === "string" && update.info.model.length > 0) {
rigDisplayName = update.info.model;
}
if (update.server_version) serverVersion = update.server_version; if (update.server_version) serverVersion = update.server_version;
if (update.server_build_date) serverBuildDate = update.server_build_date; if (update.server_build_date) serverBuildDate = update.server_build_date;
if (update.server_callsign) serverCallsign = update.server_callsign; if (update.server_callsign) serverCallsign = update.server_callsign;
if (typeof update.owner_callsign === "string" && update.owner_callsign.length > 0) {
ownerCallsign = update.owner_callsign;
}
if (update.server_latitude != null) serverLat = update.server_latitude; if (update.server_latitude != null) serverLat = update.server_latitude;
if (update.server_longitude != null) serverLon = update.server_longitude; if (update.server_longitude != null) serverLon = update.server_longitude;
updateTitle(); updateTitle();
@@ -786,17 +792,21 @@ function render(update) {
if (contentEl) contentEl.style.display = ""; if (contentEl) contentEl.style.display = "";
} }
// Server subtitle: "trx-server vX.Y.Z hosted by CALL" // Server subtitle: "trx-server vX.Y.Z hosted by CALL"
if (update.server_version || update.server_callsign) { if (serverSubtitle) {
let parts = "trx-server"; if (update.server_version && update.server_callsign) {
if (update.server_version) parts += ` v${update.server_version}`; serverSubtitle.textContent = `trx-server v${update.server_version} hosted by ${update.server_callsign}`;
if (update.server_callsign) { } else if (update.server_version) {
const cs = update.server_callsign; serverSubtitle.textContent = `trx-server v${update.server_version}`;
serverSubtitle.innerHTML = `${parts} hosted by <a href="https://www.qrzcq.com/call/${encodeURIComponent(cs)}" target="_blank" rel="noopener">${cs}</a>`; } else if (update.server_callsign) {
document.title = `${cs} - ${originalTitle}`; serverSubtitle.textContent = `trx-server hosted by ${update.server_callsign}`;
} else {
serverSubtitle.textContent = parts;
} }
} }
if (rigSubtitle) {
rigSubtitle.textContent = `Rig: ${rigDisplayName || "--"}`;
}
if (ownerSubtitle) {
ownerSubtitle.textContent = `Owner: ${ownerCallsign || "--"}`;
}
setDisabled(false); setDisabled(false);
if (update.info && update.info.capabilities && Array.isArray(update.info.capabilities.supported_modes)) { if (update.info && update.info.capabilities && Array.isArray(update.info.capabilities.supported_modes)) {
const modes = update.info.capabilities.supported_modes.map(normalizeMode).filter(Boolean); const modes = update.info.capabilities.supported_modes.map(normalizeMode).filter(Boolean);
@@ -15,7 +15,8 @@
<div class="header-text"> <div class="header-text">
<div class="title"><span id="rig-title">trx-rs</span></div> <div class="title"><span id="rig-title">trx-rs</span></div>
<div class="subtitle" id="server-subtitle"></div> <div class="subtitle" id="server-subtitle"></div>
<div class="subtitle">{pkg} v{ver}</div> <div class="subtitle" id="rig-subtitle">Rig: --</div>
<div class="subtitle" id="owner-subtitle">Owner: --</div>
</div> </div>
<div class="header-signal-wrap" aria-hidden="true"> <div class="header-signal-wrap" aria-hidden="true">
<canvas id="header-sig-canvas"></canvas> <canvas id="header-sig-canvas"></canvas>
@@ -32,9 +32,23 @@ const REQUEST_TIMEOUT: Duration = Duration::from_secs(15);
#[get("/status")] #[get("/status")]
pub async fn status_api( pub async fn status_api(
state: web::Data<watch::Receiver<RigState>>, state: web::Data<watch::Receiver<RigState>>,
clients: web::Data<Arc<AtomicUsize>>,
context: web::Data<Arc<FrontendRuntimeContext>>,
) -> Result<impl Responder, Error> { ) -> Result<impl Responder, Error> {
let state = wait_for_view(state.get_ref().clone()).await?; let state = wait_for_view(state.get_ref().clone()).await?;
Ok(HttpResponse::Ok().json(state)) let json = serde_json::to_string(&state).map_err(actix_web::error::ErrorInternalServerError)?;
let json = inject_frontend_meta(
&json,
clients.load(Ordering::Relaxed),
context.rigctl_clients.load(Ordering::Relaxed),
rigctl_addr_from_context(context.get_ref().as_ref()),
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()),
);
Ok(HttpResponse::Ok()
.insert_header((header::CONTENT_TYPE, "application/json"))
.body(json))
} }
/// Inject `"clients": N` into a JSON object string. /// Inject `"clients": N` into a JSON object string.
@@ -45,6 +59,7 @@ fn inject_frontend_meta(
rigctl_addr: Option<String>, rigctl_addr: Option<String>,
active_rig_id: Option<String>, active_rig_id: Option<String>,
rig_ids: Vec<String>, rig_ids: Vec<String>,
owner_callsign: Option<String>,
) -> String { ) -> String {
let mut value: serde_json::Value = match serde_json::from_str(json) { let mut value: serde_json::Value = match serde_json::from_str(json) {
Ok(v) => v, Ok(v) => v,
@@ -66,6 +81,9 @@ fn inject_frontend_meta(
map.insert("active_rig_id".to_string(), serde_json::json!(rig_id)); map.insert("active_rig_id".to_string(), serde_json::json!(rig_id));
} }
map.insert("rig_ids".to_string(), serde_json::json!(rig_ids)); map.insert("rig_ids".to_string(), serde_json::json!(rig_ids));
if let Some(owner) = owner_callsign {
map.insert("owner_callsign".to_string(), serde_json::json!(owner));
}
serde_json::to_string(&value).unwrap_or_else(|_| json.to_string()) serde_json::to_string(&value).unwrap_or_else(|_| json.to_string())
} }
@@ -96,6 +114,10 @@ fn rig_ids_from_context(context: &FrontendRuntimeContext) -> Vec<String> {
.unwrap_or_default() .unwrap_or_default()
} }
fn owner_callsign_from_context(context: &FrontendRuntimeContext) -> Option<String> {
context.owner_callsign.clone()
}
#[get("/events")] #[get("/events")]
pub async fn events( pub async fn events(
state: web::Data<watch::Receiver<RigState>>, state: web::Data<watch::Receiver<RigState>>,
@@ -117,6 +139,7 @@ pub async fn events(
rigctl_addr_from_context(context.get_ref().as_ref()), rigctl_addr_from_context(context.get_ref().as_ref()),
active_rig_id_from_context(context.get_ref().as_ref()), active_rig_id_from_context(context.get_ref().as_ref()),
rig_ids_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()),
); );
let initial_stream = let initial_stream =
once(async move { Ok::<Bytes, Error>(Bytes::from(format!("data: {initial_json}\n\n"))) }); once(async move { Ok::<Bytes, Error>(Bytes::from(format!("data: {initial_json}\n\n"))) });
@@ -136,6 +159,7 @@ pub async fn events(
rigctl_addr_from_context(context.as_ref()), rigctl_addr_from_context(context.as_ref()),
active_rig_id_from_context(context.as_ref()), active_rig_id_from_context(context.as_ref()),
rig_ids_from_context(context.as_ref()), rig_ids_from_context(context.as_ref()),
owner_callsign_from_context(context.as_ref()),
); );
Ok::<Bytes, Error>(Bytes::from(format!("data: {json}\n\n"))) Ok::<Bytes, Error>(Bytes::from(format!("data: {json}\n\n")))
}) })