新增:概览仪表盘及统一频谱页面

This commit is contained in:
2026-08-28 13:27:39 +08:00
parent bded7b6836
commit 8243380474
14 changed files with 3262 additions and 333 deletions
+145 -61
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title id="webtitle"></title>
<script src="js/theme.js"></script>
<link href="css/styles.css" rel="stylesheet" media="screen">
<script src="js/auth.js"></script>
<script src="js/jquery.js"></script>
@@ -41,7 +42,19 @@ function get_remote_local_ctrl() {
var table = data.split("|");
// Logic for remote/local control if needed
// g_remot_local_ctrl = Number(table[0].split("#")[5]);
document.title = table[0].split("#")[11];
/* P-4 ROOT CAUSE + the better fix (2026-08-06).
What was here: document.title = table[0].split("#")[11]; — copied from the
1.0 BY VALUE, index and all (the same copy-by-value class as C1's
INPUT_SOURCES). The 1.0's sys_cfg carries five NTP fields after ctl so its
title sits at index 11; smod's sys_cfg has no NTP fields there, so index 11
is ab_mode — the FSSI Refresh switch. The tab was displaying the FSSI switch
state: "0" or "1", flipping as FSSI was toggled (owner, 2026-08-06).
Why the line is DELETED rather than re-indexed: this page already has a
correct, authoritative title source — the dedicated web_title section polled
in SetFormDefaults below (table[7]). Two writers for one value is the defect,
not the index; re-indexing would have left a second writer racing the first
(and is exactly what C1 removed on the input-source axis). One source of
truth: the web_title section. This function now only does what its name says. */
});
}
@@ -51,6 +64,11 @@ function get_remote_local_ctrl() {
}
function SetFormDefaults() {
// R-2 (2026-08-05): NaN guards — in-tree pattern from sys_spectrum.html. Readbacks
// show "--" for absent data; finite values format as before.
function fmtScaled(v, scale, dp){ var n = Number(v); return isFinite(n) ? (n*scale).toFixed(dp) : "--"; }
function fmtRaw(v){ var n = Number(v); return isFinite(n) ? n : "--"; }
var queryData = "State_SysState=&tuner_status=&pac_status=&gps_status=&State_SysVer=&mac_sn_status=&sys_rf=&web_title=&"
$.get("atsc_status_cfg.cgi?"+queryData, function(data, status) {
var temp_unit=1;
@@ -63,10 +81,23 @@ function SetFormDefaults() {
var ab_modenum = Number(form[25]);
temp_unit = Number(form[26]);
// Input Source Map
var i = Number(form[1]);
var s_str = (i==0)?"ASI1":(i==1)?"ASI2":(i==2)?"EAS":(i==3)?"Tuner":(i==4)?"TSoIP":(i==5)?"SMPTE":"None";
cf.asi_input.value = s_str;
// Input Source — C1 (2026-08-05): the page-private ternary is DELETED; this and the
// dashboard cards read the ONE map via DashboardHelpers.getInputSource (the two lists
// disagreed at index 5 — SMPTE vs None — which is your "None for a selected input").
cf.asi_input.value = window.DashboardHelpers.getInputSource(form[1]);
/* REG-2 / ST.F11 port (2026-08-05, from the 1.0): auto-expand Tuner Status on
ENTERING tuner/translator mode — smod IS a translator, so this is its primary
mode. Same "changed" semantics as the 1.0's adaptation of 3.0's publishMode:
expand only on the transition INTO Tuner input (form[1]===3, same index here,
proven by this page's own map), tracked by _stTunerMode — NEVER on every poll,
so an operator's manual collapse is not fought. Box-headers exist by now
(initCollapsibleTables runs at DOMContentLoaded, before this callback). */
(function(){
var inTuner = (Number(form[1]) === 3);
if (inTuner && window._stTunerMode !== true) { autoExpandTunerStatus(); }
window._stTunerMode = inTuner;
})();
// Input Status
cf.asi_status.value = (Number(form[2])==0) ? "OK" : "Err";
@@ -76,12 +107,15 @@ function SetFormDefaults() {
cf.ts_rate.value = form[4]; // TS Rate
// Temperature
if(temp_unit==0){
// Temperature — R-2: absent data -> "--", never NaN cascades
var t_raw = Number(form[5]);
if(!isFinite(t_raw)){
cf.temp1.value = "--"; cf.temp1_c.value = "--";
} else if(temp_unit==0){
cf.temp1.value = form[5];
cf.temp1_c.value = ((cf.temp1.value - 32)/1.8).toFixed(2);
cf.temp1_c.value = ((t_raw - 32)/1.8).toFixed(2);
} else {
cf.temp1.value = (form[5]*1.8+32).toFixed(2);
cf.temp1.value = (t_raw*1.8+32).toFixed(2);
cf.temp1_c.value = form[5];
}
@@ -102,27 +136,27 @@ function SetFormDefaults() {
// Offset
i = Number(form[8]);
s_str = (i==0) ? "+" : "-";
var s_str = (i==0) ? "+" : "-"; // scratch var — its declaration moved here when C1 deleted the input-source ternary that used to declare it
cf.hz1.value = s_str + form[9];
// FWD Power & SNR
var rawFwd = Number(form[10]);
cf.fwd.value = (ab_modenum==1) ? "N/A" : (rawFwd + " %");
cf.fwd.value = (ab_modenum==1) ? "N/A-FSSI" : (rawFwd + " %");
// SNR. Half-dB from the app, shown as-is (PR12 removed the old fixed
// +10 dB display offset; this is the true measured MER).
var tmpdata = Number(form[11]);
var snrStr = (tmpdata/2).toFixed(1);
cf.tsnr.value = (ab_modenum==1) ? "N/A" : snrStr;
cf.tsnr.value = (ab_modenum==1) ? "N/A-FSSI" : snrStr;
// IMD. Half-dB from the app, plus the fixed +11 dB display offset.
// (Do not Math.round() the sum and then paste on the half: for an odd
// half-dB value that rounds x.5 up and reports 1 dB too much.)
tmpdata = Number(form[12]);
cf.l_imd.value = (ab_modenum==1) ? "N/A" : (tmpdata/2 + 11).toFixed(1);
cf.l_imd.value = (ab_modenum==1) ? "N/A-FSSI" : (tmpdata/2 + 11).toFixed(1);
tmpdata = Number(form[13]);
cf.u_imd.value = (ab_modenum==1) ? "N/A" : (tmpdata/2 + 11).toFixed(1);
cf.u_imd.value = (ab_modenum==1) ? "N/A-FSSI" : (tmpdata/2 + 11).toFixed(1);
// Control & Amp
cf.f1ctrl.value = (Number(form[14])==0) ? "Remote" : "Local";
@@ -143,6 +177,13 @@ function SetFormDefaults() {
cf.b_fssi.value = (ab_modenum==1) ? (Number(form[17]) + " %") : "N/A";
// AGC State
/* FSSI active alternates the RFinA feedback path AGC realizes on,
so AGC state (and its error codes) are not meaningful — show the
reason instead of a misleading adjustment value. Takes priority.
(Ported from the 1.0, status_info.html:239-243 — FSSI parity round.) */
if(ab_modenum==1){
s_str = "N/A-FSSI";
} else {
agc_err_code = Number(form[27]);
var agc_adj = Number(form[28]);
if(agc_err_code==1) s_str="Down Limit!";
@@ -154,6 +195,7 @@ function SetFormDefaults() {
s_str="OK";
}
}
}
cf.agc_state.value = s_str;
// --- 2. PARSE SYS_RF FOR DASHBOARD ---
@@ -191,37 +233,40 @@ function SetFormDefaults() {
// Form 1: Tuner
var form1 = table[1].split("#");
var cf1 = document.getElementById("tuner");
cf1.rx_freq.value = Number(form1[1]);
cf1.rssi.value = Number(form1[2]);
cf1.rsnr.value = Number(form1[3]);
cf1.rx_freq.value = fmtRaw(form1[1]);
cf1.rssi.value = fmtRaw(form1[2]);
cf1.rsnr.value = fmtRaw(form1[3]);
cf1.lock.selectedIndex = Number(form1[4]);
// Form 2: PAC Status
var form2 = table[2].split("#");
var cf2 = document.getElementById("pac_status");
cf2.v9v.value = (Number(form2[1])*0.1).toFixed(2);
cf2.v12v.value = (Number(form2[2])*0.1).toFixed(2);
cf2.v50v.value = (Number(form2[3])*0.1).toFixed(2);
cf2.pac_fwd.value = (Number(form2[4])*0.1).toFixed(2);
cf2.ref.value = (Number(form2[5])*0.1).toFixed(2);
cf2.vswr.value = (Number(form2[6])*0.1).toFixed(2);
cf2.v9v.value = fmtScaled(form2[1], 0.1, 2);
cf2.v12v.value = fmtScaled(form2[2], 0.1, 2);
cf2.v50v.value = fmtScaled(form2[3], 0.1, 2);
cf2.pac_fwd.value = fmtScaled(form2[4], 0.1, 2);
cf2.ref.value = fmtScaled(form2[5], 0.1, 2);
cf2.vswr.value = fmtScaled(form2[6], 0.1, 2);
// PAC Temp
if(temp_unit==0){
cf2.temp.value = (Number(form2[7])*0.1).toFixed(2);
// PAC Temp — R-2 guarded
var pac_t = Number(form2[7]);
if(!isFinite(pac_t)){
cf2.temp.value = "--"; cf2.temp_c.value = "--";
} else if(temp_unit==0){
cf2.temp.value = (pac_t*0.1).toFixed(2);
cf2.temp_c.value = (cf2.temp.value < 32) ? 0 : ((cf2.temp.value - 32)/1.8).toFixed(2);
} else {
cf2.temp.value = (Number(form2[7])*0.1*1.8+32).toFixed(2);
cf2.temp_c.value = (Number(form2[7])*0.1).toFixed(2);
}
cf2.temp.value = (pac_t*0.1*1.8+32).toFixed(2);
cf2.temp_c.value = (pac_t*0.1).toFixed(2);
}
cf2.pain.value = Number(form2[8]); // This is PA_IN
cf2.c50v1.value = (Number(form2[9])*0.1).toFixed(2);
cf2.c50v2.value = (Number(form2[10])*0.1).toFixed(2);
cf2.c50v3.value = (Number(form2[11])*0.1).toFixed(2);
cf2.c50v4.value = (Number(form2[12])*0.1).toFixed(2);
cf2.gv.value = (Number(form2[13])*0.1).toFixed(2);
cf2.c50v1.value = fmtScaled(form2[9], 0.1, 2);
cf2.c50v2.value = fmtScaled(form2[10], 0.1, 2);
cf2.c50v3.value = fmtScaled(form2[11], 0.1, 2);
cf2.c50v4.value = fmtScaled(form2[12], 0.1, 2);
cf2.gv.value = fmtScaled(form2[13], 0.1, 2);
cf2.lvl.selectedIndex = Number(form2[14]);
// Form 3: GPS
@@ -252,8 +297,11 @@ function SetFormDefaults() {
if (table[7]) {
var title = table[7].split("#");
var titleStr = (title[1] != null) ? title[1] : "";
document.getElementById('webtitle').textContent = titleStr;
document.getElementById('webtitle_header').textContent = titleStr;
/* P-4 guard: never paint a short/absent read over a good title (R-2 class). */
if (titleStr !== "") {
document.getElementById('webtitle').textContent = titleStr;
document.getElementById('webtitle_header').textContent = titleStr;
}
}
// Update Status Colors and UI helpers
@@ -330,14 +378,15 @@ function highlightProblemTableRows() {
}
// SNR, LIMD, UIMD: same error thresholds as summary cards / updateStatusColors
var id = inp.id || '';
if (id === 'tsnr' && v !== '' && v !== 'N/A') {
/* v.indexOf('N/A') !== 0 excludes both "N/A" and "N/A-FSSI" (FSSI-active) — 1.0 :413 pattern */
if (id === 'tsnr' && v !== '' && v.indexOf('N/A') !== 0) {
var snrNum = parseFloat(v); // true measured dB (PR12: no display offset)
if (snrNum < 15) { tr.classList.add('row-has-error'); break; }
}
if (id === 'l_imd' && v !== '' && v !== 'N/A') {
if (id === 'l_imd' && v !== '' && v.indexOf('N/A') !== 0) {
if (parseFloat(v) < 47) { tr.classList.add('row-has-error'); break; }
}
if (id === 'u_imd' && v !== '' && v !== 'N/A') {
if (id === 'u_imd' && v !== '' && v.indexOf('N/A') !== 0) {
if (parseFloat(v) < 47) { tr.classList.add('row-has-error'); break; }
}
// License fields: INACTIVE is an error
@@ -381,7 +430,7 @@ function updateStatusColors() {
// Main Status details: LIMD, UIMD, SNR – same rules as summary cards (green/red/warn by range)
var tsnrEl = document.getElementById("tsnr");
if(tsnrEl && tsnrEl.value !== "" && tsnrEl.value !== "N/A") {
if(tsnrEl && tsnrEl.value !== "" && tsnrEl.value.indexOf("N/A") !== 0) {
var snrVal = parseFloat(tsnrEl.value); // true measured dB (PR12: no display offset)
if(snrVal >= 27) tsnrEl.style.color = color_ok;
else if(snrVal >= 20) tsnrEl.style.color = color_warn;
@@ -389,12 +438,12 @@ function updateStatusColors() {
} else if(tsnrEl) tsnrEl.style.color = "";
var limdEl = document.getElementById("l_imd");
if(limdEl && limdEl.value !== "" && limdEl.value !== "N/A") {
if(limdEl && limdEl.value !== "" && limdEl.value.indexOf("N/A") !== 0) {
limdEl.style.color = parseFloat(limdEl.value) >= 47 ? color_ok : color_err;
} else if(limdEl) limdEl.style.color = "";
var uimdEl = document.getElementById("u_imd");
if(uimdEl && uimdEl.value !== "" && uimdEl.value !== "N/A") {
if(uimdEl && uimdEl.value !== "" && uimdEl.value.indexOf("N/A") !== 0) {
uimdEl.style.color = parseFloat(uimdEl.value) >= 47 ? color_ok : color_err;
} else if(uimdEl) uimdEl.style.color = "";
@@ -599,7 +648,28 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
// Collapsible Tables - MAIN STATUS, PAC STATUS, GPS STATUS
/* ST.F11 support (2026-08-05): expand the Tuner Status box if collapsed. Called only on
the transition into tuner/translator mode by the data callback — see the port note there. */
function autoExpandTunerStatus() {
var headers = document.querySelectorAll('.box-header');
for (var i = 0; i < headers.length; i++) {
var titleSpan = headers[i].querySelector('span');
if (titleSpan && titleSpan.innerText.indexOf('Tuner Status') !== -1) {
if (headers[i].classList.contains('collapsed')) {
headers[i].classList.remove('collapsed');
var content = headers[i].nextElementSibling;
if (content && content.className.indexOf('box-content') !== -1) {
content.style.display = 'block';
}
var badge = headers[i].querySelector('.toggle-text');
if (badge) badge.innerText = 'Hide Details';
}
break;
}
}
}
// Collapsible Tables - MAIN STATUS, TUNER STATUS, PAC STATUS, GPS STATUS
function initCollapsibleTables() {
var headers = document.querySelectorAll('.app-main h2');
for (var i = 0; i < headers.length; i++) {
@@ -621,20 +691,26 @@ function initCollapsibleTables() {
}
if (box) {
/* REG-1 (2026-08-05, 1.0 C.2 port — "3.0 parity"): the primary status block
is OPEN on load so the operator sees it without a click; every other
section starts collapsed. Tuner Status is additionally auto-expanded on
entering translator mode by the data callback (ST.F11) — it cannot be
decided here because the mode is not yet read at init. */
var startOpen = (headerText === "Main Status");
var boxHeader = document.createElement('div');
boxHeader.className = "box-header collapsed";
boxHeader.className = startOpen ? "box-header" : "box-header collapsed";
var titleSpan = document.createElement('span');
titleSpan.innerText = headerText + " Details";
var toggleSpan = document.createElement('span');
toggleSpan.className = "toggle-text";
toggleSpan.innerText = "View Details";
toggleSpan.innerText = startOpen ? "Hide Details" : "View Details";
boxHeader.appendChild(titleSpan);
boxHeader.appendChild(toggleSpan);
var contentDiv = document.createElement('div');
contentDiv.className = "box-content";
contentDiv.style.display = "none";
contentDiv.style.display = startOpen ? "block" : "none";
while (box.firstChild) {
contentDiv.appendChild(box.firstChild);
@@ -683,17 +759,17 @@ document.addEventListener('DOMContentLoaded', function() {
</script>
</head>
<body onload="refresh_show()">
<body onload="refresh_show()">
<div class="app-layout">
<aside class="app-sidebar">
<ul class="items">
<li><a href="overview.html" title="Overview">Overview</a></li>
<li class="active"><a href="status_info.html" title="Status">Status</a></li>
<li><a href="sys_set.html" title="Settings">Settings</a></li>
<li><a href="sys_rf.html" title="RF Control">RF Control</a></li>
<li><a href="sys_psip.html" title="TSID&PSIP">TSID&PSIP</a></li>
<li><a href="sys_ts.html" title="TSoIP">TSoIP</a></li>
<li><a href="sys_spectrum.html" title="Spectrum">Spectrum</a></li>
<li><a href="sys_spectrum.html" title="RF Performance">RF Performance</a></li>
<li><a href="log_output.html" title="Log">Log</a></li>
</ul>
<div class="sidebar-footer">
@@ -713,8 +789,15 @@ document.addEventListener('DOMContentLoaded', function() {
</button>
<h1><a href="https://anywavecom.net/" class="logo-bg" title="Anywave Broadcast Inc."></a></h1>
<h2 id="webtitle_header"><span></span></h2>
<h3>
<div class="dropdown">
<h3 class="has-toggle">
<div class="utility-cluster">
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Switch color theme" title="Switch color theme">
<span class="tt-sun" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg></span>
<span class="tt-moon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></span>
<span class="tt-label"><span class="tt-to-light">Light Mode</span><span class="tt-to-dark">Dark Mode</span></span>
</button>
<span class="utility-divider" aria-hidden="true"></span>
<div class="dropdown">
<button class="dropbtn-modern">Admin &#9662;</button>
<div class="dropdown-content">
<a href="#" onclick="openAccountModal(); return false;">
@@ -734,7 +817,8 @@ document.addEventListener('DOMContentLoaded', function() {
</a>
</div>
</div>
</h3>
</div>
</h3>
</header>
<!-- Fixed Dashboard Summary - Top of page, under header -->
@@ -760,16 +844,6 @@ document.addEventListener('DOMContentLoaded', function() {
<span class="summary-unit">dB</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_limd" aria-hidden="true"></span>
@@ -780,6 +854,16 @@ document.addEventListener('DOMContentLoaded', function() {
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_uimd" aria-hidden="true"></span>
<span class="summary-label">UIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_uimd">--</span>
<span class="summary-unit">dBc</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-off" id="dot_rf" aria-hidden="true"></span>