[feat](trx-frontend-http): add tab navigation with Main and About tabs

Add a tab bar to the HTTP frontend card. All existing controls stay in
the Main tab. A new About tab shows server version, callsign, rig info,
client version, and connected client count, updated live via SSE.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
2026-02-08 13:40:05 +01:00
parent 91e599cae0
commit e60f3db4b2
3 changed files with 47 additions and 1 deletions
@@ -289,6 +289,20 @@ function render(update) {
}
if (typeof update.clients === "number") lastClientCount = update.clients;
// Populate About tab
if (update.server_version) {
document.getElementById("about-server-ver").textContent = `trx-server v${update.server_version}`;
}
if (update.server_callsign) {
document.getElementById("about-server-call").textContent = update.server_callsign;
}
if (update.info) {
const parts = [update.info.manufacturer, update.info.model, update.info.revision].filter(Boolean).join(" ");
if (parts) document.getElementById("about-rig-info").textContent = parts;
}
if (typeof update.clients === "number") {
document.getElementById("about-clients").textContent = update.clients;
}
powerHint.textContent = readyText();
lastLocked = update.status && update.status.lock === true;
lockBtn.textContent = lastLocked ? "Unlock" : "Lock";
@@ -568,6 +582,16 @@ lockBtn.addEventListener("click", async () => {
}
});
// --- Tab navigation ---
document.querySelector(".tab-bar").addEventListener("click", (e) => {
const btn = e.target.closest(".tab[data-tab]");
if (!btn) return;
document.querySelectorAll(".tab-bar .tab").forEach((t) => t.classList.remove("active"));
btn.classList.add("active");
document.querySelectorAll(".tab-panel").forEach((p) => p.style.display = "none");
document.getElementById(`tab-${btn.dataset.tab}`).style.display = "";
});
connect();
// --- Signal measurement ---
@@ -17,6 +17,11 @@
<div class="subtitle">{pkg} v{ver}</div>
</div>
</div>
<div class="tab-bar">
<button class="tab active" data-tab="main">Main</button>
<button class="tab" data-tab="about">About</button>
</div>
<div id="tab-main" class="tab-panel">
<div id="loading" style="text-align:center; padding:2rem 0;">
<div id="loading-title" style="margin-bottom:0.4rem; font-size:1.1rem; font-weight:600;">Initializing (rig)…</div>
<div id="loading-sub" style="color:#9aa4b5;"></div>
@@ -109,12 +114,21 @@
<small id="audio-status" style="min-width: 60px;">Off</small>
</div>
</div>
</div>
<div id="tab-about" class="tab-panel" style="display:none;">
<table class="about-table">
<tr><td>Server</td><td id="about-server-ver">--</td></tr>
<tr><td>Server callsign</td><td id="about-server-call">--</td></tr>
<tr><td>Rig</td><td id="about-rig-info">--</td></tr>
<tr><td>Client</td><td>{pkg} v{ver}</td></tr>
<tr><td>Connected clients</td><td id="about-clients">--</td></tr>
</table>
</div>
<div class="footer">
<div class="copyright">Built by <a href="https://www.qrzcq.com/call/SP2SJG" target="_blank" rel="noopener">SP2SJG</a> from <a href="https://haxx.space" target="_blank" rel="noopener">haxx.space</a><span id="copyright-year"></span></div>
<div class="hint" id="power-hint">Connecting…</div>
</div>
</div>
</div>
<script src="/app.js"></script>
</body>
</html>
@@ -150,6 +150,14 @@ small { color: var(--text-muted); }
.meter-fill { height: 100%; width: 0%; background: linear-gradient(90deg, var(--accent-green), var(--accent-yellow), var(--accent-red)); transition: width 150ms ease; }
.meter-value { font-size: 0.95rem; color: var(--text-heading); min-width: 64px; text-align: right; }
#content { display: flex; flex-direction: column; gap: 1.1rem; }
.tab-bar { display: flex; border-bottom: 1px solid var(--border); margin-bottom: 1rem; position: relative; z-index: 2; }
.tab { background: transparent; border: none; border-bottom: 2px solid transparent; border-radius: 0; padding: 0.5rem 1rem; color: var(--text-muted); cursor: pointer; font-size: 0.95rem; height: auto; }
.tab.active { border-bottom-color: var(--accent-green); color: var(--accent-green); font-weight: 600; }
.tab:hover:not(.active) { color: var(--text); }
.about-table { width: 100%; border-collapse: collapse; }
.about-table td { padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--border); }
.about-table tr:last-child td { border-bottom: none; }
.about-table td:first-child { color: var(--text-muted); width: 40%; }
.footer { display: flex; justify-content: space-between; align-items: baseline; }
.full-row { grid-column: 1 / -1; }
.copyright { color: var(--text-muted); font-size: 0.75rem; opacity: 0.7; }