From 26dced7cee8a89f82d39092b9e01e43981ac7bbb Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Mon, 2 Feb 2026 18:43:39 +0100 Subject: [PATCH] Save configuration to preferences in NVS --- include/config.h | 15 ++- include/webserver.h | 2 - lib/Nmea2kTwai/Nmea2kTwai.cpp | 6 +- src/config.cpp | 237 +++++++++++++++++++++++----------- src/main.cpp | 44 +++---- src/webserver.cpp | 50 ++++++- web/config.json | 10 +- web/index.css | 50 +++---- web/index.js | 8 +- 9 files changed, 274 insertions(+), 148 deletions(-) diff --git a/include/config.h b/include/config.h index dbb78fb..e2edae4 100644 --- a/include/config.h +++ b/include/config.h @@ -35,17 +35,20 @@ struct ConfigDef { static const ConfigDef configdefs[] = { {"systemName", ConfigType::STRING, String("OBPkp61")}, {"systemMode", ConfigType::CHAR, 'K'}, - {"logLevel", ConfigType::BYTE, uint8_t(1)}, + {"logLevel", ConfigType::BYTE, uint8_t(4)}, {"adminPassword", ConfigType::STRING, String("obpkp61")}, {"useAdminPass", ConfigType::BOOL, true}, + {"instDesc1", ConfigType::STRING, String("")}, + {"instDesc2", ConfigType::STRING, String("")}, {"apEnable", ConfigType::BOOL, true}, {"apPassword", ConfigType::STRING, String("obpkp61")}, {"apIp", ConfigType::STRING, String("192.168.15.1")}, {"apMask", ConfigType::STRING, String("255.255.255.0")}, {"stopApTime", ConfigType::SHORT, int16_t(0)}, + {"apHidden", ConfigType::BOOL, false}, {"cpuSpeed", ConfigType::SHORT, int16_t(160)}, {"ledBrightness", ConfigType::SHORT, int16_t(96)}, - {"ledRgbBrightness", ConfigType::SHORT, int16_t(96)}, + {"rgbBrightness", ConfigType::SHORT, int16_t(96)}, {"tempFormat", ConfigType::CHAR, 'C'}, {"switchBank", ConfigType::BYTE, uint8_t(0)}, {"key1", ConfigType::BYTE, uint8_t(1)}, @@ -75,15 +78,19 @@ private: Preferences& prefs; std::map values; std::map defs; + void updateValue(const char* key, ConfigType type, ConfigValue newvalue); [[noreturn]] void error_abort() const; public: explicit Config(Preferences& prefs); void load(); - bool save(JsonObject json); + void save(); void dump(); + bool hasKey(const char* key); + template T get(const char* key) const; + ConfigValue get(const char* key) const; uint8_t getByte(const char* key) const; int16_t getShort(const char* key) const; @@ -94,6 +101,8 @@ public: const String& getString(const char* key) const; const char* getCString(const char* key) const; + bool setValue(const char* key, const char* value); + }; extern Config config; diff --git a/include/webserver.h b/include/webserver.h index 1ac944f..a9ff8c8 100644 --- a/include/webserver.h +++ b/include/webserver.h @@ -5,5 +5,3 @@ extern AsyncWebServer server; void webserver_init(); -void send_embedded_file(String name, AsyncWebServerRequest *request); - diff --git a/lib/Nmea2kTwai/Nmea2kTwai.cpp b/lib/Nmea2kTwai/Nmea2kTwai.cpp index 0a077d1..b14d7e1 100644 --- a/lib/Nmea2kTwai/Nmea2kTwai.cpp +++ b/lib/Nmea2kTwai/Nmea2kTwai.cpp @@ -43,7 +43,7 @@ bool Nmea2kTwai::CANSendFrame(unsigned long id, unsigned char len, const unsigne return false; } txTimeouts = 0; - LOGD(TAG, "twai transmit id %ld, len %d", LOGID(id), (int)len); + LOGV(TAG, "twai transmit id %ld, len %d", LOGID(id), (int)len); return true; } @@ -82,7 +82,7 @@ bool Nmea2kTwai::CANGetFrame(unsigned long &id, unsigned char &len, unsigned cha LOGD(TAG, "twai: received invalid message %lld, len %d", LOGID(id), len); len = 8; } - LOGD(TAG, "twai rcv id=%ld,len=%d, ext=%d", + LOGV(TAG, "twai rcv id=%ld,len=%d, ext=%d", LOGID(message.identifier), message.data_length_code, message.extd); if (! message.rtr) { memcpy(buf, message.data, message.data_length_code); @@ -193,7 +193,7 @@ void Nmea2kTwai::loop() Nmea2kTwai::Status Nmea2kTwai::logStatus() { Status canState = getStatus(); - LOGI(TAG, "twai state %s, rxerr %d, txerr %d, txfail %d, txtimeout %d, rxmiss %d, rxoverrun %d", + LOGD(TAG, "twai state %s, rxerr %d, txerr %d, txfail %d, txtimeout %d, rxmiss %d, rxoverrun %d", stateStr(canState.state), canState.rx_errors, canState.tx_errors, diff --git a/src/config.cpp b/src/config.cpp index 6dc17aa..fcdb24a 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -14,7 +14,7 @@ Config::Config(Preferences& prefs) for (size_t i = 0; i < configdefsCount; ++i) { defs[configdefs[i].key] = &configdefs[i]; } - + } [[noreturn]] void Config::error_abort() const { @@ -83,83 +83,70 @@ void Config::load() { prefs.end(); } -bool Config::save(JsonObject json) { - - // TODO Not fail if one single value is wrong? +void Config::updateValue(const char* key, ConfigType type, ConfigValue newvalue) { + switch (type) { + case ConfigType::BYTE: + prefs.putUChar(key, std::get(newvalue)); + break; + case ConfigType::SHORT: + prefs.putShort(key, std::get(newvalue)); + break; + case ConfigType::INT: + prefs.putInt(key, std::get(newvalue)); + break; + case ConfigType::BOOL: + prefs.putBool(key, std::get(newvalue)); + break; + case ConfigType::FLOAT: + prefs.putFloat(key, std::get(newvalue)); + break; + case ConfigType::CHAR: + prefs.putChar(key, std::get(newvalue)); + break; + case ConfigType::STRING: + prefs.putString(key, std::get(newvalue)); + break; + } +} +void Config::save() { + ConfigValue oldval; prefs.begin(PREF_NAME, false); - - int errors = 0; - for (JsonPair kv : json) { - const char* key = kv.key().c_str(); - JsonVariant v = kv.value(); - - auto it = values.find(key); - if (it == values.end()) { - LOGE(TAG, "Unexpected missing key: %s", key); - return false; - } - - auto itdef = defs.find(key); - if (itdef == defs.end()) { - LOGE(TAG, "Unexpected missing defs key: %s", key); - return false; - } - - // const ConfigDef* def = itdef->second; - - switch (itdef->second->type) { - case ConfigType::BYTE: { - if (!v.is()) { - errors++; + for (const auto& def : configdefs) { + if (prefs.isKey(def.key)) { + switch (def.type) { + case ConfigType::BYTE: + oldval = (uint8_t)prefs.getUChar(def.key, std::get(def.defval)); + break; + case ConfigType::SHORT: + oldval = (int16_t)prefs.getShort(def.key, std::get(def.defval)); + break; + case ConfigType::INT: + oldval = prefs.getInt(def.key, std::get(def.defval)); + break; + case ConfigType::BOOL: + oldval = prefs.getBool(def.key, std::get(def.defval)); + break; + case ConfigType::FLOAT: + oldval = prefs.getFloat(def.key, std::get(def.defval)); + break; + case ConfigType::CHAR: + oldval = (char)prefs.getChar(def.key, std::get(def.defval)); + break; + case ConfigType::STRING: + oldval = prefs.getString(def.key, std::get(def.defval)); break; - } - uint8_t newval = v.as(); - uint8_t *curval = std::get_if(&values[key]); - if (newval != *curval) { - values[key] = newval; - //prefs.putUChar(key, newval); - LOGI(TAG, "changing %s, replacing %d with %d", key, *curval, newval); - } - break; } -/* case ConfigType::SHORT: - if (!v.is()) return false; - values[key] = v.as(); - prefs.putShort(key, v.as()); - break; - case ConfigType::INT: - if (!v.is()) return false; - values[key] = v.as(); - prefs.putInt(key, v.as()); - break; - case ConfigType::BOOL: - if (!v.is()) return false; - values[key] = v.as(); - prefs.putBool(key, v.as()); - break; - case ConfigType::FLOAT: - if (!v.is()) return false; - values[key] = v.as(); - prefs.putFloat(key, v.as()); - break; - case ConfigType::CHAR: - if (!v.is()) return false; - values[key] = v.as()[0]; - prefs.putChar(key, v.as()[0]); - break; - case ConfigType::STRING: - if (!v.is()) return false; - values[key] = String(v.as()); - prefs.putString(key, v.as()); - break; */ - default: - ESP_LOGE("CFG", "Unsupported type for key %s", key); + if (values[def.key] != oldval) { + LOGD(TAG, "NVS update needed for %s", def.key); + updateValue(def.key, def.type, values[def.key]); + } + } else { + LOGD(TAG, "Key does not exist in NVS: %s, creating", def.key); + updateValue(def.key, def.type, values[def.key]); } } - prefs.end(); - return true; } void Config::dump() { @@ -211,6 +198,16 @@ T Config::get(const char* key) const { return std::get(values.at(key)); } +ConfigValue Config::get(const char* key) const { + auto it = values.find(key); + return it->second; +} + +bool Config::hasKey(const char* key) { + auto it = values.find(key); + return ! (it == values.end()); +} + uint8_t Config::getByte(const char* key) const { auto it = values.find(key); if (it == values.end()) { @@ -249,13 +246,14 @@ int32_t Config::getInt(const char* key) const { bool Config::getBool(const char* key) const { auto it = values.find(key); - if (it == values.end()) + if (it == values.end()) { LOGE(TAG, "Missing config key: %s", key); - abort(); + error_abort(); + } if (auto v = std::get_if(&it->second)) return *v; LOGE(TAG, "Type mismatch for key: %s", key); - abort(); + error_abort(); } float Config::getFloat(const char* key) const { @@ -297,3 +295,94 @@ const String& Config::getString(const char* key) const { const char* Config::getCString(const char* key) const { return getString(key).c_str(); } + +bool Config::setValue(const char* key, const char* value) { + // sets data associated to a key to a new value + // does not write back to preferences, that has to be done + // separately + LOGD(TAG, "setting: %s to %s", key, value); + bool success = false; + char* end; + unsigned long byte_temp; + long int_temp; + bool bool_temp; + float float_temp; + switch (defs[key]->type) { + case ConfigType::BYTE: + byte_temp = strtoul(value, &end, 10); + if (*end != '\0') { + LOGW(TAG, "not a number"); + break; + } + if (temp > 255) { + LOGW(TAG, "byte out of range"); + break; + } + values[key] = static_cast(byte_temp); + LOGD(TAG, "converted to BYTE"); + success = true; + break; + case ConfigType::SHORT: + int_temp = strtoul(value, &end, 10); + if (*end != '\0') { + LOGW(TAG, "not a number"); + break; + } + if (temp < INT16_MIN || temp > INT16_MAX) { + LOGW(TAG, "short out of range"); + break; + } + values[key] = static_cast(int_temp); + LOGD(TAG, "converted to SHORT"); + success = true; + break; + case ConfigType::INT: + int_temp = strtoul(value, &end, 10); + if (*end != '\0') { + LOGW(TAG, "not a number"); + break; + } + if (temp < INT32_MIN || temp > INT32_MAX) { + LOGW(TAG, "integer out of range"); + break; + } + values[key] = static_cast(int_temp); + LOGD(TAG, "converted to INT"); + success = true; + break; + case ConfigType::BOOL: + if ((value[0] == '1' && value[1] == '\0') || (strcasecmp(value, "true") == 0)) { + bool_temp = true; + } else if ((value[0] == '0' && value[1] == '\0') || (strcasecmp(value, "false") == 0)) { + bool_temp = false; + } else { + LOGW(TAG, "invalid value for boolean"); + break; + } + values[key] = bool_temp; + LOGD(TAG, "converted to BOOL"); + success = true; + break; + case ConfigType::FLOAT: + float_temp = strtof(value, &end); + if (*end != '\0') { + LOGW(TAG, "invalid float"); + break; + } + values[key] = float_temp; + LOGD(TAG, "converted to FLOAT"); + success = true; + break; + case ConfigType::CHAR: + values[key] = value[0]; + LOGD(TAG, "converted to CHAR"); + success = true; + break; + case ConfigType::STRING: + values[key] = String(value); + LOGD(TAG, "converted to STRING"); + success = true; + break; + } + return success; +} diff --git a/src/main.cpp b/src/main.cpp index ab3cf05..931a47a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,6 +40,7 @@ uint8_t loglevel = 5; const char* wifi_ssid = "OBPKP61"; const char* wifi_pass = "keypad61"; bool ap_enabled = false; +bool ap_hidden = false; unsigned long firstStart = 0; unsigned long lastSensor = 0; @@ -248,33 +249,20 @@ void setup() { } // N2K basics - nodeid = N2K_DEFAULT_NODEID; - LOGI(TAG, "N2K default node is %d", nodeid); - //preferences.begin(PREF_NAME, false); - //nodeid = preferences.getInt("LastNodeId", N2K_DEFAULT_NODEID); - //preferences.end(); + LOGI(TAG, "N2K default node is %d", N2K_DEFAULT_NODEID); nodeid = config.getByte("LastNodeId"); LOGI(TAG, "N2K node id set to %d from preferences", nodeid); - //cpuspeed = preferences.getInt("cpuSpeed", 160); + // some other settings of interest cpuspeed = config.getShort("cpuSpeed"); - //int apstoptime = preferences.getInt("stopApTime", 0); int16_t apstoptime = config.getShort("stopApTime"); - //String apip = preferences.getString("apIp", "192.168.15.1"); - //String apmask = preferences.getString("apMask", "255.255.255.0"); String apip = config.getString("apIp"); String apmask = config.getString("apMask"); - //preferences.end(); // Setup webserver WiFi.persistent(false); WiFi.mode(WIFI_MODE_AP); - int channel = WIFI_CHANNEL; - bool hidden = false; - // TODO do not enable if preferences apEnable = false - // but prepare later switch on by config key - WiFi.softAP(wifi_ssid, wifi_pass, channel, hidden, WIFI_MAX_STA); // IPAddress ap_addr(192, 168, 15, 1); IPAddress ap_addr; @@ -287,7 +275,14 @@ void setup() { IPAddress ap_gateway(ap_addr); WiFi.softAPConfig(ap_addr, ap_gateway, ap_subnet); - ap_enabled = true; + + // Only enable if preferences apEnable is true + // But later switch on by config key is possible + ap_hidden = config.getBool("apHidden"); + ap_enabled = config.getBool("apEnable"); + if (ap_enabled) { + WiFi.softAP(wifi_ssid, wifi_pass, WIFI_CHANNEL, ap_hidden, WIFI_MAX_STA); + } // Initialize WebGUI webserver_init(); @@ -309,12 +304,11 @@ void setup() { 1 // LoadEquivalency (LEN) ); - // TODO Read configuration information from preferences // Manufacturer information is not changeable NMEA2000.SetConfigurationInformation( - "Open boat projects / NMEA2000 library", // Manufacturer - "", // Info 1 - "" // Info 2 + "Open Boat Projects / NMEA2000 library", // Manufacturer + config.getCString("instDesc1"), // Info 1 + config.getCString("instDesc2") // Info 2 ); uint32_t uid; // ISO 21bit identity number devived from chipid (MAC) @@ -354,8 +348,6 @@ void setup() { // Debug: NMEA2000.EnableForward(true); NMEA2000.Open(); - - led_init(); // Buzzer @@ -373,7 +365,7 @@ void setup() { digitalWrite(RGBLED_R, LOW); // boot status off delay(500); led_test(); - + // select current destination switch (destination) { case 'A': @@ -388,8 +380,6 @@ void setup() { } // I²C - // Serial.print("SHT31_LIB_VERSION: "); - // Serial.println(SHT31_LIB_VERSION); LOGI(TAG, "SHT31_LIB_VERSION: %s", SHT31_LIB_VERSION); Wire.begin(I2C_SDA, I2C_SCL); Wire.setClock(I2C_SPEED); @@ -613,7 +603,7 @@ void loop() { ap_enabled = false; } else { Serial.println("Enable Accesspoint"); - WiFi.softAP(wifi_ssid, wifi_pass); + WiFi.softAP(wifi_ssid, wifi_pass, WIFI_CHANNEL, ap_hidden, WIFI_MAX_STA); ap_enabled = true; } break; @@ -638,7 +628,7 @@ void loop() { } } - NMEA2000.loop(); + // NMEA2000.loop(); // not implemented yet NMEA2000.ParseMessages(); if ((millis() - lastSensor >= 5000) and sht_available) { diff --git a/src/webserver.cpp b/src/webserver.cpp index 9fafef5..c102f61 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -83,6 +83,7 @@ void webserver_init() { // API fast hack server.on("/api/capabilities", HTTP_GET, [](AsyncWebServerRequest *request) { + LOGD(TAG, "capabilities called"); StaticJsonDocument<100> doc; doc["apPwChange"] = "true"; String out; @@ -91,6 +92,7 @@ void webserver_init() { }); server.on("/api/checkpass", HTTP_GET, [](AsyncWebServerRequest *request) { + LOGD(TAG, "checkpass called"); StaticJsonDocument<100> doc; doc["status"] = "FAILED"; String out; @@ -99,24 +101,28 @@ void webserver_init() { }); server.on("/api/config", HTTP_GET, [](AsyncWebServerRequest *request) { + LOGD(TAG, "config called"); StaticJsonDocument<1024> doc; doc["systemName"] = config.getString("systemName"); doc["systemMode"] = String(config.getChar("systemMode")); + doc["instDesc1"] = config.getString("instDesc1"); + doc["instDesc2"] = config.getString("instDesc2"); doc["logLevel"] = loglevel; doc["version"] = VERSION; doc["fwtype"] = "unknown"; // TODO ? doc["salt"] = "secret"; doc["AdminPassword"] = "********"; - doc["useAdminPass"] = "false"; //config.getBool("useAdminPass") ? "true" : "false"; - doc["apEnable"] = "true"; + doc["useAdminPass"] = config.getBool("useAdminPass") ? "true" : "false"; + doc["apEnable"] = config.getBool("apEnable") ? "true" : "false"; doc["apIp"] = config.getString("apIp"); doc["apMask"] = config.getString("apMask"); doc["apPassword"] = "********"; doc["stopApTime"] = config.getShort("stopApTime"); + doc["apHidden"] = config.getBool("apHidden") ? "true" : "false"; doc["cpuSpeed"] = 160; doc["tempFormat"] = String(config.getChar("tempFormat")); doc["ledBrightness"] = led_brightness; - doc["ledRgbBrightness"] = rgb_brightness; + doc["rgbBrightness"] = rgb_brightness; doc["switchBank"] = config.getByte("switchBank"); doc["key1"] = keycode[BUTTON_1]; doc["key2"] = keycode[BUTTON_2]; @@ -140,6 +146,7 @@ void webserver_init() { }); server.on("/api/resetconfig", HTTP_GET, [](AsyncWebServerRequest *request) { + LOGD(TAG, "resetconfig called"); StaticJsonDocument<100> doc; doc["status"] = "FAILED"; String out; @@ -190,9 +197,37 @@ void webserver_init() { }); server.on("/api/setconfig", HTTP_POST, [](AsyncWebServerRequest *request) { - LOGD(TAG, "API setconfig called"); + LOGD(TAG, "setconfig called"); + // TODO _hash must be first parameter + int count = request->params(); + bool need_save = false; + LOGD(TAG, "setconfig Received %d params", count); + for (int i = 0; i < count; i++) { + const AsyncWebParameter* p = request->getParam(i); + if (!config.hasKey(p->name().c_str())) { + LOGD(TAG, "POST %s=%s; key not found!", p->name(), p->value()); + continue; + } + LOGD(TAG, "POST %s=%s", p->name(), p->value()); + // get old value for comparison + ConfigValue oldval = config.get(p->name().c_str()); + config.setValue(p->name().c_str(), p->value().c_str()); + // check if value was changed + if (config.get(p->name().c_str()) == oldval) { + // comparison of variants! + LOGD(TAG, "Values are equal. No change detected."); + } else { + need_save = true; + } + } + if (need_save) { + LOGI(TAG, "Writing changed values to NVS"); + config.save(); + } else { + LOGI(TAG, "No changes, no action taken"); + } StaticJsonDocument<100> doc; - doc["status"] = "FAILED"; + doc["status"] = "OK"; String out; serializeJson(doc, out); request->send(200, "application/json", out); @@ -201,13 +236,13 @@ void webserver_init() { server.on("/api/update", HTTP_POST, [](AsyncWebServerRequest *request) { // the request handler is triggered after the upload has finished... // create the response, add header, and send response - + LOGD(TAG, "update called"); StaticJsonDocument<100> doc; doc["status"] = "FAILED"; String out; serializeJson(doc, out); request->send(200, "application/json", out); - }, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ + }, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { // this is the new image upload part Serial.print("Retrieving firmware image named: "); Serial.println(filename); @@ -230,6 +265,7 @@ void webserver_init() { server.on("/api/devicelist", HTTP_GET, [](AsyncWebServerRequest *request) { // NMEA2000 device list + LOGD(TAG, "devicelist called"); AsyncResponseStream *response = request->beginResponseStream("application/json"); response->print("["); bool first = true; diff --git a/web/config.json b/web/config.json index 8d4dd48..8070120 100644 --- a/web/config.json +++ b/web/config.json @@ -115,6 +115,14 @@ "description": "Stop the access point after that many minutes if not used.\n1 to 60 minutes.\n\n'0' means that the access point is permanently enabled.", "category": "Wifi" }, +{ + "name": "apHidden", + "label": "Hide SSID", + "type": "boolean", + "default": "false", + "description": "Hide Wifi SSID", + "category": "Wifi" +}, { "name": "cpuSpeed", "label": "CPU Speed [MHz]", @@ -139,7 +147,7 @@ "category": "Hardware" }, { - "name": "ledRgbBrightness", + "name": "rgbBrightness", "label": "RGB-LED brightness", "type": "number", "default": 64, diff --git a/web/index.css b/web/index.css index 5945c98..708bfaf 100644 --- a/web/index.css +++ b/web/index.css @@ -1,7 +1,6 @@ * { box-sizing: border-box; } - body { font-family: Arial, Helvetica, sans-serif; margin: 0; @@ -14,14 +13,12 @@ body { left: 0; right: 0; } - .main { display: flex; flex-direction: column; margin: 0.2em; overflow: hidden; } - #tabs { display: flex; flex-wrap: wrap; @@ -31,53 +28,51 @@ body { #tabs .tab { background-color: lightgray; padding: 0.5em; - /*border: 1px; - border-color: grey; - border-style: solid; */ border: 1px solid grey; opacity: 0.6; } #tabs .tab.active { opacity: 1; } - #tabPages { overflow: auto; padding-bottom: 1ex; border-bottom: 1px solid grey; } - +hr { + border-top: 1px solid grey; +} .configForm { - padding-bottom: 0.5em; + padding-bottom: 0.5em; } .configForm .buttons { - margin-bottom: 0.5em; + margin-bottom: 0.5em; } .configForm .content>div:nth-child(even) { - background-color: rgb(211 211 211 / 43%); + background-color: rgb(211 211 211 / 43%); } #statusPage .even { - background-color: rgb(211 211 211 / 43%); + background-color: rgb(211 211 211 / 43%); } #statusPageContent { margin-bottom: 0.5em; } .counter-row .value{ - text-align: right; - width: 6em; + text-align: right; + width: 6em; } .icon-row .label{ - width: 8.7em; + width: 8.7em; } .category .title .label { - opacity: 1; - margin-left: 1em; + opacity: 1; + margin-left: 1em; } .changed input{ - color: green; + color: green; } .changed select{ - color: green; + color: green; } .category.changed{ color: green; @@ -160,7 +155,6 @@ button.addunassigned { display: flex; overflow-y: auto; } - .overlay { margin: auto; background-color: white; @@ -214,7 +208,6 @@ h1 { .icon-eye.active{ opacity: 1; } - .dash { width: 6.5em; height: 4em; @@ -242,10 +235,10 @@ div#dashboardPage { } .dashValue.formatLatitude { font-size: 1.1em; - } +} .dashValue.formatLongitude { - font-size: 1.1em; - } + font-size: 1.1em; +} .dashValue.formatDate { font-size: 1.2em; } @@ -255,12 +248,12 @@ div#dashboardPage { padding: 0.1em; background-color: lightgray; font-size: 0.7em; - } -.footer .unit{ } -.footer .source{ +.footer .unit { +} +.footer .source { flex: 1; -} +} #adminPassInput { margin-bottom: 1em; } @@ -273,7 +266,6 @@ div#uploadProgress { max-width: 20em; height: 1em; margin-left: 1em; - /* margin-right: auto; */ border: 1px solid grey; margin-top: 0.5em; margin-bottom: 0.5em; diff --git a/web/index.js b/web/index.js index 978b1eb..c50cacf 100644 --- a/web/index.js +++ b/web/index.js @@ -198,7 +198,7 @@ checkers.checkPort=function(v,allValues,def){ let parsed=parseInt(v); if (isNaN(parsed)) return "must be a number"; - if (parsed <1 || parsed >= 65536) return "port must be in the range 1..65536"; + if (parsed <1 || parsed >= 65535) return "port must be in the range 1..65535"; } checkers.checkSystemName=function(v) { //2...32 characters for ssid @@ -293,6 +293,7 @@ let url = apiPrefix + "/api/setconfig" let body = "_hash=" + encodeURIComponent(pass) + "&"; let allValues = getAllConfigs(); + console.log(allValues); if (!allValues) return; for (let name in allValues) { if (name == 'adminPassword') { @@ -303,7 +304,6 @@ fetch(url, { method: 'POST', headers: { - //'Content-Type': 'application/octet-stream' //we must lie here 'Content-Type': 'application/x-www-form-urlencoded' }, body: body @@ -323,6 +323,7 @@ alert("unable to set config: " + status.status); } }) + .catch(err => console.error(err)); }) .catch(function (e) { alert(e); }) } @@ -633,6 +634,9 @@ el = addEl('input', clazz, frame); if (configItem.readOnly) el.setAttribute('disabled', true); el.setAttribute('name', configItem.name) + if (configItem.type === 'string') { + // TODO limit length + } if (configItem.type === 'password') { el.setAttribute('type', 'password'); let vis = addEl('span', 'icon-eye icon', frame);