[feat](trx-frontend-http): rework About page with grouped cards and new info
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -1037,8 +1037,28 @@ let lastUnsupportedFreqPopupAt = 0;
|
|||||||
let freqDirty = false;
|
let freqDirty = false;
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
let lastEventAt = Date.now();
|
let lastEventAt = Date.now();
|
||||||
|
let aboutUptimeStart = null;
|
||||||
let es;
|
let es;
|
||||||
let esHeartbeat;
|
let esHeartbeat;
|
||||||
|
|
||||||
|
function formatUptime(ms) {
|
||||||
|
const s = Math.floor(ms / 1000);
|
||||||
|
const d = Math.floor(s / 86400);
|
||||||
|
const h = Math.floor((s % 86400) / 3600);
|
||||||
|
const m = Math.floor((s % 3600) / 60);
|
||||||
|
const sec = s % 60;
|
||||||
|
const parts = [];
|
||||||
|
if (d > 0) parts.push(`${d}d`);
|
||||||
|
if (h > 0 || d > 0) parts.push(`${h}h`);
|
||||||
|
parts.push(`${m}m`);
|
||||||
|
parts.push(`${sec}s`);
|
||||||
|
return parts.join(" ");
|
||||||
|
}
|
||||||
|
setInterval(() => {
|
||||||
|
if (!aboutUptimeStart) return;
|
||||||
|
const el = document.getElementById("about-uptime");
|
||||||
|
if (el) el.textContent = formatUptime(Date.now() - aboutUptimeStart);
|
||||||
|
}, 1000);
|
||||||
let reconnectTimer = null;
|
let reconnectTimer = null;
|
||||||
let overviewSignalSamples = [];
|
let overviewSignalSamples = [];
|
||||||
let overviewSignalTimer = null;
|
let overviewSignalTimer = null;
|
||||||
@@ -3194,20 +3214,23 @@ function render(update) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof update.clients === "number") lastClientCount = update.clients;
|
if (typeof update.clients === "number") lastClientCount = update.clients;
|
||||||
// Populate About tab
|
// Populate About tab — Server card
|
||||||
if (update.server_version) {
|
if (update.server_version) {
|
||||||
document.getElementById("about-server-ver").textContent = `trx-server v${update.server_version}`;
|
document.getElementById("about-server-ver").textContent = `trx-server v${update.server_version}`;
|
||||||
}
|
}
|
||||||
|
if (update.server_build_date) {
|
||||||
|
document.getElementById("about-server-build-date").textContent = update.server_build_date;
|
||||||
|
}
|
||||||
document.getElementById("about-server-addr").textContent = location.host;
|
document.getElementById("about-server-addr").textContent = location.host;
|
||||||
if (update.server_callsign) {
|
if (update.server_callsign) {
|
||||||
document.getElementById("about-server-call").textContent = update.server_callsign;
|
document.getElementById("about-server-call").textContent = update.server_callsign;
|
||||||
}
|
}
|
||||||
if (update.pskreporter_status) {
|
if (Number.isFinite(serverLat) && Number.isFinite(serverLon)) {
|
||||||
document.getElementById("about-pskreporter").textContent = update.pskreporter_status;
|
const grid = latLonToMaidenhead(serverLat, serverLon);
|
||||||
}
|
document.getElementById("about-server-location").textContent = `${grid} (${serverLat.toFixed(4)}, ${serverLon.toFixed(4)})`;
|
||||||
if (update.aprs_is_status) {
|
|
||||||
document.getElementById("about-aprs-is").textContent = update.aprs_is_status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// About — Radio card
|
||||||
if (update.info) {
|
if (update.info) {
|
||||||
const parts = [update.info.manufacturer, update.info.model, update.info.revision].filter(Boolean).join(" ");
|
const parts = [update.info.manufacturer, update.info.model, update.info.revision].filter(Boolean).join(" ");
|
||||||
if (parts) document.getElementById("about-rig-info").textContent = parts;
|
if (parts) document.getElementById("about-rig-info").textContent = parts;
|
||||||
@@ -3233,21 +3256,59 @@ function render(update) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof update.clients === "number") {
|
|
||||||
document.getElementById("about-clients").textContent = update.clients;
|
|
||||||
}
|
|
||||||
if (lastActiveRigId) {
|
if (lastActiveRigId) {
|
||||||
document.getElementById("about-active-rig").textContent = lastActiveRigId;
|
document.getElementById("about-active-rig").textContent = lastActiveRigId;
|
||||||
}
|
}
|
||||||
if (Array.isArray(update.remotes)) {
|
if (Array.isArray(update.remotes)) {
|
||||||
applyRigList(update.active_remote, update.remotes);
|
applyRigList(update.active_remote, update.remotes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// About — Audio card
|
||||||
|
if (streamInfo) {
|
||||||
|
document.getElementById("about-audio-codec").textContent = "Opus";
|
||||||
|
document.getElementById("about-audio-samplerate").textContent = `${(streamInfo.sample_rate || 48000).toLocaleString()} Hz`;
|
||||||
|
document.getElementById("about-audio-channels").textContent = (streamInfo.channels || 1) === 1 ? "Mono" : "Stereo";
|
||||||
|
if (streamInfo.frame_duration_ms) {
|
||||||
|
document.getElementById("about-audio-frame").textContent = `${streamInfo.frame_duration_ms} ms`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.getElementById("about-audio-rx").textContent = rxActive ? "Active" : "Off";
|
||||||
|
|
||||||
|
// About — Decoders card
|
||||||
|
const decMap = [
|
||||||
|
["about-dec-ft8", update.ft8_decode_enabled],
|
||||||
|
["about-dec-ft4", update.ft4_decode_enabled],
|
||||||
|
["about-dec-ft2", update.ft2_decode_enabled],
|
||||||
|
["about-dec-wspr", update.wspr_decode_enabled],
|
||||||
|
["about-dec-cw", update.cw_decode_enabled],
|
||||||
|
["about-dec-aprs", update.aprs_decode_enabled || update.hf_aprs_decode_enabled],
|
||||||
|
];
|
||||||
|
for (const [id, enabled] of decMap) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) {
|
||||||
|
el.textContent = enabled ? "Active" : "Off";
|
||||||
|
el.className = enabled ? "about-status-on" : "about-status-off";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// About — Integrations card
|
||||||
|
if (update.pskreporter_status) {
|
||||||
|
document.getElementById("about-pskreporter").textContent = update.pskreporter_status;
|
||||||
|
}
|
||||||
|
if (update.aprs_is_status) {
|
||||||
|
document.getElementById("about-aprs-is").textContent = update.aprs_is_status;
|
||||||
|
}
|
||||||
if (typeof update.rigctl_clients === "number") {
|
if (typeof update.rigctl_clients === "number") {
|
||||||
document.getElementById("about-rigctl-clients").textContent = update.rigctl_clients;
|
document.getElementById("about-rigctl-clients").textContent = update.rigctl_clients;
|
||||||
}
|
}
|
||||||
if (typeof update.rigctl_addr === "string" && update.rigctl_addr.length > 0) {
|
if (typeof update.rigctl_addr === "string" && update.rigctl_addr.length > 0) {
|
||||||
document.getElementById("about-rigctl-endpoint").textContent = update.rigctl_addr;
|
document.getElementById("about-rigctl-endpoint").textContent = update.rigctl_addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// About — Clients card
|
||||||
|
if (typeof update.clients === "number") {
|
||||||
|
document.getElementById("about-clients").textContent = update.clients;
|
||||||
|
}
|
||||||
powerHint.textContent = readyText();
|
powerHint.textContent = readyText();
|
||||||
lastLocked = update.status && update.status.lock === true;
|
lastLocked = update.status && update.status.lock === true;
|
||||||
lockBtn.textContent = lastLocked ? "Unlock" : "Lock";
|
lockBtn.textContent = lastLocked ? "Unlock" : "Lock";
|
||||||
@@ -3313,6 +3374,7 @@ function connect() {
|
|||||||
lastEventAt = Date.now();
|
lastEventAt = Date.now();
|
||||||
es.onopen = () => {
|
es.onopen = () => {
|
||||||
setConnLostOverlay(false);
|
setConnLostOverlay(false);
|
||||||
|
if (!aboutUptimeStart) aboutUptimeStart = Date.now();
|
||||||
pollFreshSnapshot();
|
pollFreshSnapshot();
|
||||||
refreshRigList();
|
refreshRigList();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1015,23 +1015,92 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="tab-about" class="tab-panel" style="display:none;">
|
<div id="tab-about" class="tab-panel" style="display:none;">
|
||||||
<div id="auth-badge" style="display:none; margin-bottom: 1rem; padding: 0.5rem; background: var(--bg-secondary); border-radius: 0.25rem; color: var(--text-muted); font-size: 0.85rem;">Authenticated as: <strong id="auth-role-badge">--</strong></div>
|
<div id="auth-badge" style="display:none; margin-bottom: 1rem; padding: 0.5rem; background: var(--bg-secondary); border-radius: 0.25rem; color: var(--text-muted); font-size: 0.85rem;">Authenticated as: <strong id="auth-role-badge">--</strong></div>
|
||||||
<table class="about-table">
|
<div class="about-grid">
|
||||||
<tr><td>Server version</td><td id="about-server-ver">--</td></tr>
|
<!-- Server -->
|
||||||
<tr><td>Server address</td><td id="about-server-addr">--</td></tr>
|
<div class="about-card">
|
||||||
<tr><td>Server callsign</td><td id="about-server-call">--</td></tr>
|
<div class="about-card-title">
|
||||||
<tr><td>Rig</td><td id="about-rig-info">--</td></tr>
|
<svg class="about-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="12" height="5" rx="1"/><rect x="2" y="9" width="12" height="5" rx="1"/><circle cx="5" cy="4.5" r="0.7" fill="currentColor" stroke="none"/><circle cx="5" cy="11.5" r="0.7" fill="currentColor" stroke="none"/></svg>
|
||||||
<tr><td>Active rig</td><td id="about-active-rig">--</td></tr>
|
Server
|
||||||
<tr><td>Available rigs</td><td id="about-rig-list">--</td></tr>
|
</div>
|
||||||
<tr><td>Rig connection</td><td id="about-rig-access">--</td></tr>
|
<table class="about-table">
|
||||||
<tr><td>Supported modes</td><td id="about-modes">--</td></tr>
|
<tr><td>Version</td><td id="about-server-ver">--</td></tr>
|
||||||
<tr><td>VFOs</td><td id="about-vfos">--</td></tr>
|
<tr><td>Build date</td><td id="about-server-build-date">--</td></tr>
|
||||||
<tr><td>Rigctl endpoint</td><td id="about-rigctl-endpoint">--</td></tr>
|
<tr><td>Address</td><td id="about-server-addr">--</td></tr>
|
||||||
<tr><td>Rigctl clients</td><td id="about-rigctl-clients">--</td></tr>
|
<tr><td>Callsign</td><td id="about-server-call">--</td></tr>
|
||||||
<tr><td>PSK Reporter</td><td id="about-pskreporter">--</td></tr>
|
<tr><td>Location</td><td id="about-server-location">--</td></tr>
|
||||||
<tr><td>APRS-IS</td><td id="about-aprs-is">--</td></tr>
|
<tr><td>Uptime</td><td id="about-uptime">--</td></tr>
|
||||||
<tr><td>Client version</td><td>{pkg} v{ver}</td></tr>
|
</table>
|
||||||
<tr><td>Connected clients</td><td id="about-clients">--</td></tr>
|
</div>
|
||||||
</table>
|
<!-- Radio -->
|
||||||
|
<div class="about-card">
|
||||||
|
<div class="about-card-title">
|
||||||
|
<svg class="about-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 5h12v8H2z"/><path d="M4 5V3l8 -1v3"/><circle cx="11" cy="9" r="2"/><path d="M4 8h4"/><path d="M4 10h3"/></svg>
|
||||||
|
Radio
|
||||||
|
</div>
|
||||||
|
<table class="about-table">
|
||||||
|
<tr><td>Rig</td><td id="about-rig-info">--</td></tr>
|
||||||
|
<tr><td>Active rig</td><td id="about-active-rig">--</td></tr>
|
||||||
|
<tr><td>Available rigs</td><td id="about-rig-list">--</td></tr>
|
||||||
|
<tr><td>Connection</td><td id="about-rig-access">--</td></tr>
|
||||||
|
<tr><td>Modes</td><td id="about-modes">--</td></tr>
|
||||||
|
<tr><td>VFOs</td><td id="about-vfos">--</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Audio -->
|
||||||
|
<div class="about-card">
|
||||||
|
<div class="about-card-title">
|
||||||
|
<svg class="about-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M3 5v6"/><path d="M6 3v10"/><path d="M9 6v4"/><path d="M12 4v8"/></svg>
|
||||||
|
Audio
|
||||||
|
</div>
|
||||||
|
<table class="about-table">
|
||||||
|
<tr><td>Codec</td><td id="about-audio-codec">--</td></tr>
|
||||||
|
<tr><td>Sample rate</td><td id="about-audio-samplerate">--</td></tr>
|
||||||
|
<tr><td>Channels</td><td id="about-audio-channels">--</td></tr>
|
||||||
|
<tr><td>Frame duration</td><td id="about-audio-frame">--</td></tr>
|
||||||
|
<tr><td>RX status</td><td id="about-audio-rx">Off</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Decoders -->
|
||||||
|
<div class="about-card">
|
||||||
|
<div class="about-card-title">
|
||||||
|
<svg class="about-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12 L5 4 L8 10 L10 6 L14 12"/></svg>
|
||||||
|
Decoders
|
||||||
|
</div>
|
||||||
|
<table class="about-table">
|
||||||
|
<tr><td>FT8</td><td id="about-dec-ft8" class="about-status-off">Off</td></tr>
|
||||||
|
<tr><td>FT4</td><td id="about-dec-ft4" class="about-status-off">Off</td></tr>
|
||||||
|
<tr><td>FT2</td><td id="about-dec-ft2" class="about-status-off">Off</td></tr>
|
||||||
|
<tr><td>WSPR</td><td id="about-dec-wspr" class="about-status-off">Off</td></tr>
|
||||||
|
<tr><td>CW</td><td id="about-dec-cw" class="about-status-off">Off</td></tr>
|
||||||
|
<tr><td>APRS</td><td id="about-dec-aprs" class="about-status-off">Off</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Integrations -->
|
||||||
|
<div class="about-card">
|
||||||
|
<div class="about-card-title">
|
||||||
|
<svg class="about-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="2"/><path d="M8 2v2"/><path d="M8 12v2"/><path d="M2 8h2"/><path d="M12 8h2"/><path d="M3.8 3.8l1.4 1.4"/><path d="M10.8 10.8l1.4 1.4"/><path d="M3.8 12.2l1.4-1.4"/><path d="M10.8 5.2l1.4-1.4"/></svg>
|
||||||
|
Integrations
|
||||||
|
</div>
|
||||||
|
<table class="about-table">
|
||||||
|
<tr><td>PSK Reporter</td><td id="about-pskreporter">--</td></tr>
|
||||||
|
<tr><td>APRS-IS</td><td id="about-aprs-is">--</td></tr>
|
||||||
|
<tr><td>Rigctl endpoint</td><td id="about-rigctl-endpoint">--</td></tr>
|
||||||
|
<tr><td>Rigctl clients</td><td id="about-rigctl-clients">--</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Clients -->
|
||||||
|
<div class="about-card">
|
||||||
|
<div class="about-card-title">
|
||||||
|
<svg class="about-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="5" r="3"/><path d="M2 14c0-3 2.7-5 6-5s6 2 6 5"/></svg>
|
||||||
|
Clients
|
||||||
|
</div>
|
||||||
|
<table class="about-table">
|
||||||
|
<tr><td>HTTP clients</td><td id="about-clients">--</td></tr>
|
||||||
|
<tr><td>Client version</td><td>{pkg} v{ver}</td></tr>
|
||||||
|
<tr><td>Client build</td><td>{client_build_date}</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<div class="copyright">
|
<div class="copyright">
|
||||||
|
|||||||
@@ -1217,10 +1217,16 @@ small { color: var(--text-muted); }
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.tab-label { display: block; }
|
.tab-label { display: block; }
|
||||||
|
.about-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1rem; }
|
||||||
|
.about-card { background: var(--card-bg); border: 1px solid var(--border); border-radius: 0.5rem; padding: 0; overflow: hidden; }
|
||||||
|
.about-card-title { display: flex; align-items: center; gap: 0.5rem; padding: 0.6rem 0.75rem; font-size: 0.8rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-heading); border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--card-bg) 50%, var(--bg)); }
|
||||||
|
.about-card-icon { width: 15px; height: 15px; flex-shrink: 0; }
|
||||||
.about-table { width: 100%; border-collapse: collapse; }
|
.about-table { width: 100%; border-collapse: collapse; }
|
||||||
.about-table td { padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--border); }
|
.about-table td { padding: 0.4rem 0.75rem; border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent); font-size: 0.85rem; }
|
||||||
.about-table tr:last-child td { border-bottom: none; }
|
.about-table tr:last-child td { border-bottom: none; }
|
||||||
.about-table td:first-child { color: var(--text-muted); width: 40%; }
|
.about-table td:first-child { color: var(--text-muted); width: 40%; }
|
||||||
|
.about-status-on { color: #00d17f; }
|
||||||
|
.about-status-off { color: var(--text-muted); }
|
||||||
.plugin-item { padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--border); color: var(--text); }
|
.plugin-item { padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--border); color: var(--text); }
|
||||||
.plugin-item:last-child { border-bottom: none; }
|
.plugin-item:last-child { border-bottom: none; }
|
||||||
.footer { display: flex; justify-content: space-between; align-items: baseline; margin-top: auto; padding-top: 1rem; flex-shrink: 0; }
|
.footer { display: flex; justify-content: space-between; align-items: baseline; margin-top: auto; padding-top: 1rem; flex-shrink: 0; }
|
||||||
@@ -2848,6 +2854,9 @@ button:focus-visible, input:focus-visible, select:focus-visible {
|
|||||||
#rds-raw-copy-btn {
|
#rds-raw-copy-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.about-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
.about-table,
|
.about-table,
|
||||||
.about-table tbody,
|
.about-table tbody,
|
||||||
.about-table tr,
|
.about-table tr,
|
||||||
@@ -2857,8 +2866,8 @@ button:focus-visible, input:focus-visible, select:focus-visible {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.about-table tr {
|
.about-table tr {
|
||||||
padding: 0.35rem 0;
|
padding: 0.35rem 0.75rem;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
|
||||||
}
|
}
|
||||||
.about-table tr:last-child {
|
.about-table tr:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user