From 89cc9828c3c0676be0653eb34d908c43bacb0863 Mon Sep 17 00:00:00 2001 From: d00f Date: Tue, 4 Aug 2026 03:21:43 +0200 Subject: [PATCH] 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 --- html/navigation.js | 1 + html/stp.html | 21 ++++++++++++++++ html/stp.js | 61 ++++++++++++++++++++++++++++++++++++++++++++++ httpd/httpd.c | 2 ++ httpd/page_impl.c | 51 ++++++++++++++++++++++++++++++++++++++ httpd/page_impl.h | 1 + 6 files changed, 137 insertions(+) create mode 100644 html/stp.html create mode 100644 html/stp.js diff --git a/html/navigation.js b/html/navigation.js index b42f8fd..d9eeed0 100644 --- a/html/navigation.js +++ b/html/navigation.js @@ -4,6 +4,7 @@ document.getElementById('sidebar').innerHTML = + "
  • Port Statistics
  • " + "
  • VLAN
  • " + "
  • L2 Configuration
  • " + + "
  • Spanning Tree
  • " + "
  • Mirroring
  • " + "
  • Link Aggregation
  • " + "
  • EEE
  • " diff --git a/html/stp.html b/html/stp.html new file mode 100644 index 0000000..af968f5 --- /dev/null +++ b/html/stp.html @@ -0,0 +1,21 @@ + + + + + + FreeSwitchOS Spanning Tree + + + +
    +
    +

    Spanning Tree (RSTP)

    +

    Mode: +

    +
    +
    + +
    + + + diff --git a/html/stp.js b/html/stp.js new file mode 100644 index 0000000..3682207 --- /dev/null +++ b/html/stp.js @@ -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); + }); +}); diff --git a/httpd/httpd.c b/httpd/httpd.c index 7b90adf..74e29ce 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -680,6 +680,8 @@ void httpd_appcall(void) send_mtu(); } else if (is_word(q, "/lag.json")) { send_lag(); + } else if (is_word(q, "/stp.json")) { + send_stp(); } else if (is_word(q, "/vlanlist")) { send_vlanlist(); } else if (is_word(q, "/config")) { diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 5a3433d..a71d0ac 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -12,6 +12,7 @@ #include "phy.h" #include "version.h" #include "machine.h" +#include "rtl837x_stp.h" #include "page_impl.h" #include "syslog.h" @@ -527,6 +528,56 @@ void send_lag(void) } +/* STP status for the L2 page ("/stp.json"): enable state, the elected root + * bridge (priority+MAC) and our path cost to it, whether we are the root, and + * the live per-port STP state read from the ASIC's MSTP register (2 bits per + * port: 0 disable, 1 blocking, 2 learning, 3 forwarding - same encoding + * stp_setup() writes). Ports are reported by their physical number. */ +/* Scratch for send_stp(): a plain local would land in the near-full 8051 + * internal-RAM overlay (OSEG). */ +__xdata uint8_t stp_we_root; + +void send_stp(void) +{ + dbg_string("send_stp called\n"); + slen = strtox(outbuf, HTTP_RESPONCE_JSON); + + slen += strtox(outbuf + slen, "{\"on\":"); + bool_to_html(stpEnabled); + slen += strtox(outbuf + slen, ",\"rootPrio\":\""); + byte_to_html(root_bridge.prio); + byte_to_html(root_bridge.ext); + slen += strtox(outbuf + slen, "\",\"rootMac\":\""); + for (uint8_t j = 0; j < 6; j++) + byte_to_html(root_bridge.mac[j]); + slen += strtox(outbuf + slen, "\",\"cost\":\""); + byte_to_html(root_bridge_cost >> 24); + byte_to_html(root_bridge_cost >> 16); + byte_to_html(root_bridge_cost >> 8); + byte_to_html(root_bridge_cost); + /* are we the elected root? (our MAC == root MAC) */ + stp_we_root = 1; + for (uint8_t j = 0; j < 6; j++) { + if (root_bridge.mac[j] != uip_ethaddr.addr[j]) + stp_we_root = 0; + } + slen += strtox(outbuf + slen, "\",\"weRoot\":"); + bool_to_html(stp_we_root); + slen += strtox(outbuf + slen, ",\"ports\":["); + reg_read_m(RTL837X_MSTP_STATES); + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + slen += strtox(outbuf + slen, "{\"p\":"); + itoa_html(machine.log_to_phys_port[i]); + slen += strtox(outbuf + slen, ",\"st\":"); + /* 2-bit field per logical port, packed from byte 3 up (cf. stp_setup) */ + itoa_html((sfr_data[3 - (i >> 2)] >> ((i << 1) & 0x7)) & 0x3); + slen += strtox(outbuf + slen, "},"); + } + slen -= 1; // remove comma + slen += strtox(outbuf + slen, "]}"); +} + + void send_eee(void) { dbg_string("send_eee called\nsending EEE status\n"); diff --git a/httpd/page_impl.h b/httpd/page_impl.h index 7907285..1d44719 100644 --- a/httpd/page_impl.h +++ b/httpd/page_impl.h @@ -14,6 +14,7 @@ void send_mtu(void); void send_config(void); void send_cmd_log(void); void send_lag(void); +void send_stp(void); void send_vlanlist(void); /* Convert only the lower nibble to ascii HEX char.