From c43f121dc8fcf281e4e338cb06266fb0bdf381cb Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 8 Jun 2026 09:13:24 -0600 Subject: [PATCH] webui: preserve config edits during polling --- .../overland-controller.ino | 39 +++++++++++++++++-- tests/test_http_api_contract.py | 14 +++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/firmware/esp32/overland-controller/overland-controller.ino b/firmware/esp32/overland-controller/overland-controller.ino index b66b82f..3fcea11 100644 --- a/firmware/esp32/overland-controller/overland-controller.ino +++ b/firmware/esp32/overland-controller/overland-controller.ino @@ -401,6 +401,24 @@ function statusUrl(){ return needsFullStatus ? api("/status") : api("/status?fields="+STATUS_FIELDS_OVERVIEW); } + +const configTouched={}; + +function markConfigFieldTouched(id){ + configTouched[id]=true; +} + +function setConfigValue(id,value){ + const el=$(id); + if(!el) return; + if(configTouched[id] || document.activeElement===el) return; + el.value=value ?? ""; +} + +function clearConfigTouched(ids){ + ids.forEach(id=>{delete configTouched[id];}); +} + function mergeStatus(previous,next){ if(!previous) return next; return Object.assign({},previous,next); @@ -426,9 +444,7 @@ function renderApConfig(data){ const n=data.network||{}; if($("apCurrentSsid")) $("apCurrentSsid").textContent=n.ap_ssid||"--"; if($("apSecurity")) $("apSecurity").textContent=n.ap_auth||"--"; - if($("apSsidInput") && !$("apSsidInput").dataset.touched){ - $("apSsidInput").value=n.ap_ssid||""; - } + setConfigValue("apSsidInput",n.ap_ssid||""); } async function saveApConfig(){ @@ -466,6 +482,7 @@ async function saveApConfig(){ $("apPasswordInput").value=""; alert("AP settings saved. Reconnect to the new WiFi network if this page stops updating."); + clearConfigTouched(["apSsidInput","apPasswordInput"]); } function renderWeatherBadge(data){ @@ -699,6 +716,7 @@ async function saveDeviceConfig(){ await fetch(api("/config/save"),{method:"POST"}); await load(); alert("Device name saved"); + clearConfigTouched(["deviceNameInput"]); } async function saveBmsFullConfig(){ @@ -725,6 +743,7 @@ async function saveBmsFullConfig(){ await fetch(api("/config/save"),{method:"POST"}); await load(); alert("BMS config saved"); + clearConfigTouched(["bmsNameInput","bmsAddressInput","bmsAddressTypeInput"]); } async function scanTemps(){ @@ -896,6 +915,7 @@ async function saveTempConfig(){ await fetch(api("/config/save"),{method:"POST"}); await load(); alert("Temperature config saved"); + clearConfigTouched(["tempEnabledCount"]); } async function loadWifiConfig(){ @@ -921,6 +941,7 @@ async function saveWifi(){ await fetch(api("/config/wifi"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({networks})}); await loadWifiConfig(); await load(); + clearConfigTouched(["w1s","w1p","w1r","w2s","w2p","w2r","w3s","w3p","w3r"]); } async function connectWifi(){ await fetch(api("/wifi/connect"),{method:"POST"}); @@ -937,6 +958,18 @@ async function load(){ } load(); loadWifiConfig(); +document.addEventListener("input",e=>{ + if(e.target && e.target.id && ( + e.target.closest("#configPage") || + e.target.id.startsWith("w") || + e.target.id.startsWith("ap") || + e.target.id.startsWith("bms") || + e.target.id.startsWith("device") || + e.target.id.startsWith("temp") + )){ + markConfigFieldTouched(e.target.id); + } +}); setInterval(load,3000); diff --git a/tests/test_http_api_contract.py b/tests/test_http_api_contract.py index 3408dd7..9e05946 100644 --- a/tests/test_http_api_contract.py +++ b/tests/test_http_api_contract.py @@ -460,3 +460,17 @@ def test_temp_config_lookup_is_available_to_http_handlers(): helper_pos = source.index("int findTempConfigIndexById(") first_uart_guard_pos = source.index("#if DASHBOARD_UART_ENABLED") assert helper_pos < first_uart_guard_pos + + +def test_embedded_webui_preserves_config_input_while_polling(): + source = firmware_source() + + assert "const configTouched={};" in source + assert "function markConfigFieldTouched(id)" in source + assert "function setConfigValue(id,value)" in source + assert "document.activeElement===el" in source + assert 'document.addEventListener("input"' in source + assert 'setConfigValue("apSsidInput"' in source + assert 'setConfigValue("deviceNameInput"' in source + assert 'setConfigValue("bmsNameInput"' in source + assert 'setConfigValue("tempEnabledCount"' in source