mirror of
https://github.com/alexhopeoconnor/WiFiManager.git
synced 2026-10-22 02:32:10 +10:00
Portal SPA: single shell, JSON /api surface, remove legacy portal routes
Replace multi-page portal with hash-routed SPA, DFTE shell placeholders, and data-first JSON APIs. Remove legacy routes, root-template override path, and HTML-derived info pipeline. Add bootstrap feature flags, in-DOM toast/dialog UX, OTA via POST /u, and tests for shell, bootstrap, API JSON, and captive host rule. Rewrite README as fork disclaimer and modernization overview.
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* @author Creator tzapu
|
||||
* @author tablatronix
|
||||
* @author Alex Hope-O'Connor
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
@@ -139,8 +139,6 @@
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <TemplateEngine.h>
|
||||
|
||||
// Include utility functions
|
||||
#include "WiFiManagerUtils.h"
|
||||
|
||||
@@ -437,6 +435,9 @@ class WiFiManager
|
||||
// show OTA upload button on info page
|
||||
void setShowInfoUpdate(boolean enabled);
|
||||
|
||||
// show Info nav / info view (portal SPA); default true
|
||||
void setShowInfo(boolean enabled);
|
||||
|
||||
// set ap channel
|
||||
void setWiFiAPChannel(int32_t channel);
|
||||
|
||||
@@ -449,13 +450,6 @@ class WiFiManager
|
||||
// set the webapp title, default WiFiManager
|
||||
void setTitle(String title);
|
||||
|
||||
// DFTE (portal HTML): register extra placeholders or override defaults (%STYLES%, %PAGE_TITLE%, …).
|
||||
// Call before startConfigPortal / startWebPortal to have it applied when the server starts; if the
|
||||
// portal is already running, the shared registry is rebuilt immediately.
|
||||
void registerTemplateSetupCallback(std::function<void(PlaceholderRegistry&)> cb);
|
||||
// Rebuild the server’s shared PlaceholderRegistry (defaults + registerTemplateSetupCallback, or customizer if passed).
|
||||
void rebuildPlaceholderRegistry(std::function<void(PlaceholderRegistry&)> customizer = nullptr);
|
||||
|
||||
// add params to its own menu page and remove from wifi, NOT TO BE COMBINED WITH setMenu!
|
||||
void setParamsPage(bool enable);
|
||||
|
||||
@@ -589,7 +583,6 @@ class WiFiManager
|
||||
protected:
|
||||
// vars
|
||||
std::unique_ptr<WiFiManagerServer> _serverManager;
|
||||
std::function<void(PlaceholderRegistry&)> _templateSetupCallback;
|
||||
|
||||
// ip configs @todo struct ?
|
||||
IPAddress _ap_static_ip;
|
||||
@@ -684,6 +677,7 @@ class WiFiManager
|
||||
boolean _webClientCheck = true; // keep cp alive if web have client
|
||||
boolean _scanDispOptions = false; // show percentage in scans not icons
|
||||
boolean _paramsInWifi = true; // show custom parameters on wifi page
|
||||
boolean _showInfo = true; // show Info in portal SPA
|
||||
boolean _showInfoErase = true; // info page erase button
|
||||
boolean _showInfoUpdate = true; // info page update button
|
||||
boolean _showBack = false; // show back button
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/**
|
||||
* WiFiManagerDfteLogger.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* Optional bridge from DFTE's logging hooks to WiFiManager::log.
|
||||
* Enabled only when the sketch / library build defines WM_DFTE_LOGGING.
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
/**
|
||||
* WiFiManagerHandlers.h
|
||||
*
|
||||
* HTTP request handlers and rendering logic for WiFiManager
|
||||
* Handles all web portal page generation and request processing
|
||||
*
|
||||
* @author tablatronix
|
||||
* @author Alex Hope-O'Connor
|
||||
*
|
||||
* HTTP request handlers for WiFiManager: single HTML shell + JSON APIs.
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
@@ -16,20 +14,7 @@
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <memory>
|
||||
#include "WiFiManager.h" // Need full definition for WiFiManagerRequestArgs
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// CSS CLASSES (for body class attribute)
|
||||
|
||||
const char C_root[] PROGMEM = "home";
|
||||
const char C_wifi[] PROGMEM = "wifi";
|
||||
const char C_info[] PROGMEM = "info";
|
||||
const char C_param[] PROGMEM = "param";
|
||||
const char C_close[] PROGMEM = "close";
|
||||
const char C_restart[] PROGMEM = "restart";
|
||||
const char C_exit[] PROGMEM = "exit";
|
||||
const char C_erase[] PROGMEM = "erase";
|
||||
const char C_update[] PROGMEM = "update";
|
||||
#include "WiFiManager.h"
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// FORM FIELD NAMES (for IP configuration forms)
|
||||
@@ -50,27 +35,13 @@ const char HTTP_HEAD_CORS_ALLOW_ALL[] PROGMEM = "*";
|
||||
class WiFiManagerHandlers {
|
||||
public:
|
||||
WiFiManagerHandlers(WiFiManager* wm);
|
||||
|
||||
// HTTP Request Handlers
|
||||
|
||||
void handleRoot(AsyncWebServerRequest *request);
|
||||
void handleWifi(AsyncWebServerRequest *request, boolean scan);
|
||||
void handleWifiSave(AsyncWebServerRequest *request);
|
||||
void handleParam(AsyncWebServerRequest *request);
|
||||
void handleParamSave(AsyncWebServerRequest *request);
|
||||
void handleInfo(AsyncWebServerRequest *request);
|
||||
void handleReset(AsyncWebServerRequest *request);
|
||||
void handleExit(AsyncWebServerRequest *request);
|
||||
void handleClose(AsyncWebServerRequest *request);
|
||||
void handleErase(AsyncWebServerRequest *request, boolean opt);
|
||||
void handleNotFound(AsyncWebServerRequest *request);
|
||||
void handleRequest(AsyncWebServerRequest *request);
|
||||
void handleWiFiScanRequest(AsyncWebServerRequest *request);
|
||||
void handleWiFiScanStatus(AsyncWebServerRequest *request);
|
||||
void handleUpdate(AsyncWebServerRequest *request);
|
||||
void handleUpdating(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
||||
void handleUpdateDone(AsyncWebServerRequest *request);
|
||||
|
||||
// JSON / SPA API (portal is a single-page app; only GET / serves HTML)
|
||||
void handleApiBootstrap(AsyncWebServerRequest *request);
|
||||
void handleApiWifiScanStatus(AsyncWebServerRequest *request);
|
||||
void handleApiWifiScan(AsyncWebServerRequest *request);
|
||||
@@ -81,39 +52,58 @@ class WiFiManagerHandlers {
|
||||
void handleApiInfo(AsyncWebServerRequest *request);
|
||||
void handleApiStatus(AsyncWebServerRequest *request);
|
||||
void handleApiDeviceRestart(AsyncWebServerRequest *request);
|
||||
void handleApiDeviceErase(AsyncWebServerRequest *request);
|
||||
void handleApiDeviceErase(AsyncWebServerRequest *request, boolean optionalErase = false);
|
||||
void handleApiPortalClose(AsyncWebServerRequest *request);
|
||||
void handleApiPortalExit(AsyncWebServerRequest *request);
|
||||
|
||||
// Captive Portal
|
||||
|
||||
boolean captivePortal(AsyncWebServerRequest *request);
|
||||
void stopCaptivePortal();
|
||||
|
||||
// Rendering Methods
|
||||
String getMenuOut();
|
||||
String getMenuOut(String* outOpt);
|
||||
String getScanItemOut();
|
||||
String getParamOut();
|
||||
String getStaticOut();
|
||||
String getIpForm(String id, String title, String value);
|
||||
String getInfoData(String id);
|
||||
void reportStatus(String &page);
|
||||
|
||||
// Internal helper (needed by handlers)
|
||||
|
||||
/** True when the HTTP Host header does not match the portal’s canonical host:port (captive redirect needed). */
|
||||
static bool shouldRedirectCaptiveForHost(const String& requestHost, const String& serverLocWithPort);
|
||||
|
||||
void doParamSave(WiFiManager::WiFiManagerRequestArgs requestArgs);
|
||||
|
||||
|
||||
/** Portal bootstrap JSON (same payload as GET /api/bootstrap). Public for tests and host integration. */
|
||||
String buildPortalBootstrapJson();
|
||||
/** Same JSON body as GET /api/wifi/meta (tests + embedding). */
|
||||
String buildApiWifiMetaJson();
|
||||
/** Same JSON body as GET /api/info (tests + embedding). */
|
||||
String buildApiInfoJson();
|
||||
/** Same JSON body as GET /api/params (tests + embedding). */
|
||||
String buildApiParamsGetJson();
|
||||
/** Same JSON body as GET /api/status (tests + embedding). */
|
||||
String buildApiStatusJson();
|
||||
|
||||
/** Fixed JSON bodies for POST action endpoints (single source for handlers + tests). */
|
||||
static String jsonApiDeviceRestartScheduled();
|
||||
static String jsonApiParamsSaveOk();
|
||||
static String jsonApiPortalCloseOk();
|
||||
static String jsonApiPortalExitOk();
|
||||
static String jsonApiPortalExitForbidden();
|
||||
static String jsonApiOtaUpdateSuccess();
|
||||
static String jsonApiEraseResponse(boolean success);
|
||||
|
||||
private:
|
||||
WiFiManager* _wm;
|
||||
void collectVisibleScanResults(std::vector<const WiFiManager::WiFiScanNetwork*>& networks);
|
||||
void appendVisibleScanResultsJson(String& json, const std::vector<const WiFiManager::WiFiScanNetwork*>& networks);
|
||||
|
||||
String buildPortalBootstrapJson();
|
||||
void applyWifiAndParamsFromRequest(AsyncWebServerRequest *request);
|
||||
void buildPlainStatusSummary(String& out);
|
||||
void appendPortalJsonStaticFields(String& json, bool& first);
|
||||
void appendPortalJsonCustomParams(String& json, bool& first);
|
||||
|
||||
void appendJsonKvItem(String& json, bool& first, const char* key, const String& label, const String& value);
|
||||
void appendOneInfoItemForId(String& json, bool& first, const char* id);
|
||||
void appendInfoSectionFromIds(String& json, const char* const* ids, size_t count, bool& first);
|
||||
|
||||
void appendPortalUiFeatureFlagsJson(String& json);
|
||||
void appendApiInfoActionsJson(String& json);
|
||||
|
||||
static void sendApiJson(AsyncWebServerRequest *request, int code, const String& json);
|
||||
};
|
||||
|
||||
#endif // defined(ESP8266) || defined(ESP32)
|
||||
|
||||
#endif // WiFiManagerHandlers_h
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/**
|
||||
* WiFiManagerLogLevel.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* Compile-time and runtime log levels for WiFiManager. Higher numeric values
|
||||
* mean more verbose output. Matches the old 0..5 tier model for build flags.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/**
|
||||
* WiFiManagerLogSink.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* Optional host-provided sink for WiFiManager log output. When set, logs are
|
||||
* delivered here instead of the configured Print stream (usually Serial).
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/**
|
||||
* WiFiManagerLogTemplates.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* Template implementations for WiFiManager::log(...). Included at the end of WiFiManager.h.
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/**
|
||||
* WiFiManagerParameter.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
#ifndef WiFiManagerParameter_h
|
||||
#define WiFiManagerParameter_h
|
||||
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
/**
|
||||
* WiFiManagerServer.h
|
||||
*
|
||||
* Server lifecycle management for WiFiManager
|
||||
* Handles AsyncWebServer creation, route registration, and shutdown
|
||||
*
|
||||
* @author tablatronix
|
||||
* @author Alex Hope-O'Connor
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* HTTP server for the config portal: single HTML shell (GET /), JSON under /api/..., OTA POST /u,
|
||||
* captive-portal redirects, and 404 handling. No legacy page routes.
|
||||
*
|
||||
* Supported surface (authoritative; registerRoutes mirrors this):
|
||||
* GET /
|
||||
* GET /api/bootstrap
|
||||
* GET /api/wifi/scan-status
|
||||
* POST /api/wifi/scan
|
||||
* GET /api/wifi/meta
|
||||
* POST /api/wifi/save
|
||||
* GET /api/params
|
||||
* POST /api/params/save
|
||||
* GET /api/info
|
||||
* GET /api/status
|
||||
* POST /api/device/restart
|
||||
* POST /api/device/erase
|
||||
* POST /api/portal/close
|
||||
* POST /api/portal/exit
|
||||
* POST /u (multipart firmware upload; JSON completion response)
|
||||
* notFound -> captive redirect or 404
|
||||
*/
|
||||
|
||||
#ifndef WiFiManagerServer_h
|
||||
@@ -19,17 +36,13 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
// DFTE (template engine)
|
||||
#include <TemplateEngine.h>
|
||||
|
||||
#ifndef WM_TEMPLATE_REGISTRY_CAPACITY
|
||||
#define WM_TEMPLATE_REGISTRY_CAPACITY 24
|
||||
#define WM_TEMPLATE_REGISTRY_CAPACITY 16
|
||||
#endif
|
||||
|
||||
// Forward declarations
|
||||
class WiFiManager;
|
||||
|
||||
// Forward declaration - full definition needed in .cpp file
|
||||
class WiFiManagerHandlers;
|
||||
|
||||
#ifdef WM_DFTE_LOGGING
|
||||
@@ -37,23 +50,9 @@ class WiFiManagerDfteLogger;
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// HTTP ROUTES
|
||||
// HTTP routes (portal API only; no legacy /wifi, /info, … page URLs)
|
||||
|
||||
const char R_root[] PROGMEM = "/";
|
||||
const char R_wifi[] PROGMEM = "/wifi";
|
||||
const char R_wifinoscan[] PROGMEM = "/0wifi";
|
||||
const char R_wifisave[] PROGMEM = "/wifisave";
|
||||
const char R_info[] PROGMEM = "/info";
|
||||
const char R_param[] PROGMEM = "/param";
|
||||
const char R_paramsave[] PROGMEM = "/paramsave";
|
||||
const char R_restart[] PROGMEM = "/restart";
|
||||
const char R_exit[] PROGMEM = "/exit";
|
||||
const char R_close[] PROGMEM = "/close";
|
||||
const char R_erase[] PROGMEM = "/erase";
|
||||
const char R_scanstatus[] PROGMEM = "/wifistatus";
|
||||
const char R_scan[] PROGMEM = "/wifi/scan";
|
||||
const char R_update[] PROGMEM = "/update";
|
||||
const char R_updatedone[] PROGMEM = "/u";
|
||||
|
||||
const char R_api_bootstrap[] PROGMEM = "/api/bootstrap";
|
||||
const char R_api_wifi_scan_status[] PROGMEM = "/api/wifi/scan-status";
|
||||
@@ -69,86 +68,50 @@ const char R_api_device_erase[] PROGMEM = "/api/device/erase";
|
||||
const char R_api_portal_close[] PROGMEM = "/api/portal/close";
|
||||
const char R_api_portal_exit[] PROGMEM = "/api/portal/exit";
|
||||
|
||||
const char R_updatedone[] PROGMEM = "/u";
|
||||
|
||||
class WiFiManagerServer {
|
||||
public:
|
||||
// Singleton access
|
||||
static WiFiManagerServer* instance();
|
||||
|
||||
|
||||
WiFiManagerServer(WiFiManager* wm);
|
||||
~WiFiManagerServer();
|
||||
|
||||
// Template engine setup and access
|
||||
|
||||
void setupTemplateEngine();
|
||||
PlaceholderRegistry* getPlaceholderRegistry() { return _tplRegistry.get(); }
|
||||
|
||||
// Static zero-arg getters for DFTE RAM placeholders
|
||||
|
||||
static const char* tplGetPageTitle();
|
||||
static const char* tplGetSubtitle();
|
||||
static const char* tplGetMenu();
|
||||
static const char* tplGetStatus();
|
||||
|
||||
// Template customization API
|
||||
// Consumers can register a callback to customize placeholders
|
||||
void registerTemplateSetupCallback(std::function<void(PlaceholderRegistry&)> cb) { _tplSetupCallback = cb; }
|
||||
|
||||
// Convenience helpers to register our defaults (granular + all)
|
||||
|
||||
void registerDefaultStyles(PlaceholderRegistry& reg);
|
||||
void registerDefaultScripts(PlaceholderRegistry& reg);
|
||||
void registerDefaultPageTitle(PlaceholderRegistry& reg);
|
||||
void registerDefaultSubtitle(PlaceholderRegistry& reg);
|
||||
void registerDefaultMenu(PlaceholderRegistry& reg);
|
||||
void registerDefaultStatus(PlaceholderRegistry& reg);
|
||||
/** Shell placeholders only: %STYLES%, %SCRIPTS%, %PAGE_TITLE%. */
|
||||
void registerDefaultPlaceholders(PlaceholderRegistry& reg);
|
||||
void applyTemplateSetupCallback(PlaceholderRegistry& reg);
|
||||
|
||||
// Rebuild registry with defaults and optional customizer
|
||||
void rebuildPlaceholderRegistry(std::function<void(PlaceholderRegistry&)> customizer = nullptr);
|
||||
|
||||
// Create server instance
|
||||
|
||||
void createServer(uint16_t port);
|
||||
|
||||
// Register all routes (takes handler callbacks)
|
||||
void registerRoutes();
|
||||
|
||||
// Setup DNS server for captive portal
|
||||
void setupDNSD();
|
||||
|
||||
// Process DNS requests (call periodically)
|
||||
void processDNS();
|
||||
|
||||
// Shutdown and cleanup server and DNS
|
||||
void shutdownServer();
|
||||
|
||||
// Get server instance
|
||||
AsyncWebServer* getServer() { return server.get(); }
|
||||
|
||||
// Get DNS server instance (for testing)
|
||||
DNSServer* getDNSServer() { return dnsServer.get(); }
|
||||
|
||||
|
||||
private:
|
||||
WiFiManager* _wm;
|
||||
std::unique_ptr<WiFiManagerHandlers> _handlers;
|
||||
std::unique_ptr<AsyncWebServer> server;
|
||||
std::unique_ptr<DNSServer> dnsServer;
|
||||
|
||||
// Shared DFTE placeholder registry
|
||||
|
||||
std::unique_ptr<PlaceholderRegistry> _tplRegistry;
|
||||
std::function<void(PlaceholderRegistry&)> _tplSetupCallback;
|
||||
|
||||
#ifdef WM_DFTE_LOGGING
|
||||
// DFTE -> WiFiManager::log bridge (only installed when WM_DFTE_LOGGING and no other logger registered)
|
||||
std::unique_ptr<WiFiManagerDfteLogger> _dfteLogger;
|
||||
bool _wmOwnsDfteLogSink = false;
|
||||
#endif
|
||||
|
||||
// Singleton instance
|
||||
|
||||
static WiFiManagerServer* s_instance;
|
||||
};
|
||||
|
||||
// Include WiFiManagerHandlers.h after class definition to provide full definition
|
||||
// needed for std::unique_ptr destructor
|
||||
#include "WiFiManagerHandlers.h"
|
||||
|
||||
#endif // defined(ESP8266) || defined(ESP32)
|
||||
|
||||
#endif // WiFiManagerServer_h
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* String conversions, HTML encoding, and helper calculations
|
||||
* These functions have no dependency on WiFiManager instance state
|
||||
*
|
||||
* @author Alex Hope-O'Connor
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
/**
|
||||
* CSS.h
|
||||
* CSS stylesheet for WiFiManager web interface
|
||||
* Embedded styles to avoid external file dependencies
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* CSS stylesheet for WiFiManager web interface.
|
||||
* Embedded styles to avoid external file dependencies.
|
||||
*/
|
||||
|
||||
#ifndef _CSS_TEMPLATES_H_
|
||||
@@ -39,6 +43,21 @@ const char CSS_STYLE[] PROGMEM = "<style>"
|
||||
"body.invert .msg{color:#fff;background-color:#282828;border-top:1px solid #555;border-right:1px solid #555;border-bottom:1px solid #555;}"
|
||||
"body.invert .q[role=img]{-webkit-filter:invert(1);filter:invert(1);}"
|
||||
":disabled {opacity: 0.5;}"
|
||||
// toast + modal (SPA-native feedback; no window.alert)
|
||||
".wm-toast{position:fixed;left:50%;top:12px;transform:translateX(-50%);z-index:10001;max-width:92%;padding:10px 14px;border-radius:.35rem;"
|
||||
"box-shadow:0 2px 8px rgba(0,0,0,.2);font-size:.95rem;text-align:center;font-weight:600}"
|
||||
".wm-toast.ok{background:#e8f7ef;border:1px solid #5cb85c;color:#1a3d24}"
|
||||
".wm-toast.err{background:#fdeaea;border:1px solid #dc3630;color:#5c1616}"
|
||||
".wm-dialog{position:fixed;inset:0;z-index:10000;display:none;align-items:center;justify-content:center;padding:16px;box-sizing:border-box}"
|
||||
".wm-dialog-backdrop{position:absolute;inset:0;background:rgba(0,0,0,.45)}"
|
||||
".wm-dialog-panel{position:relative;z-index:1;background:#fff;border-radius:.4rem;padding:16px 18px;max-width:420px;width:100%;box-shadow:0 4px 24px rgba(0,0,0,.25);text-align:left}"
|
||||
".wm-dialog-msg{margin:0 0 12px 0;line-height:1.35}"
|
||||
".wm-dialog-actions{display:flex;gap:10px;justify-content:flex-end}"
|
||||
".wm-dialog-actions button{width:auto;min-width:100px}"
|
||||
".wm-dialog-ok{background-color:#1fa3ec !important}"
|
||||
"body.invert .wm-dialog-panel{background:#222;color:#fff;border:1px solid #444}"
|
||||
"body.invert .wm-toast.ok{background:#1a3322;border-color:#5cb85c;color:#d8f5e4}"
|
||||
"body.invert .wm-toast.err{background:#3a1818;border-color:#dc3630;color:#ffd4d4}"
|
||||
"</style>";
|
||||
|
||||
#endif // _CSS_TEMPLATES_H_
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Fragments.h
|
||||
* Reusable DFTE fragment templates for WiFiManager page composition.
|
||||
*/
|
||||
|
||||
#ifndef _WM_FRAGMENTS_TEMPLATE_H_
|
||||
#define _WM_FRAGMENTS_TEMPLATE_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
const char WM_ACTION_FORM_TEMPLATE[] PROGMEM =
|
||||
"%ACTION_PREFIX%<form action='%ACTION%' method='%METHOD%'><button%BUTTON_CLASS_ATTR%>%BUTTON_LABEL%</button></form>%ACTION_SUFFIX%";
|
||||
|
||||
const char WM_SUBMIT_BUTTON_TEMPLATE[] PROGMEM =
|
||||
"<br/><br/><button type='submit'>%BUTTON_LABEL%</button>";
|
||||
|
||||
const char WM_CENTERED_BUTTON_TEMPLATE[] PROGMEM =
|
||||
"<br/><div class='c'><button id='%BUTTON_ID%' type='%BUTTON_TYPE%' onclick='%BUTTON_ONCLICK%'%BUTTON_EXTRA_ATTRS%>%BUTTON_LABEL%</button></div>";
|
||||
|
||||
const char WM_SECTION_BREAK_TEMPLATE[] PROGMEM =
|
||||
"<hr><br/>%SECTION_CONTENT%";
|
||||
|
||||
const char WM_SCAN_MESSAGE_TEMPLATE[] PROGMEM =
|
||||
"%SCAN_MESSAGE%<br/><br/>";
|
||||
|
||||
const char WM_SCAN_RESULT_ROW_TEMPLATE[] PROGMEM =
|
||||
"<div><a href='#p' onclick='c(this)' data-ssid='%SSID_ATTR%'>%SSID_TEXT%</a>"
|
||||
"<div role='img' aria-label='%QUALITY_LABEL%' title='%QUALITY_LABEL%' class='q q-%QUALITY_ICON% %LOCK_CLASS% %ICON_VISIBILITY_CLASS%'></div>"
|
||||
"<div class='q %VALUE_VISIBILITY_CLASS%'>%QUALITY_VALUE%</div></div>";
|
||||
|
||||
const char WM_FIELD_LABEL_BEFORE_TEMPLATE[] PROGMEM =
|
||||
"<label for='%FIELD_ID%'>%FIELD_LABEL%</label><br/><input id='%FIELD_ID%' name='%FIELD_NAME%' maxlength='%FIELD_MAXLENGTH%' value='%FIELD_VALUE%'%FIELD_EXTRA_ATTRS%>";
|
||||
|
||||
const char WM_FIELD_LABEL_AFTER_TEMPLATE[] PROGMEM =
|
||||
"<br/><input id='%FIELD_ID%' name='%FIELD_NAME%' maxlength='%FIELD_MAXLENGTH%' value='%FIELD_VALUE%'%FIELD_EXTRA_ATTRS%>"
|
||||
"<label for='%FIELD_ID%'>%FIELD_LABEL%</label>";
|
||||
|
||||
const char WM_FIELD_INPUT_ONLY_TEMPLATE[] PROGMEM =
|
||||
"<br/><input id='%FIELD_ID%' name='%FIELD_NAME%' maxlength='%FIELD_MAXLENGTH%' value='%FIELD_VALUE%'%FIELD_EXTRA_ATTRS%>";
|
||||
|
||||
const char WM_INFO_ROW_TEMPLATE[] PROGMEM =
|
||||
"<dt>%INFO_LABEL%</dt><dd>%INFO_VALUE%</dd>";
|
||||
|
||||
const char WM_INFO_SECTION_TEMPLATE[] PROGMEM =
|
||||
"%SECTION_PREFIX%<h3>%SECTION_TITLE%</h3><hr><dl>%SECTION_ROWS%</dl>%SECTION_SUFFIX%";
|
||||
|
||||
const char WM_STATUS_MESSAGE_TEMPLATE[] PROGMEM =
|
||||
"<div class='msg%STATUS_CLASS_SUFFIX%'><strong>%STATUS_TITLE%</strong>%STATUS_BODY%</div>";
|
||||
|
||||
const char WM_PAGE_HEADING_TEMPLATE[] PROGMEM =
|
||||
"<h1>%HEADER_TITLE%</h1><h3>%HEADER_SUBTITLE%</h3>";
|
||||
|
||||
#endif // _WM_FRAGMENTS_TEMPLATE_H_
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* HTML.h
|
||||
* HTML template fragments for WiFiManager web interface
|
||||
* Static PROGMEM strings to reduce string concatenations
|
||||
*/
|
||||
|
||||
#ifndef _HTML_TEMPLATES_H_
|
||||
#define _HTML_TEMPLATES_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// Status messages
|
||||
const char HTML_STATUS_OFFPW[] PROGMEM = "<br/>Authentication failure";
|
||||
|
||||
const char HTML_STATUS_OFFNOAP[] PROGMEM = "<br/>AP not found";
|
||||
|
||||
const char HTML_STATUS_OFFFAIL[] PROGMEM = "<br/>Could not connect";
|
||||
|
||||
const char HTML_STATUS_NONE[] PROGMEM = "<div class='msg'>No AP set</div>";
|
||||
|
||||
const char HTML_SAVED[] PROGMEM = "<div class='msg'>Saving Credentials<br/>Trying to connect ESP to network.<br />If it fails reconnect to AP to try again</div>";
|
||||
|
||||
const char HTML_PARAMSAVED[] PROGMEM = "<div class='msg S'>Saved<br/></div>";
|
||||
|
||||
// Update page
|
||||
const char HTML_UPDATE[] PROGMEM = "Upload new firmware<br/><form method='POST' action='u' enctype='multipart/form-data' onchange=\"(function(el){document.getElementById('uploadbin').style.display = el.value=='' ? 'none' : 'initial';})(this)\"><input type='file' name='update' accept='.bin,application/octet-stream'><button id='uploadbin' type='submit' class='h D'>Update</button></form><small><a href='http://192.168.4.1/update' target='_blank'>* May not function inside captive portal, open in browser http://192.168.4.1</a></small>";
|
||||
|
||||
const char HTML_UPDATE_FAIL[] PROGMEM = "<div class='msg D'><strong>Update failed!</strong><Br/>Reboot device and try again</div>";
|
||||
|
||||
const char HTML_UPDATE_SUCCESS[] PROGMEM = "<div class='msg S'><strong>Update successful. </strong> <br/> Device rebooting now...</div>";
|
||||
|
||||
// Help page
|
||||
#ifndef WM_NOHELP
|
||||
const char HTML_HELP[] PROGMEM =
|
||||
"<br/><h3>Available pages</h3><hr>"
|
||||
"<table class='table'>"
|
||||
"<thead><tr><th>Page</th><th>Function</th></tr></thead><tbody>"
|
||||
"<tr><td><a href='/'>/</a></td>"
|
||||
"<td>Menu page.</td></tr>"
|
||||
"<tr><td><a href='/wifi'>/wifi</a></td>"
|
||||
"<td>Show WiFi scan results and enter WiFi configuration.</td></tr>"
|
||||
"<tr><td><a href='/0wifi'>/0wifi</a></td>"
|
||||
"<td>Show WiFi configuration without loading scan results until refresh is requested.</td></tr>"
|
||||
"<tr><td><a href='/wifisave'>/wifisave</a></td>"
|
||||
"<td>Save WiFi configuration information and configure device. Needs variables supplied.</td></tr>"
|
||||
"<tr><td><a href='/param'>/param</a></td>"
|
||||
"<td>Parameter page</td></tr>"
|
||||
"<tr><td><a href='/info'>/info</a></td>"
|
||||
"<td>Information page</td></tr>"
|
||||
"<tr><td><a href='/update'>/update</a></td>"
|
||||
"<td>OTA update page. Firmware upload posts to /u.</td></tr>"
|
||||
"<tr><td><a href='/close'>/close</a></td>"
|
||||
"<td>Close the captiveportal popup, config portal will remain active</td></tr>"
|
||||
"<tr><td>/exit</td>"
|
||||
"<td>Exit Config portal, config portal will close</td></tr>"
|
||||
"<tr><td>/restart</td>"
|
||||
"<td>Reboot the device</td></tr>"
|
||||
"<tr><td>/erase</td>"
|
||||
"<td>Erase WiFi configuration and reboot device. Device will not reconnect to a network until new WiFi configuration data is entered.</td></tr>"
|
||||
"</table>"
|
||||
"<p/>Fork <a href='https://github.com/alexhopeoconnor/WiFiManager'>https://github.com/alexhopeoconnor/WiFiManager</a>.";
|
||||
#else
|
||||
const char HTML_HELP[] PROGMEM = "";
|
||||
#endif
|
||||
|
||||
#endif // _HTML_TEMPLATES_H_
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Info.h
|
||||
* Streamed info page content template for WiFiManager.
|
||||
*/
|
||||
|
||||
#ifndef _WM_INFO_TEMPLATE_H_
|
||||
#define _WM_INFO_TEMPLATE_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
const char WM_INFO_CONTENT_TEMPLATE[] PROGMEM =
|
||||
"%INFO_STATUS%"
|
||||
"<section class='info-device'>%INFO_DEVICE_SECTION%</section>"
|
||||
"<section class='info-wifi'>%INFO_WIFI_SECTION%</section>"
|
||||
"<section class='info-about'>%INFO_ABOUT_SECTION%</section>"
|
||||
"%INFO_FOOTER%";
|
||||
|
||||
#endif // _WM_INFO_TEMPLATE_H_
|
||||
@@ -1,7 +1,11 @@
|
||||
/**
|
||||
* JS.h
|
||||
* JavaScript code for WiFiManager web interface
|
||||
* Client-side functionality for WiFi configuration
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* JavaScript code for WiFiManager web interface.
|
||||
* Client-side functionality for WiFi configuration.
|
||||
*/
|
||||
|
||||
#ifndef _JS_TEMPLATES_H_
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* Message.h
|
||||
* Streamed message page content template for WiFiManager.
|
||||
*/
|
||||
|
||||
#ifndef _WM_MESSAGE_TEMPLATE_H_
|
||||
#define _WM_MESSAGE_TEMPLATE_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
const char WM_MESSAGE_CONTENT_TEMPLATE[] PROGMEM =
|
||||
"%MESSAGE_BODY%"
|
||||
"%MESSAGE_ACTIONS%";
|
||||
|
||||
#endif // _WM_MESSAGE_TEMPLATE_H_
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* PageShell.h
|
||||
* Generic whole-document shell template for streamed WiFiManager pages.
|
||||
*/
|
||||
|
||||
#ifndef _WM_PAGE_SHELL_TEMPLATE_H_
|
||||
#define _WM_PAGE_SHELL_TEMPLATE_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
const char WM_PAGE_SHELL_TEMPLATE[] PROGMEM =
|
||||
"<!DOCTYPE html>"
|
||||
"<html lang='en'>"
|
||||
"<head>"
|
||||
"<meta name='format-detection' content='telephone=no'>"
|
||||
"<meta charset='UTF-8'>"
|
||||
"<meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>"
|
||||
"<title>%DOC_TITLE%</title>"
|
||||
"%SCRIPTS%"
|
||||
"%PAGE_SCRIPTS%"
|
||||
"%STYLES%"
|
||||
"%PAGE_STYLES%"
|
||||
"</head>"
|
||||
"<body class='%BODY_CLASS%'>"
|
||||
"<div class='wrap'>"
|
||||
"%PAGE_CONTENT%"
|
||||
"</div>"
|
||||
"</body>"
|
||||
"</html>";
|
||||
|
||||
#endif // _WM_PAGE_SHELL_TEMPLATE_H_
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Param.h
|
||||
* Streamed setup page content template for WiFiManager.
|
||||
*/
|
||||
|
||||
#ifndef _WM_PARAM_TEMPLATE_H_
|
||||
#define _WM_PARAM_TEMPLATE_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
const char WM_PARAM_CONTENT_TEMPLATE[] PROGMEM =
|
||||
"<form method='POST' action='paramsave'>"
|
||||
"%PARAM_FIELDS%"
|
||||
"%PARAM_FORM_ACTIONS%</form>"
|
||||
"%PARAM_PAGE_ACTIONS%"
|
||||
"%PARAM_STATUS%";
|
||||
|
||||
#endif // _WM_PARAM_TEMPLATE_H_
|
||||
@@ -1,5 +1,9 @@
|
||||
/**
|
||||
* PortalAppJS.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* Embedded SPA for WiFiManager config portal (hash routing + JSON APIs).
|
||||
*/
|
||||
|
||||
@@ -17,30 +21,96 @@ try{var el=document.getElementById('wm-bootstrap');if(el&&el.textContent)boot=JS
|
||||
function $(id){return document.getElementById(id);}
|
||||
function esc(t){if(!t)return'';return String(t).replace(/[&<>"']/g,function(c){return({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]);});}
|
||||
|
||||
var _wmToastTimer=null;
|
||||
function showToast(msg,isErr){
|
||||
var el=$('wm-toast');
|
||||
if(!el)return;
|
||||
el.textContent=msg||'';
|
||||
el.className='wm-toast '+(isErr?'err':'ok');
|
||||
el.style.display='block';
|
||||
if(_wmToastTimer)clearTimeout(_wmToastTimer);
|
||||
_wmToastTimer=setTimeout(function(){el.style.display='none';el.textContent='';},4500);
|
||||
}
|
||||
function showConfirm(msg,onOk){
|
||||
var dlg=$('wm-dialog'),dmsg=$('wm-dialog-msg'),bk=$('wm-dialog-backdrop'),ok=$('wm-dialog-ok'),cancel=$('wm-dialog-cancel');
|
||||
if(!dlg||!dmsg||!ok||!cancel){if(onOk)onOk();return;}
|
||||
dmsg.textContent=msg||'';
|
||||
dlg.style.display='flex';
|
||||
dlg.setAttribute('aria-hidden','false');
|
||||
function hide(){
|
||||
dlg.style.display='none';
|
||||
dlg.setAttribute('aria-hidden','true');
|
||||
ok.onclick=null;
|
||||
cancel.onclick=null;
|
||||
if(bk)bk.onclick=null;
|
||||
}
|
||||
function onCancel(){hide();}
|
||||
cancel.onclick=onCancel;
|
||||
if(bk)bk.onclick=onCancel;
|
||||
ok.onclick=function(){hide();if(onOk)onOk();};
|
||||
}
|
||||
|
||||
function setView(html){
|
||||
var a=$('app');
|
||||
if(!a)return;
|
||||
a.innerHTML="<div class='wrap'>"+html+"</div>";
|
||||
}
|
||||
|
||||
function renderMetaField(f){
|
||||
if(!f)return'';
|
||||
if(f.html)return f.html;
|
||||
var id=f.id||f.name||'';
|
||||
var nm=f.name||f.id||'';
|
||||
var domId=(id==='s')?'wm-s':(id==='p')?'wm-p':('wm-f-'+id);
|
||||
var t=f.type||'text';
|
||||
var h='<label for="'+domId+'">'+esc(f.label||'')+'</label>';
|
||||
h+='<input id="'+domId+'" name="'+esc(nm)+'" type="'+esc(t)+'"';
|
||||
if(f.maxlength)h+=' maxlength="'+f.maxlength+'"';
|
||||
if(f.value!==undefined&&f.value!==null)h+=' value="'+esc(f.value)+'"';
|
||||
if(f.placeholder)h+=' placeholder="'+esc(f.placeholder)+'"';
|
||||
if(f.customAttrs)h+=' '+f.customAttrs;
|
||||
h+='/>';
|
||||
return h;
|
||||
}
|
||||
|
||||
function renderFieldList(arr){
|
||||
if(!arr||!arr.length)return'';
|
||||
var h='',i;
|
||||
for(i=0;i<arr.length;i++)h+=renderMetaField(arr[i]);
|
||||
return h;
|
||||
}
|
||||
|
||||
function navBar(){
|
||||
var f=boot.features||{};
|
||||
var h="<p style='text-align:center'>";
|
||||
h+="<a href='#/'>Home</a> · ";
|
||||
h+="<a href='#/wifi'>WiFi</a>";
|
||||
if(!f.paramsInWifi){h+=" · <a href='#/setup'>Setup</a>";}
|
||||
h+=" · <a href='#/info'>Info</a>";
|
||||
h+=" · <a href='#/update'>Update</a>";
|
||||
if(f.showInfo!==false){h+=" · <a href='#/info'>Info</a>";}
|
||||
if(f.showUpdate){h+=" · <a href='#/update'>Update</a>";}
|
||||
if(boot.showBack){h+=" · <a href='#' onclick='history.back();return false;'>Back</a>";}
|
||||
h+="</p><hr/>";
|
||||
return h;
|
||||
}
|
||||
|
||||
function deviceActionsHtml(){
|
||||
var f=boot.features||{};
|
||||
var h='', ok=false;
|
||||
if(f.showRestart){h+="<p><button type='button' onclick='portalRestart()'>Restart</button></p>";ok=true;}
|
||||
if(f.showExitPortal){h+="<p><button type='button' onclick='portalExit()'>Exit portal</button></p>";ok=true;}
|
||||
if(f.showCloseCaptive){h+="<p><button type='button' onclick='portalCloseCaptive()'>Stop captive portal detection</button></p>";ok=true;}
|
||||
if(!ok)return'';
|
||||
return "<div class='device-actions'><h3>Device actions</h3>"+h+"</div>";
|
||||
}
|
||||
|
||||
function viewHome(){
|
||||
var sub=esc(boot.subtitle||'');
|
||||
var t=esc(boot.title||'WiFiManager');
|
||||
var html=navBar();
|
||||
html+="<h1>"+t+"</h1><h3>"+sub+"</h3>";
|
||||
if(boot.initialStatus)html+="<p class='status'>"+esc(boot.initialStatus)+"</p>";
|
||||
html+="<p>Use the links above to configure WiFi, view device info, or update firmware.</p>";
|
||||
html+=deviceActionsHtml();
|
||||
setView(html);
|
||||
}
|
||||
|
||||
@@ -99,11 +169,16 @@ function viewWifi(){
|
||||
var html=navBar()+"<h2>WiFi</h2>";
|
||||
html+="<div id='wm-scan-results'></div>";
|
||||
html+="<form id='wm-wifi-form' onsubmit='return portalWifiSave(event)'>";
|
||||
html+="<label>SSID</label><input id='wm-s' name='s' maxlength='32' value='"+esc(m.ssidPlaceholder||'')+"'/>";
|
||||
html+="<label>Password</label><input id='wm-p' name='p' type='password' maxlength='64' placeholder='"+esc(m.passwordPlaceholder||'')+"'/>";
|
||||
html+="<input type='checkbox' id='wm-showpass' onclick='var p=document.getElementById(\"wm-p\");p.type=this.checked?\"text\":\"password\"'/> <label for='wm-showpass'>Show password</label>";
|
||||
if(m.staticFieldsHtml){html+=m.staticFieldsHtml;}
|
||||
if(m.paramsHtml){html+=m.paramsHtml;}
|
||||
html+=renderFieldList(m.wifiFields||[]);
|
||||
var wf=m.wifiFields||[];
|
||||
var hasPass=false;
|
||||
var i;
|
||||
for(i=0;i<wf.length;i++){if(wf[i]&&wf[i].id==='p'){hasPass=true;break;}}
|
||||
if(hasPass){
|
||||
html+="<input type='checkbox' id='wm-showpass' onclick='var p=document.getElementById(\"wm-p\");if(p)p.type=this.checked?\"text\":\"password\"'/> <label for='wm-showpass'>Show password</label>";
|
||||
}
|
||||
html+=renderFieldList(m.staticFields||[]);
|
||||
html+=renderFieldList(m.params||[]);
|
||||
html+="<button type='submit'>Save</button></form>";
|
||||
html+="<br/><button type='button' id='wm-refresh-scan' onclick='wifiRefresh()'>Refresh scan</button>";
|
||||
html+="<div id='wm-wifi-msg'></div>";
|
||||
@@ -143,22 +218,52 @@ function viewInfo(){
|
||||
h+="</dl>";
|
||||
return h;
|
||||
}
|
||||
html+=section('Status',d.status||[]);
|
||||
var st=d.status;
|
||||
if(st && typeof st==='object' && !Array.isArray(st)){
|
||||
html+="<h3>Status</h3><hr/><dl>";
|
||||
html+="<dt>Connected</dt><dd>"+esc(String(st.connected))+"</dd>";
|
||||
if(st.ssid!==undefined)html+="<dt>SSID</dt><dd>"+esc(st.ssid)+"</dd>";
|
||||
if(st.stationIp)html+="<dt>Station IP</dt><dd>"+esc(st.stationIp)+"</dd>";
|
||||
if(st.apIp)html+="<dt>AP IP</dt><dd>"+esc(st.apIp)+"</dd>";
|
||||
if(st.summary)html+="<dt>Summary</dt><dd>"+esc(st.summary)+"</dd>";
|
||||
html+="</dl>";
|
||||
} else {
|
||||
html+=section('Status',d.status||[]);
|
||||
}
|
||||
html+=section('Device',d.device||[]);
|
||||
html+=section('WiFi',d.wifi||[]);
|
||||
html+=section('About',d.about||[]);
|
||||
var act=d.actions||{};
|
||||
html+="<h3>Actions</h3><hr/>";
|
||||
if(act.showRestart)html+="<p><button type='button' onclick='portalRestart()'>Restart</button></p>";
|
||||
if(act.showExitPortal)html+="<p><button type='button' onclick='portalExit()'>Exit portal</button></p>";
|
||||
if(act.showCloseCaptive)html+="<p><button type='button' onclick='portalCloseCaptive()'>Stop captive portal detection</button></p>";
|
||||
if(act.showUpdate)html+="<p><a href='#/update'>Firmware update</a></p>";
|
||||
if(act.showErase)html+="<p><button type='button' class='D' onclick='portalErase()'>Erase WiFi config</button></p>";
|
||||
setView(html);
|
||||
});
|
||||
}
|
||||
|
||||
function portalConfirmPost(url, promptMsg){
|
||||
showConfirm(promptMsg||'Continue?',function(){
|
||||
api(url,{method:'POST'}).then(function(res){
|
||||
try{var j=JSON.parse(res.body);}catch(e){j={};}
|
||||
var m=(j&&j.message)?j.message:(res.ok?'OK':'Error');
|
||||
showToast(m,!res.ok);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.portalRestart=function(){portalConfirmPost('/api/device/restart','Restart the device?');};
|
||||
window.portalExit=function(){portalConfirmPost('/api/portal/exit','Exit the configuration portal?');};
|
||||
window.portalCloseCaptive=function(){portalConfirmPost('/api/portal/close','Stop redirecting clients to this portal (captive detection off)?');};
|
||||
|
||||
window.portalErase=function(){
|
||||
if(!confirm('Erase WiFi configuration?'))return;
|
||||
api('/api/device/erase',{method:'POST'}).then(function(res){
|
||||
try{var j=JSON.parse(res.body);}catch(e){j={};}
|
||||
alert((j&&j.message)?j.message:'Done');
|
||||
showConfirm('Erase WiFi configuration?',function(){
|
||||
api('/api/device/erase',{method:'POST'}).then(function(res){
|
||||
try{var j=JSON.parse(res.body);}catch(e){j={};}
|
||||
showToast((j&&j.message)?j.message:(res.ok?'Done':'Error'),!res.ok);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -167,7 +272,9 @@ function viewSetup(){
|
||||
api('/api/params').then(function(res){
|
||||
try{var d=JSON.parse(res.body);}catch(e){d={};}
|
||||
var html=navBar()+"<h2>Parameters</h2>";
|
||||
html+="<form id='wm-param-form' onsubmit='return portalParamSave(event)'>"+(d.paramsHtml||'')+"<button type='submit'>Save</button></form>";
|
||||
html+="<form id='wm-param-form' onsubmit='return portalParamSave(event)'>";
|
||||
html+=renderFieldList(d.params||[]);
|
||||
html+="<button type='submit'>Save</button></form>";
|
||||
html+="<div id='wm-param-msg'></div>";
|
||||
setView(html);
|
||||
});
|
||||
@@ -186,17 +293,59 @@ window.portalParamSave=function(ev){
|
||||
};
|
||||
|
||||
function viewUpdate(){
|
||||
var f=boot.features||{};
|
||||
var html=navBar()+"<h2>Firmware update</h2>";
|
||||
html+="<form method='POST' action='/u' enctype='multipart/form-data'>";
|
||||
html+="<input type='file' name='update'/>";
|
||||
html+="<button type='submit'>Update</button></form>";
|
||||
html+="<p><small>Upload a .bin firmware. Device may restart after update.</small></p>";
|
||||
if(!f.showUpdate){html+="<p>Firmware update is disabled.</p>";setView(html);return;}
|
||||
html+="<form id='wm-ota-form' onsubmit='return portalOtaSubmit(event)'>";
|
||||
html+="<input type='file' id='wm-ota-file' name='update'/>";
|
||||
html+="<button type='submit'>Upload</button></form>";
|
||||
html+="<p><small>Upload a .bin firmware. The device restarts after a successful update.</small></p>";
|
||||
html+="<pre id='wm-ota-log'></pre>";
|
||||
setView(html);
|
||||
}
|
||||
|
||||
window.portalOtaSubmit=function(ev){
|
||||
ev.preventDefault();
|
||||
var fi=$('wm-ota-file');
|
||||
var logEl=$('wm-ota-log');
|
||||
if(!fi||!fi.files||!fi.files[0]){
|
||||
if(logEl)logEl.textContent='Choose a firmware file first.';
|
||||
return false;
|
||||
}
|
||||
if(logEl)logEl.textContent='Uploading…';
|
||||
var xhr=new XMLHttpRequest();
|
||||
xhr.upload.onprogress=function(e){
|
||||
if(!logEl||!e.lengthComputable)return;
|
||||
logEl.textContent='Uploading… '+Math.round(100*e.loaded/e.total)+'%';
|
||||
};
|
||||
xhr.onreadystatechange=function(){
|
||||
if(xhr.readyState!==4)return;
|
||||
var t=xhr.responseText||'';
|
||||
if(logEl)logEl.textContent=t;
|
||||
var httpOk=xhr.status>=200&&xhr.status<300;
|
||||
try{
|
||||
var j=JSON.parse(t);
|
||||
if(j&&j.message){
|
||||
if(logEl)logEl.textContent=j.message;
|
||||
showToast(j.message,j.ok===false);
|
||||
return;
|
||||
}
|
||||
}catch(e){}
|
||||
showToast(httpOk?'Upload finished.':('HTTP '+xhr.status),!httpOk);
|
||||
};
|
||||
xhr.open('POST','/u');
|
||||
var fd=new FormData();
|
||||
fd.append('update',fi.files[0]);
|
||||
xhr.send(fd);
|
||||
return false;
|
||||
};
|
||||
|
||||
function route(){
|
||||
var h=location.hash||'#/';
|
||||
if(h.indexOf('#/')!==0)h='#/';
|
||||
var f=boot.features||{};
|
||||
if(h==='#/info'&&f.showInfo===false){viewHome();return;}
|
||||
if(h==='#/update'&&f.showUpdate===false){viewHome();return;}
|
||||
if(h==='#/'||h==='#'){viewHome();return;}
|
||||
if(h==='#/wifi'){viewWifi();return;}
|
||||
if(h==='#/info'){viewInfo();return;}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Root.h
|
||||
* Whole-document root template for WiFiManager using DFTE placeholders
|
||||
*/
|
||||
|
||||
#ifndef _WM_ROOT_TEMPLATE_H_
|
||||
#define _WM_ROOT_TEMPLATE_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// Whole document template:
|
||||
// - Injects %SCRIPTS% and %STYLES% (these already include <script>/<style> tags)
|
||||
// - Mirrors existing structure: body class "home" and wrapper div.wrap
|
||||
// - Renders title/subtitle, then menu and status
|
||||
const char WM_ROOT_TEMPLATE[] PROGMEM =
|
||||
"<!DOCTYPE html>"
|
||||
"<html lang='en'>"
|
||||
"<head>"
|
||||
"<meta name='format-detection' content='telephone=no'>"
|
||||
"<meta charset='UTF-8'>"
|
||||
"<meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>"
|
||||
"<title>%PAGE_TITLE%</title>"
|
||||
"%SCRIPTS%"
|
||||
"%STYLES%"
|
||||
"</head>"
|
||||
"<body class='home'>"
|
||||
"<div class='wrap'>"
|
||||
"<h1>%PAGE_TITLE%</h1>"
|
||||
"<h3>%SUBTITLE%</h3>"
|
||||
"%MENU%"
|
||||
"%STATUS%"
|
||||
"</div>"
|
||||
"</body>"
|
||||
"</html>";
|
||||
|
||||
#endif // _WM_ROOT_TEMPLATE_H_
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* RootSelector.h
|
||||
* Allows overriding the root template header via build flag
|
||||
* Define WM_CUSTOM_ROOT_TEMPLATE_HEADER to a quoted header path.
|
||||
*/
|
||||
#ifndef _WM_ROOT_SELECTOR_H_
|
||||
#define _WM_ROOT_SELECTOR_H_
|
||||
|
||||
#ifdef WM_CUSTOM_ROOT_TEMPLATE_HEADER
|
||||
#include WM_CUSTOM_ROOT_TEMPLATE_HEADER
|
||||
#else
|
||||
#include "RootShell.h"
|
||||
#endif
|
||||
|
||||
#endif // _WM_ROOT_SELECTOR_H_
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
/**
|
||||
* RootShell.h
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* Single-page portal shell: only HTML document served for GET /.
|
||||
* Dynamic UI is driven by embedded JS + JSON APIs under /api/*.
|
||||
* Dynamic UI is driven by embedded JS + JSON APIs under /api/...
|
||||
*/
|
||||
|
||||
#ifndef _WM_ROOT_SHELL_TEMPLATE_H_
|
||||
@@ -21,6 +25,15 @@ const char WM_ROOT_SHELL_TEMPLATE[] PROGMEM =
|
||||
"%STYLES%"
|
||||
"</head>"
|
||||
"<body class='portal'>"
|
||||
"<div id='wm-toast' class='wm-toast' aria-live='polite' role='status' style='display:none'></div>"
|
||||
"<div id='wm-dialog' class='wm-dialog' style='display:none' aria-hidden='true'>"
|
||||
"<div class='wm-dialog-backdrop' id='wm-dialog-backdrop'></div>"
|
||||
"<div class='wm-dialog-panel'>"
|
||||
"<p class='wm-dialog-msg' id='wm-dialog-msg'></p>"
|
||||
"<div class='wm-dialog-actions'>"
|
||||
"<button type='button' id='wm-dialog-cancel'>Cancel</button>"
|
||||
"<button type='button' id='wm-dialog-ok' class='wm-dialog-ok'>OK</button>"
|
||||
"</div></div></div>"
|
||||
"<div id='app'></div>"
|
||||
"<script id='wm-bootstrap' type='application/json'>%BOOTSTRAP_JSON%</script>"
|
||||
"<script>"
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* WiFi.h
|
||||
* Streamed WiFi configuration page content template for WiFiManager.
|
||||
*/
|
||||
|
||||
#ifndef _WM_WIFI_TEMPLATE_H_
|
||||
#define _WM_WIFI_TEMPLATE_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
const char WM_WIFI_CONTENT_TEMPLATE[] PROGMEM =
|
||||
"<div id='scan-results'>%WIFI_SCAN_CONTENT%</div>"
|
||||
"<form method='POST' action='wifisave'>"
|
||||
"<label for='s'>SSID</label>"
|
||||
"<input id='s' name='s' maxlength='32' autocorrect='off' autocapitalize='none' placeholder='%WIFI_SSID_PLACEHOLDER%'>"
|
||||
"<br/>"
|
||||
"<label for='p'>Password</label>"
|
||||
"<input id='p' name='p' maxlength='64' type='password' placeholder='%WIFI_PASSWORD_PLACEHOLDER%'>"
|
||||
"<input type='checkbox' id='showpass' onclick='f()'> <label for='showpass'>Show Password</label><br/>"
|
||||
"%WIFI_STATIC_FIELDS%"
|
||||
"%WIFI_PARAM_SECTION%"
|
||||
"%WIFI_FORM_ACTIONS%</form>"
|
||||
"%WIFI_PAGE_ACTIONS%"
|
||||
"%WIFI_STATUS%";
|
||||
|
||||
#endif // _WM_WIFI_TEMPLATE_H_
|
||||
@@ -1,168 +0,0 @@
|
||||
/**
|
||||
* WiFiPollingJS.h
|
||||
* JavaScript code for WiFi network scanning with AJAX polling
|
||||
* This is embedded in the WiFi configuration page for async WiFi scanning
|
||||
*/
|
||||
|
||||
#ifndef _WIFI_POLLING_JS_H_
|
||||
#define _WIFI_POLLING_JS_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
const char PROGMEM WIFI_POLLING_JS[] = R"rawliteral(
|
||||
<script>
|
||||
let scanPollInterval = null;
|
||||
let scanBusy = false;
|
||||
|
||||
function setRefreshButtonState(disabled, label) {
|
||||
const btn = document.getElementById('refresh-btn');
|
||||
if (!btn) return;
|
||||
btn.disabled = disabled;
|
||||
btn.textContent = label;
|
||||
}
|
||||
|
||||
function showScanMessage(message) {
|
||||
const scanResults = document.getElementById('scan-results');
|
||||
if (!scanResults) return;
|
||||
scanResults.innerHTML = message + '<br/><br/>';
|
||||
}
|
||||
|
||||
async function refreshScan() {
|
||||
if (scanBusy) return false;
|
||||
scanBusy = true;
|
||||
setRefreshButtonState(true, 'Scanning...');
|
||||
|
||||
try {
|
||||
const response = await fetch('/wifi/scan', { method: 'POST' });
|
||||
if (!response.ok) throw new Error('scan request failed');
|
||||
startPolling();
|
||||
} catch (error) {
|
||||
scanBusy = false;
|
||||
setRefreshButtonState(false, 'Refresh');
|
||||
showScanMessage('Could not start scan. Try again.');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
if (scanPollInterval) return;
|
||||
updateScanStatus();
|
||||
scanPollInterval = setInterval(updateScanStatus, 1000);
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if (scanPollInterval) {
|
||||
clearInterval(scanPollInterval);
|
||||
scanPollInterval = null;
|
||||
}
|
||||
scanBusy = false;
|
||||
setRefreshButtonState(false, 'Refresh');
|
||||
}
|
||||
|
||||
function updateScanStatus() {
|
||||
fetch('/wifistatus')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.scanning) {
|
||||
showScanMessage('Scanning for networks...');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.error === 'timeout') {
|
||||
showScanMessage('Scan timed out. Refresh to try again.');
|
||||
stopPolling();
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.error === 'failed') {
|
||||
showScanMessage('Scan failed. Refresh to try again.');
|
||||
stopPolling();
|
||||
return;
|
||||
}
|
||||
|
||||
stopPolling();
|
||||
updateNetworkList(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error polling scan status:', error);
|
||||
showScanMessage('Scan status unavailable.');
|
||||
stopPolling();
|
||||
});
|
||||
}
|
||||
|
||||
function updateNetworkList(data) {
|
||||
let html = '';
|
||||
if(data.count === 0) {
|
||||
html = 'No networks found. Refresh to scan again.<br/><br/>';
|
||||
} else if(data.networks && data.networks.length > 0) {
|
||||
data.networks.forEach(function(network) {
|
||||
let qualityPercent = network.quality + '%';
|
||||
let qualityIcon = Math.round((network.quality / 100) * 3) + 1;
|
||||
if(qualityIcon < 1) qualityIcon = 1;
|
||||
if(qualityIcon > 4) qualityIcon = 4;
|
||||
let encClass = network.encrypted ? 'l' : '';
|
||||
let ssidEscaped = escapeHtml(network.ssid);
|
||||
html += '<div><a href="#p" onclick="c(this)" data-ssid="' + ssidEscaped + '">' + ssidEscaped + '</a>';
|
||||
html += '<div role="img" aria-label="' + qualityPercent + '" title="' + qualityPercent + '" class="q q-' + qualityIcon + ' ' + encClass + '"></div>';
|
||||
html += '<div class="q">' + qualityPercent + '</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
}
|
||||
document.getElementById('scan-results').innerHTML = html;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
var map = {};
|
||||
map['&'] = '&';
|
||||
map['<'] = '<';
|
||||
map['>'] = '>';
|
||||
map['"'] = '"';
|
||||
map["'"] = ''';
|
||||
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||
}
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
const refreshButton = document.getElementById('refresh-btn');
|
||||
const skipInitialScan = refreshButton && refreshButton.dataset.skipInitialScan === 'true';
|
||||
const pField = document.getElementById('p');
|
||||
if (pField) {
|
||||
pField.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
if (skipInitialScan) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/wifistatus')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.scanning) {
|
||||
scanBusy = true;
|
||||
setRefreshButtonState(true, 'Scanning...');
|
||||
startPolling();
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.error === 'timeout') {
|
||||
showScanMessage('Scan timed out. Refresh to try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.error === 'failed') {
|
||||
showScanMessage('Scan failed. Refresh to try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
updateNetworkList(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading initial scan status:', error);
|
||||
showScanMessage('Scan status unavailable.');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
)rawliteral";
|
||||
|
||||
#endif // _WIFI_POLLING_JS_H_
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
*
|
||||
* @author Creator tzapu
|
||||
* @author tablatronix
|
||||
* @author Alex Hope-O'Connor
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
#include "WiFiManager.h"
|
||||
#include "WiFiManagerLogSink.h"
|
||||
#include "WiFiManagerServer.h" // Need menu tokens
|
||||
#include "WiFiManagerServer.h"
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
|
||||
@@ -508,7 +508,6 @@ void WiFiManager::setupConfigPortal() {
|
||||
// Lazy initialization: only create server manager when config portal is actually needed
|
||||
if (!_serverManager) {
|
||||
_serverManager = std::make_unique<WiFiManagerServer>(this);
|
||||
_serverManager->registerTemplateSetupCallback(_templateSetupCallback);
|
||||
}
|
||||
_serverManager->createServer(_httpPort);
|
||||
_serverManager->registerRoutes();
|
||||
@@ -2013,6 +2012,10 @@ void WiFiManager::setShowInfoUpdate(boolean enabled){
|
||||
_showInfoUpdate = enabled;
|
||||
}
|
||||
|
||||
void WiFiManager::setShowInfo(boolean enabled){
|
||||
_showInfo = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the config portal is running
|
||||
* @return bool true if active
|
||||
@@ -2054,20 +2057,6 @@ void WiFiManager::setTitle(String title){
|
||||
_title = title;
|
||||
}
|
||||
|
||||
void WiFiManager::registerTemplateSetupCallback(std::function<void(PlaceholderRegistry&)> cb) {
|
||||
_templateSetupCallback = std::move(cb);
|
||||
if (_serverManager) {
|
||||
_serverManager->registerTemplateSetupCallback(_templateSetupCallback);
|
||||
_serverManager->rebuildPlaceholderRegistry();
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiManager::rebuildPlaceholderRegistry(std::function<void(PlaceholderRegistry&)> customizer) {
|
||||
if (_serverManager) {
|
||||
_serverManager->rebuildPlaceholderRegistry(std::move(customizer));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set params as separate page not in wifi
|
||||
* NOT COMPATIBLE WITH setMenu!
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,10 @@
|
||||
/**
|
||||
* WiFiManagerParameter.cpp
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
#include "WiFiManagerParameter.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
/**
|
||||
* WiFiManagerServer.cpp
|
||||
*
|
||||
* Server lifecycle management implementation
|
||||
*
|
||||
* @author tablatronix
|
||||
* @author Alex Hope-O'Connor
|
||||
*
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*
|
||||
* Server lifecycle: single shell + JSON APIs + OTA + captive portal.
|
||||
* Route inventory matches WiFiManagerServer.h (GET /, /api/..., POST /u, onNotFound).
|
||||
*/
|
||||
|
||||
#include "WiFiManagerServer.h"
|
||||
#include "WiFiManager.h"
|
||||
#include "WiFiManagerHandlers.h" // Full definition needed for unique_ptr
|
||||
#include "WiFiManagerHandlers.h"
|
||||
#ifdef WM_DFTE_LOGGING
|
||||
#include "WiFiManagerDfteLogger.h"
|
||||
#include <DeviceFrameworkTemplateEngineDebug.h>
|
||||
@@ -26,51 +26,19 @@ WiFiManagerServer* WiFiManagerServer::instance() {
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
WiFiManagerServer::WiFiManagerServer(WiFiManager* wm)
|
||||
: _wm(wm), _handlers(std::make_unique<WiFiManagerHandlers>(wm)) {
|
||||
WiFiManagerServer::WiFiManagerServer(WiFiManager* wm)
|
||||
: _wm(wm), _handlers(std::make_unique<WiFiManagerHandlers>(wm)) {
|
||||
s_instance = this;
|
||||
}
|
||||
|
||||
WiFiManagerServer::~WiFiManagerServer() = default;
|
||||
|
||||
// Static getters for RAM placeholders (registry requires zero-arg functions) using singleton
|
||||
const char* WiFiManagerServer::tplGetPageTitle() {
|
||||
auto srv = WiFiManagerServer::instance();
|
||||
if (!srv || !srv->_wm) return "";
|
||||
return srv->_wm->_title.c_str();
|
||||
}
|
||||
|
||||
const char* WiFiManagerServer::tplGetSubtitle() {
|
||||
static String buf;
|
||||
auto srv = WiFiManagerServer::instance();
|
||||
if (!srv || !srv->_wm) { buf = ""; return buf.c_str(); }
|
||||
WiFiManager* wm = srv->_wm;
|
||||
if (wm->configPortalActive) {
|
||||
buf = wm->_apName;
|
||||
} else {
|
||||
buf = wm->getWiFiHostname() + " - " + WiFi.localIP().toString();
|
||||
}
|
||||
return buf.c_str();
|
||||
}
|
||||
|
||||
const char* WiFiManagerServer::tplGetMenu() {
|
||||
static String buf;
|
||||
auto srv = WiFiManagerServer::instance();
|
||||
if (!srv || !srv->_handlers) { buf = ""; return buf.c_str(); }
|
||||
buf = "";
|
||||
srv->_handlers->getMenuOut(&buf);
|
||||
return buf.c_str();
|
||||
}
|
||||
|
||||
const char* WiFiManagerServer::tplGetStatus() {
|
||||
static String buf;
|
||||
auto srv = WiFiManagerServer::instance();
|
||||
if (!srv || !srv->_handlers) { buf = ""; return buf.c_str(); }
|
||||
buf = "";
|
||||
srv->_handlers->reportStatus(buf);
|
||||
return buf.c_str();
|
||||
}
|
||||
|
||||
void WiFiManagerServer::registerDefaultStyles(PlaceholderRegistry& reg) {
|
||||
reg.registerProgmemData("%STYLES%", CSS_STYLE);
|
||||
}
|
||||
@@ -83,59 +51,21 @@ void WiFiManagerServer::registerDefaultPageTitle(PlaceholderRegistry& reg) {
|
||||
reg.registerRamData("%PAGE_TITLE%", &WiFiManagerServer::tplGetPageTitle);
|
||||
}
|
||||
|
||||
void WiFiManagerServer::registerDefaultSubtitle(PlaceholderRegistry& reg) {
|
||||
reg.registerRamData("%SUBTITLE%", &WiFiManagerServer::tplGetSubtitle);
|
||||
}
|
||||
|
||||
void WiFiManagerServer::registerDefaultMenu(PlaceholderRegistry& reg) {
|
||||
reg.registerRamData("%MENU%", &WiFiManagerServer::tplGetMenu);
|
||||
}
|
||||
|
||||
void WiFiManagerServer::registerDefaultStatus(PlaceholderRegistry& reg) {
|
||||
reg.registerRamData("%STATUS%", &WiFiManagerServer::tplGetStatus);
|
||||
}
|
||||
|
||||
void WiFiManagerServer::registerDefaultPlaceholders(PlaceholderRegistry& reg) {
|
||||
registerDefaultStyles(reg);
|
||||
registerDefaultScripts(reg);
|
||||
registerDefaultPageTitle(reg);
|
||||
registerDefaultSubtitle(reg);
|
||||
registerDefaultMenu(reg);
|
||||
registerDefaultStatus(reg);
|
||||
}
|
||||
|
||||
void WiFiManagerServer::applyTemplateSetupCallback(PlaceholderRegistry& reg) {
|
||||
if (_tplSetupCallback) {
|
||||
_tplSetupCallback(reg);
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiManagerServer::rebuildPlaceholderRegistry(std::function<void(PlaceholderRegistry&)> customizer) {
|
||||
_tplRegistry = std::unique_ptr<PlaceholderRegistry>(new PlaceholderRegistry(WM_TEMPLATE_REGISTRY_CAPACITY));
|
||||
registerDefaultPlaceholders(*_tplRegistry);
|
||||
if (customizer) {
|
||||
customizer(*_tplRegistry);
|
||||
} else {
|
||||
applyTemplateSetupCallback(*_tplRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiManagerServer::setupTemplateEngine() {
|
||||
// Initialize shared placeholder registry once
|
||||
if (!_tplRegistry) {
|
||||
_tplRegistry = std::unique_ptr<PlaceholderRegistry>(new PlaceholderRegistry(WM_TEMPLATE_REGISTRY_CAPACITY));
|
||||
} else {
|
||||
_tplRegistry->clear();
|
||||
}
|
||||
|
||||
// Register all default placeholders
|
||||
registerDefaultPlaceholders(*_tplRegistry);
|
||||
|
||||
// Allow consumers to customize placeholders
|
||||
applyTemplateSetupCallback(*_tplRegistry);
|
||||
|
||||
#ifdef WM_DFTE_LOGGING
|
||||
// Install DFTE log sink only if nothing else registered yet (e.g. DeviceFramework's bridge).
|
||||
if (!deviceFrameworkTemplateEngineIsLoggingEnabled()) {
|
||||
if (!_dfteLogger) {
|
||||
_dfteLogger = std::make_unique<WiFiManagerDfteLogger>(_wm);
|
||||
@@ -147,53 +77,48 @@ void WiFiManagerServer::setupTemplateEngine() {
|
||||
}
|
||||
|
||||
void WiFiManagerServer::createServer(uint16_t port) {
|
||||
// If server already exists, shutdown first to prevent leaks and connection drops
|
||||
if (server) {
|
||||
#ifndef WM_NO_LOG
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("Server already exists, shutting down first"));
|
||||
#endif
|
||||
#endif
|
||||
shutdownServer();
|
||||
}
|
||||
|
||||
#ifndef WM_NO_LOG
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Info, kWiFiMgrLogSubsystem, F("Starting Web Portal"));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if(port != 80) {
|
||||
#ifndef WM_NO_LOG
|
||||
if (port != 80) {
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("http server started with custom port: "), port);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
server.reset(new AsyncWebServer(port));
|
||||
|
||||
// Ensure template engine is ready whenever a server is (re)created
|
||||
setupTemplateEngine();
|
||||
}
|
||||
|
||||
void WiFiManagerServer::registerRoutes() {
|
||||
if (!server) {
|
||||
#ifndef WM_NO_LOG
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Error, kWiFiMgrLogSubsystem, F("[ERROR] Server not created, call createServer() first"));
|
||||
#endif
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure template engine is initialized before routes use it
|
||||
if (!_tplRegistry) {
|
||||
setupTemplateEngine();
|
||||
}
|
||||
|
||||
if (_wm->_webservercallback != NULL) {
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("[CB] _webservercallback calling"));
|
||||
#endif
|
||||
_wm->_webservercallback(); // @CALLBACK
|
||||
}
|
||||
|
||||
/* Setup httpd callbacks, web pages: root, wifi config pages, SO captive portal detectors and not found. */
|
||||
|
||||
// Register routes with lambdas
|
||||
if (_wm->_webservercallback != NULL) {
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("[CB] _webservercallback calling"));
|
||||
#endif
|
||||
_wm->_webservercallback();
|
||||
}
|
||||
|
||||
// Shell: only HTML document. JSON + actions + OTA below (no legacy /wifi, /info, … routes).
|
||||
|
||||
server->on(WM_G(R_root), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleRoot(request);
|
||||
});
|
||||
@@ -237,88 +162,32 @@ void WiFiManagerServer::registerRoutes() {
|
||||
server->on(WM_G(R_api_portal_exit), HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleApiPortalExit(request);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_wifi), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleWifi(request, true);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_wifinoscan), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleWifi(request, false);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_wifisave), HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleWifiSave(request);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_info), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleInfo(request);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_param), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleParam(request);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_paramsave), HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleParamSave(request);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_restart), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleReset(request);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_exit), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleExit(request);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_close), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleClose(request);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_erase), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleErase(request, false);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_scanstatus), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleWiFiScanStatus(request);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_scan), HTTP_POST, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleWiFiScanRequest(request);
|
||||
});
|
||||
|
||||
// OTA Update routes
|
||||
server->on(WM_G(R_update), HTTP_GET, [this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleUpdate(request);
|
||||
});
|
||||
|
||||
server->on(WM_G(R_updatedone), HTTP_POST,
|
||||
[this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleUpdateDone(request);
|
||||
},
|
||||
[this](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||
this->_handlers->handleUpdating(request, filename, index, data, len, final);
|
||||
}
|
||||
);
|
||||
|
||||
// 404 handler
|
||||
server->on(WM_G(R_updatedone), HTTP_POST,
|
||||
[this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleUpdateDone(request);
|
||||
},
|
||||
[this](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len,
|
||||
bool final) {
|
||||
this->_handlers->handleUpdating(request, filename, index, data, len, final);
|
||||
});
|
||||
|
||||
server->onNotFound([this](AsyncWebServerRequest *request) {
|
||||
this->_handlers->handleNotFound(request);
|
||||
});
|
||||
|
||||
server->begin(); // Web server start
|
||||
#ifndef WM_NO_LOG
|
||||
|
||||
server->begin();
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("HTTP server started"));
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void WiFiManagerServer::setupDNSD() {
|
||||
dnsServer.reset(new DNSServer());
|
||||
|
||||
/* Setup the DNS server redirecting all the domains to the apIP */
|
||||
dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
|
||||
#ifndef WM_NO_LOG
|
||||
#ifndef WM_NO_LOG
|
||||
_wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem, F("dns server started with ip: "), WiFi.softAPIP());
|
||||
#endif
|
||||
#endif
|
||||
const uint8_t DNS_PORT = 53;
|
||||
dnsServer->start(DNS_PORT, F("*"), WiFi.softAPIP());
|
||||
}
|
||||
@@ -338,20 +207,16 @@ void WiFiManagerServer::shutdownServer() {
|
||||
}
|
||||
#endif
|
||||
|
||||
// AsyncWebServer doesn't have stop(), just reset the unique_ptr to free resources
|
||||
if (server) {
|
||||
server.reset();
|
||||
}
|
||||
|
||||
// Shutdown DNS server
|
||||
|
||||
if (dnsServer) {
|
||||
dnsServer->stop(); // free heap ?
|
||||
dnsServer->stop();
|
||||
dnsServer.reset();
|
||||
}
|
||||
|
||||
// Clear singleton on shutdown
|
||||
|
||||
if (s_instance == this) s_instance = nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* WiFiManagerUtils.cpp
|
||||
* Implementation of pure utility functions for WiFiManager
|
||||
*
|
||||
* @author Alex Hope-O'Connor
|
||||
* @author alexhopeoconnor
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user