mirror of
https://github.com/alexhopeoconnor/WiFiManager.git
synced 2026-10-21 18:22:37 +10:00
Portal customization API v2, SPA hardening, and test cleanup
- Group portal state into brand/pages/actions/layout structs; portal* API - Bootstrap JSON: nested brand/context/pages/actions/layout; param kind - Handlers: PROGMEM default CSS when no override/append; dynamic CSS path - PortalAppJS: portal timeout countdown; safe WiFi scan DOM; serialized scan-status polling; fetch error handling; wm:view-changed on redirects - CSS: scan list icon alignment; wm-status for timeout - Tests: remove low-value/no-op tests; trim fragile JS substring checks
This commit is contained in:
@@ -200,6 +200,74 @@ class WiFiManagerHandlers;
|
||||
class WiFiManagerDfteLogger;
|
||||
class WiFiManagerLogSink;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Portal customization (v2 contract: nested JSON; all entry points use portal* API)
|
||||
// ---------------------------------------------------------------------------
|
||||
enum class PortalParamsLocation : uint8_t { WiFiPage = 0, SetupPage = 1 };
|
||||
|
||||
enum class PortalPasswordPlaceholderMode : uint8_t { Hidden = 0, Masked = 1, Actual = 2 };
|
||||
|
||||
enum class PortalFieldVisibility : uint8_t { Hidden = 0, Auto = 1, Always = 2 };
|
||||
|
||||
enum class PortalHomeCardKind : uint8_t { Text = 0, Callout = 1, KeyValue = 2 };
|
||||
|
||||
struct PortalKeyValueItem {
|
||||
String key;
|
||||
String label;
|
||||
String value;
|
||||
};
|
||||
|
||||
struct PortalInfoSection {
|
||||
String id;
|
||||
String title;
|
||||
std::vector<PortalKeyValueItem> items;
|
||||
};
|
||||
|
||||
struct PortalHomeCard {
|
||||
String id;
|
||||
String title;
|
||||
PortalHomeCardKind kind = PortalHomeCardKind::KeyValue;
|
||||
String text;
|
||||
std::vector<PortalKeyValueItem> items;
|
||||
};
|
||||
|
||||
struct PortalBrandState {
|
||||
String title = "WiFiManager";
|
||||
String identityTextOverride;
|
||||
String homeIntro;
|
||||
String logoSvg;
|
||||
};
|
||||
|
||||
struct PortalPageState {
|
||||
bool infoVisible = true;
|
||||
bool updateVisible = true;
|
||||
bool setupVisible = true;
|
||||
};
|
||||
|
||||
struct PortalActionState {
|
||||
bool eraseVisible = true;
|
||||
bool restartVisible = true;
|
||||
bool exitVisible = true;
|
||||
bool closeCaptiveVisible = true;
|
||||
bool backVisible = false;
|
||||
};
|
||||
|
||||
struct PortalLayoutState {
|
||||
/** When true, custom parameters render on the Wi-Fi page; when false, only on #/setup. */
|
||||
bool paramsOnWifiPage = true;
|
||||
};
|
||||
|
||||
struct PortalAssetState {
|
||||
String appendedCss;
|
||||
String overriddenCss;
|
||||
String appendedJs;
|
||||
};
|
||||
|
||||
struct PortalStructuredExtrasState {
|
||||
std::vector<PortalInfoSection> infoSections;
|
||||
std::vector<PortalHomeCard> homeCards;
|
||||
};
|
||||
|
||||
class WiFiManager
|
||||
{
|
||||
public:
|
||||
@@ -322,7 +390,7 @@ class WiFiManager
|
||||
bool erase(bool opt);
|
||||
|
||||
//adds a custom parameter, returns false on failure
|
||||
bool addParameter(WiFiManagerParameter *p);
|
||||
bool portalAddParameter(WiFiManagerParameter *p);
|
||||
|
||||
//returns the list of Parameters
|
||||
WiFiManagerParameter** getParameters();
|
||||
@@ -421,12 +489,6 @@ class WiFiManager
|
||||
//if true, always show static dns, esle only show if set via setSTAStaticIPConfig
|
||||
void setShowDnsFields(boolean alwaysShow);
|
||||
|
||||
// toggle showing the saved wifi password in wifi form, could be a security issue.
|
||||
void setShowPassword(boolean show);
|
||||
|
||||
//if false, disable captive portal redirection
|
||||
void setCaptivePortalEnable(boolean enabled);
|
||||
|
||||
//if false, timeout captive portal even if a STA client connected to softAP (false), suggest disabling if captiveportal is open
|
||||
void setAPClientCheck(boolean enabled);
|
||||
|
||||
@@ -449,15 +511,6 @@ class WiFiManager
|
||||
bool setHostname(const char * hostname);
|
||||
bool setHostname(String hostname);
|
||||
|
||||
// show erase wifi onfig button on info page, true
|
||||
void setShowInfoErase(boolean enabled);
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -467,18 +520,46 @@ class WiFiManager
|
||||
// clean connect, always disconnect before connecting
|
||||
void setCleanConnect(bool enable); // default false
|
||||
|
||||
// set the webapp title, default WiFiManager
|
||||
void setTitle(String title);
|
||||
// ---- Portal (all customization entry points use the portal* prefix) ----
|
||||
void portalSetBrandTitle(const String& title);
|
||||
void portalSetContextIdentityText(const String& identityText);
|
||||
void portalSetBrandHomeIntro(const String& text);
|
||||
void portalSetBrandLogoSvg(const String& svgMarkup);
|
||||
|
||||
/**
|
||||
* Where custom parameters (`WiFiManagerParameter`) appear in the portal SPA (one placement only).
|
||||
*
|
||||
* - true (default): parameters render on the WiFi route (`#/wifi`) together with SSID/password.
|
||||
* - false: parameters render only on the Setup route (`#/setup`); they are not duplicated on WiFi.
|
||||
*
|
||||
* Bootstrap exposes the same flag as `features.paramsInWifi`.
|
||||
*/
|
||||
void setParametersEmbeddedInWifi(boolean embedded);
|
||||
void portalSetPageInfoVisible(bool visible);
|
||||
void portalSetPageUpdateVisible(bool visible);
|
||||
void portalSetPageSetupVisible(bool visible);
|
||||
|
||||
void portalSetActionEraseVisible(bool visible);
|
||||
void portalSetActionRestartVisible(bool visible);
|
||||
void portalSetActionExitVisible(bool visible);
|
||||
void portalSetActionCloseCaptiveVisible(bool visible);
|
||||
void portalSetActionBackVisible(bool visible);
|
||||
|
||||
void portalSetLayoutParamsLocation(PortalParamsLocation location);
|
||||
|
||||
void portalSetBehaviorCaptivePortalEnabled(bool enabled);
|
||||
void portalSetBehaviorConnectOnSave(bool enabled);
|
||||
void portalSetBehaviorExitAllowed(bool allowed);
|
||||
void portalSetBehaviorConnectTimeoutSeconds(unsigned long seconds);
|
||||
void portalSetBehaviorPortalTimeoutSeconds(unsigned long seconds);
|
||||
void portalSetBehaviorAutoReconnect(bool enabled);
|
||||
void portalSetBehaviorApClientCheck(bool enabled);
|
||||
void portalSetBehaviorWebClientCheck(bool enabled);
|
||||
|
||||
void portalSetFieldPasswordPlaceholderMode(PortalPasswordPlaceholderMode mode);
|
||||
void portalSetFieldStaticIpVisibility(PortalFieldVisibility visibility);
|
||||
void portalSetFieldStaticDnsVisibility(PortalFieldVisibility visibility);
|
||||
|
||||
void portalClearParameters();
|
||||
void portalAddInfoSection(const PortalInfoSection& section);
|
||||
void portalClearInfoSections();
|
||||
void portalAddHomeCard(const PortalHomeCard& card);
|
||||
void portalClearHomeCards();
|
||||
|
||||
void portalAppendCss(const String& css);
|
||||
void portalOverrideCss(const String& css);
|
||||
void portalAppendJs(const String& js);
|
||||
|
||||
// get last connection result, including autoconnect and portal credential-save attempts
|
||||
uint8_t getLastConxResult();
|
||||
@@ -706,7 +787,7 @@ class WiFiManager
|
||||
int _staShowStaticFields = 0; // ternary 1=always show static ip fields, 0=only if set, -1=never(cannot change ips via web!)
|
||||
int _staShowDns = 0; // ternary 1=always show dns, 0=only if set, -1=never(cannot change dns via web!)
|
||||
boolean _removeDuplicateAPs = true; // remove dup aps from wifiscan
|
||||
boolean _showPassword = false; // show or hide saved password on wifi form, might be a security issue!
|
||||
PortalPasswordPlaceholderMode _portalPasswordPlaceholderMode = PortalPasswordPlaceholderMode::Masked;
|
||||
boolean _shouldBreakAfterConfig = false; // stop configportal on save failure
|
||||
boolean _enableCaptivePortal = true; // enable captive portal redirection
|
||||
boolean _userpersistent = true; // users preffered persistence to restore
|
||||
@@ -714,15 +795,17 @@ class WiFiManager
|
||||
boolean _apClientCheck = false; // keep cp alive if ap have station
|
||||
boolean _webClientCheck = true; // keep cp alive if web have client
|
||||
boolean _scanDispOptions = false; // show percentage in scans not icons
|
||||
boolean _paramsInWifi = true; // if false, custom params only on #/setup (see setParametersEmbeddedInWifi)
|
||||
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
|
||||
boolean _enableConfigPortal = true; // FOR autoconnect - start config portal if autoconnect failed
|
||||
boolean _disableConfigPortal = true; // FOR autoconnect - stop config portal if cp wifi save
|
||||
String _hostname = ""; // hostname for esp8266 for dhcp, and or MDNS
|
||||
String _title = "WiFiManager"; // app title - default WiFiManager
|
||||
|
||||
// Grouped portal presentation / customization (see portal* setters; JSON in v2 bootstrap)
|
||||
PortalBrandState _portalBrand;
|
||||
PortalPageState _portalPages;
|
||||
PortalActionState _portalActions;
|
||||
PortalLayoutState _portalLayout;
|
||||
PortalAssetState _portalAssets;
|
||||
PortalStructuredExtrasState _portalStructured;
|
||||
|
||||
// internal options
|
||||
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
#define WM_TEMPLATE_REGISTRY_CAPACITY 16
|
||||
#endif
|
||||
|
||||
// Portal UI customization is driven by WiFiManager state and helpers such as setTitle(), setShowInfo(),
|
||||
// setParametersEmbeddedInWifi(), setShowInfoUpdate(), etc., surfaced through bootstrap/API JSON — not by mutating template
|
||||
// Portal UI customization is driven by WiFiManager `portal*` APIs and JSON (/api/...) — not by mutating template
|
||||
// placeholder registries.
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
@@ -106,11 +105,14 @@ class WiFiManagerHandlers {
|
||||
void appendPortalJsonStaticFields(String& json, bool& first);
|
||||
void appendPortalJsonCustomParams(String& json, bool& first);
|
||||
|
||||
String composePortalStylesheet() const;
|
||||
void appendPortalExtraInfoSectionsJson(String& json, bool& first);
|
||||
void appendPortalExtraHomeCardsJson(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);
|
||||
|
||||
@@ -70,6 +70,9 @@ const char CSS_STYLE[] PROGMEM = "<style>"
|
||||
".wm-hero{margin-bottom:20px}"
|
||||
".wm-hero-title{font-size:1.65rem;line-height:1.2;margin-bottom:.25em}"
|
||||
".wm-hero-sub{font-size:1rem;color:var(--wm-muted);font-weight:500;margin:0}"
|
||||
".wm-brand-logo{margin-bottom:12px;line-height:0}"
|
||||
".wm-brand-logo svg{display:block;max-width:160px;height:auto}"
|
||||
".wm-hero-intro{font-size:.95rem;color:var(--wm-muted);margin:.5em 0 0;line-height:1.45;max-width:42ch}"
|
||||
".wm-page-head{margin-bottom:16px}"
|
||||
".wm-page-head h1{font-size:1.45rem}"
|
||||
".wm-page-desc{font-size:.95rem;color:var(--wm-muted);margin:.35em 0 0}"
|
||||
@@ -85,7 +88,7 @@ const char CSS_STYLE[] PROGMEM = "<style>"
|
||||
"background:rgba(14,165,233,.06);margin-bottom:16px}"
|
||||
"body.invert .wm-callout{background:rgba(56,189,248,.08)}"
|
||||
".wm-callout--info{border-left:4px solid var(--wm-brand)}"
|
||||
".wm-status{margin:0;font-size:.9rem}"
|
||||
".wm-status{margin:0 0 10px;font-size:.9rem;font-weight:600;color:var(--wm-brand)}"
|
||||
/* Form fields */
|
||||
".wm-field{margin-bottom:14px;text-align:left}"
|
||||
".wm-field:last-child{margin-bottom:0}"
|
||||
@@ -130,10 +133,13 @@ const char CSS_STYLE[] PROGMEM = "<style>"
|
||||
".wm-scan-row:hover{background:rgba(14,165,233,.1)}"
|
||||
"body.invert .wm-scan-row:hover{background:rgba(56,189,248,.12)}"
|
||||
".wm-scan-ssid{flex:1;min-width:0;font-weight:600;font-size:.95rem;text-align:left;word-break:break-word}"
|
||||
".wm-scan-signal{display:flex;align-items:center;gap:6px;flex-shrink:0;font-size:.8rem;color:var(--wm-muted);font-weight:600}"
|
||||
".wm-scan-meta{display:flex;align-items:center;justify-content:flex-end;gap:6px;flex-shrink:0;min-width:78px}"
|
||||
".wm-scan-signal{display:block;flex-shrink:0;font-size:.8rem;color:var(--wm-muted);font-weight:600;min-width:38px;text-align:right}"
|
||||
".wm-scan-lock,.wm-scan-bars{display:block;flex:0 0 16px}"
|
||||
// quality icons
|
||||
".q{height:16px;width:16px;margin:0;padding:0;flex-shrink:0}.q.q-0:after{background-position-x:0}.q.q-1:after{background-position-x:-16px}.q.q-2:after{background-position-x:-32px}.q.q-3:after{background-position-x:-48px}.q.q-4:after{background-position-x:-64px}.q.l:before{background-position-x:-80px;padding-right:5px}.ql .q{float:left}.q:after,.q:before{content:'';width:16px;height:16px;display:inline-block;background-repeat:no-repeat;background-position:16px 0;"
|
||||
".q{position:relative;height:16px;width:16px;margin:0;padding:0;flex-shrink:0}.q.q-0:after{background-position-x:0}.q.q-1:after{background-position-x:-16px}.q.q-2:after{background-position-x:-32px}.q.q-3:after{background-position-x:-48px}.q.q-4:after{background-position-x:-64px}.q.l:before{background-position-x:-80px}.ql .q{float:left}.q:after,.q:before{content:'';position:absolute;left:0;top:0;width:16px;height:16px;display:block;background-repeat:no-repeat;background-position:16px 0;"
|
||||
"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAQCAMAAADeZIrLAAAAJFBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHJj5lAAAAC3RSTlMAIjN3iJmqu8zd7vF8pzcAAABsSURBVHja7Y1BCsAwCASNSVo3/v+/BUEiXnIoXkoX5jAQMxTHzK9cVSnvDxwD8bFx8PhZ9q8FmghXBhqA1faxk92PsxvRc2CCCFdhQCbRkLoAQ3q/wWUBqG35ZxtVzW4Ed6LngPyBU2CobdIDQ5oPWI5nCUwAAAAASUVORK5CYII=');}"
|
||||
".wm-scan-lock:after,.wm-scan-bars:before{content:none}"
|
||||
"@media (-webkit-min-device-pixel-ratio: 2),(min-resolution: 192dpi){.q:before,.q:after {"
|
||||
"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAAgCAMAAACfM+KhAAAALVBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAOrOgAAAADnRSTlMAESIzRGZ3iJmqu8zd7gKjCLQAAACmSURBVHgB7dDBCoMwEEXRmKlVY3L//3NLhyzqIqSUggy8uxnhCR5Mo8xLt+14aZ7wwgsvvPA/ofv9+44334UXXngvb6XsFhO/VoC2RsSv9J7x8BnYLW+AjT56ud/uePMdb7IP8Bsc/e7h8Cfk912ghsNXWPpDC4hvN+D1560A1QPORyh84VKLjjdvfPFm++i9EWq0348XXnjhhT+4dIbCW+WjZim9AKk4UZMnnCEuAAAAAElFTkSuQmCC');"
|
||||
"background-size: 95px 16px;}}"
|
||||
|
||||
@@ -24,6 +24,7 @@ function esc(t){if(!t)return'';return String(t).replace(/[&<>"']/g,function(c){r
|
||||
|
||||
// --- Toast / dialog ---
|
||||
var _wmToastTimer=null;
|
||||
var _wmPortalTimeoutTimer=null;
|
||||
function showToast(msg,isErr){
|
||||
var el=$('wm-toast');
|
||||
if(!el)return;
|
||||
@@ -72,9 +73,17 @@ function bindFormSubmitHandlers(){
|
||||
}
|
||||
|
||||
// --- View shell ---
|
||||
function stopPortalTimeoutCountdown(){
|
||||
if(_wmPortalTimeoutTimer){
|
||||
clearInterval(_wmPortalTimeoutTimer);
|
||||
_wmPortalTimeoutTimer=null;
|
||||
}
|
||||
}
|
||||
|
||||
function setView(html){
|
||||
var a=$('app');
|
||||
if(!a)return;
|
||||
stopPortalTimeoutCountdown();
|
||||
stopWifiScanPolling();
|
||||
stopWifiConnectPolling();
|
||||
a.innerHTML="<div class='wm-layout'>"+html+"</div>";
|
||||
@@ -82,10 +91,35 @@ function setView(html){
|
||||
bindFormSubmitHandlers();
|
||||
}
|
||||
|
||||
function formatDurationSeconds(totalSeconds){
|
||||
totalSeconds=Math.max(0,Math.floor(totalSeconds||0));
|
||||
var hours=Math.floor(totalSeconds/3600);
|
||||
var minutes=Math.floor((totalSeconds%3600)/60);
|
||||
var seconds=totalSeconds%60;
|
||||
if(hours>0){
|
||||
return hours+":"+String(minutes).padStart(2,'0')+":"+String(seconds).padStart(2,'0');
|
||||
}
|
||||
return minutes+":"+String(seconds).padStart(2,'0');
|
||||
}
|
||||
|
||||
function startPortalTimeoutCountdown(initialSeconds){
|
||||
var el=$('wm-portal-timeout');
|
||||
var remaining=Math.max(0,Math.floor(initialSeconds||0));
|
||||
if(!el||remaining<=0)return;
|
||||
function render(){
|
||||
if(!el)return;
|
||||
el.textContent='Captive portal timeout: '+formatDurationSeconds(remaining);
|
||||
}
|
||||
render();
|
||||
_wmPortalTimeoutTimer=setInterval(function(){
|
||||
remaining=Math.max(0,remaining-1);
|
||||
render();
|
||||
if(remaining<=0)stopPortalTimeoutCountdown();
|
||||
},1000);
|
||||
}
|
||||
|
||||
// --- Field render (WiFi / params meta) ---
|
||||
function renderMetaField(f){
|
||||
if(!f)return'';
|
||||
if(f.html)return f.html;
|
||||
function renderStandardField(f){
|
||||
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);
|
||||
@@ -102,6 +136,13 @@ function renderMetaField(f){
|
||||
return h;
|
||||
}
|
||||
|
||||
function renderMetaField(f){
|
||||
if(!f)return'';
|
||||
if(f.kind==='html')return f.html||'';
|
||||
if(f.kind==='field' || !f.kind)return renderStandardField(f);
|
||||
return '';
|
||||
}
|
||||
|
||||
function renderFieldList(arr){
|
||||
if(!arr||!arr.length)return'';
|
||||
var h='',i;
|
||||
@@ -111,43 +152,78 @@ function renderFieldList(arr){
|
||||
|
||||
function navBar(active){
|
||||
active=active||'home';
|
||||
var f=boot.features||{};
|
||||
var pages=boot.pages||{};
|
||||
var layout=boot.layout||{};
|
||||
var actions=boot.actions||{};
|
||||
var pinWifi=(layout.paramsLocation||'wifi')==='wifi';
|
||||
var ps=pages.setup||{};
|
||||
var pi=pages.info||{};
|
||||
var pu=pages.update||{};
|
||||
var h="<nav class='wm-nav' aria-label='Configuration'>";
|
||||
h+="<a class='wm-nav-link"+(active==='home'?' wm-nav-link--active':'')+"' href='#/'>Home</a>";
|
||||
h+="<a class='wm-nav-link"+(active==='wifi'?' wm-nav-link--active':'')+"' href='#/wifi'>WiFi</a>";
|
||||
if(!f.paramsInWifi){h+="<a class='wm-nav-link"+(active==='setup'?' wm-nav-link--active':'')+"' href='#/setup'>Setup</a>";}
|
||||
if(f.showInfo!==false){h+="<a class='wm-nav-link"+(active==='info'?' wm-nav-link--active':'')+"' href='#/info'>Info</a>";}
|
||||
if(f.showUpdate){h+="<a class='wm-nav-link"+(active==='update'?' wm-nav-link--active':'')+"' href='#/update'>Update</a>";}
|
||||
if(boot.showBack){h+="<a href='#' class='wm-nav-link' id='wm-nav-back'>Back</a>";}
|
||||
if(!pinWifi && ps.visible!==false){h+="<a class='wm-nav-link"+(active==='setup'?' wm-nav-link--active':'')+"' href='#/setup'>Setup</a>";}
|
||||
if(pi.visible!==false){h+="<a class='wm-nav-link"+(active==='info'?' wm-nav-link--active':'')+"' href='#/info'>Info</a>";}
|
||||
if(pu.visible){h+="<a class='wm-nav-link"+(active==='update'?' wm-nav-link--active':'')+"' href='#/update'>Update</a>";}
|
||||
if(actions.back&&actions.back.visible){h+="<a href='#' class='wm-nav-link' id='wm-nav-back'>Back</a>";}
|
||||
h+="</nav>";
|
||||
return h;
|
||||
}
|
||||
|
||||
function deviceActionsHtml(){
|
||||
var f=boot.features||{};
|
||||
var actions=boot.actions||{};
|
||||
var h='', ok=false;
|
||||
if(f.showRestart){h+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-restart-btn'>Restart device</button>";ok=true;}
|
||||
if(f.showExitPortal){h+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-exit-btn'>Exit portal</button>";ok=true;}
|
||||
if(f.showCloseCaptive){h+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-close-captive-btn'>Stop captive portal</button>";ok=true;}
|
||||
if(actions.restart&&actions.restart.visible){h+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-restart-btn'>Restart device</button>";ok=true;}
|
||||
if(actions.exitPortal&&actions.exitPortal.visible){h+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-exit-btn'>Exit portal</button>";ok=true;}
|
||||
if(actions.closeCaptive&&actions.closeCaptive.visible){h+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-close-captive-btn'>Stop captive portal</button>";ok=true;}
|
||||
if(!ok)return'';
|
||||
return "<div class='wm-card'><h2 class='wm-card-title'>Device</h2><div class='wm-btn-group'>"+h+"</div></div>";
|
||||
}
|
||||
|
||||
function renderExtraHomeCards(cards){
|
||||
if(!cards||!cards.length)return'';
|
||||
var out='',i,c,j,k,cls;
|
||||
for(i=0;i<cards.length;i++){
|
||||
c=cards[i];
|
||||
if(!c)continue;
|
||||
out+="<div class='wm-card'>";
|
||||
if(c.title){out+="<h2 class='wm-card-title'>"+esc(c.title)+"</h2>";}
|
||||
if(c.kind==='text'||c.kind==='callout'){
|
||||
cls=c.kind==='callout'?'wm-callout wm-callout--info':'wm-lead';
|
||||
out+="<p class='"+cls+"'>"+esc(c.text||'')+"</p>";
|
||||
}else{
|
||||
out+="<dl class='wm-kv'>";
|
||||
if(c.items){for(j=0;j<c.items.length;j++){k=c.items[j];out+="<dt>"+esc(k.label)+"</dt><dd>"+esc(k.value)+"</dd>";}}
|
||||
out+="</dl>";
|
||||
}
|
||||
out+="</div>";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function viewHome(){
|
||||
var sub=esc(boot.subtitle||'');
|
||||
var t=esc(boot.title||'WiFiManager');
|
||||
var brand=boot.brand||{};
|
||||
var ctx=boot.context||{};
|
||||
var t=esc(brand.title||'WiFiManager');
|
||||
var ident=ctx.identityText?esc(ctx.identityText):'';
|
||||
var summary=ctx.statusSummary||'';
|
||||
var timeoutSeconds=ctx.portalTimeoutSecondsRemaining||0;
|
||||
var html=navBar('home');
|
||||
html+="<header class='wm-hero'><h1 class='wm-hero-title'>"+t+"</h1>";
|
||||
if(sub)html+="<p class='wm-hero-sub'>"+sub+"</p>";
|
||||
html+="<header class='wm-hero'>";
|
||||
if(brand.logoSvg){html+="<div class='wm-brand-logo'>"+brand.logoSvg+"</div>";}
|
||||
html+="<h1 class='wm-hero-title'>"+t+"</h1>";
|
||||
if(ident){html+="<p class='wm-hero-sub'>"+ident+"</p>";}
|
||||
if(brand.homeIntro){html+="<p class='wm-hero-intro'>"+esc(brand.homeIntro)+"</p>";}
|
||||
html+="</header>";
|
||||
html+="<div class='wm-card'><h2 class='wm-card-title'>Status</h2>";
|
||||
if(boot.initialStatus){
|
||||
html+="<p class='wm-home-summary'>"+esc(boot.initialStatus)+"</p>";
|
||||
}
|
||||
if(summary){html+="<p class='wm-home-summary'>"+esc(summary)+"</p>";}
|
||||
if(timeoutSeconds>0){html+="<p class='wm-status' id='wm-portal-timeout'></p>";}
|
||||
html+="<p class='wm-lead'>Use <strong>WiFi</strong> to scan networks and connect. Open <strong>Info</strong> for addresses and firmware actions.</p>";
|
||||
html+="</div>";
|
||||
html+=renderExtraHomeCards(boot.extraHomeCards||[]);
|
||||
html+=deviceActionsHtml();
|
||||
setView(html);
|
||||
if(timeoutSeconds>0)startPortalTimeoutCountdown(timeoutSeconds);
|
||||
}
|
||||
|
||||
// --- HTTP ---
|
||||
@@ -159,24 +235,50 @@ function api(path, opt){
|
||||
|
||||
// --- WiFi scan view ---
|
||||
var _wmWifiScanPollTimer=null;
|
||||
var _wmWifiScanPollToken=0;
|
||||
|
||||
function getScanResultsBox(){
|
||||
return $('wm-scan-results');
|
||||
}
|
||||
|
||||
function setScanRefreshEnabled(enabled){
|
||||
var btn=$('wm-refresh-scan');
|
||||
if(btn)btn.disabled=(enabled===false);
|
||||
}
|
||||
|
||||
function stopWifiScanPolling(){
|
||||
_wmWifiScanPollToken++;
|
||||
if(_wmWifiScanPollTimer){
|
||||
clearInterval(_wmWifiScanPollTimer);
|
||||
clearTimeout(_wmWifiScanPollTimer);
|
||||
_wmWifiScanPollTimer=null;
|
||||
}
|
||||
setScanRefreshEnabled(true);
|
||||
}
|
||||
|
||||
function startWifiScanPolling(box){
|
||||
stopWifiScanPolling();
|
||||
_wmWifiScanPollTimer=setInterval(function(){
|
||||
setScanRefreshEnabled(false);
|
||||
var token=++_wmWifiScanPollToken;
|
||||
function poll(){
|
||||
api('/api/wifi/scan-status').then(function(res){
|
||||
if(token!==_wmWifiScanPollToken)return;
|
||||
var d={};
|
||||
try{d=JSON.parse(res.body);}catch(e){return;}
|
||||
box=getScanResultsBox()||box;
|
||||
if(box)box.innerHTML=renderScanList(d);
|
||||
if(!d.scanning)stopWifiScanPolling();
|
||||
if(!d.scanning){
|
||||
stopWifiScanPolling();
|
||||
return;
|
||||
}
|
||||
_wmWifiScanPollTimer=setTimeout(poll,800);
|
||||
}).catch(function(){
|
||||
if(token!==_wmWifiScanPollToken)return;
|
||||
stopWifiScanPolling();
|
||||
box=getScanResultsBox()||box;
|
||||
if(box)box.innerHTML=renderScanError('Portal became unreachable while scanning.');
|
||||
});
|
||||
},800);
|
||||
}
|
||||
poll();
|
||||
}
|
||||
|
||||
function renderScanList(data){
|
||||
@@ -197,23 +299,42 @@ function renderScanList(data){
|
||||
n=data.networks[i];
|
||||
pct=(n.quality||0)+'%';
|
||||
qi=Math.round((n.quality/100)*3)+1;if(qi<1)qi=1;if(qi>4)qi=4;
|
||||
enc=n.encrypted?'l':'';
|
||||
html+="<a class='wm-scan-row' href='#p' data-ssid='"+esc(n.ssid)+"'>";
|
||||
html+="<span class='wm-scan-ssid'>"+esc(n.ssid)+"</span>";
|
||||
html+="<span role='img' aria-label='"+pct+"' title='"+pct+"' class='q q-"+qi+" "+enc+"'></span>";
|
||||
html+="<span class='wm-scan-meta'>";
|
||||
if(n.encrypted){html+="<span class='wm-scan-lock q l' aria-hidden='true'></span>";}
|
||||
html+="<span role='img' aria-label='"+pct+"' title='"+pct+"' class='wm-scan-bars q q-"+qi+"'></span>";
|
||||
html+="<span class='wm-scan-signal'>"+pct+"</span>";
|
||||
html+="</span>";
|
||||
html+="</a>";
|
||||
}
|
||||
html+="</div>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderScanError(msg){
|
||||
return "<div class='wm-scan-list'><p class='wm-lead' style='margin:0;padding:14px 12px'>"+esc(msg||'Failed to reach portal while scanning.')+"</p></div>";
|
||||
}
|
||||
|
||||
function wifiRefresh(){
|
||||
var box=document.getElementById('wm-scan-results');
|
||||
var box=getScanResultsBox();
|
||||
if(!box)return;
|
||||
setScanRefreshEnabled(false);
|
||||
box.innerHTML="<div class='wm-scan-list'><p class='wm-lead' style='margin:0;padding:14px 12px'>Starting scan…</p></div>";
|
||||
api('/api/wifi/scan',{method:'POST'}).then(function(){
|
||||
api('/api/wifi/scan',{method:'POST'}).then(function(res){
|
||||
box=getScanResultsBox()||box;
|
||||
if(!res.ok){
|
||||
setScanRefreshEnabled(true);
|
||||
if(box)box.innerHTML=renderScanError('Failed to start WiFi scan.');
|
||||
showToast('Failed to start WiFi scan.',true);
|
||||
return;
|
||||
}
|
||||
startWifiScanPolling(box);
|
||||
}).catch(function(){
|
||||
stopWifiScanPolling();
|
||||
box=getScanResultsBox()||box;
|
||||
if(box)box.innerHTML=renderScanError('Failed to reach portal while starting scan.');
|
||||
showToast('Failed to reach portal while starting scan.',true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -273,12 +394,19 @@ function viewWifi(){
|
||||
html+="<div id='wm-wifi-msg'></div></div>";
|
||||
setView(html);
|
||||
bindWifiViewEvents();
|
||||
setScanRefreshEnabled(true);
|
||||
api('/api/wifi/scan-status').then(function(r2){
|
||||
try{var d=JSON.parse(r2.body);}catch(e){d={};}
|
||||
var box=document.getElementById('wm-scan-results');
|
||||
var box=getScanResultsBox();
|
||||
if(box)box.innerHTML=renderScanList(d);
|
||||
if(d&&d.scanning){startWifiScanPolling(box);}
|
||||
}).catch(function(){
|
||||
var box=getScanResultsBox();
|
||||
if(box)box.innerHTML=renderScanError('Failed to reach portal while loading scan status.');
|
||||
setScanRefreshEnabled(true);
|
||||
});
|
||||
}).catch(function(){
|
||||
setView(navBar('wifi')+"<div class='wm-card'><p class='wm-lead'>Failed to load WiFi options. Reconnect to the portal and try again.</p></div>");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -310,6 +438,10 @@ function pollWifiConnectStatus(){
|
||||
stopWifiConnectPolling();
|
||||
showToast(j.message||'WiFi connect failed',true);
|
||||
}
|
||||
}).catch(function(){
|
||||
stopWifiConnectPolling();
|
||||
var msg=$('wm-wifi-msg');
|
||||
if(msg)msg.innerHTML='Portal became unreachable while checking connection status.';
|
||||
});
|
||||
},700);
|
||||
}
|
||||
@@ -363,6 +495,15 @@ function viewInfo(){
|
||||
html+=section('Device',d.device||[]);
|
||||
html+=section('WiFi',d.wifi||[]);
|
||||
html+=section('About',d.about||[]);
|
||||
if(d.extraSections){
|
||||
var xi,zs,zrows,zi,it;
|
||||
for(xi=0;xi<d.extraSections.length;xi++){
|
||||
zs=d.extraSections[xi];
|
||||
zrows=[];
|
||||
if(zs.items){for(zi=0;zi<zs.items.length;zi++){it=zs.items[zi];zrows.push({label:it.label,value:esc(it.value)});}}
|
||||
html+=section(zs.title,zrows);
|
||||
}
|
||||
}
|
||||
var act=d.actions||{};
|
||||
html+="<div class='wm-card'><h2 class='wm-card-title'>Actions</h2><div class='wm-btn-group'>";
|
||||
if(act.showRestart)html+="<button type='button' class='wm-btn wm-btn--secondary wm-btn--block' id='wm-restart-btn'>Restart device</button>";
|
||||
@@ -426,10 +567,11 @@ window.portalParamSave=function(ev){
|
||||
};
|
||||
|
||||
function viewUpdate(){
|
||||
var f=boot.features||{};
|
||||
var pages=boot.pages||{};
|
||||
var pupd=pages.update||{};
|
||||
var html=navBar('update');
|
||||
html+="<div class='wm-page-head'><h1>Firmware update</h1><p class='wm-page-desc'>Flash a new firmware build (.bin). The device reboots when done.</p></div>";
|
||||
if(!f.showUpdate){
|
||||
if(!pupd.visible){
|
||||
html+="<div class='wm-card'><p class='wm-lead'>Firmware update is disabled for this configuration.</p></div>";
|
||||
setView(html);
|
||||
return;
|
||||
@@ -484,19 +626,29 @@ window.portalOtaSubmit=function(ev){
|
||||
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;}
|
||||
if(h==='#/setup'){viewSetup();return;}
|
||||
if(h==='#/update'){viewUpdate();return;}
|
||||
viewHome();
|
||||
var pages=boot.pages||{};
|
||||
var ps=pages.setup||{};
|
||||
var pi=pages.info||{};
|
||||
var pu=pages.update||{};
|
||||
if(h==='#/setup'&&ps.visible===false){viewHome();try{document.dispatchEvent(new CustomEvent('wm:view-changed',{detail:{route:'#/'}}));}catch(e){}return;}
|
||||
if(h==='#/info'&&pi.visible===false){viewHome();try{document.dispatchEvent(new CustomEvent('wm:view-changed',{detail:{route:'#/'}}));}catch(e){}return;}
|
||||
if(h==='#/update'&&pu.visible===false){viewHome();try{document.dispatchEvent(new CustomEvent('wm:view-changed',{detail:{route:'#/'}}));}catch(e){}return;}
|
||||
if(h==='#/'||h==='#'){viewHome();}
|
||||
else if(h==='#/wifi'){viewWifi();}
|
||||
else if(h==='#/info'){viewInfo();}
|
||||
else if(h==='#/setup'){viewSetup();}
|
||||
else if(h==='#/update'){viewUpdate();}
|
||||
else {viewHome();h='#/';}
|
||||
try{document.dispatchEvent(new CustomEvent('wm:view-changed',{detail:{route:h}}));}catch(e){}
|
||||
}
|
||||
|
||||
function bootPortal(){
|
||||
route();
|
||||
try{document.dispatchEvent(new CustomEvent('wm:ready',{detail:{boot:boot}}));}catch(e){}
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange',route);
|
||||
window.addEventListener('load',route);
|
||||
window.addEventListener('load',bootPortal);
|
||||
})();
|
||||
|
||||
)rawliteral";
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
* Single-page portal shell: only HTML document served for GET /.
|
||||
* Dynamic UI is driven by embedded JS + JSON APIs under /api/...
|
||||
*
|
||||
* Placeholder keys (%PAGE_TITLE%, %STYLES%, %BOOTSTRAP_JSON%, %PORTAL_APP_JS%) are filled per request in
|
||||
* WiFiManagerHandlers::handleRoot (shell contract comment in WiFiManagerHandlers.cpp); do not treat
|
||||
* placeholders as a customization API.
|
||||
* Placeholder keys (%PAGE_TITLE%, %STYLES%, %BOOTSTRAP_JSON%, %PORTAL_APP_JS%, %PORTAL_APPEND_JS%) are filled
|
||||
* per request in WiFiManagerHandlers::handleRoot; do not treat placeholders as a customization API.
|
||||
*/
|
||||
|
||||
#ifndef _WM_ROOT_SHELL_TEMPLATE_H_
|
||||
@@ -42,6 +41,9 @@ const char WM_ROOT_SHELL_TEMPLATE[] PROGMEM =
|
||||
"<script>"
|
||||
"%PORTAL_APP_JS%"
|
||||
"</script>"
|
||||
"<script>"
|
||||
"%PORTAL_APPEND_JS%"
|
||||
"</script>"
|
||||
"</body>"
|
||||
"</html>";
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ bool normalizeHostname(String& hostname) {
|
||||
* @param p Pointer to WiFiManagerParameter to add
|
||||
* @return true if added successfully, false on error
|
||||
*/
|
||||
bool WiFiManager::addParameter(WiFiManagerParameter *p) {
|
||||
bool WiFiManager::portalAddParameter(WiFiManagerParameter *p) {
|
||||
|
||||
// check param id is valid, unless null
|
||||
if(p->getID()){
|
||||
@@ -111,6 +111,15 @@ bool WiFiManager::addParameter(WiFiManagerParameter *p) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void WiFiManager::portalClearParameters() {
|
||||
if (_params != NULL) {
|
||||
free(_params);
|
||||
_params = NULL;
|
||||
}
|
||||
_paramsCount = 0;
|
||||
_max_params = WIFI_MANAGER_MAX_PARAMS;
|
||||
}
|
||||
|
||||
WiFiManagerParameter** WiFiManager::getParameters() {
|
||||
return _params;
|
||||
}
|
||||
@@ -1927,29 +1936,6 @@ void WiFiManager::setShowDnsFields(boolean alwaysShow){
|
||||
else _staShowDns = alwaysShow ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* toggle showing password in wifi password field
|
||||
* if not enabled, placeholder will be S_passph
|
||||
* @since $dev
|
||||
* @access public
|
||||
* @param boolean alwaysShow [false]
|
||||
*/
|
||||
void WiFiManager::setShowPassword(boolean show){
|
||||
_showPassword = show;
|
||||
}
|
||||
|
||||
/**
|
||||
* toggle captive portal
|
||||
* if enabled, then devices that use captive portal checks will be redirected to root
|
||||
* if not you will automatically have to navigate to ip [192.168.4.1]
|
||||
* @since $dev
|
||||
* @access public
|
||||
* @param boolean enabled [true]
|
||||
*/
|
||||
void WiFiManager::setCaptivePortalEnable(boolean enabled){
|
||||
_enableCaptivePortal = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* toggle wifi autoreconnect policy
|
||||
* if enabled, then wifi will autoreconnect automatically always
|
||||
@@ -2076,24 +2062,148 @@ void WiFiManager::setWiFiAPHidden(bool hidden){
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* toggle showing erase wifi config button on info page
|
||||
* @param boolean enabled
|
||||
*/
|
||||
void WiFiManager::setShowInfoErase(boolean enabled){
|
||||
_showInfoErase = enabled;
|
||||
void WiFiManager::portalSetBrandTitle(const String& title) {
|
||||
_portalBrand.title = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* toggle showing update upload web ota button on info page
|
||||
* @param boolean enabled
|
||||
*/
|
||||
void WiFiManager::setShowInfoUpdate(boolean enabled){
|
||||
_showInfoUpdate = enabled;
|
||||
void WiFiManager::portalSetContextIdentityText(const String& identityText) {
|
||||
_portalBrand.identityTextOverride = identityText;
|
||||
}
|
||||
|
||||
void WiFiManager::setShowInfo(boolean enabled){
|
||||
_showInfo = enabled;
|
||||
void WiFiManager::portalSetBrandHomeIntro(const String& text) {
|
||||
_portalBrand.homeIntro = text;
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetBrandLogoSvg(const String& svgMarkup) {
|
||||
_portalBrand.logoSvg = svgMarkup;
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetPageInfoVisible(bool visible) {
|
||||
_portalPages.infoVisible = visible;
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetPageUpdateVisible(bool visible) {
|
||||
_portalPages.updateVisible = visible;
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetPageSetupVisible(bool visible) {
|
||||
_portalPages.setupVisible = visible;
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetActionEraseVisible(bool visible) {
|
||||
_portalActions.eraseVisible = visible;
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetActionRestartVisible(bool visible) {
|
||||
_portalActions.restartVisible = visible;
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetActionExitVisible(bool visible) {
|
||||
_portalActions.exitVisible = visible;
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetActionCloseCaptiveVisible(bool visible) {
|
||||
_portalActions.closeCaptiveVisible = visible;
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetActionBackVisible(bool visible) {
|
||||
_portalActions.backVisible = visible;
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetLayoutParamsLocation(PortalParamsLocation location) {
|
||||
_portalLayout.paramsOnWifiPage = (location == PortalParamsLocation::WiFiPage);
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetBehaviorCaptivePortalEnabled(bool enabled) {
|
||||
_enableCaptivePortal = enabled;
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetBehaviorConnectOnSave(bool enabled) {
|
||||
setSaveConnect(enabled);
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetBehaviorExitAllowed(bool allowed) {
|
||||
_allowExit = allowed;
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetBehaviorConnectTimeoutSeconds(unsigned long seconds) {
|
||||
setConnectTimeout(seconds);
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetBehaviorPortalTimeoutSeconds(unsigned long seconds) {
|
||||
setConfigPortalTimeout(seconds);
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetBehaviorAutoReconnect(bool enabled) {
|
||||
setWiFiAutoReconnect(enabled);
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetBehaviorApClientCheck(bool enabled) {
|
||||
setAPClientCheck(enabled);
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetBehaviorWebClientCheck(bool enabled) {
|
||||
setWebPortalClientCheck(enabled);
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetFieldPasswordPlaceholderMode(PortalPasswordPlaceholderMode mode) {
|
||||
_portalPasswordPlaceholderMode = mode;
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetFieldStaticIpVisibility(PortalFieldVisibility visibility) {
|
||||
switch (visibility) {
|
||||
case PortalFieldVisibility::Always:
|
||||
setShowStaticFields(true);
|
||||
break;
|
||||
case PortalFieldVisibility::Auto:
|
||||
setShowStaticFields(false);
|
||||
break;
|
||||
case PortalFieldVisibility::Hidden:
|
||||
_staShowStaticFields = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiManager::portalSetFieldStaticDnsVisibility(PortalFieldVisibility visibility) {
|
||||
switch (visibility) {
|
||||
case PortalFieldVisibility::Always:
|
||||
setShowDnsFields(true);
|
||||
break;
|
||||
case PortalFieldVisibility::Auto:
|
||||
setShowDnsFields(false);
|
||||
break;
|
||||
case PortalFieldVisibility::Hidden:
|
||||
_staShowDns = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiManager::portalAddInfoSection(const PortalInfoSection& section) {
|
||||
_portalStructured.infoSections.push_back(section);
|
||||
}
|
||||
|
||||
void WiFiManager::portalClearInfoSections() {
|
||||
_portalStructured.infoSections.clear();
|
||||
}
|
||||
|
||||
void WiFiManager::portalAddHomeCard(const PortalHomeCard& card) {
|
||||
_portalStructured.homeCards.push_back(card);
|
||||
}
|
||||
|
||||
void WiFiManager::portalClearHomeCards() {
|
||||
_portalStructured.homeCards.clear();
|
||||
}
|
||||
|
||||
void WiFiManager::portalAppendCss(const String& css) {
|
||||
_portalAssets.appendedCss += css;
|
||||
}
|
||||
|
||||
void WiFiManager::portalOverrideCss(const String& css) {
|
||||
_portalAssets.overriddenCss = css;
|
||||
}
|
||||
|
||||
void WiFiManager::portalAppendJs(const String& js) {
|
||||
_portalAssets.appendedJs += js;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2174,18 +2284,6 @@ DNSServer* WiFiManager::getDNSServer() {
|
||||
return _serverManager ? _serverManager->getDNSServer() : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* [setTitle description]
|
||||
* @param String title, set app title
|
||||
*/
|
||||
void WiFiManager::setTitle(String title){
|
||||
_title = title;
|
||||
}
|
||||
|
||||
void WiFiManager::setParametersEmbeddedInWifi(boolean embedded) {
|
||||
_paramsInWifi = embedded;
|
||||
}
|
||||
|
||||
// GETTERS
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "templates/PortalAppJS.h"
|
||||
#include <TemplateEngine.h>
|
||||
#include <cstring>
|
||||
|
||||
#if defined(ESP8266) || defined(ESP32)
|
||||
|
||||
#ifndef WM_PAGE_RESERVE_BYTES
|
||||
@@ -54,12 +53,42 @@ size_t dynamicStringLengthGetter(const char* data, void* /*userData*/) {
|
||||
return data ? strlen(data) : 0;
|
||||
}
|
||||
|
||||
void configureDynamicStringDescriptor(DynamicTemplateDescriptor& descriptor, String& value) {
|
||||
void configureDynamicStringDescriptor(DynamicDataDescriptor& descriptor, String& value) {
|
||||
descriptor.getter = &dynamicStringGetter;
|
||||
descriptor.getLength = &dynamicStringLengthGetter;
|
||||
descriptor.userData = &value;
|
||||
}
|
||||
|
||||
String readProgmemString(const char* p) {
|
||||
return String(FPSTR(p));
|
||||
}
|
||||
|
||||
String stripStyleWrapper(const String& styleBlock) {
|
||||
String css = styleBlock;
|
||||
const String openTag = F("<style>");
|
||||
const String closeTag = F("</style>");
|
||||
|
||||
if (css.startsWith(openTag)) {
|
||||
css.remove(0, openTag.length());
|
||||
}
|
||||
if (css.endsWith(closeTag)) {
|
||||
css.remove(css.length() - closeTag.length());
|
||||
}
|
||||
return css;
|
||||
}
|
||||
|
||||
const char* portalHomeCardKindJson(PortalHomeCardKind k) {
|
||||
switch (k) {
|
||||
case PortalHomeCardKind::Text:
|
||||
return "text";
|
||||
case PortalHomeCardKind::Callout:
|
||||
return "callout";
|
||||
case PortalHomeCardKind::KeyValue:
|
||||
default:
|
||||
return "kv";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BundleT>
|
||||
AsyncWebServerResponse* beginTemplateResponse(AsyncWebServerRequest* request,
|
||||
const std::shared_ptr<BundleT>& bundle,
|
||||
@@ -73,41 +102,123 @@ AsyncWebServerResponse* beginTemplateResponse(AsyncWebServerRequest* request,
|
||||
);
|
||||
}
|
||||
|
||||
// Shell contract (must match templates/RootShell.h — customize UI via WiFiManager APIs + bootstrap JSON, not registries):
|
||||
// %PAGE_TITLE% -> document title (from WiFiManager title state)
|
||||
// %STYLES% -> embedded portal CSS
|
||||
// %BOOTSTRAP_JSON% -> initial SPA runtime payload
|
||||
// %PORTAL_APP_JS% -> embedded SPA source
|
||||
// Shell contract (must match templates/RootShell.h — customize UI via WiFiManager portal* APIs + bootstrap JSON, not registries):
|
||||
// %PAGE_TITLE% -> document title (from WiFiManager title state)
|
||||
// %STYLES% -> portal CSS (embedded default + portalAppendCss / portalOverrideCss)
|
||||
// %BOOTSTRAP_JSON% -> initial SPA runtime payload
|
||||
// %PORTAL_APP_JS% -> embedded SPA source
|
||||
// %PORTAL_APPEND_JS% -> consumer hook script (portalAppendJs)
|
||||
|
||||
struct PortalShellRenderBundle {
|
||||
String bootstrapJson;
|
||||
String pageTitleStatic;
|
||||
String stylesStatic;
|
||||
String appendJsStatic;
|
||||
PlaceholderRegistry registry;
|
||||
TemplateContext context;
|
||||
DynamicTemplateDescriptor bootstrapDescriptor;
|
||||
DynamicTemplateDescriptor pageTitleDescriptor;
|
||||
DynamicDataDescriptor bootstrapDescriptor;
|
||||
DynamicDataDescriptor pageTitleDescriptor;
|
||||
DynamicDataDescriptor stylesDescriptor;
|
||||
DynamicDataDescriptor appendJsDescriptor;
|
||||
|
||||
PortalShellRenderBundle()
|
||||
: registry(WM_TEMPLATE_REGISTRY_CAPACITY), bootstrapDescriptor{}, pageTitleDescriptor{} {}
|
||||
: registry(WM_TEMPLATE_REGISTRY_CAPACITY),
|
||||
bootstrapDescriptor{},
|
||||
pageTitleDescriptor{},
|
||||
stylesDescriptor{},
|
||||
appendJsDescriptor{} {}
|
||||
};
|
||||
|
||||
void populatePortalShellBundle(PortalShellRenderBundle& bundle,
|
||||
const String& pageTitle,
|
||||
const String& bootstrapJson) {
|
||||
bundle.pageTitleStatic = pageTitle;
|
||||
bundle.bootstrapJson = bootstrapJson;
|
||||
bundle.registry.registerProgmemData("%STYLES%", CSS_STYLE);
|
||||
bundle.registry.registerProgmemData("%PORTAL_APP_JS%", PORTAL_APP_JS);
|
||||
configureDynamicStringDescriptor(bundle.pageTitleDescriptor, bundle.pageTitleStatic);
|
||||
bundle.registry.registerDynamicTemplate("%PAGE_TITLE%", &bundle.pageTitleDescriptor);
|
||||
configureDynamicStringDescriptor(bundle.bootstrapDescriptor, bundle.bootstrapJson);
|
||||
bundle.registry.registerDynamicTemplate("%BOOTSTRAP_JSON%", &bundle.bootstrapDescriptor);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WiFiManagerHandlers::WiFiManagerHandlers(WiFiManager* wm) : _wm(wm) {}
|
||||
|
||||
String WiFiManagerHandlers::composePortalStylesheet() const {
|
||||
String css;
|
||||
if (_wm != nullptr && _wm->_portalAssets.overriddenCss.length() > 0) {
|
||||
css = _wm->_portalAssets.overriddenCss;
|
||||
} else {
|
||||
css = stripStyleWrapper(readProgmemString(CSS_STYLE));
|
||||
}
|
||||
|
||||
if (_wm != nullptr && _wm->_portalAssets.appendedCss.length() > 0) {
|
||||
css += _wm->_portalAssets.appendedCss;
|
||||
}
|
||||
|
||||
String out = F("<style>");
|
||||
out += css;
|
||||
out += F("</style>");
|
||||
return out;
|
||||
}
|
||||
|
||||
void WiFiManagerHandlers::appendPortalExtraInfoSectionsJson(String& json, bool& first) {
|
||||
if (_wm == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const auto& sec : _wm->_portalStructured.infoSections) {
|
||||
if (!first) {
|
||||
json += F(",");
|
||||
}
|
||||
first = false;
|
||||
json += F("{\"id\":\"");
|
||||
jsonAppendEscaped(json, sec.id);
|
||||
json += F("\",\"title\":\"");
|
||||
jsonAppendEscaped(json, sec.title);
|
||||
json += F("\",\"items\":[");
|
||||
bool firstItem = true;
|
||||
for (const auto& it : sec.items) {
|
||||
if (!firstItem) {
|
||||
json += F(",");
|
||||
}
|
||||
firstItem = false;
|
||||
json += F("{\"key\":\"");
|
||||
jsonAppendEscaped(json, it.key);
|
||||
json += F("\",\"label\":\"");
|
||||
jsonAppendEscaped(json, it.label);
|
||||
json += F("\",\"value\":\"");
|
||||
jsonAppendEscaped(json, it.value);
|
||||
json += F("\"}");
|
||||
}
|
||||
json += F("]}");
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiManagerHandlers::appendPortalExtraHomeCardsJson(String& json, bool& first) {
|
||||
if (_wm == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const auto& card : _wm->_portalStructured.homeCards) {
|
||||
if (!first) {
|
||||
json += F(",");
|
||||
}
|
||||
first = false;
|
||||
json += F("{\"id\":\"");
|
||||
jsonAppendEscaped(json, card.id);
|
||||
json += F("\",\"title\":\"");
|
||||
jsonAppendEscaped(json, card.title);
|
||||
json += F("\",\"kind\":\"");
|
||||
json += portalHomeCardKindJson(card.kind);
|
||||
json += F("\",\"text\":\"");
|
||||
jsonAppendEscaped(json, card.text);
|
||||
json += F("\",\"items\":[");
|
||||
bool firstItem = true;
|
||||
for (const auto& it : card.items) {
|
||||
if (!firstItem) {
|
||||
json += F(",");
|
||||
}
|
||||
firstItem = false;
|
||||
json += F("{\"key\":\"");
|
||||
jsonAppendEscaped(json, it.key);
|
||||
json += F("\",\"label\":\"");
|
||||
jsonAppendEscaped(json, it.label);
|
||||
json += F("\",\"value\":\"");
|
||||
jsonAppendEscaped(json, it.value);
|
||||
json += F("\"}");
|
||||
}
|
||||
json += F("]}");
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiManagerHandlers::collectVisibleScanResults(std::vector<const WiFiManager::WiFiScanNetwork*>& networks) {
|
||||
networks.clear();
|
||||
networks.reserve(_wm->_scanResultsCache.size());
|
||||
@@ -197,7 +308,7 @@ void WiFiManagerHandlers::appendPortalJsonCustomParams(String& json, bool& first
|
||||
first = false;
|
||||
if (p->getID() != nullptr) {
|
||||
String pid = String(p->getID());
|
||||
json += F("{\"name\":\"");
|
||||
json += F("{\"kind\":\"field\",\"name\":\"");
|
||||
jsonAppendEscaped(json, pid);
|
||||
json += F("\",\"id\":\"");
|
||||
jsonAppendEscaped(json, pid);
|
||||
@@ -219,7 +330,7 @@ void WiFiManagerHandlers::appendPortalJsonCustomParams(String& json, bool& first
|
||||
}
|
||||
json += F("}");
|
||||
} else {
|
||||
json += F("{\"html\":\"");
|
||||
json += F("{\"kind\":\"html\",\"html\":\"");
|
||||
jsonAppendEscaped(json, String(p->getCustomHTML()));
|
||||
json += F("}");
|
||||
}
|
||||
@@ -560,9 +671,25 @@ void WiFiManagerHandlers::handleRoot(AsyncWebServerRequest *request) {
|
||||
// - Populate one request-scoped PlaceholderRegistry with shell defaults + request payloads.
|
||||
// - Render WM_ROOT_SHELL_TEMPLATE.
|
||||
auto bundle = std::make_shared<PortalShellRenderBundle>();
|
||||
populatePortalShellBundle(*bundle,
|
||||
_wm ? _wm->_title : String(),
|
||||
buildPortalBootstrapJson());
|
||||
const bool hasCustomStyles = _wm != nullptr &&
|
||||
(_wm->_portalAssets.overriddenCss.length() > 0 || _wm->_portalAssets.appendedCss.length() > 0);
|
||||
bundle->pageTitleStatic = _wm ? _wm->_portalBrand.title : String();
|
||||
bundle->bootstrapJson = buildPortalBootstrapJson();
|
||||
bundle->appendJsStatic = _wm ? _wm->_portalAssets.appendedJs : String();
|
||||
bundle->registry.registerProgmemData("%PORTAL_APP_JS%", PORTAL_APP_JS);
|
||||
if (hasCustomStyles) {
|
||||
bundle->stylesStatic = composePortalStylesheet();
|
||||
configureDynamicStringDescriptor(bundle->stylesDescriptor, bundle->stylesStatic);
|
||||
bundle->registry.registerDynamicData("%STYLES%", &bundle->stylesDescriptor);
|
||||
} else {
|
||||
bundle->registry.registerProgmemData("%STYLES%", CSS_STYLE);
|
||||
}
|
||||
configureDynamicStringDescriptor(bundle->appendJsDescriptor, bundle->appendJsStatic);
|
||||
bundle->registry.registerDynamicData("%PORTAL_APPEND_JS%", &bundle->appendJsDescriptor);
|
||||
configureDynamicStringDescriptor(bundle->pageTitleDescriptor, bundle->pageTitleStatic);
|
||||
bundle->registry.registerDynamicData("%PAGE_TITLE%", &bundle->pageTitleDescriptor);
|
||||
configureDynamicStringDescriptor(bundle->bootstrapDescriptor, bundle->bootstrapJson);
|
||||
bundle->registry.registerDynamicData("%BOOTSTRAP_JSON%", &bundle->bootstrapDescriptor);
|
||||
|
||||
request->send(beginTemplateResponse(request, bundle, WM_ROOT_SHELL_TEMPLATE));
|
||||
}
|
||||
@@ -634,7 +761,7 @@ void WiFiManagerHandlers::applyWifiAndParamsFromRequest(AsyncWebServerRequest *r
|
||||
_wm->_presavewificallback();
|
||||
}
|
||||
|
||||
if (_wm->_paramsInWifi) {
|
||||
if (_wm->_portalLayout.paramsOnWifiPage) {
|
||||
doParamSave(requestArgs);
|
||||
}
|
||||
}
|
||||
@@ -821,38 +948,20 @@ void WiFiManagerHandlers::sendApiJson(AsyncWebServerRequest *request, int code,
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void WiFiManagerHandlers::appendPortalUiFeatureFlagsJson(String& json) {
|
||||
const bool portalRunning = _wm->configPortalActive || _wm->webPortalActive;
|
||||
json += F("\"showInfo\":");
|
||||
json += _wm->_showInfo ? F("true") : F("false");
|
||||
json += F(",\"showUpdate\":");
|
||||
json += _wm->_showInfoUpdate ? F("true") : F("false");
|
||||
json += F(",\"showErase\":");
|
||||
json += _wm->_showInfoErase ? F("true") : F("false");
|
||||
json += F(",\"paramsInWifi\":");
|
||||
json += _wm->_paramsInWifi ? F("true") : F("false");
|
||||
json += F(",\"showRestart\":");
|
||||
json += portalRunning ? F("true") : F("false");
|
||||
json += F(",\"showExitPortal\":");
|
||||
json += (_wm->_allowExit && portalRunning) ? F("true") : F("false");
|
||||
json += F(",\"showCloseCaptive\":");
|
||||
json += (_wm->_enableCaptivePortal && _wm->configPortalActive) ? F("true") : F("false");
|
||||
}
|
||||
|
||||
void WiFiManagerHandlers::appendApiInfoActionsJson(String& json) {
|
||||
const bool portalRunning = _wm->configPortalActive || _wm->webPortalActive;
|
||||
json += F("\"showUpdate\":");
|
||||
json += _wm->_showInfoUpdate ? F("true") : F("false");
|
||||
json += _wm->_portalPages.updateVisible ? F("true") : F("false");
|
||||
json += F(",\"showErase\":");
|
||||
json += _wm->_showInfoErase ? F("true") : F("false");
|
||||
json += _wm->_portalActions.eraseVisible ? F("true") : F("false");
|
||||
json += F(",\"showBack\":");
|
||||
json += _wm->_showBack ? F("true") : F("false");
|
||||
json += _wm->_portalActions.backVisible ? F("true") : F("false");
|
||||
json += F(",\"showRestart\":");
|
||||
json += portalRunning ? F("true") : F("false");
|
||||
json += (_wm->_portalActions.restartVisible && portalRunning) ? F("true") : F("false");
|
||||
json += F(",\"showExitPortal\":");
|
||||
json += (_wm->_allowExit && portalRunning) ? F("true") : F("false");
|
||||
json += (_wm->_portalActions.exitVisible && _wm->_allowExit && portalRunning) ? F("true") : F("false");
|
||||
json += F(",\"showCloseCaptive\":");
|
||||
json += (_wm->_enableCaptivePortal && _wm->configPortalActive) ? F("true") : F("false");
|
||||
json += (_wm->_portalActions.closeCaptiveVisible && _wm->_enableCaptivePortal && _wm->configPortalActive) ? F("true") : F("false");
|
||||
}
|
||||
|
||||
String WiFiManagerHandlers::buildPortalBootstrapJson() {
|
||||
@@ -882,38 +991,87 @@ String WiFiManagerHandlers::buildPortalBootstrapJson() {
|
||||
stateStr = "idle";
|
||||
break;
|
||||
}
|
||||
|
||||
const bool portalRunning = _wm->configPortalActive || _wm->webPortalActive;
|
||||
String json;
|
||||
reservePage(json, 512);
|
||||
json += F("{");
|
||||
reservePage(json, 1200);
|
||||
json += F("{\"contractVersion\":2");
|
||||
json += F(",\"brand\":{");
|
||||
json += F("\"title\":\"");
|
||||
jsonAppendEscaped(json, _wm->_title);
|
||||
json += F("\",\"subtitle\":\"");
|
||||
jsonAppendEscaped(json, _wm->_portalBrand.title);
|
||||
json += F("\",\"homeIntro\":\"");
|
||||
jsonAppendEscaped(json, _wm->_portalBrand.homeIntro);
|
||||
json += F("\",\"logoSvg\":\"");
|
||||
jsonAppendEscaped(json, _wm->_portalBrand.logoSvg);
|
||||
json += F("\"}");
|
||||
json += F(",\"context\":{");
|
||||
json += F("\"portalActive\":");
|
||||
json += portalRunning ? F("true") : F("false");
|
||||
json += F(",\"portalTimeoutSecondsRemaining\":");
|
||||
{
|
||||
String sub;
|
||||
if (_wm->configPortalActive) {
|
||||
sub = _wm->_apName;
|
||||
} else {
|
||||
sub = _wm->getWiFiHostname() + " - " + WiFi.localIP().toString();
|
||||
unsigned long timeoutRemainingSeconds = 0;
|
||||
if (_wm->configPortalActive && _wm->_configPortalTimeout > 0) {
|
||||
const unsigned long now = millis();
|
||||
const unsigned long timeoutAt = _wm->_configPortalStart + _wm->_configPortalTimeout;
|
||||
if (timeoutAt > now) {
|
||||
timeoutRemainingSeconds = (timeoutAt - now + 999UL) / 1000UL;
|
||||
}
|
||||
}
|
||||
jsonAppendEscaped(json, sub);
|
||||
json += String(timeoutRemainingSeconds);
|
||||
}
|
||||
json += F("\",\"portalActive\":");
|
||||
json += _wm->configPortalActive ? F("true") : F("false");
|
||||
json += F(",\"features\":{");
|
||||
appendPortalUiFeatureFlagsJson(json);
|
||||
json += F("},\"showBack\":");
|
||||
json += _wm->_showBack ? F("true") : F("false");
|
||||
json += F(",\"scan\":{\"state\":\"");
|
||||
json += stateStr;
|
||||
json += F("\",\"count\":");
|
||||
json += String((unsigned int)networks.size());
|
||||
json += F("},\"initialStatus\":\"");
|
||||
json += F(",\"identityText\":\"");
|
||||
{
|
||||
String idText;
|
||||
if (_wm->_portalBrand.identityTextOverride.length() > 0) {
|
||||
idText = _wm->_portalBrand.identityTextOverride;
|
||||
} else if (_wm->configPortalActive) {
|
||||
idText = _wm->_apName;
|
||||
} else {
|
||||
idText = _wm->getWiFiHostname() + " - " + WiFi.localIP().toString();
|
||||
}
|
||||
jsonAppendEscaped(json, idText);
|
||||
}
|
||||
json += F("\",\"statusSummary\":\"");
|
||||
{
|
||||
String st;
|
||||
buildPlainStatusSummary(st);
|
||||
jsonAppendEscaped(json, st);
|
||||
}
|
||||
json += F("\",\"scan\":{\"state\":\"");
|
||||
json += stateStr;
|
||||
json += F("\",\"count\":");
|
||||
json += String((unsigned int)networks.size());
|
||||
json += F("}}");
|
||||
json += F(",\"pages\":{");
|
||||
json += F("\"wifi\":{\"visible\":true}");
|
||||
json += F(",\"setup\":{\"visible\":");
|
||||
json += (!_wm->_portalLayout.paramsOnWifiPage && _wm->_portalPages.setupVisible) ? F("true") : F("false");
|
||||
json += F("}");
|
||||
json += F(",\"info\":{\"visible\":");
|
||||
json += _wm->_portalPages.infoVisible ? F("true") : F("false");
|
||||
json += F("}");
|
||||
json += F(",\"update\":{\"visible\":");
|
||||
json += _wm->_portalPages.updateVisible ? F("true") : F("false");
|
||||
json += F("}}");
|
||||
json += F(",\"actions\":{");
|
||||
json += F("\"erase\":{\"visible\":");
|
||||
json += _wm->_portalActions.eraseVisible ? F("true") : F("false");
|
||||
json += F("},\"restart\":{\"visible\":");
|
||||
json += (_wm->_portalActions.restartVisible && portalRunning) ? F("true") : F("false");
|
||||
json += F("},\"exitPortal\":{\"visible\":");
|
||||
json += (_wm->_portalActions.exitVisible && _wm->_allowExit && portalRunning) ? F("true") : F("false");
|
||||
json += F("},\"closeCaptive\":{\"visible\":");
|
||||
json += (_wm->_portalActions.closeCaptiveVisible && _wm->_enableCaptivePortal && _wm->configPortalActive) ? F("true") : F("false");
|
||||
json += F("},\"back\":{\"visible\":");
|
||||
json += _wm->_portalActions.backVisible ? F("true") : F("false");
|
||||
json += F("}}");
|
||||
json += F(",\"layout\":{\"paramsLocation\":\"");
|
||||
json += _wm->_portalLayout.paramsOnWifiPage ? F("wifi") : F("setup");
|
||||
json += F("\"}");
|
||||
bool first = true;
|
||||
json += F(",\"extraHomeCards\":[");
|
||||
appendPortalExtraHomeCardsJson(json, first);
|
||||
json += F("]}");
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -1019,10 +1177,19 @@ void WiFiManagerHandlers::handleApiWifiScan(AsyncWebServerRequest *request) {
|
||||
String WiFiManagerHandlers::buildApiWifiMetaJson() {
|
||||
String ssidPlaceholder = _wm->WiFi_SSID();
|
||||
String passwordPlaceholder = "";
|
||||
if (_wm->_showPassword) {
|
||||
passwordPlaceholder = _wm->WiFi_psk();
|
||||
} else if (_wm->WiFi_psk() != "") {
|
||||
passwordPlaceholder = F("********");
|
||||
switch (_wm->_portalPasswordPlaceholderMode) {
|
||||
case PortalPasswordPlaceholderMode::Actual:
|
||||
passwordPlaceholder = _wm->WiFi_psk();
|
||||
break;
|
||||
case PortalPasswordPlaceholderMode::Masked:
|
||||
if (_wm->WiFi_psk() != "") {
|
||||
passwordPlaceholder = F("********");
|
||||
}
|
||||
break;
|
||||
case PortalPasswordPlaceholderMode::Hidden:
|
||||
default:
|
||||
passwordPlaceholder = "";
|
||||
break;
|
||||
}
|
||||
|
||||
String json = F("{\"wifiFields\":[");
|
||||
@@ -1035,11 +1202,11 @@ String WiFiManagerHandlers::buildApiWifiMetaJson() {
|
||||
appendPortalJsonStaticFields(json, first);
|
||||
json += F("],\"params\":[");
|
||||
first = true;
|
||||
if (_wm->_paramsInWifi && _wm->getParametersCount() > 0) {
|
||||
if (_wm->_portalLayout.paramsOnWifiPage && _wm->getParametersCount() > 0) {
|
||||
appendPortalJsonCustomParams(json, first);
|
||||
}
|
||||
json += F("],\"actions\":{\"canRefreshScan\":true,\"showBack\":");
|
||||
json += _wm->_showBack ? F("true") : F("false");
|
||||
json += _wm->_portalActions.backVisible ? F("true") : F("false");
|
||||
json += F("}}");
|
||||
return json;
|
||||
}
|
||||
@@ -1096,7 +1263,7 @@ String WiFiManagerHandlers::buildApiParamsGetJson() {
|
||||
bool first = true;
|
||||
appendPortalJsonCustomParams(json, first);
|
||||
json += F("],\"actions\":{\"showBack\":");
|
||||
json += _wm->_showBack ? F("true") : F("false");
|
||||
json += _wm->_portalActions.backVisible ? F("true") : F("false");
|
||||
json += F("}}");
|
||||
return json;
|
||||
}
|
||||
@@ -1156,6 +1323,9 @@ String WiFiManagerHandlers::buildApiInfoJson() {
|
||||
first = true;
|
||||
static const char *const aboutIds[] = {"aboutver", "aboutarduinover", "aboutsdkver", "aboutdate"};
|
||||
appendInfoSectionFromIds(json, aboutIds, sizeof(aboutIds) / sizeof(aboutIds[0]), first);
|
||||
json += F("],\"extraSections\":[");
|
||||
first = true;
|
||||
appendPortalExtraInfoSectionsJson(json, first);
|
||||
json += F("],\"actions\":{");
|
||||
appendApiInfoActionsJson(json);
|
||||
json += F("}}");
|
||||
|
||||
Reference in New Issue
Block a user