feat(webui): turn the Web UI into a single-page application

index.html now contains all pages as sections (overview, ports, stat,
vlan, l2, mirror, lag, eee, bandwidth, system, update); the sidebar
switches between them via the URL hash (#/ports, #/vlan, ...).
login.html stays separate as the authentication gate.

A section's initialisation runs when the section is shown, its polling
intervals run only while it is visible, and re-entering a section
refetches the data; the checkbox builders (lag/vlan/mirror) and the L2
scanner were made idempotent so re-initialising cannot duplicate rows.

tools/minify.py: recognise regex literals - an escaped slash before the
closing delimiter was previously stripped as a // comment.

Measured (make MACHINE=KP_9000_6XHML_X2): 8 embedded files, 109,814 ->
97,730 -> 25,302 bytes gzip (23.0 %); the data block ends at 0x462d6
(~86 KB freed).  On a real PCB-K0402WS-V3: cold load ~450 ms, section
switches ~60-160 ms with no asset re-downloads, all sections render
without JavaScript errors.
This commit is contained in:
eraiza0816 committed 2026-08-11 06:37:20 +09:00
1 parent b651890a3a
commit 67ae4598d9
13 files changed
+397 -438

No files matched your search

+98 -52
View File
@@ -899,17 +899,17 @@ document.addEventListener('DOMContentLoaded', function() {
var sidebarEl = document.getElementById('sidebar');
if (sidebarEl)
sidebarEl.innerHTML =
"<ul><li><a href='index.html' data-i18n='nav_overview'>Overview</a></li>"
+ "<li><a href='ports.html' data-i18n='nav_port_config'>Port Configuration</a></li>"
+ "<li><a href='stat.html' data-i18n='nav_port_stat'>Port Statistics</a></li>"
+ "<li><a href='vlan.html' >VLAN</a></li>"
+ "<li><a href='l2.html' data-i18n='nav_l2'>L2 Configuration</a></li>"
+ "<li><a href='mirror.html' data-i18n='nav_mirror'>Mirroring</a></li>"
+ "<li><a href='lag.html' data-i18n='nav_lag'>Link Aggregation</a></li>"
+ "<li><a href='eee.html' data-i18n='nav_eee'>EEE</a></li>"
+ "<li><a href='bandwidth.html' data-i18n='nav_bandwidth'>Bandwidth Limits</a></li>"
+ "<li><a href='system.html' data-i18n='nav_system'>System Settings</a></li>"
+ "<li><a href='update.html' data-i18n='nav_fw_update'>Firmware Update</a></li></ul>";
"<ul><li><a href='#/overview' data-i18n='nav_overview'>Overview</a></li>"
+ "<li><a href='#/ports' data-i18n='nav_port_config'>Port Configuration</a></li>"
+ "<li><a href='#/stat' data-i18n='nav_port_stat'>Port Statistics</a></li>"
+ "<li><a href='#/vlan' >VLAN</a></li>"
+ "<li><a href='#/l2' data-i18n='nav_l2'>L2 Configuration</a></li>"
+ "<li><a href='#/mirror' data-i18n='nav_mirror'>Mirroring</a></li>"
+ "<li><a href='#/lag' data-i18n='nav_lag'>Link Aggregation</a></li>"
+ "<li><a href='#/eee' data-i18n='nav_eee'>EEE</a></li>"
+ "<li><a href='#/bandwidth' data-i18n='nav_bandwidth'>Bandwidth Limits</a></li>"
+ "<li><a href='#/system' data-i18n='nav_system'>System Settings</a></li>"
+ "<li><a href='#/update' data-i18n='nav_fw_update'>Firmware Update</a></li></ul>";
});
document.addEventListener('DOMContentLoaded', function() {
@@ -920,6 +920,54 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
/* Single-page navigation: the sidebar links set the URL hash
* ("#/ports" and friends) and the matching section in index.html is
* shown. Each section initialises on first display, and its polling
* intervals run only while the section is visible. */
var sectionIntervals = [];
var sectionInits = {};
function setSectionInterval(fn, ms) {
sectionIntervals.push(setInterval(fn, ms));
}
function clearSectionIntervals() {
sectionIntervals.forEach(function(t) { clearInterval(t); });
sectionIntervals = [];
}
function showSection(name) {
document.querySelectorAll('.page').forEach(function(el) { el.style.display = 'none'; });
var sec = document.getElementById('page-' + name);
if (sec) sec.style.display = 'block';
clearSectionIntervals();
if (sectionInits[name])
sectionInits[name]();
}
window.addEventListener('hashchange', function() {
showSection((location.hash || '#/overview').replace(/^#\//, ''));
});
document.addEventListener('DOMContentLoaded', function() {
showSection((location.hash || '#/overview').replace(/^#\//, ''));
});
sectionInits.overview = function() {
update( () => {
setSectionInterval(update, 2000);
});
};
/* System page tabs (system.html section of index.html) */
function openTab(evt, tabId) {
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.getElementById(tabId).classList.add('active');
evt.currentTarget.classList.add('active');
}
var configInterval = Number();
var configuration = [];
const conf_cmds = [
@@ -1195,12 +1243,11 @@ function resetSwitch() {
}, 3000);
}
window.addEventListener("load", function() {
if (!document.getElementById('ip')) return;
sectionInits.system = function() {
var langSel = document.getElementById('lang-select');
if (langSel) langSel.value = rtlLang;
systemInterval = setInterval(fetchIP, 1000);
});
systemInterval = setSectionInterval(fetchIP, 1000);
};
var mgmtVlanCurrent = 0;
@@ -1382,16 +1429,15 @@ function getMTUs() {
xhttp.timeout = 1500; sendXHTTP(xhttp);
}
window.addEventListener("load", function() {
if (!document.getElementById('speedtable')) return;
sectionInits.ports = function() {
update( () => {
createPortTable();
updatePortTable();
getMTUs()
const interval = setInterval(update, 2000);
const updatePortTableInterval = setInterval(updatePortTable, 1000);
setSectionInterval(update, 2000);
setSectionInterval(updatePortTable, 1000);
});
});
};
const mib_counters = [
"Interface in Octets", 8,
@@ -1595,15 +1641,14 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
window.addEventListener("load", function() {
if (!document.getElementById('statstable')) return;
sectionInits.stat = function() {
update( () => {
update();
fillStats();
const stat = setInterval(fillStats, 1000);
const interval = setInterval(update, 2000);
setSectionInterval(fillStats, 1000);
setSectionInterval(update, 2000);
});
});
};
var l2GetInterval;
var l2Entries = [];
@@ -1765,20 +1810,23 @@ function getL2() {
xhttp.timeout = 1500; sendXHTTP(xhttp);
}
window.addEventListener("load", function() {
if (!document.getElementById('l2table')) return;
sectionInits.l2 = function() {
l2Entries = [];
l2CurrentEntry = 0;
update( () => {
getL2();
const interval = setInterval(update, 2000);
l2GetInterval = setInterval(getL2, 1000);
});;
});
setSectionInterval(update, 2000);
l2GetInterval = setSectionInterval(getL2, 1000);
});
};
var vlanInterval = Number();
function vlanForm() {
if (!numPorts)
return;
document.querySelectorAll('#tPorts .cbgroup, #uPorts .cbgroup, #pPorts .cbgroup')
.forEach(function(el) { el.remove(); });
var t = document.getElementById('tPorts');
var u = document.getElementById('uPorts');
var p = document.getElementById('pPorts');
@@ -1974,8 +2022,7 @@ function loadVlanList() {
sendXHTTP(xhttp);
}
window.addEventListener("load", function() {
if (!document.getElementById('vlanSelect')) return;
sectionInits.vlan = function() {
update( () => {
vlanForm();
refreshVlanViews();
@@ -1983,9 +2030,9 @@ window.addEventListener("load", function() {
document.getElementById('vid').value = this.value;
fetchVLAN();
};
const interval = setInterval(update, 2000);
setSectionInterval(update, 2000);
});
});
};
async function vlanSub() {
var commands = [];
@@ -2028,6 +2075,8 @@ var lagInterval = Number();
function lagForm() {
if (!numPorts)
return;
for (let j = 0; j < 4; j++)
document.getElementById("mLAG" + j).innerHTML = '';
for (let j=0; j < 4; j++) {
var lag = "mLAG" + j
console.log("Adding LAG " + lag)
@@ -2098,13 +2147,12 @@ async function lagSub(l) {
}
}
window.addEventListener("load", function() {
if (!document.getElementById('mLAG0')) return;
sectionInits.lag = function() {
update( () => {
lagForm();
const interval = setInterval(update, 2000);
setSectionInterval(update, 2000);
});
});
};
var mirrorInterval = Number();
const mirrors = ["mPortsTX", "mPortsRX"];
@@ -2112,6 +2160,7 @@ const mirrors = ["mPortsTX", "mPortsRX"];
function mirrorForm() {
if (!numPorts)
return;
mirrors.forEach(function(m) { document.getElementById(m).innerHTML = ''; });
for (let j=0; j < mirrors.length; j++) {
console.log("Adding Mirror " + j)
var m = document.getElementById(mirrors[j]);
@@ -2164,13 +2213,12 @@ function fetchMirror() {
sendXHTTP(xhttp);
}
window.addEventListener("load", function() {
if (!document.getElementById('mPortsTX')) return;
sectionInits.mirror = function() {
update( () => {
mirrorForm();
const interval = setInterval(update, 2000);
setSectionInterval(update, 2000);
});
});
};
async function mirrorSub() {
var cmd = "mirror ";
@@ -2260,15 +2308,14 @@ function getEEE() {
xhttp.timeout = 1500; sendXHTTP(xhttp);
}
window.addEventListener("load", function() {
if (!document.getElementById('eeetable')) return;
sectionInits.eee = function() {
update( () => {
createEEE();
getEEE();
const interval = setInterval(update, 2000);
const iCount = setInterval(getEEE, 2000);
setSectionInterval(update, 2000);
setSectionInterval(getEEE, 2000);
});
});
};
async function eeeSub(port, enable) {
var cmd = "eee ";
@@ -2419,14 +2466,13 @@ function getBW() {
xhttp.timeout = 1500; sendXHTTP(xhttp);
}
window.addEventListener("load", function() {
if (!document.getElementById('bwtable')) return;
sectionInits.bandwidth = function() {
update( () => {
createBW();
getBW();
const interval = setInterval(update, 2000);
setSectionInterval(update, 2000);
});
});
};
document.addEventListener("DOMContentLoaded", function () {
if (!document.getElementById('infoTable')) return;