[feat](trx-frontend-http): enhance header telemetry and responsive UX

Implement UI refinements for the HTTP frontend main and plugin views.\n\n- add dimmed header signal graph with live rendering and scale\n- make graph responsive, colorized by signal strength, and keep last 10s only\n- add APRS and WSPR text filtering, matching FT8 behavior\n- refine responsive layout for controls/map/header behavior\n- tune jog wheel/button sizing and mode selector height alignment\n\nCo-authored-by: OpenAI Codex <codex@openai.com>

Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
2026-02-13 02:03:20 +01:00
parent 630a02789c
commit 53ce663adc
5 changed files with 240 additions and 16 deletions
@@ -1,7 +1,9 @@
// --- APRS Decoder Plugin (server-side decode) ---
const aprsStatus = document.getElementById("aprs-status");
const aprsPacketsEl = document.getElementById("aprs-packets");
const aprsFilterInput = document.getElementById("aprs-filter");
const APRS_MAX_PACKETS = 100;
let aprsFilterText = "";
// Persistent packet history
let aprsPacketHistory = loadSetting("aprsPackets", []);
@@ -66,10 +68,37 @@ function renderAprsRow(pkt) {
const osmUrl = `https://www.openstreetmap.org/?mlat=${pkt.lat}&mlon=${pkt.lon}#map=15/${pkt.lat}/${pkt.lon}`;
posHtml = ` <a class="aprs-pos" href="${osmUrl}" target="_blank">${pkt.lat.toFixed(4)}, ${pkt.lon.toFixed(4)}</a>`;
}
row.dataset.filterText = [
pkt.srcCall,
pkt.destCall,
pkt.path,
pkt.info,
pkt.type,
pkt.lat != null ? pkt.lat.toFixed(4) : "",
pkt.lon != null ? pkt.lon.toFixed(4) : "",
]
.filter(Boolean)
.join(" ")
.toUpperCase();
row.innerHTML = `<span class="aprs-time">${ts}</span>${symbolHtml}<span class="aprs-call">${pkt.srcCall}</span>&gt;${pkt.destCall}${pkt.path ? "," + pkt.path : ""}: <span title="${pkt.type}">${renderAprsInfo(pkt)}</span>${posHtml}${crcTag}`;
applyAprsFilterToRow(row);
return row;
}
function applyAprsFilterToRow(row) {
if (!aprsFilterText) {
row.style.display = "";
return;
}
const message = row.dataset.filterText || "";
row.style.display = message.includes(aprsFilterText) ? "" : "none";
}
function applyAprsFilterToAll() {
const rows = aprsPacketsEl.querySelectorAll(".aprs-packet");
rows.forEach((row) => applyAprsFilterToRow(row));
}
function addAprsPacket(pkt) {
const tag = pkt.crcOk ? "[APRS]" : "[APRS-CRC-FAIL]";
console.log(tag, `${pkt.srcCall}>${pkt.destCall}${pkt.path ? "," + pkt.path : ""}: ${pkt.info}`, pkt);
@@ -108,6 +137,13 @@ for (let i = aprsPacketHistory.length - 1; i >= 0; i--) {
}
}
if (aprsFilterInput) {
aprsFilterInput.addEventListener("input", () => {
aprsFilterText = aprsFilterInput.value.trim().toUpperCase();
applyAprsFilterToAll();
});
}
// --- Server-side APRS decode handler ---
window.onServerAprs = function(pkt) {
aprsStatus.textContent = "Receiving";
@@ -2,8 +2,10 @@
const wsprStatus = document.getElementById("wspr-status");
const wsprPeriodEl = document.getElementById("wspr-period");
const wsprMessagesEl = document.getElementById("wspr-messages");
const wsprFilterInput = document.getElementById("wspr-filter");
const WSPR_MAX_MESSAGES = 200;
const WSPR_PERIOD_SECONDS = 120;
let wsprFilterText = "";
function fmtWsprTime(tsMs) {
if (!tsMs) return "--:--:--";
@@ -31,7 +33,9 @@ function renderWsprRow(msg) {
const rfHz = Number.isFinite(msg.freq_hz) && Number.isFinite(baseHz) ? (baseHz + msg.freq_hz) : null;
const freq = Number.isFinite(rfHz) ? rfHz.toFixed(0) : "--";
const message = (msg.message || "").toString();
row.dataset.message = message.toUpperCase();
row.innerHTML = `<span class="ft8-time">${fmtWsprTime(msg.ts_ms)}</span><span class="ft8-snr">${snr}</span><span class="ft8-dt">${dt}</span><span class="ft8-freq">${freq}</span><span class="ft8-msg">${escapeWsprHtml(message)}</span>`;
applyWsprFilterToRow(row);
return row;
}
@@ -50,6 +54,27 @@ function escapeWsprHtml(input) {
.replaceAll("\"", "&quot;");
}
function applyWsprFilterToRow(row) {
if (!wsprFilterText) {
row.style.display = "";
return;
}
const message = row.dataset.message || "";
row.style.display = message.includes(wsprFilterText) ? "" : "none";
}
function applyWsprFilterToAll() {
const rows = wsprMessagesEl.querySelectorAll(".ft8-row");
rows.forEach((row) => applyWsprFilterToRow(row));
}
if (wsprFilterInput) {
wsprFilterInput.addEventListener("input", () => {
wsprFilterText = wsprFilterInput.value.trim().toUpperCase();
applyWsprFilterToAll();
});
}
document.getElementById("wspr-decode-toggle-btn").addEventListener("click", async () => {
try { await postPath("/toggle_wspr_decode"); } catch (e) { console.error("WSPR toggle failed", e); }
});