webui: organize config page with subtabs

This commit is contained in:
2026-06-08 08:01:54 -06:00
parent 40ce727986
commit 2f973a279c
2 changed files with 184 additions and 119 deletions
@@ -34,6 +34,7 @@ body{margin:0;background:var(--bg);color:var(--text);font-family:-apple-system,B
h1{font-size:21px;margin:0}.sub{color:var(--muted);font-size:13px;margin-top:4px} h1{font-size:21px;margin:0}.sub{color:var(--muted);font-size:13px;margin-top:4px}
.pill{border:1px solid var(--line);border-radius:999px;padding:7px 10px;color:var(--muted);font-size:12px;white-space:nowrap} .pill{border:1px solid var(--line);border-radius:999px;padding:7px 10px;color:var(--muted);font-size:12px;white-space:nowrap}
.tabs{position:fixed;left:0;right:0;bottom:0;background:#080c11;border-top:1px solid var(--line);display:grid;grid-template-columns:repeat(3,1fr);z-index:10} .tabs{position:fixed;left:0;right:0;bottom:0;background:#080c11;border-top:1px solid var(--line);display:grid;grid-template-columns:repeat(3,1fr);z-index:10}
.subtabs{display:flex;gap:8px;overflow-x:auto;padding:2px 0 12px;margin-bottom:4px}.subtabbtn{width:auto;white-space:nowrap;border:1px solid var(--line);background:#101722;color:var(--muted);padding:10px 12px;border-radius:999px;font-size:13px}.subtabbtn.active{background:#1f2b3c;color:var(--text);border-color:#3b536f}.configSubpage{display:none}.configSubpage.active{display:block}
.tabbtn{border:0;border-radius:0;background:#080c11;color:var(--muted);padding:13px 8px;font-weight:800} .tabbtn{border:0;border-radius:0;background:#080c11;color:var(--muted);padding:13px 8px;font-weight:800}
.tabbtn.active{color:var(--text);background:#111923} .tabbtn.active{color:var(--text);background:#111923}
.page{display:none}.page.active{display:block} .page{display:none}.page.active{display:block}
@@ -173,141 +174,182 @@ input{width:100%;border:1px solid var(--line);border-radius:12px;padding:12px;ba
</div> </div>
<div id="configPage" class="page"> <div id="configPage" class="page">
<div class="grid"> <div class="subtabs">
<section class="card span12 configIntro"> <button id="configTab-general" class="subtabbtn active" onclick="showConfigSubtab('general')">General</button>
<div class="configGroupHeader"> <button id="configTab-wifi" class="subtabbtn" onclick="showConfigSubtab('wifi')">WiFi / AP</button>
<div> <button id="configTab-relays" class="subtabbtn" onclick="showConfigSubtab('relays')">Relays</button>
<div class="configGroupTitle">Settings</div> <button id="configTab-temps" class="subtabbtn" onclick="showConfigSubtab('temps')">Temperatures</button>
<div class="configGroupHelp">Configure the Cargo ESP32 controller. The Cargo ESP remains the source of truth for relays, sensors, WiFi, and AP settings.</div> <button id="configTab-bms" class="subtabbtn" onclick="showConfigSubtab('bms')">BMS</button>
</div> <button id="configTab-maintenance" class="subtabbtn" onclick="showConfigSubtab('maintenance')">Maintenance</button>
</div> </div>
</section>
<section class="card span12"> <div id="config-general" class="configSubpage active">
<div class="configGroupHeader"> <div class="grid">
<div> <section class="card span12 configIntro">
<div class="configGroupTitle">Access Point</div> <div class="configGroupHeader">
<div class="configGroupHelp">This is the local camp network hosted by the Cargo ESP32. Phones and the dashboard connect here when no Starlink/home WiFi is available.</div> <div>
<div class="configGroupTitle">Settings</div>
<div class="configGroupHelp">Configure the Cargo ESP32 controller. The Cargo ESP remains the source of truth for relays, sensors, WiFi, and AP settings.</div>
</div>
</div> </div>
</div> </section>
<div class="list">
<div class="row"><span class="muted">Current AP</span><span class="v" id="apCurrentSsid">--</span></div>
<div class="row"><span class="muted">Security</span><span class="v" id="apSecurity">--</span></div>
<input id="apSsidInput" placeholder="AP SSID, example: Xterra-Camp">
<div class="inputHelp">SSID must be 1-32 characters.</div>
<input id="apPasswordInput" placeholder="New AP password" type="password">
<div class="inputHelp">Password must be 8-63 characters. The current password is never displayed.</div>
<div class="inlineActions">
<button onclick="toggleApPassword()">Show / Hide Password</button>
<button class="on" onclick="saveApConfig()">Save AP Settings</button>
</div>
<div class="configWarning">Saving AP settings restarts the access point. Your phone may disconnect and need to reconnect to the new SSID/password. Dashboard auto-migration is planned but not implemented yet.</div>
</div>
</section>
<section class="card span12"> <section class="card span12">
<div class="configGroupHeader"> <div class="configGroupHeader">
<div> <div>
<div class="configGroupTitle">Home / Starlink WiFi</div> <div class="configGroupTitle">General</div>
<div class="configGroupHelp">Optional client networks the Cargo ESP can join. Lower priority number is tried first. The AP remains available for local access.</div> <div class="configGroupHelp">Controller identity and general display settings.</div>
</div>
</div> </div>
</div> <div class="list">
<div class="list"> <input id="deviceNameInput" placeholder="Device name">
<input id="w1s" placeholder="Network 1 SSID"> <button class="on" onclick="saveDeviceConfig()">Save Device Name</button>
<input id="w1p" placeholder="Network 1 password" type="password">
<input id="w1r" placeholder="Network 1 priority" inputmode="numeric">
<input id="w2s" placeholder="Network 2 SSID">
<input id="w2p" placeholder="Network 2 password" type="password">
<input id="w2r" placeholder="Network 2 priority" inputmode="numeric">
<input id="w3s" placeholder="Network 3 SSID">
<input id="w3p" placeholder="Network 3 password" type="password">
<input id="w3r" placeholder="Network 3 priority" inputmode="numeric">
<button onclick="saveWifi()">Save WiFi</button>
<button onclick="connectWifi()">Connect Now</button>
</div>
</section>
<section class="card span12">
<div class="configGroupHeader">
<div>
<div class="configGroupTitle">Relay Outputs</div>
<div class="configGroupHelp">Rename relay outputs. IDs stay fixed for API/UART compatibility.</div>
</div> </div>
</div> </section>
<div class="list" id="relayConfigList"></div> </div>
<button onclick="saveRelayConfig()">Save Relay Names</button> </div>
</section>
<section class="card span12"> <div id="config-wifi" class="configSubpage">
<div class="configGroupHeader"> <div class="grid">
<div> <section class="card span12">
<div class="configGroupTitle">Temperature Assignment</div> <div class="configGroupHeader">
<div class="configGroupHelp">Scan probes, then assign a discovered sensor to a configured temp slot.</div> <div>
<div class="configGroupTitle">Access Point</div>
<div class="configGroupHelp">This is the local camp network hosted by the Cargo ESP32. Phones and the dashboard connect here when no Starlink/home WiFi is available.</div>
</div>
</div> </div>
</div> <div class="list">
<div class="list"> <div class="row"><span class="muted">Current AP</span><span class="v" id="apCurrentSsid">--</span></div>
<button onclick="scanTemps()">Scan Temp Probes</button> <div class="row"><span class="muted">Security</span><span class="v" id="apSecurity">--</span></div>
<div id="tempScanResults" class="list"></div> <input id="apSsidInput" placeholder="AP SSID, example: Xterra-Camp">
</div> <div class="inputHelp">SSID must be 1-32 characters.</div>
</section> <input id="apPasswordInput" placeholder="New AP password" type="password">
<div class="inputHelp">Password must be 8-63 characters. The current password is never displayed.</div>
<section class="card span12"> <div class="inlineActions">
<div class="label">Temperature Sensor Config</div> <button onclick="toggleApPassword()">Show / Hide Password</button>
<div class="sub">Set how many temp sensors are enabled and rename each slot.</div> <button class="on" onclick="saveApConfig()">Save AP Settings</button>
<div class="list"> </div>
<input id="tempEnabledCount" placeholder="Enabled sensor count, 0-8" inputmode="numeric"> <div class="configWarning">Saving AP settings restarts the access point. Your phone may disconnect and need to reconnect to the new SSID/password. Dashboard auto-migration is planned but not implemented yet.</div>
<div id="tempConfigList" class="list"></div>
<button onclick="saveTempConfig()">Save Temperature Config</button>
</div>
</section>
<section class="card span12">
<div class="configGroupHeader">
<div>
<div class="configGroupTitle">Device</div>
<div class="configGroupHelp">Controller identity and BMS connection settings.</div>
</div> </div>
</div> </section>
<div class="list">
<input id="deviceNameInput" placeholder="Device name">
<button onclick="saveDeviceConfig()">Save Device Name</button>
</div>
</section>
<section class="card span12"> <section class="card span12">
<div class="label">BMS Config</div> <div class="configGroupHeader">
<div class="sub">Use this for initial setup, bench mode, or replacing the battery/BMS.</div> <div>
<div class="list"> <div class="configGroupTitle">Home / Starlink WiFi</div>
<input id="bmsNameInput" placeholder="BMS name"> <div class="configGroupHelp">Optional client networks the Cargo ESP can join. Lower priority number is tried first. The AP remains available for local access.</div>
<input id="bmsAddressInput" placeholder="BMS BLE address"> </div>
<input id="bmsAddressTypeInput" placeholder="Address type: public or random">
<div class="btnrow">
<button onclick="saveBmsFullConfig()">Save BMS</button>
<button class="off" onclick="setBmsEnabled(false)">Disable BMS</button>
</div> </div>
<div class="list">
<input id="w1s" placeholder="Network 1 SSID">
<input id="w1p" placeholder="Network 1 password" type="password">
<input id="w1r" placeholder="Network 1 priority" inputmode="numeric">
<input id="w2s" placeholder="Network 2 SSID">
<input id="w2p" placeholder="Network 2 password" type="password">
<input id="w2r" placeholder="Network 2 priority" inputmode="numeric">
<input id="w3s" placeholder="Network 3 SSID">
<input id="w3p" placeholder="Network 3 password" type="password">
<input id="w3r" placeholder="Network 3 priority" inputmode="numeric">
<button onclick="saveWifi()">Save WiFi</button>
<button onclick="connectWifi()">Connect Now</button>
</div>
</section>
</div>
</div>
<div id="config-relays" class="configSubpage">
<div class="grid">
<section class="card span12">
<div class="configGroupHeader">
<div>
<div class="configGroupTitle">Relay Outputs</div>
<div class="configGroupHelp">Rename relay outputs. IDs stay fixed for API/UART compatibility.</div>
</div>
</div>
<div class="list" id="relayConfigList"></div>
<button onclick="saveRelayConfig()">Save Relay Names</button>
</section>
</div>
</div>
<div id="config-temps" class="configSubpage">
<div class="grid">
<section class="card span12">
<div class="configGroupHeader">
<div>
<div class="configGroupTitle">Temperature Assignment</div>
<div class="configGroupHelp">Scan probes, then assign discovered sensors to configured temp slots.</div>
</div>
</div>
<div class="list">
<button onclick="scanTemps()">Scan Temp Probes</button>
<div id="tempScanResults" class="list"></div>
</div>
</section>
<section class="card span12">
<div class="configGroupHeader">
<div>
<div class="configGroupTitle">Temperature Slots</div>
<div class="configGroupHelp">Enable only the probes you use. Pick one weather sensor for the top badge.</div>
</div>
</div>
<div class="list">
<input id="tempEnabledCount" placeholder="Enabled temp count" inputmode="numeric">
<div id="tempConfigList" class="list"></div>
<button onclick="saveTempConfig()">Save Temperature Config</button>
</div>
</section>
</div>
</div>
<div id="config-bms" class="configSubpage">
<div class="grid">
<section class="card span12">
<div class="configGroupHeader">
<div>
<div class="configGroupTitle">BMS</div>
<div class="configGroupHelp">JBD/Xiaoxiang BLE settings. Do not change these unless pairing a different BMS.</div>
</div>
</div>
<div class="list">
<input id="bmsNameInput" placeholder="BMS name">
<input id="bmsAddressInput" placeholder="BMS BLE address">
<input id="bmsAddressTypeInput" placeholder="Address type: public or random">
<button class="on" onclick="saveBmsFullConfig()">Save BMS Config</button>
</div>
</section>
<section class="card span12">
<div class="configGroupHeader">
<div>
<div class="configGroupTitle">BMS Polling</div>
<div class="configGroupHelp">Enable or disable BMS polling without changing BLE pairing details.</div>
</div>
</div>
<div class="row"><span class="muted">BMS Polling</span><span class="v" id="bmsEnabledText">--</span></div>
<div class="btnrow"> <div class="btnrow">
<button onclick="setBmsEnabled(true)">Enable BMS</button> <button onclick="setBmsEnabled(true)">Enable BMS</button>
<button class="off" onclick="setBmsEnabled(false)">Disable BMS</button>
</div> </div>
</div> </section>
</section> </div>
</div>
<section class="card span12"> <div id="config-maintenance" class="configSubpage">
<div class="label">System Actions</div> <div class="grid">
<div class="sub">Use carefully. Factory reset clears install-specific configuration.</div> <section class="card span12">
<div class="btnrow"> <div class="configGroupHeader">
<button class="off" onclick="factoryReset()">Factory Reset</button> <div>
</div> <div class="configGroupTitle">Maintenance</div>
</section> <div class="configGroupHelp">Reset and recovery actions. These can disrupt the controller.</div>
</div>
<section class="card span12"> </div>
<div class="label">System</div> <button class="off" onclick="factoryReset()">Factory Reset Config</button>
<div class="row"><span class="muted">Firmware</span><span class="v" id="fw">--</span></div> </section>
<div class="row"><span class="muted">Uptime</span><span class="v" id="uptime">--</span></div> </div>
<div class="row"><span class="muted">AP IP</span><span class="v" id="apIp">--</span></div>
</section>
</div> </div>
</div> </div>
</div>
<div class="tabs"> <div class="tabs">
<button class="tabbtn active" onclick="showTab('overview')">Overview</button> <button class="tabbtn active" onclick="showTab('overview')">Overview</button>
@@ -474,6 +516,15 @@ function batteryTimeInfo(b){
return {label:"Battery time", value:"Idle"}; return {label:"Battery time", value:"Idle"};
} }
function showConfigSubtab(name){
["general","wifi","relays","temps","bms","maintenance"].forEach(t=>{
const page=$("config-"+t);
const btn=$("configTab-"+t);
if(page) page.classList.toggle("active",t===name);
if(btn) btn.classList.toggle("active",t===name);
});
}
function showTab(name){ function showTab(name){
["overview","battery","config"].forEach(t=>{ ["overview","battery","config"].forEach(t=>{
$(t+"Page").classList.toggle("active",t===name); $(t+"Page").classList.toggle("active",t===name);
+14
View File
@@ -352,3 +352,17 @@ def test_embedded_webui_config_page_is_grouped():
assert "Relay Outputs" in source assert "Relay Outputs" in source
assert "Temperature Assignment" in source assert "Temperature Assignment" in source
assert "Maintenance" in source assert "Maintenance" in source
def test_embedded_webui_config_subtabs():
source = firmware_source()
assert "function showConfigSubtab(name)" in source
assert "config-general" in source
assert "config-wifi" in source
assert "config-relays" in source
assert "config-temps" in source
assert "config-bms" in source
assert "config-maintenance" in source
assert "WiFi / AP" in source
assert "Maintenance" in source