From a96fdfe10c471cd04db86b76a53a57edd4c29684 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sat, 15 Aug 2026 16:59:01 +0200 Subject: [PATCH] html: move the L2 table walk into a shared walkL2() helper Both the L2 page and the ports page in #335 need to page through /l2.json and decode the same fields, and the second copy arrived carrying the two bugs the first one had only just been fixed for. Rather than keep two copies in step by hand, the transport and the decoding move to main.js, which every page already loads, and each page keeps only what is its own. walkL2(onDone) pages through the table once, parses idx and vlan out of hex, maps the port to a physical number or to 'CPU', and calls onDone(entries, ok). It stops on a wrapped index, an empty page or 4096 entries, all of which set ok. A page that comes back as anything other than 200, or with a body JSON.parse rejects, is asked for again at the same index up to three times; only once those run out does the walk end with ok clear, so a caller can tell a finished table from a partial one. l2.js keeps the s and l to label mapping, since that needs the page's own translations, redraws only when ok is set, and restarts the walk from its callback either way. Two things change while moving: The next request goes out from the previous reply rather than from a setInterval that fires whether or not the last one came back. The httpd serves one connection at a time, so a timer that outruns the responses only queues work it cannot use. A walk that reaches 4096 entries hands over what it collected. Before it threw the entries away and cleared its own interval, which left the page unable to refresh again until it was reloaded. The retry is not a new idea, it is the old behaviour written down. The previous code ignored anything that was not a 200 and let the interval ask for the same index again, so a blip never disturbed the table on screen. Dropping that on the way to a chained walk would have made every timeout redraw the page with a truncated table, which at one connection at a time is not a rare event. Driven with a scripted server in node, running the helper itself rather than a copy of it: an empty table gives 0 entries in 1 request; three pages ending in a repeated index give 61 entries in 3 requests, asking for 0, 30 and 60; an empty page ends the walk after 2; a 500 and a malformed body are each retried at the same index and then complete normally, asking 0, 30, 30 and 31; three failures in a row end the walk with ok clear and the 30 entries already collected; 4096 entries in one page end it with ok set; the CPU port decodes to 'CPU'; vlan and idx come back as numbers. main.js grows by 1331 bytes and l2.js loses 1039, so 292 bytes of flash. Worth stating where they land: main.js is loaded by every page, so pages that never walk the table now carry the helper too. That is the cost of having the decoding exist exactly once, which is the point of the move. --- html/l2.js | 52 ++++++------------------------------------- html/main.js | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 45 deletions(-) diff --git a/html/l2.js b/html/l2.js index aa5742f..2011c65 100644 --- a/html/l2.js +++ b/html/l2.js @@ -1,7 +1,3 @@ -var l2GetInterval; -var l2Entries = []; -var l2CurrentEntry = 0; - function fillStats() { var tbl = document.getElementById('statstable'); if (!numPorts) @@ -118,7 +114,6 @@ function fillL2(s) s = uniq(s); l2All = s; renderL2(); - l2Entries = []; } function paintL2(tbl, s) @@ -147,53 +142,20 @@ function paintL2(tbl, s) } function getL2() { - var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function() { - if (this.readyState == 4 && this.status == 200) { - var s = JSON.parse(xhttp.responseText); - var s = s.map(function(e) { - e.vlan = parseInt(e.vlan, 16); - e.idx = parseInt(e.idx, 16); - e.type = e.type == "s" ? t('l2_static') : t('l2_learned'); - e.port = e.port == 9 ? 'CPU' : logToPhysPort[e.port]; - return e; - }); - l2Entries.push(...s); - if (l2Entries.length >= 4096) { - l2Entries = []; - l2CurrentEntry = 0; - clearInterval(l2GetInterval); - return; - } - if (!s.length) { - l2CurrentEntry = 0; - fillL2(l2Entries); - return; - } - var w = 0; - for (var i = l2Entries.length-1; i > 0; i--) { - if (l2Entries[0].idx == l2Entries[i].idx) { - w = 1; - break; - } - } - if (w) { - l2CurrentEntry = 0; - fillL2(l2Entries); - } else { - l2CurrentEntry = s[s.length-1].idx + 1; - } + walkL2(function(entries, ok) { + if (ok) { + for (var i = 0; i < entries.length; i++) + entries[i].type = entries[i].type == "s" ? t('l2_static') : t('l2_learned'); + fillL2(entries); } - }; - xhttp.open("GET", "/l2.json?idx=" + l2CurrentEntry, true); - xhttp.timeout = 1500; sendXHTTP(xhttp); + setTimeout(getL2, 1000); + }); } window.addEventListener("load", function() { update( () => { getL2(); const interval = setInterval(update, 2000); - l2GetInterval = setInterval(getL2, 1000); });; }); diff --git a/html/main.js b/html/main.js index f82b232..bd9c19d 100644 --- a/html/main.js +++ b/html/main.js @@ -285,3 +285,66 @@ function sendXHTTP(x) currentRequests.push(x); } + +function walkL2(onDone) +{ + var entries = []; + var idx = 0; + var tries = 0; + + function retry() { + if (++tries < 3) { + setTimeout(page, 1000); + return; + } + onDone(entries, false); + } + + function page() { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState != 4) + return; + if (this.status != 200) { + retry(); + return; + } + var s; + try { + s = JSON.parse(xhttp.responseText); + } catch (err) { + retry(); + return; + } + tries = 0; + s = s.map(function(e) { + e.vlan = parseInt(e.vlan, 16); + e.idx = parseInt(e.idx, 16); + e.port = e.port == 9 ? 'CPU' : logToPhysPort[e.port]; + return e; + }); + if (!s.length) { + onDone(entries, true); + return; + } + entries.push(...s); + for (var i = entries.length - 1; i > 0; i--) { + if (entries[0].idx == entries[i].idx) { + onDone(entries, true); + return; + } + } + if (entries.length >= 4096) { + onDone(entries, true); + return; + } + idx = s[s.length - 1].idx + 1; + setTimeout(page, 1000); + }; + xhttp.open("GET", "/l2.json?idx=" + idx, true); + xhttp.timeout = 1500; + sendXHTTP(xhttp); + } + + page(); +}