gui: Spanning Tree page (config + live status)

Add a Spanning Tree page: an on/off toggle driving the existing "stp"
command over /cmd, and a live status section fed by a new /stp.json
endpoint - the elected root bridge (priority + MAC), our path cost,
whether we are the root, and the per-port STP state read live from the
ASIC's MSTP register (same 2-bit encoding stp_setup() writes). Ports are
reported by their physical numbers.

Recovered-from: 3132319, 9365c86
This commit is contained in:
d00f
2026-08-04 03:22:19 +02:00
parent 886975d856
commit 89cc9828c3
6 changed files with 137 additions and 0 deletions
+1
View File
@@ -4,6 +4,7 @@ document.getElementById('sidebar').innerHTML =
+ "<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='stp.html'>Spanning Tree</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>"
+21
View File
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<script src="/i18n.js"></script>
<link rel="stylesheet" href="style.css">
<title>FreeSwitchOS Spanning Tree</title>
</head>
<body>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div>
<h1>Spanning Tree (RSTP)</h1>
<h2>Mode: <select id="stpMode"><option value="off">Disabled</option><option value="on">Enabled</option></select>
<input style="width:15%;margin-left: 3em;" class="action" id="stp_sub" onclick="stpSub();" type="button" value="Apply"></h2>
<div id="stpStat" style="font-family:monospace;"></div>
<div id="stpPorts" style="font-family:monospace;white-space:pre;"></div>
<script src="/stp.js"></script>
</div>
</body>
<script src="/navigation.js"></script>
</html>
+61
View File
@@ -0,0 +1,61 @@
/* ---- Spanning Tree (RSTP) section ---- */
// STP port states as encoded in the ASIC's MSTP register (2 bits per port)
const STP_STATES = ["Disabled", "Blocking", "Learning", "Forwarding"];
// "user is editing" flag: while set, the periodic refresh must not overwrite
// the mode dropdown (same pattern as the LAG page - without it the 2 s refresh
// silently reverts the user's choice before Apply).
var stpDirty = false;
function fetchStp() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
const s = JSON.parse(xhttp.responseText);
// textContent throughout: rootMac comes from received BPDUs
// (remote-controlled), never render it as HTML
if (!stpDirty)
document.getElementById("stpMode").value = s.on ? "on" : "off";
document.getElementById("stpStat").textContent = s.on
? (s.weRoot
? "This switch is the root bridge (priority 0x" + s.rootPrio + ")"
: "Root bridge: 0x" + s.rootPrio + " / " + s.rootMac
+ " \u2014 path cost: 0x" + s.cost)
: "";
let t = "";
if (s.on) {
t = "port state\n";
for (const p of s.ports)
t += String(p.p).padEnd(6) + STP_STATES[p.st] + "\n";
}
document.getElementById("stpPorts").textContent = t;
}
};
xhttp.open("GET", `/stp.json`, true);
sendXHTTP(xhttp);
}
async function stpSub() {
const on = document.getElementById("stpMode").value === "on";
try {
await fetch('/cmd', { method: 'POST', body: on ? "stp on" : "stp off" });
} catch(err) {
console.error(`Error: ${err}`);
}
stpDirty = false; // editing done - let the refresh show the truth
fetchStp();
}
window.addEventListener("load", function() {
document.getElementById("stpMode")
.addEventListener("change", () => { stpDirty = true; });
});
window.addEventListener("load", function() {
update( () => {
fetchStp();
const interval = setInterval(update, 2000);
const stpInt = setInterval(fetchStp, 2000);
});
});