mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-29 00:36:11 +02:00
improved web API for live
added dark mode option converted all forms to reponsive design repaired menu with password protection #720, #716, #709
This commit is contained in:
parent
53624e466b
commit
70cb0dcd45
19 changed files with 1082 additions and 589 deletions
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
(starting from release version `0.5.66`)
|
(starting from release version `0.5.66`)
|
||||||
|
|
||||||
|
## 0.5.93
|
||||||
|
* improved web API for `live`
|
||||||
|
* added dark mode option
|
||||||
|
* converted all forms to reponsive design
|
||||||
|
* repaired menu with password protection #720, #716, #709
|
||||||
|
|
||||||
## 0.5.92
|
## 0.5.92
|
||||||
* fix mobile menu
|
* fix mobile menu
|
||||||
* fix inverters in select `serial.html` #709
|
* fix inverters in select `serial.html` #709
|
||||||
|
|
|
@ -51,6 +51,7 @@ typedef struct {
|
||||||
char deviceName[DEVNAME_LEN];
|
char deviceName[DEVNAME_LEN];
|
||||||
char adminPwd[PWD_LEN];
|
char adminPwd[PWD_LEN];
|
||||||
uint16_t protectionMask;
|
uint16_t protectionMask;
|
||||||
|
bool darkMode;
|
||||||
|
|
||||||
// wifi
|
// wifi
|
||||||
char stationSsid[SSID_LEN];
|
char stationSsid[SSID_LEN];
|
||||||
|
@ -292,6 +293,7 @@ class settings {
|
||||||
memset(&mCfg, 0, sizeof(settings_t));
|
memset(&mCfg, 0, sizeof(settings_t));
|
||||||
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP
|
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP
|
||||||
| DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT;
|
| DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT;
|
||||||
|
mCfg.sys.darkMode = false;
|
||||||
// restore temp settings
|
// restore temp settings
|
||||||
if(keepWifi)
|
if(keepWifi)
|
||||||
memcpy(&mCfg.sys, &tmp, sizeof(cfgSys_t));
|
memcpy(&mCfg.sys, &tmp, sizeof(cfgSys_t));
|
||||||
|
@ -354,6 +356,7 @@ class settings {
|
||||||
obj[F("dev")] = mCfg.sys.deviceName;
|
obj[F("dev")] = mCfg.sys.deviceName;
|
||||||
obj[F("adm")] = mCfg.sys.adminPwd;
|
obj[F("adm")] = mCfg.sys.adminPwd;
|
||||||
obj[F("prot_mask")] = mCfg.sys.protectionMask;
|
obj[F("prot_mask")] = mCfg.sys.protectionMask;
|
||||||
|
obj[F("dark")] = mCfg.sys.darkMode;
|
||||||
ah::ip2Char(mCfg.sys.ip.ip, buf); obj[F("ip")] = String(buf);
|
ah::ip2Char(mCfg.sys.ip.ip, buf); obj[F("ip")] = String(buf);
|
||||||
ah::ip2Char(mCfg.sys.ip.mask, buf); obj[F("mask")] = String(buf);
|
ah::ip2Char(mCfg.sys.ip.mask, buf); obj[F("mask")] = String(buf);
|
||||||
ah::ip2Char(mCfg.sys.ip.dns1, buf); obj[F("dns1")] = String(buf);
|
ah::ip2Char(mCfg.sys.ip.dns1, buf); obj[F("dns1")] = String(buf);
|
||||||
|
@ -365,6 +368,7 @@ class settings {
|
||||||
snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as<const char*>());
|
snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as<const char*>());
|
||||||
snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as<const char*>());
|
snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as<const char*>());
|
||||||
mCfg.sys.protectionMask = obj[F("prot_mask")];
|
mCfg.sys.protectionMask = obj[F("prot_mask")];
|
||||||
|
mCfg.sys.darkMode = obj[F("dark")];
|
||||||
ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as<const char*>());
|
ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as<const char*>());
|
||||||
ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as<const char*>());
|
ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as<const char*>());
|
||||||
ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as<const char*>());
|
ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as<const char*>());
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
#define VERSION_MAJOR 0
|
#define VERSION_MAJOR 0
|
||||||
#define VERSION_MINOR 5
|
#define VERSION_MINOR 5
|
||||||
#define VERSION_PATCH 92
|
#define VERSION_PATCH 93
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
@ -29,6 +29,10 @@ const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "
|
||||||
"active_PowerLimit", /*"reactivePowerLimit","Powerfactor",*/ "LastAlarmCode"};
|
"active_PowerLimit", /*"reactivePowerLimit","Powerfactor",*/ "LastAlarmCode"};
|
||||||
const char* const notAvail = "n/a";
|
const char* const notAvail = "n/a";
|
||||||
|
|
||||||
|
const uint8_t fieldUnits[] = {UNIT_V, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_KWH,
|
||||||
|
UNIT_V, UNIT_A, UNIT_W, UNIT_HZ, UNIT_C, UNIT_NONE, UNIT_PCT, UNIT_PCT, UNIT_VAR,
|
||||||
|
UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_PCT, UNIT_NONE};
|
||||||
|
|
||||||
// mqtt discovery device classes
|
// mqtt discovery device classes
|
||||||
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
|
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
|
||||||
const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"};
|
const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"};
|
||||||
|
|
|
@ -15,6 +15,7 @@ include_dir = .
|
||||||
[env]
|
[env]
|
||||||
framework = arduino
|
framework = arduino
|
||||||
board_build.filesystem = littlefs
|
board_build.filesystem = littlefs
|
||||||
|
upload_speed = 921600
|
||||||
|
|
||||||
;build_flags =
|
;build_flags =
|
||||||
; ;;;;; Possible Debug options ;;;;;;
|
; ;;;;; Possible Debug options ;;;;;;
|
||||||
|
|
|
@ -24,6 +24,9 @@
|
||||||
#define F(sl) (sl)
|
#define F(sl) (sl)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q};
|
||||||
|
const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR};
|
||||||
|
|
||||||
template<class HMSYSTEM>
|
template<class HMSYSTEM>
|
||||||
class RestApi {
|
class RestApi {
|
||||||
public:
|
public:
|
||||||
|
@ -91,8 +94,12 @@ class RestApi {
|
||||||
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);
|
||||||
else if(path == "record/live") getRecord(root, RealTimeRunData_Debug);
|
else if(path == "record/live") getRecord(root, RealTimeRunData_Debug);
|
||||||
else
|
else {
|
||||||
getNotFound(root, F("http://") + request->host() + F("/api/"));
|
if(path.substring(0, 12) == "inverter/id/")
|
||||||
|
getInverter(root, request->url().substring(17).toInt());
|
||||||
|
else
|
||||||
|
getNotFound(root, F("http://") + request->host() + F("/api/"));
|
||||||
|
}
|
||||||
|
|
||||||
//DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage()));
|
//DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage()));
|
||||||
response->addHeader("Access-Control-Allow-Origin", "*");
|
response->addHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
@ -158,7 +165,7 @@ class RestApi {
|
||||||
File fp = LittleFS.open("/settings.json", "r");
|
File fp = LittleFS.open("/settings.json", "r");
|
||||||
if(!fp) {
|
if(!fp) {
|
||||||
DPRINTLN(DBG_ERROR, F("failed to load settings"));
|
DPRINTLN(DBG_ERROR, F("failed to load settings"));
|
||||||
response = request->beginResponse(200, F("application/json"), "{}");
|
response = request->beginResponse(200, F("application/json; charset=utf-8"), "{}");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
String tmp = fp.readString();
|
String tmp = fp.readString();
|
||||||
|
@ -171,7 +178,7 @@ class RestApi {
|
||||||
tmp.remove(i, tmp.indexOf("\"", i)-i);
|
tmp.remove(i, tmp.indexOf("\"", i)-i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response = request->beginResponse(200, F("application/json"), tmp);
|
response = request->beginResponse(200, F("application/json; charset=utf-8"), tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
response->addHeader("Content-Type", "application/octet-stream");
|
response->addHeader("Content-Type", "application/octet-stream");
|
||||||
|
@ -182,12 +189,11 @@ class RestApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
void getGeneric(JsonObject obj) {
|
void getGeneric(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();
|
||||||
obj[F("menu_maskH")] = ((mConfig->sys.protectionMask >> 8) & 0xff);
|
obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask );
|
||||||
obj[F("menu_maskL")] = ((mConfig->sys.protectionMask ) & 0xff);
|
obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0);
|
||||||
obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0);
|
|
||||||
|
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
obj[F("esp_type")] = F("ESP32");
|
obj[F("esp_type")] = F("ESP32");
|
||||||
|
@ -199,6 +205,7 @@ class RestApi {
|
||||||
void getSysInfo(JsonObject obj) {
|
void getSysInfo(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("mac")] = WiFi.macAddress();
|
obj[F("mac")] = WiFi.macAddress();
|
||||||
obj[F("hostname")] = mConfig->sys.deviceName;
|
obj[F("hostname")] = mConfig->sys.deviceName;
|
||||||
|
@ -312,6 +319,41 @@ class RestApi {
|
||||||
obj[F("rstComStop")] = (bool)mConfig->inst.rstValsCommStop;
|
obj[F("rstComStop")] = (bool)mConfig->inst.rstValsCommStop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void getInverter(JsonObject obj, uint8_t id) {
|
||||||
|
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||||
|
if(NULL != iv) {
|
||||||
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
|
obj[F("id")] = id;
|
||||||
|
obj[F("enabled")] = (bool)iv->config->enabled;
|
||||||
|
obj[F("name")] = String(iv->config->name);
|
||||||
|
obj[F("serial")] = String(iv->config->serial.u64, HEX);
|
||||||
|
obj[F("version")] = String(iv->getFwVersion());
|
||||||
|
obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit);
|
||||||
|
obj[F("ts_last_success")] = rec->ts;
|
||||||
|
|
||||||
|
JsonArray ch = obj.createNestedArray("ch");
|
||||||
|
|
||||||
|
// AC
|
||||||
|
uint8_t pos;
|
||||||
|
obj[F("ch_name")][0] = "AC";
|
||||||
|
JsonArray ch0 = ch.createNestedArray();
|
||||||
|
for (uint8_t fld = 0; fld < sizeof(acList); fld++) {
|
||||||
|
pos = (iv->getPosByChFld(CH0, acList[fld], rec));
|
||||||
|
ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DC
|
||||||
|
for(uint8_t j = 0; j < iv->channels; j ++) {
|
||||||
|
obj[F("ch_name")][j+1] = iv->config->chName[j];
|
||||||
|
JsonArray cur = ch.createNestedArray();
|
||||||
|
for (uint8_t fld = 0; fld < sizeof(dcList); fld++) {
|
||||||
|
pos = (iv->getPosByChFld((j+1), dcList[fld], rec));
|
||||||
|
cur[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void getMqtt(JsonObject obj) {
|
void getMqtt(JsonObject obj) {
|
||||||
obj[F("broker")] = String(mConfig->mqtt.broker);
|
obj[F("broker")] = String(mConfig->mqtt.broker);
|
||||||
obj[F("port")] = String(mConfig->mqtt.port);
|
obj[F("port")] = String(mConfig->mqtt.port);
|
||||||
|
@ -444,12 +486,28 @@ class RestApi {
|
||||||
|
|
||||||
void getLive(JsonObject obj) {
|
void getLive(JsonObject obj) {
|
||||||
getGeneric(obj.createNestedObject(F("generic")));
|
getGeneric(obj.createNestedObject(F("generic")));
|
||||||
JsonArray invArr = obj.createNestedArray(F("inverter"));
|
//JsonArray invArr = obj.createNestedArray(F("inverter"));
|
||||||
obj["refresh_interval"] = mConfig->nrf.sendInterval;
|
obj[F("refresh")] = mConfig->nrf.sendInterval;
|
||||||
|
|
||||||
uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q};
|
for (uint8_t fld = 0; fld < sizeof(acList); fld++) {
|
||||||
|
obj[F("ch0_fld_units")][fld] = String(units[fieldUnits[acList[fld]]]);
|
||||||
|
obj[F("ch0_fld_names")][fld] = String(fields[acList[fld]]);
|
||||||
|
}
|
||||||
|
for (uint8_t fld = 0; fld < sizeof(dcList); fld++) {
|
||||||
|
obj[F("fld_units")][fld] = String(units[fieldUnits[dcList[fld]]]);
|
||||||
|
obj[F("fld_names")][fld] = String(fields[dcList[fld]]);
|
||||||
|
}
|
||||||
|
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||||
|
iv = mSys->getInverterByPos(i);
|
||||||
|
bool parse = false;
|
||||||
|
if(NULL != iv)
|
||||||
|
parse = iv->config->enabled;
|
||||||
|
obj[F("iv")][i] = parse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Inverter<> *iv;
|
||||||
uint8_t pos;
|
uint8_t pos;
|
||||||
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) {
|
||||||
iv = mSys->getInverterByPos(i);
|
iv = mSys->getInverterByPos(i);
|
||||||
|
@ -493,7 +551,7 @@ class RestApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void getRecord(JsonObject obj, uint8_t recType) {
|
void getRecord(JsonObject obj, uint8_t recType) {
|
||||||
|
|
|
@ -33,23 +33,63 @@ iconSuccess = [
|
||||||
/**
|
/**
|
||||||
* GENERIC FUNCTIONS
|
* GENERIC FUNCTIONS
|
||||||
*/
|
*/
|
||||||
|
function ml(tagName, ...args) {
|
||||||
|
var el = document.createElement(tagName);
|
||||||
|
if(args[0]) {
|
||||||
|
for(var name in args[0]) {
|
||||||
|
if(name.indexOf("on") === 0) {
|
||||||
|
el.addEventListener(name.substr(2).toLowerCase(), args[0][name], false)
|
||||||
|
} else {
|
||||||
|
el.setAttribute(name, args[0][name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!args[1]) {
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
return nester(el, args[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
function nester(el, n) {
|
||||||
|
if (typeof n === "string") {
|
||||||
|
var t = document.createTextNode(n);
|
||||||
|
el.appendChild(t);
|
||||||
|
} else if (n instanceof Array) {
|
||||||
|
for(var i = 0; i < n.length; i++) {
|
||||||
|
if (typeof n[i] === "string") {
|
||||||
|
var t = document.createTextNode(n[i]);
|
||||||
|
el.appendChild(t);
|
||||||
|
} else if (n[i] instanceof Node){
|
||||||
|
el.appendChild(n[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (n instanceof Node){
|
||||||
|
el.appendChild(n)
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
function topnav() {
|
function topnav() {
|
||||||
toggle("topnav", "mobile");
|
toggle("topnav", "mobile");
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseNav(obj) {
|
function parseNav(obj) {
|
||||||
for(i = 0; i < 7; i++) {
|
for(i = 0; i < 10; i++) {
|
||||||
|
if(i == 2)
|
||||||
|
continue;
|
||||||
var l = document.getElementById("nav"+i);
|
var l = document.getElementById("nav"+i);
|
||||||
if(window.location.pathname == "/" + l.href.split('/').pop())
|
if(window.location.pathname == "/" + l.href.split('/').pop())
|
||||||
l.classList.add("active");
|
l.classList.add("active");
|
||||||
|
|
||||||
if(obj["menu_protEn"]) {
|
if(obj["menu_protEn"]) {
|
||||||
if(obj["menu_prot"]) {
|
if(obj["menu_prot"]) {
|
||||||
if((((obj["menu_mask"] >> i) & 0x01) == 0x01) || (1 == i))
|
if(0 == i)
|
||||||
l.classList.remove("hide");
|
l.classList.remove("hide");
|
||||||
|
else if(i > 2) {
|
||||||
} else if(0 == i)
|
if(((obj["menu_mask"] >> (i-2)) & 0x01) == 0x00)
|
||||||
|
l.classList.remove("hide");
|
||||||
|
}
|
||||||
|
} else if(0 != i)
|
||||||
l.classList.remove("hide");
|
l.classList.remove("hide");
|
||||||
} else if(i > 1)
|
} else if(i > 1)
|
||||||
l.classList.remove("hide");
|
l.classList.remove("hide");
|
||||||
|
@ -72,7 +112,7 @@ function parseRssi(obj) {
|
||||||
icon = iconWifi1;
|
icon = iconWifi1;
|
||||||
else if(obj["wifi_rssi"] <= -70)
|
else if(obj["wifi_rssi"] <= -70)
|
||||||
icon = iconWifi2;
|
icon = iconWifi2;
|
||||||
document.getElementById("wifiicon").replaceChildren(svg(icon, 32, 32, "#fff", null, obj["wifi_rssi"]));
|
document.getElementById("wifiicon").replaceChildren(svg(icon, 32, 32, "wifi", obj["wifi_rssi"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHide(id, hide) {
|
function setHide(id, hide) {
|
||||||
|
@ -201,11 +241,10 @@ function link(dst, text, target=null) {
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
function svg(data=null, w=24, h=24, color="#000", cl=null, tooltip=null) {
|
function svg(data=null, w=24, h=24, cl=null, tooltip=null) {
|
||||||
var s = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
var s = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||||
s.setAttribute('width', w);
|
s.setAttribute('width', w);
|
||||||
s.setAttribute('height', h);
|
s.setAttribute('height', h);
|
||||||
s.setAttribute('fill', color);
|
|
||||||
s.setAttribute('viewBox', '0 0 16 16');
|
s.setAttribute('viewBox', '0 0 16 16');
|
||||||
if(null != cl) s.setAttribute('class', cl);
|
if(null != cl) s.setAttribute('class', cl);
|
||||||
if(null != data) {
|
if(null != data) {
|
||||||
|
|
27
src/web/html/colorBright.css
Normal file
27
src/web/html/colorBright.css
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
:root {
|
||||||
|
--bg: #fff;
|
||||||
|
--fg: #000;
|
||||||
|
--fg2: #fff;
|
||||||
|
|
||||||
|
--info: #0000dd;
|
||||||
|
--warn: #ff7700;
|
||||||
|
--success: #009900;
|
||||||
|
|
||||||
|
--input-bg: #eee;
|
||||||
|
|
||||||
|
--nav-bg: #333;
|
||||||
|
--primary: #006ec0;
|
||||||
|
--primary-hover: #044e86;
|
||||||
|
--secondary: #0072c8;
|
||||||
|
--nav-active: #555;
|
||||||
|
--footer-bg: #282828;
|
||||||
|
|
||||||
|
--total-head-title: #8e5903;
|
||||||
|
--total-bg: #b06e04;
|
||||||
|
--iv-head-title: #1c6800;
|
||||||
|
--iv-head-bg: #32b004;
|
||||||
|
--ch-head-title: #003c80;
|
||||||
|
--ch-head-bg: #006ec0;
|
||||||
|
--ts-head: #333;
|
||||||
|
--ts-bg: #555;
|
||||||
|
}
|
27
src/web/html/colorDark.css
Normal file
27
src/web/html/colorDark.css
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
:root {
|
||||||
|
--bg: #222;
|
||||||
|
--fg: #ccc;
|
||||||
|
--fg2: #fff;
|
||||||
|
|
||||||
|
--info: #0072c8;
|
||||||
|
--warn: #ffaa00;
|
||||||
|
--success: #00bb00;
|
||||||
|
|
||||||
|
--input-bg: #333;
|
||||||
|
|
||||||
|
--nav-bg: #333;
|
||||||
|
--primary: #004d87;
|
||||||
|
--primary-hover: #023155;
|
||||||
|
--secondary: #0072c8;
|
||||||
|
--nav-active: #555;
|
||||||
|
--footer-bg: #282828;
|
||||||
|
|
||||||
|
--total-head-title: #555511;
|
||||||
|
--total-bg: #666622;
|
||||||
|
--iv-head-title: #115511;
|
||||||
|
--iv-head-bg: #226622;
|
||||||
|
--ch-head-title: #112255;
|
||||||
|
--ch-head-bg: #223366;
|
||||||
|
--ts-head: #333;
|
||||||
|
--ts-bg: #555;
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
<link rel="stylesheet" type="text/css" href="colors.css"/>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta charset="UTF-8">
|
||||||
<script type="text/javascript" src="api.js"></script>
|
<script type="text/javascript" src="api.js"></script>
|
||||||
|
|
|
@ -6,15 +6,15 @@
|
||||||
<span></span>
|
<span></span>
|
||||||
</a>
|
</a>
|
||||||
<div id="topnav" class="mobile">
|
<div id="topnav" class="mobile">
|
||||||
<a id="nav2" class="hide" href="/live">Live</a>
|
<a id="nav3" class="hide" href="/live">Live</a>
|
||||||
<a id="nav3" class="hide" href="/serial">Serial / Control</a>
|
<a id="nav4" class="hide" href="/serial">Serial / Control</a>
|
||||||
<a id="nav4" class="hide" href="/setup">Settings</a>
|
<a id="nav5" class="hide" href="/setup">Settings</a>
|
||||||
<span class="seperator"></span>
|
<span class="seperator"></span>
|
||||||
<a id="nav5" class="hide" href="/update">Update</a>
|
<a id="nav6" class="hide" href="/update">Update</a>
|
||||||
<a id="nav6" class="hide" href="/system">System</a>
|
<a id="nav7" class="hide" href="/system">System</a>
|
||||||
<span class="seperator"></span>
|
<span class="seperator"></span>
|
||||||
<a id="nav7" href="/api" target="_blank">REST API</a>
|
<a id="nav8" href="/api" target="_blank">REST API</a>
|
||||||
<a id="nav8" href="https://ahoydtu.de" target="_blank">Documentation</a>
|
<a id="nav9" href="https://ahoydtu.de" target="_blank">Documentation</a>
|
||||||
<span class="seperator"></span>
|
<span class="seperator"></span>
|
||||||
<a id="nav0" class="hide" href="/login">Login</a>
|
<a id="nav0" class="hide" href="/login">Login</a>
|
||||||
<a id="nav1" class="hide" href="/logout">Logout</a>
|
<a id="nav1" class="hide" href="/logout">Logout</a>
|
||||||
|
|
|
@ -67,12 +67,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseGeneric(obj) {
|
function parseGeneric(obj) {
|
||||||
// Disclaimer
|
if(exeOnce)
|
||||||
//if(obj["disclaimer"] == false) sessionStorage.setItem("gDisclaimer", promptFunction());
|
|
||||||
/*if(exeOnce){
|
|
||||||
parseVersion(obj);
|
|
||||||
parseESP(obj);
|
parseESP(obj);
|
||||||
}*/
|
|
||||||
parseRssi(obj);
|
parseRssi(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,14 +120,14 @@
|
||||||
var p = div(["none"]);
|
var p = div(["none"]);
|
||||||
for(var i of obj) {
|
for(var i of obj) {
|
||||||
var icon = iconWarn;
|
var icon = iconWarn;
|
||||||
var color = "#F70";
|
var cl = "icon-warn";
|
||||||
avail = "";
|
avail = "";
|
||||||
if(false == i["enabled"]) {
|
if(false == i["enabled"]) {
|
||||||
avail = "disabled";
|
avail = "disabled";
|
||||||
}
|
}
|
||||||
else if(false == i["is_avail"]) {
|
else if(false == i["is_avail"]) {
|
||||||
icon = iconInfo;
|
icon = iconInfo;
|
||||||
color = "#00d";
|
cl = "icon-info";
|
||||||
avail = "not yet available";
|
avail = "not yet available";
|
||||||
}
|
}
|
||||||
else if(0 == i["ts_last_success"]) {
|
else if(0 == i["ts_last_success"]) {
|
||||||
|
@ -144,12 +140,12 @@
|
||||||
if(false == i["is_producing"])
|
if(false == i["is_producing"])
|
||||||
avail += "not ";
|
avail += "not ";
|
||||||
else
|
else
|
||||||
color = "#090";
|
cl = "icon-success";
|
||||||
avail += "producing";
|
avail += "producing";
|
||||||
}
|
}
|
||||||
|
|
||||||
p.append(
|
p.append(
|
||||||
svg(icon, 20, 20, color, "icon"),
|
svg(icon, 30, 30, "icon " + cl),
|
||||||
span("Inverter #" + i["id"] + ": " + i["name"] + " (v" + i["version"] + ") is " + avail),
|
span("Inverter #" + i["id"] + ": " + i["name"] + " (v" + i["version"] + ") is " + avail),
|
||||||
br()
|
br()
|
||||||
);
|
);
|
||||||
|
@ -167,22 +163,22 @@
|
||||||
function parseWarnInfo(warn, success) {
|
function parseWarnInfo(warn, success) {
|
||||||
var p = div(["none"]);
|
var p = div(["none"]);
|
||||||
for(var w of warn) {
|
for(var w of warn) {
|
||||||
p.append(svg(iconWarn, 20, 20, "#F70", "icon"), span(w), br());
|
p.append(svg(iconWarn, 30, 30, "icon icon-warn"), span(w), br());
|
||||||
}
|
}
|
||||||
for(var i of success) {
|
for(var i of success) {
|
||||||
p.append(svg(iconSuccess, 20, 20, "#090", "icon"), span(i), br());
|
p.append(svg(iconSuccess, 30, 30, "icon icon-success"), span(i), br());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(commInfo.length > 0)
|
if(commInfo.length > 0)
|
||||||
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span(commInfo), br());
|
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span(commInfo), br());
|
||||||
|
|
||||||
if(null != release) {
|
if(null != release) {
|
||||||
if(getVerInt("{#VERSION}") < getVerInt(release))
|
if(getVerInt("{#VERSION}") < getVerInt(release))
|
||||||
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span("Update available, current released version: " + release), br());
|
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("Update available, current released version: " + release), br());
|
||||||
else if(getVerInt("{#VERSION}") > getVerInt(release))
|
else if(getVerInt("{#VERSION}") > getVerInt(release))
|
||||||
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span("You are using development version {#VERSION}. In case of issues you may want to try the current stable release: " + release), br());
|
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("You are using development version {#VERSION}. In case of issues you may want to try the current stable release: " + release), br());
|
||||||
else
|
else
|
||||||
p.append(svg(iconInfo, 20, 20, "#00d", "icon"), span("You are using the current stable release: " + release), br());
|
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("You are using the current stable release: " + release), br());
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("warn_info").replaceChildren(p);
|
document.getElementById("warn_info").replaceChildren(p);
|
||||||
|
|
|
@ -7,11 +7,13 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="login">
|
<div id="login">
|
||||||
<div class="pad">
|
<div class="p-4">
|
||||||
<form action="/login" method="post">
|
<form action="/login" method="post">
|
||||||
<h2>AhoyDTU</h2>
|
<div class="row"><h2>AhoyDTU</h2></div>
|
||||||
<input type="password" name="pwd" value="" autofocus>
|
<div class="row">
|
||||||
<input type="submit" name="login" value="login" class="btn">
|
<div class="col-8"><input type="password" name="pwd" autofocus></div>
|
||||||
|
<div class="col-4"><input type="submit" name="login" value="login" class="btn"></div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,37 +8,56 @@
|
||||||
{#HTML_NAV}
|
{#HTML_NAV}
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<div class="serial">
|
<div class="row">
|
||||||
<textarea id="serial" cols="80" rows="20" readonly></textarea><br/>
|
<textarea id="serial" class="mt-3" cols="80" rows="20" readonly></textarea>
|
||||||
connected: <span class="dot" id="connected"></span>
|
</div>
|
||||||
Uptime: <span id="uptime"></span>
|
<div class="row my-3">
|
||||||
<input type="button" value="clear" class="btn" id="clear"/>
|
<div class="col-3">connected: <span class="dot" id="connected"></span></div>
|
||||||
<input type="button" value="autoscroll" class="btn" id="scroll"/>
|
<div class="col-3 col-sm-4 my-3">Uptime: <span id="uptime"></span></div>
|
||||||
<div class="hr mt-3 mb-3"></div>
|
<div class="col-6 col-sm-4">
|
||||||
|
<input type="button" value="clear" class="btn" id="clear"/>
|
||||||
|
<input type="button" value="autoscroll" class="btn" id="scroll"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hr my-3"></div>
|
||||||
|
<div class="row mb-3">
|
||||||
<h3>Commands</h3>
|
<h3>Commands</h3>
|
||||||
<label for="iv">Select Inverter:</label>
|
</div>
|
||||||
<select name="iv" id="InvID">
|
<div class="row mb-3">
|
||||||
</select>
|
<div class="col-12 col-sm-3 my-2">Select Inverter</div>
|
||||||
<label for="pwrlimval">Power Limit Value</label>
|
<div class="col-12 col-sm-9"><select name="iv" id="InvID"></select></div>
|
||||||
<input type="number" class="text" name="pwrlimval" maxlength="4"/>
|
</div>
|
||||||
<label for="pwrlimctrl">Power Limit Command</label>
|
<div class="row mb-3">
|
||||||
<select name="pwrlimctrl">
|
<div class="col-12 col-sm-3 my-2">Power Limit Command</div>
|
||||||
<option value="" selected disabled hidden>select the unit and persistence</option>
|
<div class="col-12 col-sm-9">
|
||||||
<option value="limit_nonpersistent_absolute">absolute non persistent [W]</option>
|
<select name="pwrlimctrl">
|
||||||
<option value="limit_nonpersistent_relative">relative non persistent [%]</option>
|
<option value="" selected disabled hidden>select the unit and persistence</option>
|
||||||
<option value="limit_persistent_absolute">absolute persistent [W]</option>
|
<option value="limit_nonpersistent_absolute">absolute non persistent [W]</option>
|
||||||
<option value="limit_persistent_relative">relative persistent [%]</option>
|
<option value="limit_nonpersistent_relative">relative non persistent [%]</option>
|
||||||
</select>
|
<option value="limit_persistent_absolute">absolute persistent [W]</option>
|
||||||
<br/>
|
<option value="limit_persistent_relative">relative persistent [%]</option>
|
||||||
<input type="button" value="Send Power Limit" class="btn" id="sendpwrlim"/>
|
</select>
|
||||||
<div class="hr mt-3 mb-3"></div>
|
</div>
|
||||||
<div id="power" class="mt-3">
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Power Limit Value</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="number" name="pwrlimval" maxlength="4"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3"></div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="button" value="Send Power Limit" class="btn" id="sendpwrlim"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Control Inverter</div>
|
||||||
|
<div class="col-12 col-sm-9" id="power">
|
||||||
<input type="button" value="Restart" class="btn" id="restart"/>
|
<input type="button" value="Restart" class="btn" id="restart"/>
|
||||||
<input type="button" value="Turn Off" class="btn" id="power_off"/>
|
<input type="button" value="Turn Off" class="btn" id="power_off"/>
|
||||||
<input type="button" value="Turn On" class="btn" id="power_on"/>
|
<input type="button" value="Turn On" class="btn" id="power_on"/>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
</div>
|
||||||
<p>Ctrl result: <span id="result">n/a</span></p>
|
<div class="row mb-5">
|
||||||
|
<div class="col-3 my-2">Ctrl result</div>
|
||||||
|
<div class="col-9 my-2"><span id="result">n/a</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,141 +20,20 @@
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<form method="post" action="/save">
|
<form method="post" action="/save">
|
||||||
<fieldset>
|
<button type="button" class="s_collapsible mt-4">System Config</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-2">
|
||||||
<legend class="des">Device Host Name</legend>
|
<legend class="des">Device Host Name</legend>
|
||||||
<label for="device">Device Name</label>
|
<div class="row mb-3">
|
||||||
<input type="text" name="device" class="text"/>
|
<div class="col-12 col-sm-3">Device Name</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="device"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-8 col-sm-3">Dark Mode</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="darkMode"/></div>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<fieldset class="mb-4">
|
||||||
<button type="button" class="s_collapsible">Network</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">WiFi</legend>
|
|
||||||
<p>Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.</p>
|
|
||||||
<label for="scanbtn">Search Networks</label>
|
|
||||||
<input type="button" name="scanbtn" id="scanbtn" class="btn" value="scan" onclick="scan()"/><br/>
|
|
||||||
<label for="networks">Avail Networks</label>
|
|
||||||
<select name="networks" id="networks" onChange="selNet()">
|
|
||||||
<option value="-1" selected disabled hidden>not scanned</option>
|
|
||||||
</select>
|
|
||||||
<label for="ssid">SSID</label>
|
|
||||||
<input type="text" name="ssid" class="text"/>
|
|
||||||
<label for="pwd">Password</label>
|
|
||||||
<input type="password" class="text" name="pwd" value="{PWD}"/>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">Static IP (optional)</legend>
|
|
||||||
<p>
|
|
||||||
Leave fields blank for DHCP<br/>
|
|
||||||
The following fields are parsed in this format: 192.168.1.1
|
|
||||||
</p>
|
|
||||||
<label for="ipAddr">IP Address</label>
|
|
||||||
<input type="text" name="ipAddr" class="text" maxlength="15" />
|
|
||||||
<label for="ipMask">Submask</label>
|
|
||||||
<input type="text" name="ipMask" class="text" maxlength="15" />
|
|
||||||
<label for="ipDns1">DNS 1</label>
|
|
||||||
<input type="text" name="ipDns1" class="text" maxlength="15" />
|
|
||||||
<label for="ipDns2">DNS 2</label>
|
|
||||||
<input type="text" name="ipDns2" class="text" maxlength="15" />
|
|
||||||
<label for="ipGateway">Gateway</label>
|
|
||||||
<input type="text" name="ipGateway" class="text" maxlength="15" />
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">Protection</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">Protection</legend>
|
|
||||||
<label for="adminpwd">Admin Password</label>
|
|
||||||
<input type="password" name="adminpwd" class="text" value="{PWD}"/>
|
|
||||||
<input type="hidden" name="disclaimer" value="false" id="disclaimer">
|
|
||||||
<p>Select pages which should be protected by password</p>
|
|
||||||
<div id="prot_mask"></div>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">Inverter</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">Inverter</legend>
|
|
||||||
<div id="inverter"></div><br/>
|
|
||||||
<input type="button" id="btnAdd" class="btn" value="Add Inverter"/>
|
|
||||||
<p class="subdes">General</p>
|
|
||||||
<label for="invInterval">Interval [s]</label>
|
|
||||||
<input type="text" class="text" name="invInterval" pattern="[0-9]+" title="Invalid input"/>
|
|
||||||
<label for="invRetry">Max retries per Payload</label>
|
|
||||||
<input type="text" class="text" name="invRetry"/>
|
|
||||||
|
|
||||||
|
|
||||||
<label for="invRstMid">Reset values and YieldDay at midnight</label>
|
|
||||||
<input type="checkbox" class="cb" name="invRstMid"/><br/>
|
|
||||||
<label for="invRstComStop">Reset values when inverter polling stops at sunset</label>
|
|
||||||
<input type="checkbox" class="cb" name="invRstComStop"/><br/>
|
|
||||||
<label for="invRstNotAvail">Reset values when inverter status is 'not available'</label>
|
|
||||||
<input type="checkbox" class="cb" name="invRstNotAvail"/><br/>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">NTP Server</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">NTP Server</legend>
|
|
||||||
<label for="ntpAddr">NTP Server / IP</label>
|
|
||||||
<input type="text" class="text" name="ntpAddr"/>
|
|
||||||
<label for="ntpPort">NTP Port</label>
|
|
||||||
<input type="text" class="text" name="ntpPort"/>
|
|
||||||
<label for="ntpBtn">set system time</label>
|
|
||||||
<input type="button" name="ntpBtn" id="ntpBtn" class="btn" value="from browser" onclick="setTime()"/>
|
|
||||||
<input type="button" name="ntpSync" id="ntpSync" class="btn" value="sync NTP" onclick="syncTime()"/>
|
|
||||||
<span id="apiResultNtp"></span>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">Sunrise & Sunset</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">Sunrise & Sunset</legend>
|
|
||||||
<p>
|
|
||||||
Use a decimal separator: '.' (dot) for Latitude and Longitude
|
|
||||||
</p>
|
|
||||||
<label for="sunLat">Latitude (decimal)</label>
|
|
||||||
<input type="text" class="text" name="sunLat"/>
|
|
||||||
<label for="sunLon">Longitude (decimal)</label>
|
|
||||||
<input type="text" class="text" name="sunLon"/>
|
|
||||||
<label for="sunOffs">Offset (pre sunrise, post sunset)</label>
|
|
||||||
<select name="sunOffs"></select>
|
|
||||||
<br>
|
|
||||||
<label for="sunDisNightCom">Stop polling inverters during night</label>
|
|
||||||
<input type="checkbox" class="cb" name="sunDisNightCom"/><br/>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">MQTT</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">MQTT</legend>
|
|
||||||
<label for="mqttAddr">Broker / Server IP</label>
|
|
||||||
<input type="text" class="text" name="mqttAddr"/>
|
|
||||||
<label for="mqttPort">Port</label>
|
|
||||||
<input type="text" class="text" name="mqttPort"/>
|
|
||||||
<label for="mqttUser">Username (optional)</label>
|
|
||||||
<input type="text" class="text" name="mqttUser"/>
|
|
||||||
<label for="mqttPwd">Password (optional)</label>
|
|
||||||
<input type="password" class="text" name="mqttPwd"/>
|
|
||||||
<label for="mqttTopic">Topic</label>
|
|
||||||
<input type="text" class="text" name="mqttTopic" pattern="[A-Za-z0-9./#$%&=+_-]+" title="Invalid input" />
|
|
||||||
<p class="des">Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)</p>
|
|
||||||
<label for="mqttIntvl">Interval [s]</label>
|
|
||||||
<input type="text" class="text" name="mqttInterval" pattern="[0-9]+" title="Invalid input" />
|
|
||||||
<label for="mqttBtn">Discovery Config (homeassistant)</label>
|
|
||||||
<input type="button" name="mqttDiscovery" id="mqttDiscovery" class="btn" value="send" onclick="sendDiscoveryConfig()"/>
|
|
||||||
<span id="apiResultMqtt"></span>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">System Config</button>
|
|
||||||
<div class="s_content">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="des">System Config</legend>
|
<legend class="des">System Config</legend>
|
||||||
<p class="des">Pinout</p>
|
<p class="des">Pinout</p>
|
||||||
<div id="pinout"></div>
|
<div id="pinout"></div>
|
||||||
|
@ -163,52 +42,265 @@
|
||||||
<div id="rf24"></div>
|
<div id="rf24"></div>
|
||||||
|
|
||||||
<p class="des">Serial Console</p>
|
<p class="des">Serial Console</p>
|
||||||
<label for="serEn">print inverter data</label>
|
<div class="row mb-3">
|
||||||
<input type="checkbox" class="cb" name="serEn"/><br/>
|
<div class="col-8 col-sm-3">print inverter data</div>
|
||||||
<label for="serDbg">Serial Debug</label>
|
<div class="col-4 col-sm-9"><input type="checkbox" name="serEn"/></div>
|
||||||
<input type="checkbox" class="cb" name="serDbg"/><br/>
|
</div>
|
||||||
<label for="serIntvl">Interval [s]</label>
|
<div class="row mb-3">
|
||||||
<input type="text" class="text" name="serIntvl" pattern="[0-9]+" title="Invalid input"/>
|
<div class="col-8 col-sm-3">Serial Debug</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="serDbg"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Interval [s]</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="serIntvl" pattern="[0-9]+" title="Invalid input"/></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="s_collapsible">Network</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-2">
|
||||||
|
<legend class="des">WiFi</legend>
|
||||||
|
<p>Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.</p>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Search Networks</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="button" name="scanbtn" id="scanbtn" class="btn" value="scan" onclick="scan()"/></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-2 mb-sm-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Avail Networks</div>
|
||||||
|
<div class="col-12 col-sm-9">
|
||||||
|
<select name="networks" id="networks" onChange="selNet()">
|
||||||
|
<option value="-1" selected disabled hidden>not scanned</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2 mb-sm-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">SSID</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ssid"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2 mb-sm-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Password</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="password" name="pwd" value="{PWD}"/></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="mb-4">
|
||||||
|
<legend class="des">Static IP (optional)</legend>
|
||||||
|
<p>
|
||||||
|
Leave fields blank for DHCP<br/>
|
||||||
|
The following fields are parsed in this format: 192.168.4.1
|
||||||
|
</p>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">IP Address</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ipAddr" maxlength="15" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Submask</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ipMask" maxlength="15" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">DNS 1</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ipDns1" maxlength="15" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">DNS 2</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ipDns2" maxlength="15" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Gateway</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ipGateway" maxlength="15" /></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="s_collapsible">Protection</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-4">
|
||||||
|
<legend class="des mx-2">Protection</legend>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 mb-2 mt-2">Admin Password</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="password" name="adminpwd" value="{PWD}"/></div>
|
||||||
|
</div>
|
||||||
|
<p>Select pages which should be protected by password</p>
|
||||||
|
<div id="prot_mask"></div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="s_collapsible">Inverter</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-4">
|
||||||
|
<legend class="des">Inverter</legend>
|
||||||
|
<div id="inverter"></div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-12 col-sm-3"></div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="button" id="btnAdd" class="btn" value="Add Inverter"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-12 col-sm-3"><p class="subdes">General</p></div>
|
||||||
|
<div class="col-12 col-sm-9"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Interval [s]</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="invInterval" pattern="[0-9]+" title="Invalid input"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Max retries per Payload</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="invRetry"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-8 col-sm-3 mb-2">Reset values and YieldDay at midnight</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="invRstMid"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-8 col-sm-3 mb-2">Reset values when inverter polling stops at sunset</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="invRstComStop"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-8 col-sm-3">Reset values when inverter status is 'not available'</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="invRstNotAvail"/></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="s_collapsible">NTP Server</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-4">
|
||||||
|
<legend class="des">NTP Server</legend>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">NTP Server / IP</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ntpAddr"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">NTP Port</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="ntpPort"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">set system time</div>
|
||||||
|
<div class="col-12 col-sm-9">
|
||||||
|
<input type="button" name="ntpBtn" id="ntpBtn" class="btn" value="from browser" onclick="setTime()"/>
|
||||||
|
<input type="button" name="ntpSync" id="ntpSync" class="btn" value="sync NTP" onclick="syncTime()"/>
|
||||||
|
<span id="apiResultNtp"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="s_collapsible">Sunrise & Sunset</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-4">
|
||||||
|
<legend class="des">Sunrise & Sunset</legend>
|
||||||
|
<p>Use a decimal separator: '.' (dot) for Latitude and Longitude</p>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Latitude (decimal)</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="sunLat"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Longitude (decimal)</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="sunLon"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Offset (pre sunrise, post sunset)</div>
|
||||||
|
<div class="col-12 col-sm-9"><select name="sunOffs"></select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-8 col-sm-3">Stop polling inverters during night</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="sunDisNightCom"/></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="s_collapsible">MQTT</button>
|
||||||
|
<div class="s_content">
|
||||||
|
<fieldset class="mb-4">
|
||||||
|
<legend class="des">MQTT</legend>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Broker / Server IP</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="mqttAddr"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Port</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="mqttPort"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Username (optional)</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="mqttUser"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Password (optional)</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="password" name="mqttPwd"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Topic</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="mqttTopic" pattern="[A-Za-z0-9./#$%&=+_-]+" title="Invalid input" /></div>
|
||||||
|
</div>
|
||||||
|
<p class="des">Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)</p>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Interval [s]</div>
|
||||||
|
<div class="col-12 col-sm-9"><input type="text" name="mqttInterval" pattern="[0-9]+" title="Invalid input" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Discovery Config (homeassistant)</div>
|
||||||
|
<div class="col-12 col-sm-9">
|
||||||
|
<input type="button" name="mqttDiscovery" id="mqttDiscovery" class="btn" value="send" onclick="sendDiscoveryConfig()"/>
|
||||||
|
<span id="apiResultMqtt"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="s_collapsible">Display Config</button>
|
<button type="button" class="s_collapsible">Display Config</button>
|
||||||
<div class="s_content">
|
<div class="s_content">
|
||||||
<fieldset>
|
<fieldset class="mb-4">
|
||||||
<legend class="des">Display Config</legend>
|
<legend class="des">Display Config</legend>
|
||||||
<div id="dispType"></div>
|
<div id="dispType"></div>
|
||||||
<label for="logoEn">Show Logo</label>
|
<div class="row mb-3">
|
||||||
<input type="checkbox" class="cb" name="logoEn"/><br/>
|
<div class="col-8 col-sm-3">Show Logo</div>
|
||||||
<label for="dispPwr">Turn off while inverters are offline</label>
|
<div class="col-4 col-sm-9"><input type="checkbox" name="logoEn"/></div>
|
||||||
<input type="checkbox" class="cb" name="dispPwr"/><br/>
|
</div>
|
||||||
<label for="dispPxSh">Enable pixel shifting</label>
|
<div class="row mb-3">
|
||||||
<input type="checkbox" class="cb" name="dispPxSh"/><br/>
|
<div class="col-8 col-sm-3">Turn off while inverters are offline</div>
|
||||||
<label for="disp180">Rotate 180 degree</label>
|
<div class="col-4 col-sm-9"><input type="checkbox" name="dispPwr"/></div>
|
||||||
<input type="checkbox" class="cb" name="disp180"/><br/>
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-8 col-sm-3">Enable pixel shifting</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="dispPxSh"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-8 col-sm-3">Rotate 180 degree</div>
|
||||||
|
<div class="col-4 col-sm-9"><input type="checkbox" name="disp180"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-sm-3 my-2">Contrast</div>
|
||||||
|
<div class="col-12 col-sm-9"><select name="dispCont" id="contrast"></select></div>
|
||||||
|
</div>
|
||||||
|
<label for="dispCont"></label>
|
||||||
|
|
||||||
<label for="dispCont">Contrast</label>
|
|
||||||
<select name="dispCont" id="contrast"></select>
|
|
||||||
<p class="des">Pinout</p>
|
<p class="des">Pinout</p>
|
||||||
<div id="dispPins"></div>
|
<div id="dispPins"></div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="row mb-4 mt-4">
|
||||||
<label for="reboot">Reboot device after successful save</label>
|
<div class="col-8 col-sm-3">Reboot device after successful save</div>
|
||||||
<input type="checkbox" class="cb" name="reboot" checked />
|
<div class="col-2 col-md-6">
|
||||||
<input type="submit" value="save" class="btn right"/>
|
<input type="checkbox" name="reboot" checked />
|
||||||
|
<input type="submit" value="save" class="btn right"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hr mb-3 mt-3"></div>
|
<div class="hr mb-3 mt-3"></div>
|
||||||
<div class="mb-4">
|
<div class="mb-4 mt-4">
|
||||||
<a class="btn" href="/erase">ERASE SETTINGS (not WiFi)</a>
|
<a class="btn" href="/erase">ERASE SETTINGS (not WiFi)</a>
|
||||||
<fieldset>
|
<fieldset class="mb-4">
|
||||||
<legend class="des">Upload / Store JSON Settings</legend>
|
<legend class="des">Upload / Store JSON Settings</legend>
|
||||||
<form id="form" method="POST" action="/upload" enctype="multipart/form-data" accept-charset="utf-8">
|
<form id="form" method="POST" action="/upload" enctype="multipart/form-data" accept-charset="utf-8">
|
||||||
<input type="file" name="upload">
|
<input type="file" name="upload">
|
||||||
<input type="button" class="btn" value="Upload" onclick="hide()">
|
<input type="button" class="btn" value="Upload" onclick="hide()">
|
||||||
</form>
|
</form>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<a class="btn" href="/get_setup" target="_blank">Download settings (JSON file)</a> (only saved values, passwords will be removed!)
|
<a class="btn" href="/get_setup" target="_blank">Download settings (JSON file)</a><span> (only saved values, passwords will be removed!)</span>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -343,23 +435,38 @@
|
||||||
document.getElementsByName(id + "Name")[0].value = "";
|
document.getElementsByName(id + "Name")[0].value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mlCb(id, des, chk=false) {
|
||||||
|
var cb = ml("input", {type: "checkbox", id: id, name: id}, "");
|
||||||
|
if(chk)
|
||||||
|
cb.checked = true;
|
||||||
|
return ml("div", {class: "row mb-3"}, [
|
||||||
|
ml("div", {class: "col-8 col-sm-3"}, des),
|
||||||
|
ml("div", {class: "col-4 col-sm-9"}, cb)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mlE(des, e) {
|
||||||
|
return ml("div", {class: "row mb-3"}, [
|
||||||
|
ml("div", {class: "col-12 col-sm-3 my-2"}, des),
|
||||||
|
ml("div", {class: "col-12 col-sm-9"}, e)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
function ivHtml(obj, id) {
|
function ivHtml(obj, id) {
|
||||||
highestId = id + 1;
|
highestId = id + 1;
|
||||||
if(highestId == maxInv)
|
if(highestId == maxInv)
|
||||||
setHide("btnAdd", true);
|
setHide("btnAdd", true);
|
||||||
iv = document.getElementById("inverter");
|
|
||||||
|
var iv = document.getElementById("inverter");
|
||||||
iv.appendChild(des("Inverter " + id));
|
iv.appendChild(des("Inverter " + id));
|
||||||
id = "inv" + id;
|
id = "inv" + id;
|
||||||
|
|
||||||
iv.appendChild(lbl(id + "Enable", "Communication Enable"));
|
|
||||||
var en = inp(id + "Enable", null, null, ["cb"], id + "Enable", "checkbox");
|
|
||||||
en.checked = obj["enabled"];
|
|
||||||
iv.appendChild(en);
|
|
||||||
iv.appendChild(br());
|
|
||||||
|
|
||||||
iv.appendChild(lbl(id + "Addr", "Serial Number (12 digits)*"));
|
|
||||||
var addr = inp(id + "Addr", obj["serial"], 12, ["text"], null, "text", "[0-9]+", "Invalid input");
|
var addr = inp(id + "Addr", obj["serial"], 12, ["text"], null, "text", "[0-9]+", "Invalid input");
|
||||||
iv.appendChild(addr);
|
iv.append(
|
||||||
|
mlCb(id + "Enable", "Communication Enable", obj["enabled"]),
|
||||||
|
mlE("Serial Number (12 digits)*", addr)
|
||||||
|
);
|
||||||
|
|
||||||
['keyup', 'change'].forEach(function(evt) {
|
['keyup', 'change'].forEach(function(evt) {
|
||||||
addr.addEventListener(evt, (e) => {
|
addr.addEventListener(evt, (e) => {
|
||||||
var serial = addr.value.substring(0,4);
|
var serial = addr.value.substring(0,4);
|
||||||
|
@ -369,9 +476,9 @@
|
||||||
setHide(id+"ModName"+i, true);
|
setHide(id+"ModName"+i, true);
|
||||||
setHide(id+"YieldCor"+i, true);
|
setHide(id+"YieldCor"+i, true);
|
||||||
}
|
}
|
||||||
setHide("lbl"+id+"ModPwr", true);
|
setHide("row"+id+"ModPwr", true);
|
||||||
setHide("lbl"+id+"ModName", true);
|
setHide("row"+id+"ModName", true);
|
||||||
setHide("lbl"+id+"YieldCor", true);
|
setHide("row"+id+"YieldCor", true);
|
||||||
|
|
||||||
if(serial.charAt(0) == 1) {
|
if(serial.charAt(0) == 1) {
|
||||||
if((serial.charAt(1) == 0) || (serial.charAt(1) == 1)) {
|
if((serial.charAt(1) == 0) || (serial.charAt(1) == 1)) {
|
||||||
|
@ -391,39 +498,44 @@
|
||||||
setHide(id+"ModName"+i, false);
|
setHide(id+"ModName"+i, false);
|
||||||
setHide(id+"YieldCor"+i, false);
|
setHide(id+"YieldCor"+i, false);
|
||||||
}
|
}
|
||||||
setHide("lbl"+id+"ModPwr", false);
|
setHide("row"+id+"ModPwr", false);
|
||||||
setHide("lbl"+id+"ModName", false);
|
setHide("row"+id+"ModName", false);
|
||||||
setHide("lbl"+id+"YieldCor", false);
|
setHide("row"+id+"YieldCor", false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
iv.append(
|
iv.append(mlE("Name*", inp(id + "Name", obj["name"], 16, ["text"], null, "text", "[A-Za-z0-9./#$%&=+_-]+", "Invalid input")));
|
||||||
lbl(id + "Name", "Name*"),
|
|
||||||
inp(id + "Name", obj["name"], 16, ["text"], null, "text", "[A-Za-z0-9./#$%&=+_-]+", "Invalid input")
|
|
||||||
);
|
|
||||||
|
|
||||||
for(var j of [
|
for(var j of [
|
||||||
["ModPwr", "ch_max_power", "Max Module Power (Wp)", 4, "[0-9]+"],
|
["ModPwr", "ch_max_power", "Max Module Power (Wp)", 4, "[0-9]+"],
|
||||||
["ModName", "ch_name", "Module Name", 16, null],
|
["ModName", "ch_name", "Module Name", 16, null],
|
||||||
["YieldCor", "ch_yield_cor", "Yield Total Correction [kWh]", 16, "[0-9-]+"]]) {
|
["YieldCor", "ch_yield_cor", "Yield Total Correction [kWh]", 16, "[0-9-]+"]]) {
|
||||||
|
|
||||||
var cl = (re.test(obj["serial"])) ? null : ["hide"];
|
var cl = (re.test(obj["serial"])) ? null : ["hide"];
|
||||||
iv.appendChild(lbl(null, j[2], cl, "lbl" + id + j[0]));
|
|
||||||
d = div([j[0]]);
|
|
||||||
i = 0;
|
i = 0;
|
||||||
cl = (re.test(obj["serial"])) ? ["text", "sh"] : ["text", "sh", "hide"];
|
arrIn = [];
|
||||||
for(it of obj[j[1]]) {
|
for(it of obj[j[1]]) {
|
||||||
d.appendChild(inp(id + j[0] + i, it, j[3], cl, id + j[0] + i, "text", j[4], "Invalid input"));
|
arrIn.push(ml("div", {class: "col-3 "},
|
||||||
|
inp(id + j[0] + i, it, j[3], [], id + j[0] + i, "text", j[4], "Invalid input")
|
||||||
|
));
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
iv.appendChild(d);
|
|
||||||
|
iv.append(
|
||||||
|
ml("div", {class: "row mb-2 mb-sm-3", id: "row" + id + j[0]}, [
|
||||||
|
ml("div", {class: "col-12 col-sm-3 my-2"}, j[2]),
|
||||||
|
ml("div", {class: "col-12 col-sm-9"},
|
||||||
|
ml("div", {class: "row"}, arrIn)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var del = inp(id+"del", "X", 0, ["btn", "btnDel"], id+"del", "button");
|
var del = inp(id+"del", "X", 0, ["btn", "btnDel"], id+"del", "button");
|
||||||
del.addEventListener("click", delIv);
|
del.addEventListener("click", delIv);
|
||||||
iv.append(
|
iv.append(mlE("Delete", del));
|
||||||
lbl(id + "lbldel", "Delete"),
|
|
||||||
del
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ivGlob(obj) {
|
function ivGlob(obj) {
|
||||||
|
@ -436,20 +548,18 @@
|
||||||
function parseSys(obj) {
|
function parseSys(obj) {
|
||||||
for(var i of [["device", "device_name"], ["ssid", "ssid"]])
|
for(var i of [["device", "device_name"], ["ssid", "ssid"]])
|
||||||
document.getElementsByName(i[0])[0].value = obj[i[1]];
|
document.getElementsByName(i[0])[0].value = obj[i[1]];
|
||||||
var e = document.getElementsByName("adminpwd")[0];
|
document.getElementsByName("darkMode")[0].checked = obj["dark_mode"];
|
||||||
|
e = document.getElementsByName("adminpwd")[0];
|
||||||
if(!obj["pwd_set"])
|
if(!obj["pwd_set"])
|
||||||
e.value = "";
|
e.value = "";
|
||||||
var d = document.getElementById("prot_mask");
|
var d = document.getElementById("prot_mask");
|
||||||
var a = ["Index", "Live", "Serial / Console", "Settings", "Update", "System"]
|
var a = ["Index", "Live", "Serial / Console", "Settings", "Update", "System"];
|
||||||
|
var el = [];
|
||||||
for(var i = 0; i < 6; i++) {
|
for(var i = 0; i < 6; i++) {
|
||||||
var chkd = ((obj["prot_mask"] & (1 << i)) == (1 << i));
|
var chk = ((obj["prot_mask"] & (1 << i)) == (1 << i));
|
||||||
var sp = lbl("protMask" + i, a[i]);
|
el.push(mlCb("protMask" + i, a[i], chk))
|
||||||
var cb = inp("protMask" + i, null, null, ["cb"], "protMask" + i, "checkbox", null, null, chkd);
|
|
||||||
if(0 == i)
|
|
||||||
d.replaceChildren(sp, cb, br());
|
|
||||||
else
|
|
||||||
d.append(sp, cb, br());
|
|
||||||
}
|
}
|
||||||
|
d.append(...el);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseGeneric(obj) {
|
function parseGeneric(obj) {
|
||||||
|
@ -495,20 +605,31 @@
|
||||||
var e = document.getElementById("pinout");
|
var e = document.getElementById("pinout");
|
||||||
pins = [['cs', 'pinCs'], ['ce', 'pinCe'], ['irq', 'pinIrq'], ['led0', 'pinLed0'], ['led1', 'pinLed1']];
|
pins = [['cs', 'pinCs'], ['ce', 'pinCe'], ['irq', 'pinIrq'], ['led0', 'pinLed0'], ['led1', 'pinLed1']];
|
||||||
for(p of pins) {
|
for(p of pins) {
|
||||||
e.appendChild(lbl(p[1], p[0].toUpperCase()));
|
e.append(
|
||||||
e.appendChild(sel(p[1], ("ESP8266" == type) ? esp8266pins : esp32pins, obj[p[0]]));
|
ml("div", {class: "row mb-3"}, [
|
||||||
|
ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()),
|
||||||
|
ml("div", {class: "col-12 col-sm-9"},
|
||||||
|
sel(p[1], ("ESP8266" == type) ? esp8266pins : esp32pins, obj[p[0]])
|
||||||
|
)
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRadio(obj) {
|
function parseRadio(obj) {
|
||||||
var e = document.getElementById("rf24");
|
var e = document.getElementById("rf24").append(
|
||||||
e.appendChild(lbl("rf24Power", "Amplifier Power Level"));
|
ml("div", {class: "row mb-3"}, [
|
||||||
e.appendChild(sel("rf24Power", [
|
ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()),
|
||||||
[0, "MIN"],
|
ml("div", {class: "col-12 col-sm-9"},
|
||||||
[1, "LOW"],
|
sel("rf24Power", [
|
||||||
[2, "HIGH"],
|
[0, "MIN"],
|
||||||
[3, "MAX"]
|
[1, "LOW"],
|
||||||
], obj["power_level"]));
|
[2, "HIGH"],
|
||||||
|
[3, "MAX"]
|
||||||
|
], obj["power_level"])
|
||||||
|
)
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSerial(obj) {
|
function parseSerial(obj) {
|
||||||
|
@ -524,14 +645,22 @@
|
||||||
var e = document.getElementById("dispPins");
|
var e = document.getElementById("dispPins");
|
||||||
pins = [['SCL / CS', 'pinDisp0'], ['SDA / DC', 'pinDisp1']];
|
pins = [['SCL / CS', 'pinDisp0'], ['SDA / DC', 'pinDisp1']];
|
||||||
for(p of pins) {
|
for(p of pins) {
|
||||||
e.appendChild(lbl(p[1], p[0].toUpperCase()));
|
e.append(
|
||||||
e.appendChild(sel(p[1], ("ESP8266" == type) ? esp8266pins : esp32pins, obj[p[1]]));
|
ml("div", {class: "row mb-3"}, [
|
||||||
|
ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()),
|
||||||
|
ml("div", {class: "col-12 col-sm-9"},
|
||||||
|
sel(p[1], ("ESP8266" == type) ? esp8266pins : esp32pins, obj[p[1]])
|
||||||
|
)
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var opts = [[0, "None"], [1, "Nokia5110"], [2, "SSD1306 0.96\""], [3, "SH1106 1.3\""]];
|
var opts = [[0, "None"], [1, "Nokia5110"], [2, "SSD1306 0.96\""], [3, "SH1106 1.3\""]];
|
||||||
document.getElementById("dispType").append(
|
document.getElementById("dispType").append(
|
||||||
lbl("dispType", "Type"),
|
ml("div", {class: "row mb-3"}, [
|
||||||
sel("dispType", opts, obj["disp_type"])
|
ml("div", {class: "col-12 col-sm-3 my-2"}, "Type"),
|
||||||
|
ml("div", {class: "col-12 col-sm-9"}, sel("dispType", opts, obj["disp_type"]))
|
||||||
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
e = document.getElementById("contrast");
|
e = document.getElementById("contrast");
|
||||||
|
@ -576,11 +705,7 @@
|
||||||
e.value = s.value;
|
e.value = s.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
hiddenInput = document.getElementById("disclaimer")
|
|
||||||
hiddenInput.value = sessionStorage.getItem("gDisclaimer");
|
|
||||||
|
|
||||||
getAjax("/api/setup", parse);
|
getAjax("/api/setup", parse);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -4,26 +4,39 @@ html, body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
background-color: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span, li, h3, label, fieldset {
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset, input[type=submit], .btn {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#live span {
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
.topnav {
|
.topnav {
|
||||||
background-color: #333;
|
background-color: var(--nav-bg);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnav a {
|
.topnav a {
|
||||||
color: #fff;
|
color: var(--fg2);
|
||||||
padding: 14px 14px;
|
padding: 14px 14px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
display: block;
|
display: block;
|
||||||
height: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#topnav a {
|
#topnav a {
|
||||||
|
@ -33,18 +46,17 @@ h2 {
|
||||||
.topnav a.icon {
|
.topnav a.icon {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background: #333;
|
background: var(--nav-bg);
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnav a:hover {
|
.topnav a:hover {
|
||||||
background-color: #044e86 !important;
|
background-color: var(--primary-hover) !important;
|
||||||
color: #000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnav .info {
|
.topnav .info {
|
||||||
color: #fff;
|
color: var(--fg2);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 24px;
|
right: 24px;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
|
@ -61,8 +73,24 @@ svg.icon {
|
||||||
padding: 5px 7px 5px 0px;
|
padding: 5px 7px 5px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-info {
|
||||||
|
fill: var(--info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-warn {
|
||||||
|
fill: var(--warn);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-success {
|
||||||
|
fill: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi {
|
||||||
|
fill: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
background-color: #006ec0;
|
background-color: var(--primary);
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
padding-left: 80px !important
|
padding-left: 80px !important
|
||||||
}
|
}
|
||||||
|
@ -78,7 +106,7 @@ svg.icon {
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnav .active {
|
.topnav .active {
|
||||||
background-color: #555;
|
background-color: var(--nav-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
span.seperator {
|
span.seperator {
|
||||||
|
@ -89,6 +117,197 @@ span.seperator {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
max-width: 1140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-h {
|
||||||
|
background-color: var(--total-head-title);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-bg {
|
||||||
|
background-color: var(--total-bg);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.iv-h {
|
||||||
|
background-color: var(--iv-head-title);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.iv-bg {
|
||||||
|
background-color: var(--iv-head-bg);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ch-h {
|
||||||
|
background-color: var(--ch-head-title);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ch-bg {
|
||||||
|
background-color: var(--ch-head-bg);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ts-h {
|
||||||
|
background-color: var(--ts-head);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ts-bg {
|
||||||
|
background-color: var(--ts-bg);
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hr {
|
||||||
|
border-top: 1px solid var(--iv-head-title);
|
||||||
|
margin: 1rem 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-align: justify;
|
||||||
|
font-size: 13pt;
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
background-color: var(--footer-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.row { display: flex; max-width: 100%; flex-wrap: wrap; }
|
||||||
|
.col { flex: 1 0 0%; }
|
||||||
|
|
||||||
|
.col-1, .col-2, .col-3, .col-4,
|
||||||
|
.col-5, .col-6, .col-7, .col-8,
|
||||||
|
.col-9, .col-10, .col-11, .col-12 { flex: 0 0 auto; }
|
||||||
|
|
||||||
|
.col-1 { width: 8.333333333%; }
|
||||||
|
.col-2 { width: 16.66666667%; }
|
||||||
|
.col-3 { width: 25%; }
|
||||||
|
.col-4 { width: 33.33333333%; }
|
||||||
|
.col-5 { width: 41.66666667%; }
|
||||||
|
.col-6 { width: 50%; }
|
||||||
|
.col-7 { width: 58.33333333%; }
|
||||||
|
.col-8 { width: 66.66666667%; }
|
||||||
|
.col-9 { width: 75%; }
|
||||||
|
.col-10 { width: 83.33333333%; }
|
||||||
|
.col-11 { width: 91.66666667%; }
|
||||||
|
.col-12 { width: 100%; }
|
||||||
|
|
||||||
|
.p-1 { padding: 0.25rem; }
|
||||||
|
.p-2 { padding: 0.5rem; }
|
||||||
|
.p-3 { padding: 1rem; }
|
||||||
|
.p-4 { padding: 1.5rem; }
|
||||||
|
.p-5 { padding: 3rem; }
|
||||||
|
|
||||||
|
.px-1 { padding: 0 0.25rem 0 0.25rem; }
|
||||||
|
.px-2 { padding: 0 0.5rem 0 0.5rem; }
|
||||||
|
.px-3 { padding: 0 1rem 0 1rem; }
|
||||||
|
.px-4 { padding: 0 1.5rem 0 1.5rem; }
|
||||||
|
.px-5 { padding: 0 3rem 0 3rem; }
|
||||||
|
|
||||||
|
.py-1 { padding: 0.25rem 0 0.25rem; }
|
||||||
|
.py-2 { padding: 0.5rem 0 0.5rem; }
|
||||||
|
.py-3 { padding: 1rem 0 1rem; }
|
||||||
|
.py-4 { padding: 1.5rem 0 1.5rem; }
|
||||||
|
.py-5 { padding: 3rem 0 3rem; }
|
||||||
|
|
||||||
|
.mx-1 { margin: 0 0.25rem 0 0.25rem; }
|
||||||
|
.mx-2 { margin: 0 0.5rem 0 0.5rem; }
|
||||||
|
.mx-3 { margin: 0 1rem 0 1rem; }
|
||||||
|
.mx-4 { margin: 0 1.5rem 0 1.5rem; }
|
||||||
|
.mx-5 { margin: 0 3rem 0 3rem; }
|
||||||
|
|
||||||
|
.my-1 { margin: 0.25rem 0 0.25rem; }
|
||||||
|
.my-2 { margin: 0.5rem 0 0.5rem; }
|
||||||
|
.my-3 { margin: 1rem 0 1rem; }
|
||||||
|
.my-4 { margin: 1.5rem 0 1.5rem; }
|
||||||
|
.my-5 { margin: 3rem 0 3rem; }
|
||||||
|
|
||||||
|
.mt-1 { margin-top: 0.25rem }
|
||||||
|
.mt-2 { margin-top: 0.5rem }
|
||||||
|
.mt-3 { margin-top: 1rem }
|
||||||
|
.mt-4 { margin-top: 1.5rem }
|
||||||
|
.mt-5 { margin-top: 3rem }
|
||||||
|
|
||||||
|
.mb-1 { margin-bottom: 0.25rem }
|
||||||
|
.mb-2 { margin-bottom: 0.5rem }
|
||||||
|
.mb-3 { margin-bottom: 1rem }
|
||||||
|
.mb-4 { margin-bottom: 1.5rem }
|
||||||
|
.mb-5 { margin-bottom: 3rem }
|
||||||
|
|
||||||
|
.fs-1 { font-size: 3.5rem; }
|
||||||
|
.fs-2 { font-size: 3rem; }
|
||||||
|
.fs-3 { font-size: 2.5rem; }
|
||||||
|
.fs-4 { font-size: 2rem; }
|
||||||
|
.fs-5 { font-size: 1.75rem; }
|
||||||
|
.fs-6 { font-size: 1.5rem; }
|
||||||
|
.fs-7 { font-size: 1.25rem; }
|
||||||
|
.fs-8 { font-size: 1rem; }
|
||||||
|
.fs-9 { font-size: 0.75rem; }
|
||||||
|
.fs-10 { font-size: 0.5rem; }
|
||||||
|
|
||||||
|
.a-r { text-align: right; }
|
||||||
|
.a-c { text-align: center; }
|
||||||
|
|
||||||
|
.row > * {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
*, ::after, ::before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sm */
|
||||||
|
@media(min-width: 768px) {
|
||||||
|
.col-sm-1 { width: 8.333333333%; }
|
||||||
|
.col-sm-2 { width: 16.66666667%; }
|
||||||
|
.col-sm-3 { width: 25%; }
|
||||||
|
.col-sm-4 { width: 33.33333333%; }
|
||||||
|
.col-sm-5 { width: 41.66666667%; }
|
||||||
|
.col-sm-6 { width: 50%; }
|
||||||
|
.col-sm-7 { width: 58.33333333%; }
|
||||||
|
.col-sm-8 { width: 66.66666667%; }
|
||||||
|
.col-sm-9 { width: 75%; }
|
||||||
|
.col-sm-10 { width: 83.33333333%; }
|
||||||
|
.col-sm-11 { width: 91.66666667%; }
|
||||||
|
.col-sm-12 { width: 100%; }
|
||||||
|
|
||||||
|
.mb-sm-1 { margin-bottom: 0.25rem }
|
||||||
|
.mb-sm-2 { margin-bottom: 0.5rem }
|
||||||
|
.mb-sm-3 { margin-bottom: 1rem }
|
||||||
|
.mb-sm-4 { margin-bottom: 1.5rem }
|
||||||
|
.mb-sm-5 { margin-bottom: 3rem }
|
||||||
|
|
||||||
|
.fs-sm-1 { font-size: 3.5rem; }
|
||||||
|
.fs-sm-2 { font-size: 3rem; }
|
||||||
|
.fs-sm-3 { font-size: 2.5rem; }
|
||||||
|
.fs-sm-4 { font-size: 2rem; }
|
||||||
|
.fs-sm-5 { font-size: 1.75rem; }
|
||||||
|
.fs-sm-6 { font-size: 1.5rem; }
|
||||||
|
.fs-sm-7 { font-size: 1.25rem; }
|
||||||
|
.fs-sm-8 { font-size: 1rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* md */
|
||||||
|
@media(min-width: 992px) {
|
||||||
|
.col-md-1 { width: 8.333333333%; }
|
||||||
|
.col-md-2 { width: 16.66666667%; }
|
||||||
|
.col-md-3 { width: 25%; }
|
||||||
|
.col-md-4 { width: 33.33333333%; }
|
||||||
|
.col-md-5 { width: 41.66666667%; }
|
||||||
|
.col-md-6 { width: 50%; }
|
||||||
|
.col-md-7 { width: 58.33333333%; }
|
||||||
|
.col-md-8 { width: 66.66666667%; }
|
||||||
|
.col-md-9 { width: 75%; }
|
||||||
|
.col-md-10 { width: 83.33333333%; }
|
||||||
|
.col-md-11 { width: 91.66666667%; }
|
||||||
|
.col-md-12 { width: 100%; }
|
||||||
|
}
|
||||||
|
|
||||||
#wrapper {
|
#wrapper {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +320,6 @@ span.seperator {
|
||||||
#footer {
|
#footer {
|
||||||
height: 121px;
|
height: 121px;
|
||||||
margin-top: -121px;
|
margin-top: -121px;
|
||||||
background-color: #555;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
@ -176,13 +394,6 @@ span.seperator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** old CSS below **/
|
|
||||||
|
|
||||||
p {
|
|
||||||
text-align: justify;
|
|
||||||
font-size: 13pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.lic, p.lic a {
|
p.lic, p.lic a {
|
||||||
font-size: 8pt;
|
font-size: 8pt;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
@ -191,11 +402,11 @@ p.lic, p.lic a {
|
||||||
.des {
|
.des {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
font-size: 13pt;
|
font-size: 13pt;
|
||||||
color: #006ec0;
|
color: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.s_active, .s_collapsible:hover {
|
.s_active, .s_collapsible:hover {
|
||||||
background-color: #044e86;
|
background-color: var(--primary-hover);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,34 +416,34 @@ p.lic, p.lic a {
|
||||||
}
|
}
|
||||||
|
|
||||||
.s_collapsible {
|
.s_collapsible {
|
||||||
background-color: #006ec0;
|
background-color: var(--primary);
|
||||||
color: white;
|
color: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 18px;
|
padding: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
outline: none;
|
outline: none;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subdes {
|
.subdes {
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
color: #006ec0;
|
color: var(--secondary);
|
||||||
margin-left: 7px;
|
margin-left: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subsubdes {
|
.subsubdes {
|
||||||
font-size:12pt;
|
font-size:12pt;
|
||||||
color:#006ec0;
|
color:var(--secondary);
|
||||||
margin: 0 0 7px 12px;
|
margin: 0 0 7px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:link, a:visited {
|
a:link, a:visited {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 13pt;
|
font-size: 13pt;
|
||||||
color: #006ec0;
|
color: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover, a:focus {
|
a:hover, a:focus {
|
||||||
|
@ -240,14 +451,14 @@ a:hover, a:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
a.btn {
|
a.btn {
|
||||||
background-color: #006ec0;
|
background-color: var(--primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 7px 15px 7px 15px;
|
padding: 7px 15px 7px 15px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.btn:hover {
|
a.btn:hover {
|
||||||
background-color: #044e86 !important;
|
background-color: var(--primary-hover) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, select {
|
input, select {
|
||||||
|
@ -255,11 +466,13 @@ input, select {
|
||||||
font-size: 13pt;
|
font-size: 13pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.text, select {
|
input[type=text], input[type=password], select, input[type=number] {
|
||||||
width: 70%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin-bottom: 10px;
|
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--input-bg);
|
||||||
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
input.sh {
|
input.sh {
|
||||||
|
@ -272,7 +485,7 @@ input.btnDel {
|
||||||
}
|
}
|
||||||
|
|
||||||
input.btn {
|
input.btn {
|
||||||
background-color: #006ec0;
|
background-color: var(--primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
padding: 7px 20px 7px 20px;
|
padding: 7px 20px 7px 20px;
|
||||||
|
@ -303,10 +516,6 @@ pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
@ -315,88 +524,11 @@ fieldset {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ch-iv {
|
|
||||||
width: 100%;
|
|
||||||
background-color: #32b004;
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch {
|
|
||||||
width: 220px;
|
|
||||||
min-height: 350px;
|
|
||||||
background-color: #006ec0;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 20px 10px 0px;
|
|
||||||
overflow: auto;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch-all {
|
|
||||||
width: 100%;
|
|
||||||
background-color: #b06e04;
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch .value, div.ch .info, div.ch .head, div.ch-iv .value, div.ch-iv .info, div.ch-iv .head, div.ch-all .value, div.ch-all .info, div.ch-all .head {
|
|
||||||
color: #fff;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subgrp {
|
.subgrp {
|
||||||
float: left;
|
float: left;
|
||||||
width: 220px;
|
width: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ch .unit, div.ch-iv .unit, div.ch-all .unit {
|
|
||||||
font-size: 19px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch .value, div.ch-iv .value, div.ch-all .value {
|
|
||||||
margin-top: 20px;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch .info, div.ch-iv .info, div.ch-all .info {
|
|
||||||
margin-top: 3px;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch .head {
|
|
||||||
background-color: #003c80;
|
|
||||||
padding: 10px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch-all .head {
|
|
||||||
background-color: #8e5903;
|
|
||||||
padding: 10px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ch-iv .head {
|
|
||||||
background-color: #1c6800;
|
|
||||||
padding: 10px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.iv {
|
|
||||||
max-width: 960px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ts {
|
|
||||||
font-size: 13px;
|
|
||||||
background-color: #ddd;
|
|
||||||
border-top: 7px solid #999;
|
|
||||||
padding: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ModPwr, div.ModName, div.YieldCor {
|
div.ModPwr, div.ModName, div.YieldCor {
|
||||||
width:70%;
|
width:70%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -447,104 +579,19 @@ div.hr {
|
||||||
}
|
}
|
||||||
|
|
||||||
#login {
|
#login {
|
||||||
width: 300px;
|
width: 450px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
background-color: #eee;
|
background-color: var(--ts-head);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
margin-top: -160px;
|
margin-top: -160px;
|
||||||
margin-left: -150px;
|
margin-left: -225px;
|
||||||
}
|
|
||||||
|
|
||||||
#login .pad {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#login .pad input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 7px 0 7px 0;
|
|
||||||
border: 0px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.head {
|
.head {
|
||||||
background-color: #006ec0;
|
background-color: var(--primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row { display: flex; max-width: 100%; flex-wrap: wrap; }
|
|
||||||
.col { flex: 1 0 0%; }
|
|
||||||
|
|
||||||
.col-1, .col-2, .col-3, .col-4,
|
|
||||||
.col-5, .col-6, .col-7, .col-8,
|
|
||||||
.col-9, .col-10, .col-11, .col-12 { flex: 0 0 auto; }
|
|
||||||
|
|
||||||
|
|
||||||
.col-1 { width: 8.333333333%; }
|
|
||||||
.col-2 { width: 16.66666667%; }
|
|
||||||
.col-3 { width: 25%; }
|
|
||||||
.col-4 { width: 33.33333333%; }
|
|
||||||
.col-5 { width: 41.66666667%; }
|
|
||||||
.col-6 { width: 50%; }
|
|
||||||
.col-7 { width: 58.33333333%; }
|
|
||||||
.col-8 { width: 66.66666667%; }
|
|
||||||
.col-9 { width: 75%; }
|
|
||||||
.col-10 { width: 83.33333333%; }
|
|
||||||
.col-11 { width: 91.66666667%; }
|
|
||||||
.col-12 { width: 100%; }
|
|
||||||
|
|
||||||
.p-1 { padding: 0.25rem; }
|
|
||||||
.p-2 { padding: 0.5rem; }
|
|
||||||
.p-3 { padding: 1rem; }
|
|
||||||
.p-4 { padding: 1.5rem; }
|
|
||||||
.p-5 { padding: 3rem; }
|
|
||||||
|
|
||||||
.mt-1 { margin-top: 0.25rem }
|
|
||||||
.mt-2 { margin-top: 0.5rem }
|
|
||||||
.mt-3 { margin-top: 1rem }
|
|
||||||
.mt-4 { margin-top: 1.5rem }
|
|
||||||
.mt-5 { margin-top: 3rem }
|
|
||||||
|
|
||||||
.mb-1 { margin-bottom: 0.25rem }
|
|
||||||
.mb-2 { margin-bottom: 0.5rem }
|
|
||||||
.mb-3 { margin-bottom: 1rem }
|
|
||||||
.mb-4 { margin-bottom: 1.5rem }
|
|
||||||
.mb-5 { margin-bottom: 3rem }
|
|
||||||
|
|
||||||
.a-r { text-align: right; }
|
|
||||||
.a-c { text-align: center; }
|
|
||||||
|
|
||||||
/* sm */
|
|
||||||
@media(min-width: 768px) {
|
|
||||||
.col-sm-1 { width: 8.333333333%; }
|
|
||||||
.col-sm-2 { width: 16.66666667%; }
|
|
||||||
.col-sm-3 { width: 25%; }
|
|
||||||
.col-sm-4 { width: 33.33333333%; }
|
|
||||||
.col-sm-5 { width: 41.66666667%; }
|
|
||||||
.col-sm-6 { width: 50%; }
|
|
||||||
.col-sm-7 { width: 58.33333333%; }
|
|
||||||
.col-sm-8 { width: 66.66666667%; }
|
|
||||||
.col-sm-9 { width: 75%; }
|
|
||||||
.col-sm-10 { width: 83.33333333%; }
|
|
||||||
.col-sm-11 { width: 91.66666667%; }
|
|
||||||
.col-sm-12 { width: 100%; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* md */
|
|
||||||
@media(min-width: 992px) {
|
|
||||||
.col-md-1 { width: 8.333333333%; }
|
|
||||||
.col-md-2 { width: 16.66666667%; }
|
|
||||||
.col-md-3 { width: 25%; }
|
|
||||||
.col-md-4 { width: 33.33333333%; }
|
|
||||||
.col-md-5 { width: 41.66666667%; }
|
|
||||||
.col-md-6 { width: 50%; }
|
|
||||||
.col-md-7 { width: 58.33333333%; }
|
|
||||||
.col-md-8 { width: 66.66666667%; }
|
|
||||||
.col-md-9 { width: 75%; }
|
|
||||||
.col-md-10 { width: 83.33333333%; }
|
|
||||||
.col-md-11 { width: 91.66666667%; }
|
|
||||||
.col-md-12 { width: 100%; }
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,10 +8,13 @@
|
||||||
{#HTML_NAV}
|
{#HTML_NAV}
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<form id="form" method="POST" action="/update" enctype="multipart/form-data" accept-charset="utf-8">
|
<fieldset>
|
||||||
<input type="file" name="update">
|
<legend class="des">Select firmware file (*.bin)</legend>
|
||||||
<input type="button" class="btn" value="Update" onclick="hide()">
|
<form id="form" method="POST" action="/update" enctype="multipart/form-data" accept-charset="utf-8">
|
||||||
</form>
|
<input type="file" name="update">
|
||||||
|
<input type="button" class="btn" value="Update" onclick="hide()">
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#HTML_FOOTER}
|
{#HTML_FOOTER}
|
||||||
|
|
|
@ -16,6 +16,11 @@
|
||||||
{#HTML_FOOTER}
|
{#HTML_FOOTER}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var exeOnce = true;
|
var exeOnce = true;
|
||||||
|
var units, ivEn;
|
||||||
|
var mIvHtml = [];
|
||||||
|
var mNum = 0;
|
||||||
|
var names = ["Voltage", "Current", "Power", "Yield Day", "Yield Total", "Irradiation"];
|
||||||
|
var total = Array(5).fill(0);
|
||||||
|
|
||||||
function parseGeneric(obj) {
|
function parseGeneric(obj) {
|
||||||
if(true == exeOnce){
|
if(true == exeOnce){
|
||||||
|
@ -25,93 +30,193 @@
|
||||||
parseRssi(obj);
|
parseRssi(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseIv(obj, root) {
|
function numBig(val, unit, des) {
|
||||||
var ivHtml = [];
|
return ml("div", {class: "col-6 col-sm-4 a-c"}, [
|
||||||
|
ml("div", {class: "row"},
|
||||||
|
ml("div", {class: "col"}, [
|
||||||
|
ml("span", {class: "fs-5 fs-md-4"}, String(val)),
|
||||||
|
ml("span", {class: "fs-6 fs-md-7 mx-1"}, unit)
|
||||||
|
])),
|
||||||
|
ml("div", {class: "row"},
|
||||||
|
ml("div", {class: "col"},
|
||||||
|
ml("span", {class: "fs-9 px-1"}, des)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
var tDiv = div(["ch-all", "iv"]);
|
function numMid(val, unit, des) {
|
||||||
tDiv.appendChild(span("Total", ["head"]));
|
return ml("div", {class: "col-6 col-sm-4 col-md-3"}, [
|
||||||
var total = new Array(root.ch0_fld_names.length).fill(0);
|
ml("div", {class: "row"},
|
||||||
if(obj.length > 1)
|
ml("div", {class: "col"}, [
|
||||||
ivHtml.push(tDiv);
|
ml("span", {class: "fs-6"}, String(val)),
|
||||||
|
ml("span", {class: "fs-8 mx-1"}, unit)
|
||||||
|
])),
|
||||||
|
ml("div", {class: "row"},
|
||||||
|
ml("div", {class: "col"},
|
||||||
|
ml("span", {class: "fs-9"}, des)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
for(var iv of obj) {
|
function totals() {
|
||||||
if(iv["enabled"]) {
|
return ml("div", {class: "row mt-3 mb-5"},
|
||||||
main = div(["iv"]);
|
ml("div", {class: "col"}, [
|
||||||
var ch0 = div(["ch-iv"]);
|
ml("div", {class: "p-2 total-h"},
|
||||||
var limit = iv["power_limit_read"] + "%";
|
ml("div", {class: "row"},
|
||||||
if(limit == "65535%")
|
ml("div", {class: "col mx-2 mx-md-1"}, "TOTAL")
|
||||||
limit = "n/a";
|
),
|
||||||
ch0.appendChild(span(iv["name"] + " Limit " + limit, ["head"]));
|
),
|
||||||
|
ml("div", {class: "p-2 total-bg"}, [
|
||||||
|
ml("div", {class: "row"}, [
|
||||||
|
numBig(total[0], "W", "AC Power"),
|
||||||
|
numBig(total[1], "Wh", "Yield Day"),
|
||||||
|
numBig(total[2], "kWh", "Yield Total")
|
||||||
|
]),
|
||||||
|
ml("div", {class: "hr"}),
|
||||||
|
ml("div", {class: "row"}, [
|
||||||
|
numMid(total[3], "W", "DC Power"),
|
||||||
|
numMid(total[4], "var", "Reactive Power")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function ivHead(obj) {
|
||||||
|
total[0] += obj.ch[0][2]; // P_AC
|
||||||
|
total[1] += obj.ch[0][7]/1000; // YieldDay
|
||||||
|
total[2] += obj.ch[0][6]; // YieldTotal
|
||||||
|
total[3] += obj.ch[0][8]; // P_DC
|
||||||
|
total[4] += obj.ch[0][10]; // Q_AC
|
||||||
|
return ml("div", {class: "row"},
|
||||||
|
ml("div", {class: "col"}, [
|
||||||
|
ml("div", {class: "p-2 iv-h"},
|
||||||
|
ml("div", {class: "row"}, [
|
||||||
|
ml("div", {class: "col mx-2 mx-md-1"}, obj.name),
|
||||||
|
ml("div", {class: "col a-c"}, "Power limit " + obj.power_limit_read + " %"),
|
||||||
|
ml("div", {class: "col a-r mx-2 mx-md-1"}, String(obj.ch[0][5]) + " C")
|
||||||
|
])
|
||||||
|
),
|
||||||
|
ml("div", {class: "p-2 iv-bg"}, [
|
||||||
|
ml("div", {class: "row"},[
|
||||||
|
numBig(obj.ch[0][2], "W", "AC Power"),
|
||||||
|
numBig(obj.ch[0][7]/1000, "kWh", "Yield Day"),
|
||||||
|
numBig(obj.ch[0][6], "kWh", "Yield Total")
|
||||||
|
]),
|
||||||
|
ml("div", {class: "hr"}),
|
||||||
|
ml("div", {class: "row mt-2"},[
|
||||||
|
numMid(obj.ch[0][8], "W", "DC Power"),
|
||||||
|
numMid(obj.ch[0][0], "V", "Voltage"),
|
||||||
|
numMid(obj.ch[0][1], "A", "Current"),
|
||||||
|
numMid(obj.ch[0][3], "Hz", "Frequency"),
|
||||||
|
numMid(obj.ch[0][9], "%", "Efficiency"),
|
||||||
|
numMid(obj.ch[0][10], "var", "Reactive Power"),
|
||||||
|
numMid(obj.ch[0][4], "", "Power Factor")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for(var j = 0; j < root.ch0_fld_names.length; j++) {
|
function numCh(val, unit, des) {
|
||||||
var val = Math.round(iv["ch"][0][j] * 100) / 100;
|
return ml("div", {class: "col-12 col-sm-6 col-md-12 mb-2"}, [
|
||||||
var sub = div(["subgrp"]);
|
ml("div", {class: "row"},
|
||||||
sub.appendChild(span(val + " " + span(root["ch0_fld_units"][j], ["unit"]).innerHTML, ["value"]));
|
ml("div", {class: "col"}, [
|
||||||
sub.appendChild(span(root["ch0_fld_names"][j], ["info"]));
|
ml("span", {class: "fs-6 fs-md-7"}, String(val)),
|
||||||
ch0.appendChild(sub);
|
ml("span", {class: "fs-8 mx-2"}, unit)
|
||||||
|
])),
|
||||||
|
ml("div", {class: "row"},
|
||||||
|
ml("div", {class: "col"},
|
||||||
|
ml("span", {class: "fs-9"}, des)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
switch(j) {
|
function ch(name, vals) {
|
||||||
case 2: total[j] += val; break; // P_AC
|
return ml("div", {class: "col-6 col-md-3 mt-2"}, [
|
||||||
case 6: total[j] += val; break; // YieldTotal
|
ml("div", {class: "ch-h p-2 a-c"}, name),
|
||||||
case 7: total[j] += val; break; // YieldDay
|
ml("div", {class: "p-2 ch-bg"}, [
|
||||||
case 8: total[j] += val; break; // P_DC
|
ml("div", {class: "row"}, [
|
||||||
case 10: total[j] += val; break; // Q_AC
|
numCh(vals[2], units[2], "Power"),
|
||||||
}
|
numCh(vals[5], units[5], "Irradiation"),
|
||||||
}
|
numCh(vals[3], units[3], "Yield Day"),
|
||||||
main.appendChild(ch0);
|
numCh(vals[4], units[4], "Yield Total"),
|
||||||
|
numCh(vals[0], units[0], "Voltage"),
|
||||||
|
numCh(vals[1], units[1], "Current")
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tsInfo(ts) {
|
||||||
|
var ageInfo = "Last received data requested at: ";
|
||||||
|
if(ts > 0) {
|
||||||
|
var date = new Date(ts * 1000);
|
||||||
|
ageInfo += date.toLocaleString('de-DE');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ageInfo += "nothing received";
|
||||||
|
|
||||||
for(var i = 1; i < (iv["channels"] + 1); i++) {
|
return ml("div", {class: "mb-5"}, [
|
||||||
var ch = div(["ch"]);
|
ml("div", {class: "row p-1 ts-h mx-2"},
|
||||||
ch.appendChild(span(("" == iv["ch_names"][i]) ? ("CHANNEL " + i) : iv["ch_names"][i], ["head"]));
|
ml("div", {class: "col"}, "")
|
||||||
|
),
|
||||||
|
ml("div", {class: "row p-2 ts-bg mx-2"},
|
||||||
|
ml("div", {class: "col mx-2"}, ageInfo)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
for(var j = 0; j < root.fld_names.length; j++) {
|
function parseIv(obj) {
|
||||||
var val = Math.round(iv["ch"][i][j] * 100) / 100;
|
mNum++;
|
||||||
ch.appendChild(span(val + " " + span(root["fld_units"][j], ["unit"]).innerHTML, ["value"]));
|
|
||||||
ch.appendChild(span(root["fld_names"][j], ["info"]));
|
|
||||||
}
|
|
||||||
main.appendChild(ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
var ts = div(["ts"]);
|
var chn = [];
|
||||||
var ageInfo = "Last received data requested at: ";
|
for(var i = 1; i < obj.ch.length; i++) {
|
||||||
if(iv["ts_last_success"] > 0) {
|
var name = obj.ch_name[i];
|
||||||
var date = new Date(iv["ts_last_success"] * 1000);
|
if(name.length == 0)
|
||||||
ageInfo += date.toLocaleString('de-DE');
|
name = "CHANNEL " + i;
|
||||||
}
|
chn.push(ch(name, obj.ch[i]));
|
||||||
else
|
}
|
||||||
ageInfo += "nothing received";
|
mIvHtml.push(
|
||||||
|
ml("div", {}, [
|
||||||
|
ivHead(obj),
|
||||||
|
ml("div", {class: "row mb-2"}, chn),
|
||||||
|
tsInfo(obj.ts_last_succcess)
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
ts.innerHTML = ageInfo;
|
var last = true;
|
||||||
|
for(var i = obj.id + 1; i < ivEn.length; i++) {
|
||||||
main.appendChild(ts);
|
if((i != ivEn.length) && ivEn[i]) {
|
||||||
ivHtml.push(main);
|
last = false;
|
||||||
|
getAjax("http://10.20.3.44/api/inverter/id/" + i, parseIv);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(last) {
|
||||||
// total
|
if(mNum > 1)
|
||||||
if(obj.length > 1) {
|
mIvHtml.unshift(totals());
|
||||||
for(var j = 0; j < root.ch0_fld_names.length; j++) {
|
document.getElementById("live").replaceChildren(...mIvHtml);
|
||||||
var val = Math.round(total[j] * 100) / 100;
|
|
||||||
if((j == 2) || (j == 6) || (j == 7) || (j == 8) || (j == 10)) {
|
|
||||||
var sub = div(["subgrp"]);
|
|
||||||
sub.appendChild(span(val + " " + span(root["ch0_fld_units"][j], ["unit"]).innerHTML, ["value"]));
|
|
||||||
sub.appendChild(span(root["ch0_fld_names"][j], ["info"]));
|
|
||||||
tDiv.appendChild(sub);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("live").replaceChildren(...ivHtml);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse(obj) {
|
function parse(obj) {
|
||||||
if(null != obj) {
|
if(null != obj) {
|
||||||
parseGeneric(obj["generic"]);
|
parseGeneric(obj["generic"]);
|
||||||
parseIv(obj["inverter"], obj);
|
units = Object.assign({}, obj["fld_units"]);
|
||||||
document.getElementById("refresh").innerHTML = obj["refresh_interval"];
|
ivEn = Object.values(Object.assign({}, obj["iv"]));
|
||||||
|
mIvHtml = [];
|
||||||
|
mNum = 0;
|
||||||
|
for(var i = 0; i < obj.iv.length; i++) {
|
||||||
|
if(obj.iv[i])
|
||||||
|
getAjax("/api/inverter/id/" + i, parseIv);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
document.getElementById("refresh").innerHTML = obj["refresh"];
|
||||||
if(true == exeOnce) {
|
if(true == exeOnce) {
|
||||||
window.setInterval("getAjax('/api/live', parse)", obj["refresh_interval"] * 1000);
|
window.setInterval("getAjax('/api/live', parse)", obj["refresh"] * 1000);
|
||||||
exeOnce = false;
|
exeOnce = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
#include "html/h/index_html.h"
|
#include "html/h/index_html.h"
|
||||||
#include "html/h/login_html.h"
|
#include "html/h/login_html.h"
|
||||||
#include "html/h/style_css.h"
|
#include "html/h/style_css.h"
|
||||||
|
#include "html/h/colorDark_css.h"
|
||||||
|
#include "html/h/colorBright_css.h"
|
||||||
#include "html/h/api_js.h"
|
#include "html/h/api_js.h"
|
||||||
#include "html/h/favicon_ico.h"
|
#include "html/h/favicon_ico.h"
|
||||||
#include "html/h/setup_html.h"
|
#include "html/h/setup_html.h"
|
||||||
|
@ -57,6 +59,7 @@ class Web {
|
||||||
mWeb.on("/", HTTP_GET, std::bind(&Web::onIndex, this, std::placeholders::_1));
|
mWeb.on("/", HTTP_GET, std::bind(&Web::onIndex, this, std::placeholders::_1));
|
||||||
mWeb.on("/login", HTTP_ANY, std::bind(&Web::onLogin, this, std::placeholders::_1));
|
mWeb.on("/login", HTTP_ANY, std::bind(&Web::onLogin, this, std::placeholders::_1));
|
||||||
mWeb.on("/logout", HTTP_GET, std::bind(&Web::onLogout, this, std::placeholders::_1));
|
mWeb.on("/logout", HTTP_GET, std::bind(&Web::onLogout, this, std::placeholders::_1));
|
||||||
|
mWeb.on("/colors.css", HTTP_GET, std::bind(&Web::onColor, this, std::placeholders::_1));
|
||||||
mWeb.on("/style.css", HTTP_GET, std::bind(&Web::onCss, this, std::placeholders::_1));
|
mWeb.on("/style.css", HTTP_GET, std::bind(&Web::onCss, this, std::placeholders::_1));
|
||||||
mWeb.on("/api.js", HTTP_GET, std::bind(&Web::onApiJs, this, std::placeholders::_1));
|
mWeb.on("/api.js", HTTP_GET, std::bind(&Web::onApiJs, this, std::placeholders::_1));
|
||||||
mWeb.on("/favicon.ico", HTTP_GET, std::bind(&Web::onFavicon, this, std::placeholders::_1));
|
mWeb.on("/favicon.ico", HTTP_GET, std::bind(&Web::onFavicon, this, std::placeholders::_1));
|
||||||
|
@ -219,17 +222,30 @@ class Web {
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void checkRedirect(AsyncWebServerRequest *request) {
|
||||||
|
if((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX)
|
||||||
|
request->redirect(F("/index"));
|
||||||
|
else if((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE)
|
||||||
|
request->redirect(F("/live"));
|
||||||
|
else if((mConfig->sys.protectionMask & PROT_MASK_SERIAL) != PROT_MASK_SERIAL)
|
||||||
|
request->redirect(F("/serial"));
|
||||||
|
else if((mConfig->sys.protectionMask & PROT_MASK_SYSTEM) != PROT_MASK_SYSTEM)
|
||||||
|
request->redirect(F("/system"));
|
||||||
|
else
|
||||||
|
request->redirect(F("/login"));
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
if(mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), 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");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -244,11 +260,10 @@ class Web {
|
||||||
html += "failed";
|
html += "failed";
|
||||||
html += F("<br/><br/>rebooting ... auto reload after 20s</body></html>");
|
html += F("<br/><br/>rebooting ... auto reload after 20s</body></html>");
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), html);
|
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html; charset=UTF-8"), html);
|
||||||
response->addHeader("Connection", "close");
|
response->addHeader("Connection", "close");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
//if(reboot)
|
mApp->setRebootFlag();
|
||||||
mApp->setRebootFlag();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUpload(AsyncWebServerRequest *request) {
|
void onUpload(AsyncWebServerRequest *request) {
|
||||||
|
@ -261,11 +276,10 @@ class Web {
|
||||||
html += "failed";
|
html += "failed";
|
||||||
html += F("<br/><br/>rebooting ... auto reload after 20s</body></html>");
|
html += F("<br/><br/>rebooting ... auto reload after 20s</body></html>");
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), html);
|
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html; charset=UTF-8"), html);
|
||||||
response->addHeader("Connection", "close");
|
response->addHeader("Connection", "close");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
//if(reboot)
|
mApp->setRebootFlag();
|
||||||
mApp->setRebootFlag();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onConnect(AsyncEventSourceClient *client) {
|
void onConnect(AsyncEventSourceClient *client) {
|
||||||
|
@ -284,12 +298,12 @@ class Web {
|
||||||
|
|
||||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) {
|
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_INDEX)) {
|
||||||
if(mProtected) {
|
if(mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), 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");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -304,7 +318,7 @@ class Web {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), login_html, login_html_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), login_html, login_html_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -313,13 +327,24 @@ class Web {
|
||||||
DPRINTLN(DBG_VERBOSE, F("onLogout"));
|
DPRINTLN(DBG_VERBOSE, F("onLogout"));
|
||||||
|
|
||||||
if(mProtected) {
|
if(mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mProtected = true;
|
mProtected = true;
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), 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");
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onColor(AsyncWebServerRequest *request) {
|
||||||
|
DPRINTLN(DBG_VERBOSE, F("onColor"));
|
||||||
|
AsyncWebServerResponse *response;
|
||||||
|
if(mConfig->sys.darkMode)
|
||||||
|
response = request->beginResponse_P(200, F("text/css"), colorDark_css, colorDark_css_len);
|
||||||
|
else
|
||||||
|
response = request->beginResponse_P(200, F("text/css"), colorBright_css, colorBright_css_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -349,21 +374,21 @@ class Web {
|
||||||
|
|
||||||
void showNotFound(AsyncWebServerRequest *request) {
|
void showNotFound(AsyncWebServerRequest *request) {
|
||||||
if(mProtected)
|
if(mProtected)
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
else
|
else
|
||||||
request->redirect("/setup");
|
request->redirect("/setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
void onReboot(AsyncWebServerRequest *request) {
|
void onReboot(AsyncWebServerRequest *request) {
|
||||||
mApp->setRebootFlag();
|
mApp->setRebootFlag();
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), 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");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showErase(AsyncWebServerRequest *request) {
|
void showErase(AsyncWebServerRequest *request) {
|
||||||
if(mProtected) {
|
if(mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,7 +399,7 @@ class Web {
|
||||||
|
|
||||||
void showFactoryRst(AsyncWebServerRequest *request) {
|
void showFactoryRst(AsyncWebServerRequest *request) {
|
||||||
if(mProtected) {
|
if(mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,7 +424,7 @@ class Web {
|
||||||
"<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>");
|
"<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>");
|
||||||
refresh = 120;
|
refresh = 120;
|
||||||
}
|
}
|
||||||
request->send(200, F("text/html"), F("<!doctype html><html><head><title>Factory Reset</title><meta http-equiv=\"refresh\" content=\"") + String(refresh) + F("; URL=/\"></head><body>") + content + F("</body></html>"));
|
request->send(200, F("text/html; charset=UTF-8"), F("<!doctype html><html><head><title>Factory Reset</title><meta http-equiv=\"refresh\" content=\"") + String(refresh) + F("; URL=/\"></head><body>") + content + F("</body></html>"));
|
||||||
if(refresh == 10) {
|
if(refresh == 10) {
|
||||||
delay(1000);
|
delay(1000);
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
|
@ -411,12 +436,12 @@ class Web {
|
||||||
|
|
||||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) {
|
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SETUP)) {
|
||||||
if(mProtected) {
|
if(mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), 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");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -425,7 +450,7 @@ class Web {
|
||||||
DPRINTLN(DBG_VERBOSE, F("showSave"));
|
DPRINTLN(DBG_VERBOSE, F("showSave"));
|
||||||
|
|
||||||
if(mProtected) {
|
if(mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,6 +466,7 @@ class Web {
|
||||||
request->arg("pwd").toCharArray(mConfig->sys.stationPwd, PWD_LEN);
|
request->arg("pwd").toCharArray(mConfig->sys.stationPwd, PWD_LEN);
|
||||||
if(request->arg("device") != "")
|
if(request->arg("device") != "")
|
||||||
request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN);
|
request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN);
|
||||||
|
mConfig->sys.darkMode = (request->arg("darkMode") == "on");
|
||||||
|
|
||||||
// protection
|
// protection
|
||||||
if(request->arg("adminpwd") != "{PWD}") {
|
if(request->arg("adminpwd") != "{PWD}") {
|
||||||
|
@ -580,7 +606,7 @@ class Web {
|
||||||
if(request->arg("reboot") == "on")
|
if(request->arg("reboot") == "on")
|
||||||
onReboot(request);
|
onReboot(request);
|
||||||
else {
|
else {
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), 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");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -591,13 +617,15 @@ class Web {
|
||||||
|
|
||||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
|
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_LIVE)) {
|
||||||
if(mProtected) {
|
if(mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), 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");
|
||||||
|
response->addHeader(F("content-type"), "text/html; charset=UTF-8");
|
||||||
|
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -668,7 +696,7 @@ class Web {
|
||||||
|
|
||||||
void onDebug(AsyncWebServerRequest *request) {
|
void onDebug(AsyncWebServerRequest *request) {
|
||||||
mApp->getSchedulerNames();
|
mApp->getSchedulerNames();
|
||||||
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), "ok");
|
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html; charset=UTF-8"), "ok");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -677,12 +705,12 @@ class Web {
|
||||||
|
|
||||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) {
|
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SERIAL)) {
|
||||||
if(mProtected) {
|
if(mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), 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");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
@ -692,12 +720,12 @@ class Web {
|
||||||
|
|
||||||
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) {
|
if(CHECK_MASK(mConfig->sys.protectionMask, PROT_MASK_SYSTEM)) {
|
||||||
if(mProtected) {
|
if(mProtected) {
|
||||||
request->redirect("/login");
|
checkRedirect(request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), 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");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue