From caad366b3bcfdd91b1673dba2832bac98f951a3e Mon Sep 17 00:00:00 2001 From: Erdnusschokolade <96622762+Erdnusschokolade@users.noreply.github.com> Date: Sat, 23 May 2026 15:09:59 +0200 Subject: [PATCH 1/2] Fix config persistence: missing commands, delete handling, multipart Multiple related bugs in the Save-to-Flash path: 1. Multipart upload was missing the required filename argument, causing the backend parser to fail. Added 'config.txt' to the form.append call. This was likely the primary reason Save-to-Flash was unreliable. 2. Web UI was not pausing its polling interval during flash write, causing CPU contention and intermittent crashes. Added isSaving lock and clearInterval before sendConfig. 3. conf_cmds whitelist was incomplete. Added: syslog, passwd, pvid, ingress, port name, lag, laghash, isolate, stp, igmp, mtu, bw, vlan N mgmt, vlan N d. 4. VLAN regex blocked named VLANs. New pattern allows optional name (starts with letter, matching CLI parser semantics). 5. vlan N d (delete) was not persisted. parseConf now removes the matching vlan N ... entry from configuration[] when seeing a delete command, without storing the delete itself. Result: the saved config describes the end state. 6. configuration[] was not cleared between flashSave invocations, leading to stale entries from prior interactions. 7. conf_overwrite boundary fix: 'pvid 1' no longer matches 'pvid 10' etc. Added trailing space in startsWith check. 8. All conf_cmds patterns now anchored with ^...$ for full-line match. parseConf normalizes whitespace before testing. 9. Port range widened to \d{1,2} so ports 11+ are accepted. Structural fixes (1, 2, 6, 7, 8) ported from mcaptur's closed PR #219; remaining fixes (3, 4, 5, 9) and overall regex strategy are new. --- html/config.js | 92 ++++++++++++++++++++++++++++++++++++++------------ html/system.js | 35 +++++++++++-------- 2 files changed, 90 insertions(+), 37 deletions(-) diff --git a/html/config.js b/html/config.js index 0cb4bf1..5b292e2 100644 --- a/html/config.js +++ b/html/config.js @@ -1,35 +1,83 @@ var configInterval = Number(); var configuration = []; const conf_cmds = [ - /ip\s+(\d{1,3}\.){3}\d{1,3}/, /gw\s+(\d{1,3}\.){3}\d{1,3}/, /netmask\s+(\d{1,3}\.){3}\d{1,3}/, - /eee(\s+\d)?\s+(on|off)/, /mirror(\s+(\d|10))(\s+(\d|10)(t|r)?)+/, /vlan\s+(\d{1,4})(\s+(\d|10)(t|u)?)+/ + /^ip\s+(\d{1,3}\.){3}\d{1,3}$/, + /^ip\s+dhcp$/, + /^gw\s+(\d{1,3}\.){3}\d{1,3}$/, + /^netmask\s+(\d{1,3}\.){3}\d{1,3}$/, + /^syslog\s+(on|off)$/, + /^syslog\s+ip\s+(\d{1,3}\.){3}\d{1,3}$/, + /^passwd\s+\S+$/, + /^vlan\s+\d{1,4}\s+d$/, + /^vlan\s+\d{1,4}\s+mgmt$/, + /^vlan\s+\d{1,4}(\s+[a-zA-Z]\w*)?(\s+\d{1,2}[tu]?)+$/, + /^pvid\s+\d{1,2}\s+\d{1,4}$/, + /^ingress(\s+\d{1,2}[tua])+$/, + /^ingress\s+[tua]$/, + /^port\s+\d{1,2}\s+name\s+\S+$/, + /^eee(\s+\d{1,2})?\s+(on|off)$/, + /^mirror(\s+\d{1,2})(\s+\d{1,2}[tr]?)+$/, + /^lag\s+\d(\s+\d{1,2})+$/, + /^laghash\s+\d(\s+\w+)+$/, + /^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/, + /^stp\s+(on|off)$/, + /^igmp\s+(on|off)$/, + /^mtu\s+\d{1,2}\s+\d+$/, + /^bw\s+(in|out)\s+\d{1,2}\s+\S+$/, ]; const conf_overwrite = [ - /ip/, /gw/, /netmask/, /eee\s+\w+/, /eee(\s+\w)/, /mirror/, /vlan\s+(\d{1,4})/ + /^ip\b/, + /^gw\b/, + /^netmask\b/, + /^syslog\s+ip\b/, + /^syslog\b/, + /^passwd\b/, + /^vlan\s+\d{1,4}\b/, + /^pvid\s+\d{1,2}\b/, + /^ingress\b/, + /^port\s+\d{1,2}\s+name\b/, + /^eee\s+\d{1,2}\b/, + /^eee\b/, + /^mirror\b/, + /^lag\s+\d+\b/, + /^laghash\b/, + /^isolate\s+\d{1,2}\b/, + /^stp\b/, + /^igmp\b/, + /^mtu\s+\d{1,2}\b/, + /^bw\s+(in|out)\s+\d{1,2}\b/, ]; function parseConf(s){ var a = s.split(/\r\n|\n/); - for (var l = 0; l < a.length; l++) { - if (!a[l].length || a[l] == "\n" || a[l] == "\r\n") - continue; - console.log(l + ' --> ' + a[l]); - var ignore = true; - for (const x of conf_cmds) - if (x.test(a[l])) ignore = false; - if (ignore) continue; - for (const x of conf_overwrite) { - if (x.test(a[l])) { - console.log("Match ", x, " to ", a[l]); - m = a[l].match(x); - console.log("Starts with ", m[0]); - configuration = configuration.filter(item => !(item.startsWith(m[0]))); - } - } - configuration.push(a[l]); + for (var l = 0; l < a.length; l++) { + var line = a[l].trim().replace(/\s+/g, ' '); + if (!line.length) continue; + const deleteMatch = line.match(/^vlan\s+(\d{1,4})\s+d$/); + if (deleteMatch) { + const prefix = "vlan " + deleteMatch[1] + " "; + configuration = configuration.filter(c => + c !== "vlan " + deleteMatch[1] && !c.startsWith(prefix)); + continue; } - console.log("Configuration now:"); - for (const x of configuration) { console.log(x); } + console.log(l + ' --> ' + line); + var ignore = true; + for (const x of conf_cmds) + if (x.test(line)) { ignore = false; break; } + if (ignore) continue; + for (const x of conf_overwrite) { + if (x.test(line)) { + let m = line.match(x); + let matchStr = m[0]; + configuration = configuration.filter(item => + !(item === matchStr || item.startsWith(matchStr + " "))); + break; + } + } + configuration.push(line); + } + console.log("Configuration now:"); + for (const x of configuration) { console.log(x); } } async function fetchConfig() { diff --git a/html/system.js b/html/system.js index 322e057..a90a173 100644 --- a/html/system.js +++ b/html/system.js @@ -1,4 +1,5 @@ var systemInterval = Number(); +var isSaving = false; const ips = ["ip", "netmask", "gw"]; function checkIp(ip) { @@ -43,35 +44,39 @@ async function cmdSub() { async function sendConfig(c) { - const form = new FormData(); + if (isSaving) return; + isSaving = true; + clearInterval(systemInterval); + const form = new FormData(); form.append("MAX_FILE_SIZE", "4096"); - form.append("configuration", new Blob([c], {type: "application/octet-stream"})); + form.append("configuration", new Blob([c], {type: "application/octet-stream"}), "config.txt"); try { const response = await fetch('/config', { method: 'POST', body: form }); console.log('Completed!', response); + try { + await fetch('/cmd_log_clear', { method: 'GET' }); + } catch(e) {} } catch(err) { console.error(`Error: ${err}`); + } finally { + isSaving = false; + systemInterval = setInterval(fetchIP, 1000); } } async function flashSave() { - fetchConfig().then((s) => { - parseConf(s); - fetchCmdLog().then((s) => { - parseConf(s); - var body = ""; - for (const x of configuration) { body = body + x + "\n"; } - console.log("CONFIGURATION to save: ", body); - sendConfig(body); - }); - }); - setTimeout(() => { - fetchIP(); - }, 500); + configuration = []; + const savedConfig = await fetchConfig(); + const cmdLog = await fetchCmdLog(); + if (savedConfig) parseConf(savedConfig); + if (cmdLog) parseConf(cmdLog); + const body = configuration.join('\n') + '\n'; + console.log("CONFIGURATION to save: ", body); + await sendConfig(body); } async function flashStartupSave() { From dcc60c38247fac20eb07c2e10e72feed05815949 Mon Sep 17 00:00:00 2001 From: Erdnusschokolade <96622762+Erdnusschokolade@users.noreply.github.com> Date: Sun, 24 May 2026 10:51:20 +0200 Subject: [PATCH 2/2] Fix conf_overwrite vlan/mgmt conflict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pattern /^vlan\s+\d{1,4}\b/ matched both VLAN membership entries (vlan N ) and management entries (vlan N mgmt), causing them to overwrite each other in configuration[]. When saving, whichever form came last in the cmd_log would dedupe the other out of the final config — resulting in either lost membership or a stale mgmt setting. Split into two patterns using negative lookahead: - /^vlan\s+\d{1,4}\s+mgmt$/ matches only mgmt entries - /^vlan\s+\d{1,4}(?!\s+mgmt\b)/ matches everything else Both dedup independently. Discovered via hardware test on 6XH-X where 'vlan 44 mgmt' silently dropped 'vlan 44 management 2t 5u' from saved config, locking out web UI on next boot. --- html/config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/html/config.js b/html/config.js index 5b292e2..dd8512b 100644 --- a/html/config.js +++ b/html/config.js @@ -32,7 +32,8 @@ const conf_overwrite = [ /^syslog\s+ip\b/, /^syslog\b/, /^passwd\b/, - /^vlan\s+\d{1,4}\b/, + /^vlan\s+\d{1,4}\s+mgmt$/, + /^vlan\s+\d{1,4}(?!\s+mgmt\b)/, /^pvid\s+\d{1,2}\b/, /^ingress\b/, /^port\s+\d{1,2}\s+name\b/,