Files
repo-smod-atsc/src/web/www/status_info.html
T

1211 lines
46 KiB
HTML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<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>
<script src="js/dashboard.js"></script>
<script>
var lastTime = new Date().getTime();
var currentTime = new Date().getTime();
var timeOut = 20 * 60 * 1000; // 20 min timeout
// Arrays for mapping RF Power indices to values (matching sys_rf.html)
var arry_p = ["00", "05", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55", "60", "65", "70", "75", "80", "85", "90", "95"];
$(function(){
// Listen to multiple types of user activity to prevent timeout
$(document).on('mousemove keydown click scroll touchstart', function(){
lastTime = new Date().getTime();
});
});
function testTime(){
currentTime = new Date().getTime();
if(currentTime - lastTime > timeOut){
window.location.href="login.cgi?logout=1"
}
}
// Timer check (1s)
window.setInterval(testTime, 1000);
// Refresh data (2s)
setInterval(SetFormDefaults, 2000);
function get_remote_local_ctrl() {
var queryData = "sys_cfg=&";
$.get("atsc_status_cfg.cgi?"+queryData, function(data, status) {
var table = data.split("|");
// Logic for remote/local control if needed
// g_remot_local_ctrl = Number(table[0].split("#")[5]);
/* 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. */
});
}
function refresh_show(){
get_remote_local_ctrl();
SetFormDefaults();
}
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;
var agc_err_code=0;
var table = data.split("|");
// --- 1. SYSTEM STATUS (Form 0) ---
var form = table[0].split("#");
var cf = document.getElementById("iostatus"); // iostatus
var ab_modenum = Number(form[25]);
temp_unit = Number(form[26]);
// 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";
// AGC
cf.agc0.value = (Number(form[3])==0) ? "Off" : "On";
cf.ts_rate.value = form[4]; // TS Rate
// 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 = ((t_raw - 32)/1.8).toFixed(2);
} else {
cf.temp1.value = (t_raw*1.8+32).toFixed(2);
cf.temp1_c.value = form[5];
}
// System Error
cf.syserr.value = (Number(form[6])==0) ? "OK" : "Err";
// Frequency Logic
i = Number(form[7]);
var freqVal = 0;
if((i>=2)&&(i<=6)){
freqVal = (i-2)*6+57; if(freqVal>69) freqVal+=4;
} else if((i>=7)&&(i<=13)){
freqVal = 177+6*(i-7);
} else if((i>=14)&&(i<=83)){
freqVal = 473+(i-14)*6;
}
cf.cha.value = freqVal; // Just the number for display
// Offset
i = Number(form[8]);
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-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-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-FSSI" : (tmpdata/2 + 11).toFixed(1);
tmpdata = Number(form[13]);
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";
i = Number(form[15]);
if(i>1){
// Hide if invalid
document.getElementById("amp").style.visibility="hidden";
document.getElementById("amp+").style.visibility="hidden";
} else {
document.getElementById("amp").style.visibility="visible";
document.getElementById("amp+").style.visibility="visible";
cf.amp.value = (i==0) ? "OK" : "FLT";
}
// FSSI
cf.a_fssi.value = Number(form[16]) + " %";
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!";
else if(agc_err_code==2) s_str="Up Limit!";
else {
if(Number(form[3])==1) { // AGC ON
s_str = (agc_adj>0) ? ("+"+(agc_adj/100)) : (agc_adj/100).toString();
} else {
s_str="OK";
}
}
}
cf.agc_state.value = s_str;
// --- 2. PARSE SYS_RF FOR DASHBOARD ---
var rfStat = 0;
var finalPower = '--';
if(table[6]) {
var rf_form = table[6].split("#");
rfStat = Number(rf_form[4]); // RFY index
var p_sign = (Number(rf_form[1]) == 0) ? "+" : "-";
var p_int = Number(rf_form[2]);
var p_dec = arry_p[Number(rf_form[3])];
finalPower = p_sign + p_int + "." + p_dec;
}
// --- 3. UPDATE DASHBOARD USING SHARED MODULE ---
if (typeof updateDashboardCards === 'function') {
updateDashboardCards({
freqVal: freqVal,
snr: cf.tsnr.value,
limd: cf.l_imd.value,
uimd: cf.u_imd.value,
inputSrc: cf.asi_input.value,
inputStatus: cf.asi_status.value,
bitrate: cf.ts_rate.value,
temp: cf.temp1.value,
agc: cf.agc0.value,
ctrl: cf.f1ctrl.value,
rfStat: rfStat,
finalPower: finalPower
});
}
// --- 4. PARSE OTHER FORMS ---
// Form 1: Tuner
var form1 = table[1].split("#");
var cf1 = document.getElementById("tuner");
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 = 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 — 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 = (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 = 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
var form3 = table[3].split("#");
var cf3 = document.getElementById("gps_form");
cf3.gps.value = (Number(form3[1])==0)?"NoGPS":(Number(form3[1])==1)?"Unlock":"Lock";
cf3.gps_in.value = (Number(form3[2])==0)?"External":"Internal";
cf3.gps_date.value = form3[3];
cf3.gps_time.value = form3[4];
cf3.gps_sv.value = form3[5];
cf3.gps_status.value = form3[6];
cf3.gps_freq.value = form3[7];
// Form 5: Version
var form5 = table[4].split("#");
var cf5 = document.getElementById("version");
cf5.atsc_ver.value = form5[1];
cf5.fpga_ver.value = form5[2];
cf5.mcu_ver.value = form5[3];
cf5.plc_ver.value = form5[4];
// Form 6: MAC/SN
var form6 = table[5].split("#");
var cf6 = document.getElementById("macsn");
cf6.mac.value = form6[1];
cf6.sn.value = form6[2];
if (table[7]) {
var title = table[7].split("#");
var titleStr = (title[1] != null) ? title[1] : "";
/* 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
updateStatusColors();
updateStatusAlertStrip();
highlightProblemTableRows();
updateBoxHeaderErrorState();
get_license();
});
}
function updateStatusAlertStrip() {
var strip = document.getElementById('status-alert-strip');
var textEl = strip ? strip.querySelector('.status-alert-text') : null;
var detailEl = document.getElementById('status-alert-detail');
if (!strip || !textEl) return;
var cards = document.querySelectorAll('.summary-card');
var errLabels = [], warnLabels = [];
cards.forEach(function(card) {
var val = card.querySelector('.summary-value');
var label = card.querySelector('.summary-label');
var name = label ? label.textContent.trim() : '';
if (val && val.classList.contains('value-err')) errLabels.push(name);
else if (val && val.classList.contains('value-warn')) warnLabels.push(name);
});
strip.classList.remove('status-alert-ok', 'status-alert-warn', 'status-alert-err');
if (errLabels.length > 0) {
strip.classList.add('status-alert-err');
textEl.textContent = errLabels.length + ' issue(s) detected';
if (detailEl) detailEl.textContent = ' - e.g. ' + errLabels.slice(0, 2).join(', ');
} else if (warnLabels.length > 0) {
strip.classList.add('status-alert-warn');
textEl.textContent = warnLabels.length + ' warning(s)';
if (detailEl) detailEl.textContent = ' - e.g. ' + warnLabels.slice(0, 2).join(', ');
} else {
strip.classList.add('status-alert-ok');
textEl.textContent = 'System OK';
if (detailEl) detailEl.textContent = '';
}
updateSummaryCardTooltips();
}
function updateSummaryCardTooltips() {
document.querySelectorAll('.summary-card').forEach(function(card) {
var label = card.querySelector('.summary-label');
var value = card.querySelector('.summary-value');
var status = value && value.classList.contains('value-err') ? 'Error' : (value && value.classList.contains('value-warn') ? 'Warning' : (value && value.classList.contains('value-ok') ? 'OK' : ''));
var name = label ? label.textContent.trim() : '';
var val = value ? value.textContent.trim() : '--';
card.title = name + ': ' + val + (status ? ' (' + status + ')' : '');
});
}
function highlightProblemTableRows() {
var tables = document.querySelectorAll('.app-main .box table');
tables.forEach(function(table) {
var rows = table.querySelectorAll('tr');
rows.forEach(function(tr) {
tr.classList.remove('row-has-error');
var inputs = tr.querySelectorAll('input[readonly], select');
for (var i = 0; i < inputs.length; i++) {
var inp = inputs[i];
var v = '';
if (inp.tagName === 'SELECT') {
var opt = inp.options[inp.selectedIndex];
v = opt ? opt.text.trim() : '';
} else {
v = inp.value || '';
}
// Literal error values (case-insensitive: values may be "Err", "Unlock", etc.)
if ((v && v.toUpperCase() === 'ERR') || v === 'FLT' || (v && v.toUpperCase() === 'UNLOCK')) {
tr.classList.add('row-has-error');
break;
}
// SNR, LIMD, UIMD: same error thresholds as summary cards / updateStatusColors
var id = inp.id || '';
/* 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.indexOf('N/A') !== 0) {
if (parseFloat(v) < 47) { tr.classList.add('row-has-error'); break; }
}
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
if (id === 'pisp_editing_lc' || id === 'eas_lc') {
if (v === 'INACTIVE') {
tr.classList.add('row-has-error');
break;
}
}
}
});
});
}
function updateBoxHeaderErrorState() {
document.querySelectorAll('.box-header').forEach(function(boxHeader) {
var box = boxHeader.parentElement;
if (!box || !box.classList.contains('box')) return;
var content = box.querySelector('.box-content');
var hasError = content && content.querySelector('tr.row-has-error');
if (hasError) boxHeader.classList.add('has-error');
else boxHeader.classList.remove('has-error');
});
}
// Color Logic: Main Status details (OK/ERR, LIMD/UIMD/SNR ranges) + Tuner Lock/Unlock
function updateStatusColors() {
var color_ok = getComputedStyle(document.documentElement).getPropertyValue('--accent-green').trim();
var color_err = getComputedStyle(document.documentElement).getPropertyValue('--status-err').trim();
var color_warn = getComputedStyle(document.documentElement).getPropertyValue('--status-warn').trim();
var inputs = document.getElementsByTagName("input");
for(var i=0; i<inputs.length; i++) {
// Skip inputs inside the account modal to prevent flicker
var inModal = inputs[i].closest('#accountModal');
if(inModal) continue;
if(inputs[i].value.toUpperCase() === "OK" || inputs[i].value.toUpperCase() === "LOCK") inputs[i].style.color = color_ok;
else if(inputs[i].value.toUpperCase() === "ERR" || inputs[i].value.toUpperCase() === "UNLOCK") inputs[i].style.color = color_err;
else inputs[i].style.color = "";
}
// 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.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;
else tsnrEl.style.color = color_err;
} else if(tsnrEl) tsnrEl.style.color = "";
var limdEl = document.getElementById("l_imd");
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.indexOf("N/A") !== 0) {
uimdEl.style.color = parseFloat(uimdEl.value) >= 47 ? color_ok : color_err;
} else if(uimdEl) uimdEl.style.color = "";
// Tuner section only: Lock (green) / Unlock (red). PAC section PA Level (#lvl) is power level 0/160/310/610 W – never color it.
var selects = document.getElementsByTagName("select");
for(var j=0; j<selects.length; j++) {
var sel = selects[j];
// Skip selects inside the account modal to prevent flicker
var inModal = sel.closest('#accountModal');
if(inModal) continue;
if(sel.id !== "lock" && sel.name !== "lock") {
sel.style.color = "";
continue;
}
var opt = sel.options[sel.selectedIndex];
var text = opt ? opt.text.trim() : "";
var val = opt ? opt.value : "";
sel.classList.remove("state-error", "state-ok");
if(text === "Lock" || val === "1") {
sel.style.color = color_ok;
sel.style.fontWeight = "bold";
sel.classList.add("state-ok");
}
else if(text === "Unlock" || val === "0") {
sel.style.color = color_err;
sel.style.fontWeight = "bold";
sel.classList.add("state-error");
}
else {
sel.style.color = "";
sel.style.fontWeight = "normal";
}
}
// License fields
var licenseFields = ["pisp_editing_lc", "eas_lc"];
for (var k = 0; k < licenseFields.length; k++) {
var field = document.getElementById(licenseFields[k]);
if (field) {
if (field.value.toUpperCase() === "ACTIVE") {
field.style.color = color_ok;
} else if (field.value.toUpperCase() === "INACTIVE") {
field.style.color = color_err;
} else {
field.style.color = "";
}
}
}
}
// Modal Logic - Open and close account modal
function openAccountModal() {
// Fetch current username from server
$.get("login.cgi?action=get_user", function(data) {
var trimmedData = (data || "").trim();
if (trimmedData && trimmedData !== "UNAUTHORIZED") {
document.getElementById('current_user').value = trimmedData;
}
document.getElementById('accountModal').style.display = 'block';
setTimeout(function() { document.getElementById('current_pwd').focus(); }, 100);
}).fail(function() {
// On error, modal still opens but current_user remains empty
document.getElementById('accountModal').style.display = 'block';
setTimeout(function() { document.getElementById('current_pwd').focus(); }, 100);
});
}
function closeAccountModal() {
document.getElementById('accountModal').style.display = 'none';
// Clear sensitive fields
document.getElementById('current_pwd').value = '';
document.getElementById('new_pwd').value = '';
document.getElementById('confirm_pwd').value = '';
}
// Validates that a string contains only printable characters (excluding space)
function isValidPrintableString(str) {
// Regular expression to check for printable characters (ASCII 33-126), excluding space
// Space is ASCII 32, and we want to exclude it
var printableCharRegex = /^[!-~]+$/; // ! is ASCII 33, ~ is ASCII 126
return printableCharRegex.test(str);
}
// Validate Account Form - All required fields must be filled
function validateAccountForm() {
var currentUser = document.getElementById('current_user').value;
var currentPwd = document.getElementById('current_pwd').value;
var newUsername = document.getElementById('new_user').value;
var newPwd = document.getElementById('new_pwd').value;
var confirmPwd = document.getElementById('confirm_pwd').value;
// Current password is required (cannot be empty)
if (!currentPwd || currentPwd.trim() === "") {
alert("Current password is required");
return false;
}
// Validate current password contains only printable characters
if (!isValidPrintableString(currentPwd)) {
alert("Current password contains invalid characters. Only printable characters (excluding space) are allowed.");
return false;
}
// New password is required (cannot be empty)
if (!newPwd || newPwd.trim() === "") {
alert("New password is required");
return false;
}
// Validate new password contains only printable characters
if (!isValidPrintableString(newPwd)) {
alert("New password contains invalid characters. Only printable characters (excluding space) are allowed.");
return false;
}
// Validate new password length (max 16 chars)
if (newPwd.length > 16) {
alert("New password must be no more than 16 characters");
return false;
}
// Validate new password length
if (newPwd.length < 6) {
alert("New password must be at least 6 characters");
return false;
}
// Confirm password must match new password
if (newPwd !== confirmPwd) {
alert("New passwords do not match");
return false;
}
// Validate confirm password contains only printable characters
if (!isValidPrintableString(confirmPwd)) {
alert("Confirm password contains invalid characters. Only printable characters (excluding space) are allowed.");
return false;
}
// Validate confirm password length (max 16 chars)
if (confirmPwd.length > 16) {
alert("Confirm password must be no more than 16 characters");
return false;
}
// Validate new username if provided
if (newUsername && newUsername.trim() !== "") {
// Validate new username contains only printable characters
if (!isValidPrintableString(newUsername)) {
alert("New username contains invalid characters. Only printable characters (excluding space) are allowed.");
return false;
}
// Validate new username length (max 16 chars)
if (newUsername.length > 16) {
alert("New username must be no more than 16 characters");
return false;
}
}
return true; // Allows the form to submit
}
// Form submission handler using fetch API
document.addEventListener('DOMContentLoaded', function() {
var accountForm = document.getElementById('accountForm');
if (accountForm) {
accountForm.addEventListener('submit', function(e) {
e.preventDefault();
if (!validateAccountForm()) return;
var formData = new URLSearchParams({
Username: document.getElementById('current_user').value,
OldPassword: document.getElementById('current_pwd').value,
NewUsername: document.getElementById('new_user').value,
NewPassword: document.getElementById('new_pwd').value,
ConfirmPassword: document.getElementById('confirm_pwd').value
});
fetch('login.cgi?action=change_account', {
method: 'POST',
body: formData
})
.then(r => r.json())
.then(data => {
if (data.success) {
alert("Update successful! Please login again.");
closeAccountModal();
// Redirect to login page after 1.5 seconds
setTimeout(function() {
window.location.href = 'login.cgi?logout=1';
}, 1500);
} else {
alert(data.error || "Update failed");
}
})
.catch(function(err) {
alert("Network error: " + err);
});
});
}
});
/* 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++) {
var header = headers[i];
var headerText = header.innerText.trim();
if (headerText === "Main Status" || headerText === "Tuner Status" || headerText === "PAC Status" || headerText === "GPS Status") {
// Smart Search for the box
var nextEl = header.nextElementSibling;
var box = null;
while (nextEl) {
if (nextEl.className && nextEl.className.indexOf("box") !== -1) {
box = nextEl;
break;
}
if (nextEl.tagName === "H2") break;
nextEl = nextEl.nextElementSibling;
}
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 = 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 = startOpen ? "Hide Details" : "View Details";
boxHeader.appendChild(titleSpan);
boxHeader.appendChild(toggleSpan);
var contentDiv = document.createElement('div');
contentDiv.className = "box-content";
contentDiv.style.display = startOpen ? "block" : "none";
while (box.firstChild) {
contentDiv.appendChild(box.firstChild);
}
box.appendChild(boxHeader);
box.appendChild(contentDiv);
boxHeader.onclick = function() {
this.classList.toggle("collapsed");
var content = this.nextElementSibling;
var badge = this.querySelector('.toggle-text');
if (content.style.display === "none") {
content.style.display = "block";
badge.innerText = "Hide Details";
} else {
content.style.display = "none";
badge.innerText = "View Details";
}
};
}
}
}
}
function get_license() {
var queryData = "sn_license=&";
$.get("atsc_status_cfg.cgi?" + queryData, function(data, status) {
var table = data.split("|");
var licence_para = table[0].split("#");
var cf = document.getElementById("license");
if (cf) {
cf.pisp_editing_lc.value = (licence_para[2] == "1") ? "ACTIVE" : "INACTIVE";
cf.eas_lc.value = (licence_para[4] == "1") ? "ACTIVE" : "INACTIVE";
updateStatusColors();
highlightProblemTableRows();
updateBoxHeaderErrorState();
updateStatusAlertStrip();
}
});
}
document.addEventListener('DOMContentLoaded', function() {
initCollapsibleTables();
});
</script>
</head>
<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="RF Performance">RF Performance</a></li>
<li><a href="log_output.html" title="Log">Log</a></li>
</ul>
<div class="sidebar-footer">
<h4>Anywave Broadcast Inc.</h4>
<p>Copyright 2026<br><br><span class="redbox-label">Technical Support</span><br>+1 847 415 2258<br><a href="mailto:support@anywaveus.com">support@anywaveus.com</a></p>
</div>
</aside>
<div class="sidebar-overlay" onclick="toggleSidebar()"></div>
<header class="app-header">
<button class="menu-toggle" onclick="toggleSidebar()" aria-label="Toggle menu">
<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">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<h1><a href="https://anywavecom.net/" class="logo-bg" title="Anywave Broadcast Inc."></a></h1>
<h2 id="webtitle_header"><span></span></h2>
<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;">
<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="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>
Account Settings
</a>
<a href="login.cgi?logout=1">
<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="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" y1="12" x2="9" y2="12"></line>
</svg>
Logout
</a>
</div>
</div>
</div>
</h3>
</header>
<!-- Fixed Dashboard Summary - Top of page, under header -->
<div class="dashboard-fixed">
<div class="summary-dashboard">
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-neutral" id="dot_freq" aria-hidden="true"></span>
<span class="summary-label">Frequency</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-neutral" id="sum_freq">--</span>
<span class="summary-unit">MHz</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_snr" aria-hidden="true"></span>
<span class="summary-label">SNR</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_snr">--</span>
<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_limd" aria-hidden="true"></span>
<span class="summary-label">LIMD</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_limd">--</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_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>
<span class="summary-label">RF Switch</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-muted" id="sum_rf">--</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_outpow" aria-hidden="true"></span>
<span class="summary-label">Output Power</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_outpow">--</span>
<span class="summary-unit">dBm</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-neutral" id="dot_input" aria-hidden="true"></span>
<span class="summary-label">Input Source</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-neutral" id="sum_input">--</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-neutral" id="dot_input_status" aria-hidden="true"></span>
<span class="summary-label">Input Status</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-neutral" id="sum_input_status">--</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-neutral" id="dot_bitrate" aria-hidden="true"></span>
<span class="summary-label">Bitrate</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-neutral" id="sum_bitrate">--</span>
<span class="summary-unit">Mbps</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-ok" id="dot_temp" aria-hidden="true"></span>
<span class="summary-label">Temperature</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-ok" id="sum_temp">--</span>
<span class="summary-unit">&#176;F</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-off" id="dot_agc" aria-hidden="true"></span>
<span class="summary-label">AGC</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-muted" id="sum_agc">--</span>
</div>
</div>
<div class="summary-card">
<div class="summary-label-row">
<span class="status-dot status-neutral" id="dot_control" aria-hidden="true"></span>
<span class="summary-label">Control</span>
</div>
<div class="summary-value-wrapper">
<span class="summary-value value-neutral" id="sum_control">--</span>
</div>
</div>
</div>
</div>
<main class="app-main">
<div id="status-alert-strip" class="status-alert status-alert-ok" role="status" aria-live="polite">
<span class="status-alert-icon" aria-hidden="true"></span>
<span class="status-alert-text">System OK</span>
<span class="status-alert-detail" id="status-alert-detail"></span>
</div>
<h2>Main Status</h2>
<div class="box">
<form name="iostatus" id="iostatus">
<table>
<tr>
<td>Input</td>
<td><input type="text" readonly name="asi_input" id="asi_input" value=""></td>
<td>Status</td>
<td><input type="text" readonly name="asi_status" id="asi_status" value=""></td>
</tr>
<tr>
<td>AGC</td>
<td>
<input type="text" readonly name="agc0" id="agc0" value="">
<input type="text" readonly name="agc_state" id="agc_state" style="margin-top: 4px;" value="">
</td>
<td>TS Bitrate</td>
<td><input type="text" readonly name="ts_rate" id="ts_rate" value=""> Mbps</td>
</tr>
<tr>
<td>Temperature</td>
<td>
<fieldset>
<input type="text" readonly name="temp1" id="temp1" value="">&#176;F
<input type="text" readonly name="temp1_c" id="temp1_c" value="">&#176;C
</fieldset>
</td>
<td>SYSERR</td>
<td><input type="text" readonly name="syserr" id="syserr" value=""></td>
</tr>
<tr>
<td>TX Frequency</td>
<td><input type="text" readonly name="cha" id="cha" value=""> MHz</td>
<td>Hz(+/-50000Hz)</td>
<td><fieldset>
<input type="text" readonly name="hz1" id="hz1" value="">
</fieldset></td>
</tr>
<tr>
<td>FWD</td>
<td><input type="text" readonly name="fwd" id="fwd" value=""></td>
<td>SNR</td>
<td><input type="text" readonly name="tsnr" id="tsnr" value=""> dB</td>
</tr>
<tr>
<td>LIMD</td>
<td><input type="text" readonly name="l_imd" id="l_imd" value=""> dBc</td>
<td>UIMD</td>
<td><input type="text" readonly name="u_imd" id="u_imd" value=""> dBc</td>
</tr>
<tr>
<td>Control</td>
<td><input type="text" readonly name="f1ctrl" id="f1ctrl" value=""></td>
<td id="amp+">AMP</td>
<td id="amp">
<input type="text" readonly name="amp" value="">
</td>
</tr>
<tr>
<td>A FSSI</td>
<td><input type="text" readonly name="a_fssi" id="a_fssi" value=""></td>
<td>B FSSI</td>
<td><input type="text" readonly name="b_fssi" id="b_fssi" value=""></td>
</tr>
</table>
</form>
</div>
<h2>Tuner Status</h2>
<div class="box">
<form name="tuner" id="tuner">
<table>
<tr>
<td>Rx Frequency</td>
<td><input type="text" readonly name="rx_freq" id="rx_freq" value="">MHz</td>
<td>RSSI</td>
<td><input type="text" readonly name="rssi" id="rssi" value="">dBm</td>
</tr>
<tr>
<td>RSNR</td>
<td><input type="text" readonly name="rsnr" id="rsnr" value="">dB</td>
<td>Lock</td>
<td>
<select class="display-only-select" disabled name="lock" id="lock">
<option value="0">Unlock</option>
<option value="1">Lock</option>
</select>
</td>
</tr>
</table>
</form>
</div>
<h2>PAC Status</h2>
<div class="box">
<form name="pac" id="pac_status">
<table>
<tr>
<td>Vol 9V</td><td><input type="text" readonly name="v9v" id="v9v" value="">V</td>
<td>Vol 12V</td><td><input type="text" readonly name="v12v" id="v12v" value="">V</td>
</tr>
<tr>
<td>Vol 50V</td><td><input type="text" readonly name="v50v" id="v50v" value="">V</td>
<td>PA FWD</td><td><input type="text" readonly name="pac_fwd" id="pac_fwd" value="">W</td>
</tr>
<tr>
<td>PA REF</td><td><input type="text" readonly name="ref" id="ref" value="">W</td>
<td>VSWR</td><td><input type="text" readonly name="vswr" id="vswr" value=""></td>
</tr>
<tr>
<td>Temperature</td>
<td>
<fieldset>
<input type="text" readonly name="temp" id="temp" value="">&#176;F
<input type="text" readonly name="temp_c" id="temp_c" value="">&#176;C
</fieldset>
</td>
<td>PA IN</td><td><input type="text" readonly name="pain" id="pain" value="">dBm</td>
</tr>
<tr>
<td>GV</td><td><input type="text" readonly name="gv" id="gv" value="">mV</td>
<td>PA Level</td>
<td>
<select class="display-only-select" disabled name="lvl" id="lvl">
<option value="0">0</option>
<option value="1">160</option>
<option value="2">310</option>
<option value="3">610</option>
</select>W
</td>
</tr>
<tr>
<td>50V Cur1</td><td><input type="text" readonly name="c50v1" id="c50v1" value="">A</td>
<td>50V Cur2</td><td><input type="text" readonly name="c50v2" id="c50v2" value="">A</td>
</tr>
<tr>
<td>50V Cur3</td><td><input type="text" readonly name="c50v3" id="c50v3" value="">A</td>
<td>50V Cur4</td><td><input type="text" readonly name="c50v4" id="c50v4" value="">A</td>
</tr>
</table>
</form>
</div>
<h2>GPS Status</h2>
<div class="box">
<form name="gps_form" id="gps_form">
<table>
<tr>
<td>GPS Lock</td><td><input type="text" readonly name="gps" id="gps" value=""></td>
<td>GPS</td><td><input type="text" readonly name="gps_in" id="gps_in" value=""></td>
</tr>
<tr>
<td>Date</td><td><input type="text" readonly name="gps_date" id="gps_date" value=""></td>
<td>Time</td><td><input type="text" readonly name="gps_time" id="gps_time" value=""></td>
</tr>
<tr>
<td>Satellite</td><td><input type="text" readonly name="gps_sv" id="gps_sv" value=""></td>
<td>Status</td><td><input type="text" readonly name="gps_status" id="gps_status" value=""></td>
</tr>
<tr>
<td>Frequency</td><td><input type="text" readonly name="gps_freq" id="gps_freq" value=""></td>
</tr>
</table>
</form>
</div>
<h2>Version</h2>
<div class="box">
<form name="ver" id="version">
<table>
<tr>
<td>ATSC</td><td><input type="text" style="max-width: 150px;" readonly name="atsc_ver" id="atsc_ver" value=""></td>
<td>FPGA</td><td><input type="text" style="max-width: 150px;" readonly name="fpga_ver" id="fpga_ver" value=""></td>
</tr>
<tr>
<td>MCU</td><td><input type="text" style="max-width: 150px;" readonly name="mcu_ver" id="mcu_ver" value=""></td>
<td>DryLoop</td><td><input type="text" style="max-width: 150px;" readonly name="plc_ver" id="plc_ver" value=""></td>
</tr>
</table>
</form>
</div>
<h2>MAC&SN</h2>
<div class="box">
<form name="macsn" id="macsn">
<table>
<tr>
<td>MAC</td><td><input type="text" style="max-width: 150px;" readonly name="mac" id="mac" value=""></td>
<td>SN</td><td><input type="text" style="max-width: 150px;" readonly name="sn" id="sn" value=""></td>
</tr>
</table>
</form>
</div>
<h2>License</h2>
<div class="box">
<form name="license" id="license">
<table>
<tr>
<td>PSIP Editing</td>
<td><input type="text" style="max-width: 150px;" readonly name="pisp_editing_lc" id="pisp_editing_lc" value=""></td>
<td>EAS</td>
<td><input type="text" style="max-width: 150px;" readonly name="eas_lc" id="eas_lc" value=""></td>
</tr>
</table>
</form>
</div>
<br><br><br>
</main>
</div>
<div id="accountModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>Account Settings</h3>
<span class="close" onclick="closeAccountModal()">&times;</span>
</div>
<div class="modal-body">
<form id="accountForm" method="post" action="login.cgi?action=change_account" onsubmit="return validateAccountForm()">
<table class="modal-table">
<tr>
<td>Current Username</td>
<td><input type="text" id="current_user" name="current_user" readonly></td>
</tr>
<tr>
<td>Current Password</td>
<td><input type="password" id="current_pwd" name="current_pwd" required></td>
</tr>
<tr>
<td>New Username</td>
<td><input type="text" id="new_user" name="new_user"></td>
</tr>
<tr>
<td>New Password</td>
<td><input type="password" id="new_pwd" name="new_pwd"></td>
</tr>
<tr>
<td>Confirm Password</td>
<td><input type="password" id="confirm_pwd" name="confirm_pwd"></td>
</tr>
</table>
<div class="modal-footer">
<input type="button" class="btn-modern" style="background-color: #64748b;" value="Cancel" onclick="closeAccountModal()">
<input type="submit" class="btn-modern" value="Save Changes">
</div>
</form>
</div>
</div>
</div>
<script>
function toggleSidebar(){
document.querySelector('.app-sidebar').classList.toggle('sidebar-open');
document.querySelector('.sidebar-overlay').classList.toggle('active');
}
</script>
</body>
</html>