edit wifi networks in webui (upstream #1743 by osteotek)

This commit is contained in:
jpirnay
2026-04-25 12:10:38 +02:00
parent 7f6cb65344
commit c1e51ac8a2
4 changed files with 281 additions and 4 deletions
+140
View File
@@ -15,6 +15,7 @@
#include "SettingsList.h"
#include "SystemStatus.h"
#include "WebDAVHandler.h"
#include "WifiCredentialStore.h"
#include "html/FilesPageHtml.generated.h"
#include "html/HomePageHtml.generated.h"
#include "html/SettingsPageHtml.generated.h"
@@ -199,6 +200,11 @@ void CrossPointWebServer::begin() {
server->on("/api/opds", HTTP_POST, [this] { handlePostOpdsServer(); });
server->on("/api/opds/delete", HTTP_POST, [this] { handleDeleteOpdsServer(); });
// Wi-Fi credential endpoints
server->on("/api/wifi", HTTP_GET, [this] { handleGetWifiNetworks(); });
server->on("/api/wifi", HTTP_POST, [this] { handlePostWifiNetwork(); });
server->on("/api/wifi/delete", HTTP_POST, [this] { handleDeleteWifiNetwork(); });
server->onNotFound([this] { handleNotFound(); });
LOG_DBG("WEB", "[MEM] Free heap after route setup: %d bytes", ESP.getFreeHeap());
@@ -1375,6 +1381,140 @@ void CrossPointWebServer::handlePostSettings() {
server->send(200, "text/plain", String("Applied ") + String(applied) + " setting(s)");
}
// ---- Wi-Fi Credentials API ----
void CrossPointWebServer::handleGetWifiNetworks() const {
const auto& credentials = WIFI_STORE.getCredentials();
const std::string& lastConnectedSsid = WIFI_STORE.getLastConnectedSsid();
// Stream JSON array incrementally to avoid allocating the full response in memory
server->setContentLength(CONTENT_LENGTH_UNKNOWN);
server->send(200, "application/json", "");
server->sendContent("[");
char output[320];
constexpr size_t outputSize = sizeof(output);
JsonDocument doc;
for (size_t i = 0; i < credentials.size(); i++) {
doc.clear();
doc["index"] = i;
doc["ssid"] = credentials[i].ssid;
// Never expose Wi-Fi passwords over the API — only indicate whether one is set
doc["hasPassword"] = !credentials[i].password.empty();
doc["isLastConnected"] = credentials[i].ssid == lastConnectedSsid;
const size_t written = serializeJson(doc, output, outputSize);
if (written >= outputSize) continue;
if (i > 0) server->sendContent(",");
server->sendContent(output);
}
server->sendContent("]");
server->sendContent("");
LOG_DBG("WEB", "Served Wi-Fi credentials API (%zu network(s))", credentials.size());
}
void CrossPointWebServer::handlePostWifiNetwork() {
if (!server->hasArg("plain")) {
server->send(400, "text/plain", "Missing JSON body");
return;
}
const String body = server->arg("plain");
JsonDocument doc;
const DeserializationError err = deserializeJson(doc, body);
if (err) {
server->send(400, "text/plain", String("Invalid JSON: ") + err.c_str());
return;
}
std::string ssid = doc["ssid"] | std::string("");
if (ssid.empty()) {
server->send(400, "text/plain", "SSID is required");
return;
}
// The password field is optional in the JSON payload. When absent (vs. present but empty),
// preserve the existing password for updates. Empty passwords are valid for open networks.
bool hasPasswordField = doc["password"].is<const char*>() || doc["password"].is<std::string>();
std::string password = doc["password"] | std::string("");
if (doc["index"].is<int>()) {
int idx = doc["index"].as<int>();
const auto& credentials = WIFI_STORE.getCredentials();
if (idx < 0 || idx >= static_cast<int>(credentials.size())) {
server->send(400, "text/plain", "Invalid network index");
return;
}
const std::string oldSsid = credentials[static_cast<size_t>(idx)].ssid;
if (!hasPasswordField) {
password = credentials[static_cast<size_t>(idx)].password;
}
bool ok = true;
if (oldSsid != ssid) {
ok = WIFI_STORE.removeCredential(oldSsid) && WIFI_STORE.addCredential(ssid, password);
} else {
ok = WIFI_STORE.addCredential(ssid, password);
}
if (!ok) {
server->send(400, "text/plain", "Failed to update Wi-Fi network");
return;
}
LOG_DBG("WEB", "Updated Wi-Fi network at index %d (SSID: %s)", idx, ssid.c_str());
} else {
if (!WIFI_STORE.addCredential(ssid, password)) {
server->send(400, "text/plain", "Cannot add network (limit reached)");
return;
}
LOG_DBG("WEB", "Added Wi-Fi network: %s", ssid.c_str());
}
server->send(200, "text/plain", "OK");
}
// Uses POST (not HTTP DELETE) because ESP32 WebServer doesn't support DELETE with body.
void CrossPointWebServer::handleDeleteWifiNetwork() {
if (!server->hasArg("plain")) {
server->send(400, "text/plain", "Missing JSON body");
return;
}
const String body = server->arg("plain");
JsonDocument doc;
const DeserializationError err = deserializeJson(doc, body);
if (err) {
server->send(400, "text/plain", String("Invalid JSON: ") + err.c_str());
return;
}
if (!doc["index"].is<int>()) {
server->send(400, "text/plain", "Missing index");
return;
}
int idx = doc["index"].as<int>();
const auto& credentials = WIFI_STORE.getCredentials();
if (idx < 0 || idx >= static_cast<int>(credentials.size())) {
server->send(400, "text/plain", "Invalid network index");
return;
}
const std::string ssid = credentials[static_cast<size_t>(idx)].ssid;
if (!WIFI_STORE.removeCredential(ssid)) {
server->send(400, "text/plain", "Failed to delete Wi-Fi network");
return;
}
LOG_DBG("WEB", "Deleted Wi-Fi network at index %d (SSID: %s)", idx, ssid.c_str());
server->send(200, "text/plain", "OK");
}
// ---- OPDS Server API ----
void CrossPointWebServer::handleGetOpdsServers() const {