- Ring-Buffer _logBuf in web_server.cpp: webLogForward() schreibt auf Core 1 - GET /log-data liefert Puffer als Plain-Text (kein WebSocket, kein Core-Konflikt) - Browser pollt alle 2 s, Auto-Scroll, dunkles Terminal-Theme - LOG_I/LOG_E/LOG_D: Timestamp (HH:MM:SS nach NTP, sonst +Xs), webLogForward() - Alle Serial.* in laser_tracker.cpp, mqtt_client.cpp, web_server.cpp auf LOG_I/LOG_E - main.cpp: esp_reset_reason() beim Booten loggen (POWER_ON / WATCHDOG / PANIC...) - telnet_logger.h entfernt (war nur noch Deprecated-Stub) - Feature-Requests.md: FR-002 abgeschlossen
473 lines
22 KiB
C++
473 lines
22 KiB
C++
// =============================================================================
|
||
// web_server.cpp – HTTP-Webinterface für Statusanzeige und Konfiguration
|
||
// Projekt: MQTT-Display LaserCutter
|
||
// =============================================================================
|
||
|
||
#include "web_server.h"
|
||
#include <ESPAsyncWebServer.h> // voller Include nur in .cpp (PIMPL) – enthält AsyncWebSocket
|
||
#include <ArduinoOTA.h> // ESP32 built-in, kein lib_deps-Eintrag noetig
|
||
#include "config.h"
|
||
#include "settings.h"
|
||
#include "laser_tracker.h"
|
||
#include "mqtt_client.h"
|
||
// wifi_connector.h wird hier NICHT eingebunden (Konflikt WiFiManager vs. ESPAsyncWebServer)
|
||
// stattdessen freie Funktion aus wifi_connector.cpp:
|
||
extern void wifiResetCredentialsAndRestart();
|
||
#include <ElegantOTA.h>
|
||
#include <WiFi.h>
|
||
|
||
// Globale Instanz
|
||
WebServerManager webServer;
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// Datei-statischer WebSocket-Pointer für webLogForward() (gesetzt in begin())
|
||
// -----------------------------------------------------------------------------
|
||
static AsyncWebSocket* _wsPtr = nullptr;
|
||
|
||
// Ring-Buffer für Web-Log-Console.
|
||
// webLogForward() läuft auf Core 1 (Arduino loop) – kein Task, kein Queue,
|
||
// kein Core-Problem. Browser pollt GET /log-data alle 2 s.
|
||
static const size_t LOG_BUF_MAX = 6000;
|
||
static String _logBuf = "";
|
||
|
||
void webLogForward(const char* msg) {
|
||
_logBuf += msg;
|
||
_logBuf += '\n';
|
||
if (_logBuf.length() > LOG_BUF_MAX) {
|
||
_logBuf = _logBuf.substring(_logBuf.length() - LOG_BUF_MAX / 2);
|
||
}
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// Hilfsmakro: HTML-Template-Platzhalter ersetzen
|
||
// -----------------------------------------------------------------------------
|
||
static String htmlReplace(String html, const String& key, const String& value) {
|
||
html.replace(key, value);
|
||
return html;
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// Konstruktor
|
||
// -----------------------------------------------------------------------------
|
||
WebServerManager::WebServerManager()
|
||
: _server(new AsyncWebServer(80))
|
||
, _ws(new AsyncWebSocket("/log-ws"))
|
||
{}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// begin() – Routen registrieren und Server starten
|
||
// -----------------------------------------------------------------------------
|
||
void WebServerManager::begin() {
|
||
// WebSocket-Handler registrieren
|
||
_ws->onEvent([](AsyncWebSocket* server, AsyncWebSocketClient* client,
|
||
AwsEventType type, void* arg, uint8_t* data, size_t len) {
|
||
(void)server; (void)arg; (void)data; (void)len;
|
||
if (type == WS_EVT_CONNECT) {
|
||
LOG_I("WEB", "Log Console Client #%u verbunden", client->id());
|
||
} else if (type == WS_EVT_DISCONNECT) {
|
||
LOG_I("WEB", "Log Console Client #%u getrennt", client->id());
|
||
}
|
||
});
|
||
_server->addHandler(_ws);
|
||
_wsPtr = _ws; // ab jetzt ist webLogForward() aktiv
|
||
|
||
registerRoutes();
|
||
// ElegantOTA-Auth: nur wenn Passwort gesetzt
|
||
const Settings& s = settings.get();
|
||
if (s.webPassword[0] != '\0') {
|
||
ElegantOTA.setAuth(s.webUser, s.webPassword);
|
||
}
|
||
ElegantOTA.begin(_server);
|
||
_server->begin();
|
||
|
||
// ArduinoOTA – erscheint als Netzwerk-Port in Arduino IDE und PlatformIO
|
||
const Settings& cfg = settings.get();
|
||
ArduinoOTA.setHostname("lasercutter-display");
|
||
if (cfg.webPassword[0] != '\0') {
|
||
ArduinoOTA.setPassword(cfg.webPassword); // gleiche Auth wie Webinterface
|
||
}
|
||
ArduinoOTA.onStart([]() { LOG_I("OTA", "Start OTA-Update..."); });
|
||
ArduinoOTA.onEnd([]() { LOG_I("OTA", "OTA fertig – Neustart"); });
|
||
ArduinoOTA.onError([](ota_error_t e) { LOG_E("OTA", "Fehler [%u]", e); });
|
||
ArduinoOTA.begin();
|
||
LOG_I("WEB", "ArduinoOTA aktiv: lasercutter-display.local");
|
||
|
||
LOG_I("WEB", "gestartet auf http://%s", WiFi.localIP().toString().c_str());
|
||
LOG_I("WEB", "Auth: %s", s.webPassword[0] ? "aktiv" : "deaktiviert (kein Passwort)");
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// loop() – ArduinoOTA verarbeiten + WebSocket-Clients bereinigen (in main loop() aufrufen)
|
||
// -----------------------------------------------------------------------------
|
||
void WebServerManager::loop() {
|
||
ArduinoOTA.handle();
|
||
_ws->cleanupClients(); // abgemeldete Clients freigeben (verhindert Memory-Leak)
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// requireAuth() – HTTP Basic Auth pruefen (nur wenn Passwort konfiguriert)
|
||
// -----------------------------------------------------------------------------
|
||
bool WebServerManager::requireAuth(AsyncWebServerRequest* request) {
|
||
const Settings& s = settings.get();
|
||
if (s.webPassword[0] == '\0') return true; // kein Passwort = kein Schutz
|
||
if (!request->authenticate(s.webUser, s.webPassword)) {
|
||
request->requestAuthentication("LaserCutter Display", false); // false = Basic Auth
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// registerRoutes()
|
||
// -----------------------------------------------------------------------------
|
||
void WebServerManager::registerRoutes() {
|
||
|
||
// --- GET / – Statusseite -------------------------------------------------
|
||
_server->on("/", HTTP_GET, [this](AsyncWebServerRequest* request) {
|
||
if (!requireAuth(request)) return;
|
||
request->send(200, "text/html; charset=utf-8", buildStatusPage());
|
||
});
|
||
|
||
// --- GET /config – Konfigurationsformular --------------------------------
|
||
_server->on("/config", HTTP_GET, [this](AsyncWebServerRequest* request) {
|
||
if (!requireAuth(request)) return;
|
||
request->send(200, "text/html; charset=utf-8", buildConfigPage());
|
||
});
|
||
|
||
// --- POST /config – Konfiguration speichern ------------------------------
|
||
_server->on("/config", HTTP_POST, [this](AsyncWebServerRequest* request) {
|
||
if (!requireAuth(request)) return;
|
||
const Settings& s = settings.get();
|
||
|
||
// MQTT
|
||
const char* broker = s.mqttBroker;
|
||
uint16_t port = s.mqttPort;
|
||
const char* user = s.mqttUser;
|
||
const char* pass = s.mqttPassword;
|
||
|
||
if (request->hasParam("broker", true))
|
||
broker = request->getParam("broker", true)->value().c_str();
|
||
if (request->hasParam("port", true))
|
||
port = (uint16_t)request->getParam("port", true)->value().toInt();
|
||
if (request->hasParam("user", true))
|
||
user = request->getParam("user", true)->value().c_str();
|
||
if (request->hasParam("password", true))
|
||
pass = request->getParam("password", true)->value().c_str();
|
||
|
||
settings.saveMqttConfig(broker, port, user, pass);
|
||
|
||
// Gratiszeit
|
||
if (request->hasParam("gratis", true)) {
|
||
uint8_t g = (uint8_t)request->getParam("gratis", true)->value().toInt();
|
||
settings.saveGratisSeconds(g);
|
||
}
|
||
|
||
// Signal-Polarität
|
||
if (request->hasParam("polarity", true)) {
|
||
uint8_t pol = (uint8_t)request->getParam("polarity", true)->value().toInt();
|
||
settings.saveSignalPolarity(pol);
|
||
}
|
||
|
||
// Webinterface-Zugangsdaten
|
||
if (request->hasParam("web_user", true)) {
|
||
const char* wUser = request->getParam("web_user", true)->value().c_str();
|
||
bool clearAuth = request->hasParam("clear_auth", true) &&
|
||
request->getParam("clear_auth", true)->value() == "1";
|
||
if (clearAuth) {
|
||
// Passwortschutz explizit deaktivieren
|
||
settings.saveWebCredentials(wUser, "");
|
||
} else {
|
||
// Passwortfeld leer => bestehendes Passwort unveraendert lassen
|
||
String wPassStr = request->hasParam("web_password", true)
|
||
? request->getParam("web_password", true)->value()
|
||
: String("");
|
||
const char* effectivePass = (wPassStr.length() > 0)
|
||
? wPassStr.c_str()
|
||
: settings.get().webPassword;
|
||
settings.saveWebCredentials(wUser, effectivePass);
|
||
}
|
||
}
|
||
|
||
LOG_I("WEB", "Konfiguration gespeichert – Neustart empfohlen");
|
||
|
||
// Weiterleitung mit Hinweis
|
||
request->send(200, "text/html; charset=utf-8",
|
||
"<!DOCTYPE html><html><head><meta charset='utf-8'>"
|
||
"<meta http-equiv='refresh' content='3;url=/'></head>"
|
||
"<body style='font-family:sans-serif;max-width:480px;margin:2rem auto;padding:0 1rem'>"
|
||
"<h2>✓ Gespeichert</h2>"
|
||
"<p>Einstellungen wurden in NVS geschrieben. "
|
||
"MQTT-Änderungen werden nach einem <strong>Neustart</strong> aktiv.</p>"
|
||
"<p>Weiterleitung in 3 s …</p>"
|
||
"</body></html>");
|
||
});
|
||
|
||
// --- POST /reset – Session zurücksetzen ---------------------------------
|
||
_server->on("/reset", HTTP_POST, [this](AsyncWebServerRequest* request) {
|
||
if (!requireAuth(request)) return;
|
||
laserTracker.resetSessionSum();
|
||
LOG_I("WEB", "Session zurueckgesetzt");
|
||
request->send(200, "text/html; charset=utf-8",
|
||
"<!DOCTYPE html><html><head><meta charset='utf-8'>"
|
||
"<meta http-equiv='refresh' content='2;url=/'></head>"
|
||
"<body style='font-family:sans-serif;max-width:480px;margin:2rem auto;padding:0 1rem'>"
|
||
"<h2>✓ Session beendet</h2>"
|
||
"<p>Weiterleitung …</p>"
|
||
"</body></html>");
|
||
});
|
||
|
||
// --- POST /wifi-reset – WiFi-Credentials löschen und neu starten --------
|
||
_server->on("/wifi-reset", HTTP_POST, [this](AsyncWebServerRequest* request) {
|
||
if (!requireAuth(request)) return;
|
||
LOG_I("WEB", "WiFi-Credentials geloescht – Neustart");
|
||
request->send(200, "text/html; charset=utf-8",
|
||
"<!DOCTYPE html><html><head><meta charset='utf-8'>"
|
||
"<meta http-equiv='refresh' content='10;url=/'></head>"
|
||
"<body style='font-family:sans-serif;max-width:480px;margin:2rem auto;padding:0 1rem'>"
|
||
"<h2>✓ WLAN-Daten gelöscht</h2>"
|
||
"<p>Das Gerät startet neu.<br>"
|
||
"Verbinde dich dann mit dem WLAN <strong>LaserCutter-Setup</strong> "
|
||
"und öffne <strong>http://192.168.4.1</strong> zur Konfiguration.</p>"
|
||
"</body></html>");
|
||
// Neustart nach kurzer Pause (Response muss zuerst gesendet werden)
|
||
wifiResetCredentialsAndRestart();
|
||
});
|
||
|
||
// --- GET /log – Web-Log-Console ----------------------------------------
|
||
_server->on("/log", HTTP_GET, [this](AsyncWebServerRequest* request) {
|
||
if (!requireAuth(request)) return;
|
||
request->send(200, "text/html; charset=utf-8", buildLogPage());
|
||
});
|
||
|
||
// --- GET /log-data – aktuellen Log-Puffer als Plain-Text liefern --------
|
||
_server->on("/log-data", HTTP_GET, [this](AsyncWebServerRequest* request) {
|
||
if (!requireAuth(request)) return;
|
||
request->send(200, "text/plain; charset=utf-8", _logBuf);
|
||
});
|
||
|
||
// 404
|
||
_server->onNotFound([](AsyncWebServerRequest* request) {
|
||
request->send(404, "text/plain", "Not found");
|
||
});
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// buildStatusPage()
|
||
// -----------------------------------------------------------------------------
|
||
String WebServerManager::buildStatusPage() {
|
||
const Settings& s = settings.get();
|
||
|
||
String mqttStatus = mqttClient.isConnected()
|
||
? "<span style='color:green'>✓ verbunden</span>"
|
||
: "<span style='color:red'>✗ getrennt</span>";
|
||
|
||
String laserStatus = laserTracker.isActive()
|
||
? "<span style='color:orange'>aktiv</span>"
|
||
: "inaktiv";
|
||
|
||
String ip = WiFi.isConnected() ? WiFi.localIP().toString() : "nicht verbunden";
|
||
|
||
// Gesamtzeit formatieren
|
||
char totalBuf[16];
|
||
snprintf(totalBuf, sizeof(totalBuf), "%.2f", laserTracker.getTotalMinutes());
|
||
|
||
String html = F(
|
||
"<!DOCTYPE html><html>"
|
||
"<head><meta charset='utf-8'>"
|
||
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
|
||
"<title>LaserCutter Display</title>"
|
||
"<style>"
|
||
"body{font-family:sans-serif;max-width:480px;margin:2rem auto;padding:0 1rem}"
|
||
"h1{color:#333}table{width:100%;border-collapse:collapse;margin-bottom:1.5rem}"
|
||
"td{padding:.5rem;border-bottom:1px solid #eee}td:first-child{color:#666;width:50%}"
|
||
".btn{display:inline-block;padding:.5rem 1.2rem;border:none;border-radius:4px;"
|
||
"cursor:pointer;text-decoration:none;font-size:1rem}"
|
||
".btn-primary{background:#3182ce;color:#fff}"
|
||
"</style></head>"
|
||
"<body>"
|
||
"<h1>LaserCutter Display</h1>"
|
||
"<table>"
|
||
"<tr><td>Summe Laserzeit</td><td>%ALLSESSIONS% min</td></tr>"
|
||
"<tr><td>Aktuelle Laserzeit</td><td>%CURRENTSESSION% s</td></tr>"
|
||
"<tr><td>Letzte Laserzeit</td><td>%LASTSESSION% s</td></tr>"
|
||
"<tr><td>Laser</td><td>%LASER%</td></tr>"
|
||
"<tr><td>Maschinenlaufzeit (gesamt)</td><td>%TOTAL% min</td></tr>"
|
||
"<tr><td>IP-Adresse</td><td>%IP%</td></tr>"
|
||
"<tr><td>MQTT</td><td>%MQTT%</td></tr>"
|
||
"<tr><td>Broker</td><td>%BROKER%:%PORT%</td></tr>"
|
||
"<tr><td>Gratiszeit</td><td>%GRATIS% s</td></tr>"
|
||
"<tr><td>Firmware</td><td>v" FIRMWARE_VERSION " (" __DATE__ ")</td></tr>"
|
||
"</table>"
|
||
"<div style='display:flex;flex-direction:column;gap:.6rem;margin-top:.5rem'>"
|
||
"<form action='/reset' method='post' style='width:100%' "
|
||
"onsubmit=\"return confirm('Summe Laserzeit wirklich zur\\u00fccksetzen?')\">"
|
||
"<button class='btn btn-primary' type='submit' style='width:100%'>Summe Laserzeit zurücksetzen</button>"
|
||
"</form>"
|
||
"<a href='/config' style='display:block'><button class='btn btn-primary' type='button' style='width:100%'>Konfiguration</button></a>"
|
||
"<a href='/update' style='display:block'><button class='btn btn-primary' type='button' style='width:100%'>OTA Update</button></a>"
|
||
"<a href='/log' style='display:block'><button class='btn btn-primary' type='button' style='width:100%'>💿 Log Console</button></a>"
|
||
"</div>"
|
||
"<script>setTimeout(()=>location.reload(),10000)</script>"
|
||
"</body></html>"
|
||
);
|
||
|
||
html.replace("%ALLSESSIONS%", String(laserTracker.getAllSessionsSumMinutes()));
|
||
html.replace("%CURRENTSESSION%", String(laserTracker.getRunningSessionSeconds()));
|
||
html.replace("%TOTAL%", String(totalBuf));
|
||
html.replace("%LASTSESSION%", String(laserTracker.getLastSessionSeconds()));
|
||
html.replace("%LASER%", laserStatus);
|
||
html.replace("%IP%", ip);
|
||
html.replace("%MQTT%", mqttStatus);
|
||
html.replace("%BROKER%", String(s.mqttBroker));
|
||
html.replace("%PORT%", String(s.mqttPort));
|
||
html.replace("%GRATIS%", String(s.gratisSeconds));
|
||
|
||
return html;
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// buildLogPage()
|
||
// -----------------------------------------------------------------------------
|
||
String WebServerManager::buildLogPage() {
|
||
return String(F(
|
||
"<!DOCTYPE html><html>"
|
||
"<head><meta charset='utf-8'>"
|
||
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
|
||
"<title>Log Console – LaserCutter Display</title>"
|
||
"<style>"
|
||
"body{font-family:monospace;background:#1a1a1a;color:#e0e0e0;margin:0;padding:1rem}"
|
||
"h1{font-size:1.1rem;color:#90ee90;margin:0 0 .4rem}"
|
||
"#ctrl{margin:.4rem 0}"
|
||
"button{background:#333;color:#e0e0e0;border:1px solid #555;padding:.3rem .8rem;"
|
||
"cursor:pointer;border-radius:3px;margin-right:.4rem}"
|
||
"button:hover{background:#555}"
|
||
"#status{display:inline-block;font-size:.8rem;color:#888;margin-left:.5rem}"
|
||
"#log{background:#000;border:1px solid #333;padding:.6rem;height:78vh;"
|
||
"overflow-y:auto;white-space:pre-wrap;word-break:break-all;font-size:.82rem}"
|
||
".i{color:#90ee90}.e{color:#ff6b6b}.d{color:#87ceeb}"
|
||
"</style></head>"
|
||
"<body>"
|
||
"<h1>💿 Log Console</h1>"
|
||
"<div id='ctrl'>"
|
||
"<button onclick='lastLen=0;logDiv.innerHTML=\"\"'>Leeren</button>"
|
||
"<button id='asBtn' onclick='toggleAS()'>Auto-Scroll: AN</button>"
|
||
"<a href='/' style='margin-left:.5rem'><button>← Status</button></a>"
|
||
"<span id='status'>Lade…</span>"
|
||
"</div>"
|
||
"<div id='log'></div>"
|
||
"<script>"
|
||
"var logDiv=document.getElementById('log'),"
|
||
"st=document.getElementById('status'),"
|
||
"asBt=document.getElementById('asBtn'),"
|
||
"autoS=true,lastLen=0;"
|
||
"function toggleAS(){autoS=!autoS;asBt.textContent='Auto-Scroll:'+(autoS?' AN':' AUS');}"
|
||
"function renderLog(text){"
|
||
"if(text.length===lastLen)return;"
|
||
"lastLen=text.length;"
|
||
"logDiv.innerHTML='';"
|
||
"text.split('\\n').forEach(function(line){"
|
||
"if(!line)return;"
|
||
"var s=document.createElement('span');"
|
||
"s.className=line.indexOf('[E]')!==-1?'e':line.indexOf('[D]')!==-1?'d':'i';"
|
||
"s.textContent=line+'\\n';"
|
||
"logDiv.appendChild(s);"
|
||
"});"
|
||
"if(autoS)logDiv.scrollTop=logDiv.scrollHeight;"
|
||
"}"
|
||
"function poll(){"
|
||
"fetch('/log-data')"
|
||
".then(function(r){return r.text();})"
|
||
".then(function(t){st.textContent='OK';st.style.color='#90ee90';renderLog(t);})"
|
||
".catch(function(){st.textContent='Fehler';st.style.color='#ff6b6b';});"
|
||
"}"
|
||
"poll();setInterval(poll,2000);"
|
||
"</script></body></html>"
|
||
));
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// buildConfigPage()
|
||
// -----------------------------------------------------------------------------
|
||
String WebServerManager::buildConfigPage() {
|
||
const Settings& s = settings.get();
|
||
|
||
String html = F(
|
||
"<!DOCTYPE html><html>"
|
||
"<head><meta charset='utf-8'>"
|
||
"<meta name='viewport' content='width=device-width,initial-scale=1'>"
|
||
"<title>Konfiguration – LaserCutter Display</title>"
|
||
"<style>"
|
||
"body{font-family:sans-serif;max-width:480px;margin:2rem auto;padding:0 1rem}"
|
||
"label{display:block;margin-top:1rem;color:#555;font-size:.9rem}"
|
||
"input[type=text],input[type=number],input[type=password]"
|
||
"{width:100%;padding:.4rem;box-sizing:border-box;border:1px solid #ccc;border-radius:4px}"
|
||
"input[type=range]{width:100%}"
|
||
".radio-label{display:flex;align-items:center;gap:.5rem;margin-top:.3rem}"
|
||
".btn{padding:.5rem 1.5rem;border:none;border-radius:4px;cursor:pointer;font-size:1rem;width:100%}"
|
||
".btn-primary{background:#3182ce;color:#fff}"
|
||
".btn-back{background:#718096;color:#fff}"
|
||
"</style></head>"
|
||
"<body>"
|
||
"<h1>Konfiguration</h1>"
|
||
"<form action='/config' method='post'>"
|
||
"<label>MQTT Broker</label>"
|
||
"<input type='text' name='broker' value='%BROKER%' maxlength='63' required>"
|
||
"<label>MQTT Port</label>"
|
||
"<input type='number' name='port' value='%PORT%' min='1' max='65535'>"
|
||
"<label>MQTT Benutzer</label>"
|
||
"<input type='text' name='user' value='%USER%' maxlength='31'>"
|
||
"<label>MQTT Passwort</label>"
|
||
"<input type='password' name='password' value='%PASSWORD%' maxlength='31'>"
|
||
"<label>Gratiszeit: <span id='gv'>%GRATIS%</span> s</label>"
|
||
"<input type='range' name='gratis' min='0' max='120' value='%GRATIS%'"
|
||
" oninput=\"document.getElementById('gv').textContent=this.value\">"
|
||
"<label>Signal-Polarität</label>"
|
||
"<label class='radio-label'>"
|
||
"<input type='radio' name='polarity' value='0' %POL_LOW%>"
|
||
"LOW-aktiv (Schalter nach GND, Standard)</label>"
|
||
"<label class='radio-label'>"
|
||
"<input type='radio' name='polarity' value='1' %POL_HIGH%>"
|
||
"HIGH-aktiv</label>"
|
||
"<hr style='margin-top:1.5rem'>"
|
||
"<h2 style='font-size:1.1rem'>Web-Zugang</h2>"
|
||
"<label>Benutzername</label>"
|
||
"<input type='text' name='web_user' value='%WEB_USER%' maxlength='31'>"
|
||
"<label>Passwort <small style='color:#888'>(leer lassen = unverändert)</small></label>"
|
||
"<input type='password' name='web_password' value='' maxlength='63' "
|
||
"placeholder='Neues Passwort eingeben oder leer lassen'>"
|
||
"<label class='radio-label' style='margin-top:0.4rem'>"
|
||
"<input type='checkbox' name='clear_auth' value='1'> "
|
||
"Passwortschutz deaktivieren (Passwort löschen)</label>"
|
||
"<br>"
|
||
"<div style='display:flex;flex-direction:column;gap:.6rem;margin-top:1.5rem'>"
|
||
"<button class='btn btn-primary' type='submit'>Speichern & Zurück</button>"
|
||
"</form>"
|
||
"<a href='/' style='display:block'><button class='btn btn-back' type='button'>Abbrechen</button></a>"
|
||
"</div>"
|
||
"<hr style='margin-top:1.5rem'>"
|
||
"<h2 style='font-size:1.1rem;color:#c53030'>WLAN ändern</h2>"
|
||
"<p style='font-size:.9rem;color:#555'>Löscht die gespeicherten WLAN-Daten und startet das Gerät neu.<br>"
|
||
"Danach mit <strong>LaserCutter-Setup</strong> verbinden und neues WLAN eingeben.</p>"
|
||
"<form action='/wifi-reset' method='post' "
|
||
"onsubmit=\"return confirm('WLAN-Daten wirklich l\u00f6schen und neu starten?')\">"
|
||
"<button class='btn' style='background:#c53030;color:#fff;width:100%' type='submit'>"
|
||
"WLAN neu konfigurieren</button>"
|
||
"</form>"
|
||
"<footer style='margin-top:2rem;text-align:center;font-size:.8rem;color:#aaa'>"
|
||
"v" FIRMWARE_VERSION " (" __DATE__ ")"
|
||
"</footer>"
|
||
"</body></html>"
|
||
);
|
||
|
||
html.replace("%BROKER%", String(s.mqttBroker));
|
||
html.replace("%PORT%", String(s.mqttPort));
|
||
html.replace("%USER%", String(s.mqttUser));
|
||
html.replace("%PASSWORD%", String(s.mqttPassword));
|
||
html.replace("%GRATIS%", String(s.gratisSeconds));
|
||
html.replace("%POL_LOW%", s.signalPolarity == 0 ? "checked" : "");
|
||
html.replace("%POL_HIGH%", s.signalPolarity == 1 ? "checked" : "");
|
||
html.replace("%WEB_USER%", String(s.webUser));
|
||
|
||
return html;
|
||
}
|