Browse Source

Merge branch 'lumapu:development03' into development03

pull/846/head
rejoe2 2 years ago
committed by GitHub
parent
commit
ce78534066
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      src/CHANGES.md
  2. 4
      src/app.h
  3. 3
      src/appInterface.h
  4. 2
      src/defines.h
  5. 64
      src/web/RestApi.h
  6. 4
      src/web/html/login.html
  7. 29
      src/web/html/style.css
  8. 107
      src/web/web.h

4
src/CHANGES.md

@ -1,5 +1,9 @@
# Development Changes # Development Changes
## 0.6.2 - 2023-04-04
* fix login from multiple clients #819
* fix login screen on small displays
## 0.6.1 - 2023-04-01 ## 0.6.1 - 2023-04-01
* merge LED fix - LED1 shows MqTT state, LED configureable active high/low #839 * merge LED fix - LED1 shows MqTT state, LED configureable active high/low #839
* only publish new inverter data #826 * only publish new inverter data #826

4
src/app.h

@ -161,8 +161,8 @@ class app : public IApp, public ah::Scheduler {
return mMqtt.getRxCnt(); return mMqtt.getRxCnt();
} }
bool getProtection() { bool getProtection(AsyncWebServerRequest *request) {
return mWeb.getProtection(); return mWeb.isProtected(request);
} }
uint8_t getIrqPin(void) { uint8_t getIrqPin(void) {

3
src/appInterface.h

@ -8,6 +8,7 @@
#include "defines.h" #include "defines.h"
#include "hm/hmSystem.h" #include "hm/hmSystem.h"
#include "ESPAsyncWebServer.h"
// abstract interface to App. Make members of App accessible from child class // abstract interface to App. Make members of App accessible from child class
// like web or API without forward declaration // like web or API without forward declaration
@ -47,7 +48,7 @@ class IApp {
virtual uint32_t getMqttRxCnt() = 0; virtual uint32_t getMqttRxCnt() = 0;
virtual uint32_t getMqttTxCnt() = 0; virtual uint32_t getMqttTxCnt() = 0;
virtual bool getProtection() = 0; virtual bool getProtection(AsyncWebServerRequest *request) = 0;
}; };
#endif /*__IAPP_H__*/ #endif /*__IAPP_H__*/

2
src/defines.h

@ -13,7 +13,7 @@
//------------------------------------- //-------------------------------------
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 6 #define VERSION_MINOR 6
#define VERSION_PATCH 1 #define VERSION_PATCH 2
//------------------------------------- //-------------------------------------
typedef struct { typedef struct {

64
src/web/RestApi.h

@ -77,19 +77,19 @@ class RestApi {
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
String path = request->url().substring(5); String path = request->url().substring(5);
if(path == "html/system") getHtmlSystem(root); if(path == "html/system") getHtmlSystem(request, root);
else if(path == "html/logout") getHtmlLogout(root); else if(path == "html/logout") getHtmlLogout(request, root);
else if(path == "html/reboot") getHtmlReboot(root); else if(path == "html/reboot") getHtmlReboot(request, root);
else if(path == "html/save") getHtmlSave(root); else if(path == "html/save") getHtmlSave(request, root);
else if(path == "system") getSysInfo(root); else if(path == "system") getSysInfo(request, root);
else if(path == "generic") getGeneric(root); else if(path == "generic") getGeneric(request, root);
else if(path == "reboot") getReboot(root); else if(path == "reboot") getReboot(request, root);
else if(path == "statistics") getStatistics(root); else if(path == "statistics") getStatistics(root);
else if(path == "inverter/list") getInverterList(root); else if(path == "inverter/list") getInverterList(root);
else if(path == "index") getIndex(root); else if(path == "index") getIndex(request, root);
else if(path == "setup") getSetup(root); else if(path == "setup") getSetup(request, root);
else if(path == "setup/networks") getNetworks(root); else if(path == "setup/networks") getNetworks(root);
else if(path == "live") getLive(root); else if(path == "live") getLive(request, root);
else if(path == "record/info") getRecord(root, InverterDevInform_All); else if(path == "record/info") getRecord(root, InverterDevInform_All);
else if(path == "record/alarm") getRecord(root, AlarmData); else if(path == "record/alarm") getRecord(root, AlarmData);
else if(path == "record/config") getRecord(root, SystemConfigPara); else if(path == "record/config") getRecord(root, SystemConfigPara);
@ -189,10 +189,10 @@ class RestApi {
fp.close(); fp.close();
} }
void getGeneric(JsonObject obj) { void getGeneric(AsyncWebServerRequest *request, JsonObject obj) {
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI(); obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI();
obj[F("ts_uptime")] = mApp->getUptime(); obj[F("ts_uptime")] = mApp->getUptime();
obj[F("menu_prot")] = mApp->getProtection(); obj[F("menu_prot")] = mApp->getProtection(request);
obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask ); obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask );
obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0); obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0);
@ -203,7 +203,7 @@ class RestApi {
#endif #endif
} }
void getSysInfo(JsonObject obj) { void getSysInfo(AsyncWebServerRequest *request, JsonObject obj) {
obj[F("ssid")] = mConfig->sys.stationSsid; obj[F("ssid")] = mConfig->sys.stationSsid;
obj[F("device_name")] = mConfig->sys.deviceName; obj[F("device_name")] = mConfig->sys.deviceName;
obj[F("dark_mode")] = (bool)mConfig->sys.darkMode; obj[F("dark_mode")] = (bool)mConfig->sys.darkMode;
@ -218,7 +218,7 @@ class RestApi {
obj[F("heap_free")] = mHeapFree; obj[F("heap_free")] = mHeapFree;
obj[F("sketch_total")] = ESP.getFreeSketchSpace(); obj[F("sketch_total")] = ESP.getFreeSketchSpace();
obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb
getGeneric(obj); getGeneric(request, obj);
getRadio(obj.createNestedObject(F("radio"))); getRadio(obj.createNestedObject(F("radio")));
getStatistics(obj.createNestedObject(F("statistics"))); getStatistics(obj.createNestedObject(F("statistics")));
@ -252,34 +252,34 @@ class RestApi {
obj[F("schMax")] = max; obj[F("schMax")] = max;
} }
void getHtmlSystem(JsonObject obj) { void getHtmlSystem(AsyncWebServerRequest *request, JsonObject obj) {
getSysInfo(obj.createNestedObject(F("system"))); getSysInfo(request, obj.createNestedObject(F("system")));
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>"); obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>");
} }
void getHtmlLogout(JsonObject obj) { void getHtmlLogout(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("refresh")] = 3; obj[F("refresh")] = 3;
obj[F("refresh_url")] = "/"; obj[F("refresh_url")] = "/";
obj[F("html")] = F("succesfully logged out"); obj[F("html")] = F("succesfully logged out");
} }
void getHtmlReboot(JsonObject obj) { void getHtmlReboot(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("refresh")] = 20; obj[F("refresh")] = 20;
obj[F("refresh_url")] = "/"; obj[F("refresh_url")] = "/";
obj[F("html")] = F("rebooting ..."); obj[F("html")] = F("rebooting ...");
} }
void getHtmlSave(JsonObject obj) { void getHtmlSave(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(request, obj.createNestedObject(F("generic")));
obj["pending"] = (bool)mApp->getSavePending(); obj["pending"] = (bool)mApp->getSavePending();
obj["success"] = (bool)mApp->getLastSaveSucceed(); obj["success"] = (bool)mApp->getLastSaveSucceed();
} }
void getReboot(JsonObject obj) { void getReboot(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("refresh")] = 10; obj[F("refresh")] = 10;
obj[F("refresh_url")] = "/"; obj[F("refresh_url")] = "/";
obj[F("html")] = F("reboot. Autoreload after 10 seconds"); obj[F("html")] = F("reboot. Autoreload after 10 seconds");
@ -430,8 +430,8 @@ class RestApi {
obj[F("disp_bsy")] = (mConfig->plugin.display.type < 10) ? DEF_PIN_OFF : mConfig->plugin.display.disp_busy; obj[F("disp_bsy")] = (mConfig->plugin.display.type < 10) ? DEF_PIN_OFF : mConfig->plugin.display.disp_busy;
} }
void getIndex(JsonObject obj) { void getIndex(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("ts_now")] = mApp->getTimestamp(); obj[F("ts_now")] = mApp->getTimestamp();
obj[F("ts_sunrise")] = mApp->getSunrise(); obj[F("ts_sunrise")] = mApp->getSunrise();
obj[F("ts_sunset")] = mApp->getSunset(); obj[F("ts_sunset")] = mApp->getSunset();
@ -479,9 +479,9 @@ class RestApi {
info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds")); info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds"));
} }
void getSetup(JsonObject obj) { void getSetup(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(request, obj.createNestedObject(F("generic")));
getSysInfo(obj.createNestedObject(F("system"))); getSysInfo(request, obj.createNestedObject(F("system")));
//getInverterList(obj.createNestedObject(F("inverter"))); //getInverterList(obj.createNestedObject(F("inverter")));
getMqtt(obj.createNestedObject(F("mqtt"))); getMqtt(obj.createNestedObject(F("mqtt")));
getNtp(obj.createNestedObject(F("ntp"))); getNtp(obj.createNestedObject(F("ntp")));
@ -497,8 +497,8 @@ class RestApi {
mApp->getAvailNetworks(obj); mApp->getAvailNetworks(obj);
} }
void getLive(JsonObject obj) { void getLive(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(obj.createNestedObject(F("generic"))); getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("refresh")] = mConfig->nrf.sendInterval; obj[F("refresh")] = mConfig->nrf.sendInterval;
for (uint8_t fld = 0; fld < sizeof(acList); fld++) { for (uint8_t fld = 0; fld < sizeof(acList); fld++) {

4
src/web/html/login.html

@ -11,8 +11,8 @@
<form action="/login" method="post"> <form action="/login" method="post">
<div class="row"><h2>AhoyDTU</h2></div> <div class="row"><h2>AhoyDTU</h2></div>
<div class="row"> <div class="row">
<div class="col-8"><input type="password" name="pwd" autofocus></div> <div class="col-12 col-sm-8 mb-3"><input type="password" name="pwd" autofocus></div>
<div class="col-4"><input type="submit" name="login" value="login" class="btn"></div> <div class="col-12 col-sm-4"><input type="submit" name="login" value="login" class="btn"></div>
</div> </div>
</form> </form>
</div> </div>

29
src/web/html/style.css

@ -546,6 +546,18 @@ div.hr {
width: 100%; width: 100%;
} }
#login {
width: 450px;
height: 200px;
border: 1px solid #ccc;
background-color: var(--input-bg);
position: absolute;
top: 50%;
left: 50%;
margin-top: -160px;
margin-left: -225px;
}
@media(max-width: 500px) { @media(max-width: 500px) {
div.ch .unit, div.ch-iv .unit { div.ch .unit, div.ch-iv .unit {
font-size: 18px; font-size: 18px;
@ -559,6 +571,11 @@ div.hr {
.subgrp { .subgrp {
width: 180px; width: 180px;
} }
#login {
margin-left: -150px;
width: 300px;
}
} }
#serial { #serial {
@ -578,18 +595,6 @@ div.hr {
margin-top: 15px; margin-top: 15px;
} }
#login {
width: 450px;
height: 200px;
border: 1px solid #ccc;
background-color: var(--input-bg);
position: absolute;
top: 50%;
left: 50%;
margin-top: -160px;
margin-left: -225px;
}
.head { .head {
background-color: var(--primary); background-color: var(--primary);

107
src/web/web.h

@ -126,8 +126,19 @@ class Web {
mProtected = protect; mProtected = protect;
} }
bool getProtection() { bool isProtected(AsyncWebServerRequest *request) {
return mProtected; bool prot;
prot = mProtected;
if(!prot) {
uint8_t ip[4];
ah::ip2Arr(ip, request->client()->remoteIP().toString().c_str());
for(uint8_t i = 0; i < 4; i++) {
if(mLoginIp[i] != ip[i])
prot = true;
}
}
return prot;
} }
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
@ -216,7 +227,7 @@ class Web {
} }
private: private:
void checkRedirect(AsyncWebServerRequest *request) { inline void checkRedirect(AsyncWebServerRequest *request) {
if ((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX) if ((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX)
request->redirect(F("/index")); request->redirect(F("/index"));
else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE) else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE)
@ -229,15 +240,18 @@ class Web {
request->redirect(F("/login")); request->redirect(F("/login"));
} }
void checkProtection(AsyncWebServerRequest *request) {
if(isProtected(request)) {
checkRedirect(request);
return;
}
}
void onUpdate(AsyncWebServerRequest *request) { void onUpdate(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onUpdate")); DPRINTLN(DBG_VERBOSE, F("onUpdate"));
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE)) { if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_UPDATE))
if (mProtected) { checkProtection(request);
checkRedirect(request);
return;
}
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), update_html, update_html_len); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), update_html, update_html_len);
response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("Content-Encoding"), "gzip");
@ -290,12 +304,8 @@ class Web {
void onIndex(AsyncWebServerRequest *request) { void onIndex(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onIndex")); DPRINTLN(DBG_VERBOSE, F("onIndex"));
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) { if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX))
if (mProtected) { checkProtection(request);
checkRedirect(request);
return;
}
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), index_html, index_html_len); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), index_html, index_html_len);
response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("Content-Encoding"), "gzip");
@ -308,6 +318,7 @@ class Web {
if (request->args() > 0) { if (request->args() > 0) {
if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) { if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
mProtected = false; mProtected = false;
ah::ip2Arr(mLoginIp, request->client()->remoteIP().toString().c_str());
request->redirect("/"); request->redirect("/");
} }
} }
@ -320,10 +331,7 @@ class Web {
void onLogout(AsyncWebServerRequest *request) { void onLogout(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onLogout")); DPRINTLN(DBG_VERBOSE, F("onLogout"));
if (mProtected) { checkProtection(request);
checkRedirect(request);
return;
}
mProtected = true; mProtected = true;
@ -367,10 +375,8 @@ class Web {
} }
void showNotFound(AsyncWebServerRequest *request) { void showNotFound(AsyncWebServerRequest *request) {
if (mProtected) checkProtection(request);
checkRedirect(request); request->redirect("/setup");
else
request->redirect("/setup");
} }
void onReboot(AsyncWebServerRequest *request) { void onReboot(AsyncWebServerRequest *request) {
@ -381,10 +387,7 @@ class Web {
} }
void showErase(AsyncWebServerRequest *request) { void showErase(AsyncWebServerRequest *request) {
if (mProtected) { checkProtection(request);
checkRedirect(request);
return;
}
DPRINTLN(DBG_VERBOSE, F("showErase")); DPRINTLN(DBG_VERBOSE, F("showErase"));
mApp->eraseSettings(false); mApp->eraseSettings(false);
@ -392,10 +395,7 @@ class Web {
} }
void showFactoryRst(AsyncWebServerRequest *request) { void showFactoryRst(AsyncWebServerRequest *request) {
if (mProtected) { checkProtection(request);
checkRedirect(request);
return;
}
DPRINTLN(DBG_VERBOSE, F("showFactoryRst")); DPRINTLN(DBG_VERBOSE, F("showFactoryRst"));
String content = ""; String content = "";
@ -424,12 +424,8 @@ class Web {
void onSetup(AsyncWebServerRequest *request) { void onSetup(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onSetup")); DPRINTLN(DBG_VERBOSE, F("onSetup"));
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) { if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP))
if (mProtected) { checkProtection(request);
checkRedirect(request);
return;
}
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), setup_html, setup_html_len); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), setup_html, setup_html_len);
response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("Content-Encoding"), "gzip");
@ -439,10 +435,7 @@ class Web {
void showSave(AsyncWebServerRequest *request) { void showSave(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("showSave")); DPRINTLN(DBG_VERBOSE, F("showSave"));
if (mProtected) { checkProtection(request);
checkRedirect(request);
return;
}
if (request->args() == 0) if (request->args() == 0)
return; return;
@ -605,12 +598,8 @@ class Web {
void onLive(AsyncWebServerRequest *request) { void onLive(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onLive")); DPRINTLN(DBG_VERBOSE, F("onLive"));
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) { if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE))
if (mProtected) { checkProtection(request);
checkRedirect(request);
return;
}
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), visualization_html, visualization_html_len); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), visualization_html, visualization_html_len);
response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("Content-Encoding"), "gzip");
@ -620,13 +609,6 @@ class Web {
} }
void onAbout(AsyncWebServerRequest *request) { void onAbout(AsyncWebServerRequest *request) {
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
if (mProtected) {
checkRedirect(request);
return;
}
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), about_html, about_html_len); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), about_html, about_html_len);
response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("Content-Encoding"), "gzip");
response->addHeader(F("content-type"), "text/html; charset=UTF-8"); response->addHeader(F("content-type"), "text/html; charset=UTF-8");
@ -643,12 +625,8 @@ class Web {
void onSerial(AsyncWebServerRequest *request) { void onSerial(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onSerial")); DPRINTLN(DBG_VERBOSE, F("onSerial"));
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) { if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL))
if (mProtected) { checkProtection(request);
checkRedirect(request);
return;
}
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), serial_html, serial_html_len); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), serial_html, serial_html_len);
response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("Content-Encoding"), "gzip");
@ -658,12 +636,8 @@ class Web {
void onSystem(AsyncWebServerRequest *request) { void onSystem(AsyncWebServerRequest *request) {
DPRINTLN(DBG_VERBOSE, F("onSystem")); DPRINTLN(DBG_VERBOSE, F("onSystem"));
if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) { if (CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM))
if (mProtected) { checkProtection(request);
checkRedirect(request);
return;
}
}
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len);
response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("Content-Encoding"), "gzip");
@ -840,6 +814,7 @@ class Web {
AsyncEventSource mEvts; AsyncEventSource mEvts;
bool mProtected; bool mProtected;
uint32_t mLogoutTimeout; uint32_t mLogoutTimeout;
uint8_t mLoginIp[4];
IApp *mApp; IApp *mApp;
HMSYSTEM *mSys; HMSYSTEM *mSys;

Loading…
Cancel
Save