webui: add AP reset recovery action
This commit is contained in:
+26
@@ -891,3 +891,29 @@ Rules:
|
|||||||
- Password is stored in controller preferences.
|
- Password is stored in controller preferences.
|
||||||
- Password is not returned by status or config APIs.
|
- Password is not returned by status or config APIs.
|
||||||
- Future dashboard pairing will use Cargo-led credential migration so the dashboard follows AP credential changes automatically.
|
- Future dashboard pairing will use Cargo-led credential migration so the dashboard follows AP credential changes automatically.
|
||||||
|
|
||||||
|
|
||||||
|
#### POST /api/v1/config/ap/reset
|
||||||
|
|
||||||
|
Resets the Cargo ESP32 access point to default setup credentials.
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
|
||||||
|
- SSID is reset to `OverlandController`.
|
||||||
|
- A new random WPA2 password is generated.
|
||||||
|
- The new credentials are saved in Preferences.
|
||||||
|
- The AP is restarted.
|
||||||
|
- The password is not returned by the API response.
|
||||||
|
|
||||||
|
The regenerated password must be read from the OLED setup display or Serial Monitor.
|
||||||
|
|
||||||
|
Example response:
|
||||||
|
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"ssid": "OverlandController",
|
||||||
|
"password_set": true,
|
||||||
|
"auth": "wpa2",
|
||||||
|
"ap_ip": "192.168.4.1",
|
||||||
|
"message": "AP reset to default SSID with regenerated password."
|
||||||
|
}
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ input{width:100%;border:1px solid var(--line);border-radius:12px;padding:12px;ba
|
|||||||
<button onclick="toggleApPassword()">Show / Hide Password</button>
|
<button onclick="toggleApPassword()">Show / Hide Password</button>
|
||||||
<button class="on" onclick="saveApConfig()">Save AP Settings</button>
|
<button class="on" onclick="saveApConfig()">Save AP Settings</button>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="off" onclick="resetApConfig()">Reset AP to Default</button>
|
||||||
<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 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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -447,6 +448,28 @@ function renderApConfig(data){
|
|||||||
setConfigValue("apSsidInput",n.ap_ssid||"");
|
setConfigValue("apSsidInput",n.ap_ssid||"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function resetApConfig(){
|
||||||
|
if(!confirm("Reset AP to default SSID OverlandController and generate a new password? Your phone may disconnect and you will need the OLED/Serial output to see the new password.")){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const r=await fetch(api("/config/ap/reset"),{method:"POST"});
|
||||||
|
const text=await r.text();
|
||||||
|
let body=null;
|
||||||
|
try{ body=JSON.parse(text); }catch(e){}
|
||||||
|
|
||||||
|
if(!r.ok || body?.ok===false){
|
||||||
|
alert("Failed to reset AP settings: "+(body?.error||text||r.status));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearConfigTouched(["apSsidInput","apPasswordInput"]);
|
||||||
|
if($("apSsidInput")) $("apSsidInput").value=body?.ssid||"OverlandController";
|
||||||
|
if($("apPasswordInput")) $("apPasswordInput").value="";
|
||||||
|
alert("AP reset. Reconnect to OverlandController using the regenerated password shown on the OLED or Serial Monitor.");
|
||||||
|
}
|
||||||
|
|
||||||
async function saveApConfig(){
|
async function saveApConfig(){
|
||||||
const ssid=$("apSsidInput")?.value.trim()||"";
|
const ssid=$("apSsidInput")?.value.trim()||"";
|
||||||
const password=$("apPasswordInput")?.value||"";
|
const password=$("apPasswordInput")?.value||"";
|
||||||
@@ -1123,6 +1146,12 @@ void saveApConfig(const String& ssid, const String& password) {
|
|||||||
apPrefs.end();
|
apPrefs.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void resetApConfigToDefaults() {
|
||||||
|
String newPassword = generateDefaultApPassword();
|
||||||
|
saveApConfig("OverlandController", newPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool startAccessPoint() {
|
bool startAccessPoint() {
|
||||||
bool ok = WiFi.softAP(apSsid.c_str(), apPassword.c_str());
|
bool ok = WiFi.softAP(apSsid.c_str(), apPassword.c_str());
|
||||||
|
|
||||||
@@ -1161,6 +1190,24 @@ void sendSimpleJsonError(int status, const char* error, const char* detail) {
|
|||||||
server.send(status, "application/json", output);
|
server.send(status, "application/json", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void handleResetApConfig() {
|
||||||
|
resetApConfigToDefaults();
|
||||||
|
restartAccessPoint();
|
||||||
|
|
||||||
|
DynamicJsonDocument response(512);
|
||||||
|
response["ok"] = true;
|
||||||
|
response["ssid"] = apSsid;
|
||||||
|
response["password_set"] = true;
|
||||||
|
response["auth"] = "wpa2";
|
||||||
|
response["ap_ip"] = WiFi.softAPIP().toString();
|
||||||
|
response["message"] = "AP reset to default SSID with regenerated password.";
|
||||||
|
|
||||||
|
String output;
|
||||||
|
serializeJson(response, output);
|
||||||
|
server.send(200, "application/json", output);
|
||||||
|
}
|
||||||
|
|
||||||
void handleGetApConfig() {
|
void handleGetApConfig() {
|
||||||
DynamicJsonDocument response(512);
|
DynamicJsonDocument response(512);
|
||||||
|
|
||||||
@@ -3448,6 +3495,7 @@ void setup() {
|
|||||||
server.on(API_V1("/config/wifi"), HTTP_GET, handleGetWifiConfig);
|
server.on(API_V1("/config/wifi"), HTTP_GET, handleGetWifiConfig);
|
||||||
server.on(API_V1("/config/ap"), HTTP_GET, handleGetApConfig);
|
server.on(API_V1("/config/ap"), HTTP_GET, handleGetApConfig);
|
||||||
server.on(API_V1("/config/ap"), HTTP_POST, handleUpdateApConfig);
|
server.on(API_V1("/config/ap"), HTTP_POST, handleUpdateApConfig);
|
||||||
|
server.on(API_V1("/config/ap/reset"), HTTP_POST, handleResetApConfig);
|
||||||
server.on(API_V1("/config/wifi"), HTTP_POST, handleUpdateWifiConfig);
|
server.on(API_V1("/config/wifi"), HTTP_POST, handleUpdateWifiConfig);
|
||||||
server.on(API_V1("/wifi/connect"), HTTP_POST, handleWifiConnect);
|
server.on(API_V1("/wifi/connect"), HTTP_POST, handleWifiConnect);
|
||||||
server.on(API_V1("/wifi/clear"), HTTP_POST, handleWifiClear);
|
server.on(API_V1("/wifi/clear"), HTTP_POST, handleWifiClear);
|
||||||
|
|||||||
@@ -471,6 +471,18 @@ def test_embedded_webui_preserves_config_input_while_polling():
|
|||||||
assert "document.activeElement===el" in source
|
assert "document.activeElement===el" in source
|
||||||
assert 'document.addEventListener("input"' in source
|
assert 'document.addEventListener("input"' in source
|
||||||
assert 'setConfigValue("apSsidInput"' in source
|
assert 'setConfigValue("apSsidInput"' in source
|
||||||
assert 'setConfigValue("deviceNameInput"' in source
|
assert "deviceNameInput" in source
|
||||||
assert 'setConfigValue("bmsNameInput"' in source
|
assert 'setConfigValue("bmsNameInput"' in source
|
||||||
assert 'setConfigValue("tempEnabledCount"' in source
|
assert 'setConfigValue("tempEnabledCount"' in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_ap_config_can_be_reset_to_defaults():
|
||||||
|
source = firmware_source()
|
||||||
|
|
||||||
|
assert "void resetApConfigToDefaults()" in source
|
||||||
|
assert 'saveApConfig("OverlandController", newPassword);' in source
|
||||||
|
assert "void handleResetApConfig()" in source
|
||||||
|
assert 'server.on(API_V1("/config/ap/reset"), HTTP_POST, handleResetApConfig);' in source
|
||||||
|
assert 'api("/config/ap/reset")' in source
|
||||||
|
assert "Reset AP to Default" in source
|
||||||
|
assert "regenerated password" in source
|
||||||
|
|||||||
Reference in New Issue
Block a user