diff --git a/tools/cdata.js b/tools/cdata.js index c05b28e522..4001b12c51 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -26,7 +26,7 @@ const packageJson = require("../package.json"); // Export functions for testing module.exports = { isFileNewerThan, isAnyFileInFolderNewerThan }; -const output = ["wled00/html_ui.h", "wled00/html_pixart.h", "wled00/html_cpal.h", "wled00/html_pxmagic.h", "wled00/html_settings.h", "wled00/html_other.h"] +const output = ["wled00/html_ui.h", "wled00/html_pixart.h", "wled00/html_cpal.h", "wled00/html_edit.h", "wled00/html_pxmagic.h", "wled00/html_settings.h", "wled00/html_other.h"] // \x1b[34m is blue, \x1b[36m is cyan, \x1b[0m is reset const wledBanner = ` @@ -246,6 +246,21 @@ writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index'); writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixart'); //writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal'); writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'pxmagic'); +//writeHtmlGzipped("wled00/data/edit.htm", "wled00/html_edit.h", 'edit'); + + +writeChunks( + "wled00/data", + [ + { + file: "edit.htm", + name: "PAGE_edit", + method: "gzip", + filter: "html-minify" + } + ], + "wled00/html_edit.h" +); writeChunks( "wled00/data/cpal", diff --git a/wled00/data/edit.htm b/wled00/data/edit.htm index 4f06642331..4c0429016f 100644 --- a/wled00/data/edit.htm +++ b/wled00/data/edit.htm @@ -1,586 +1,583 @@ - -ESP8266 SPIFFS File Editor - - - - - - -
-
-
-
- - + +
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/wled00/data/index.js b/wled00/data/index.js index 2514f03fb1..2d49a26400 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -672,7 +672,6 @@ function parseInfo(i) { //syncTglRecv = i.str; maxSeg = i.leds.maxseg; pmt = i.fs.pmt; - if (pcMode && !i.wifi.ap) gId('edit').classList.remove("hide"); else gId('edit').classList.add("hide"); gId('buttonNodes').style.display = lastinfo.ndc > 0 ? null:"none"; // do we have a matrix set-up mw = i.leds.matrix ? i.leds.matrix.w : 0; @@ -3151,7 +3150,6 @@ function togglePcMode(fromB = false) if (!fromB && ((wW < 1024 && lastw < 1024) || (wW >= 1024 && lastw >= 1024))) return; // no change in size and called from size() if (pcMode) openTab(0, true); gId('buttonPcm').className = (pcMode) ? "active":""; - if (pcMode && !ap) gId('edit').classList.remove("hide"); else gId('edit').classList.add("hide"); gId('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto"; sCol('--bh', gId('bot').clientHeight + "px"); _C.style.width = (pcMode || simplifiedUI)?'100%':'400%'; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 1d81655d6d..88bcdcba7c 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -539,7 +539,6 @@ void handleSerial(); void updateBaudRate(uint32_t rate); //wled_server.cpp -void createEditHandler(bool enable); void initServer(); void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& headl, const String& subl="", byte optionT=255); void serveJsonError(AsyncWebServerRequest* request, uint16_t code, uint16_t error); diff --git a/wled00/set.cpp b/wled00/set.cpp index 893081b986..9ce1eef2b3 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -613,7 +613,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) #ifndef WLED_DISABLE_OTA aOtaEnabled = request->hasArg(F("AO")); #endif - //createEditHandler(correctPIN && !otaLock); otaSameSubnet = request->hasArg(F("SU")); } } diff --git a/wled00/util.cpp b/wled00/util.cpp index 8aaaf34cab..487494e545 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -369,7 +369,6 @@ void checkSettingsPIN(const char* pin) { if (!correctPIN && millis() - lastEditTime < PIN_RETRY_COOLDOWN) return; // guard against PIN brute force bool correctBefore = correctPIN; correctPIN = (strlen(settingsPIN) == 0 || strncmp(settingsPIN, pin, 4) == 0); - if (correctBefore != correctPIN) createEditHandler(correctPIN); lastEditTime = millis(); } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 923688106d..9aba63df80 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -166,7 +166,6 @@ void WLED::loop() // 15min PIN time-out if (strlen(settingsPIN)>0 && correctPIN && millis() - lastEditTime > PIN_TIMEOUT) { correctPIN = false; - createEditHandler(false); } // reconnect WiFi to clear stale allocations if heap gets too low diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 75b4ae3f5a..8a4d53b69e 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -17,6 +17,7 @@ #include "html_pxmagic.h" #endif #include "html_cpal.h" +#include "html_edit.h" // define flash strings once (saves flash memory) static const char s_redirecting[] PROGMEM = "Redirecting..."; @@ -26,6 +27,13 @@ static const char s_unlock_cfg [] PROGMEM = "Please unlock settings using PIN co static const char s_rebooting [] PROGMEM = "Rebooting now..."; static const char s_notimplemented[] PROGMEM = "Not implemented"; static const char s_accessdenied[] PROGMEM = "Access Denied"; +static const char s_not_found[] PROGMEM = "Not found"; +static const char s_wsec[] PROGMEM = "wsec.json"; +static const char s_func[] PROGMEM = "func"; +static const char s_path[] PROGMEM = "path"; +static const char s_cache_control[] PROGMEM = "Cache-Control"; +static const char s_no_store[] PROGMEM = "no-store"; +static const char s_expires[] PROGMEM = "Expires"; static const char _common_js[] PROGMEM = "/common.js"; //Is this an IP? @@ -71,9 +79,9 @@ static void setStaticContentCacheHeaders(AsyncWebServerResponse *response, int c #ifndef WLED_DEBUG // this header name is misleading, "no-cache" will not disable cache, // it just revalidates on every load using the "If-None-Match" header with the last ETag value - response->addHeader(F("Cache-Control"), F("no-cache")); + response->addHeader(FPSTR(s_cache_control), F("no-cache")); #else - response->addHeader(F("Cache-Control"), F("no-store,max-age=0")); // prevent caching if debug build + response->addHeader(FPSTR(s_cache_control), F("no-store,max-age=0")); // prevent caching if debug build #endif char etag[32]; generateEtag(etag, eTagSuffix); @@ -198,7 +206,7 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, request->_tempFile.close(); if (filename.indexOf(F("cfg.json")) >= 0) { // check for filename with or without slash doReboot = true; - request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("Configuration restore successful.\nRebooting...")); + request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("Config restore ok.\nRebooting...")); } else { if (filename.indexOf(F("palette")) >= 0 && filename.indexOf(F(".json")) >= 0) loadCustomPalettes(); request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("File Uploaded!")); @@ -207,25 +215,94 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, } } -void createEditHandler(bool enable) { +static const char _edit_htm[] PROGMEM = "/edit.htm"; + +void createEditHandler() { if (editHandler != nullptr) server.removeHandler(editHandler); - if (enable) { - #ifdef WLED_ENABLE_FS_EDITOR - #ifdef ARDUINO_ARCH_ESP32 - editHandler = &server.addHandler(new SPIFFSEditor(WLED_FS));//http_username,http_password)); - #else - editHandler = &server.addHandler(new SPIFFSEditor("","",WLED_FS));//http_username,http_password)); - #endif - #else - editHandler = &server.on(F("/edit"), HTTP_GET, [](AsyncWebServerRequest *request){ - serveMessage(request, 501, FPSTR(s_notimplemented), F("The FS editor is disabled in this build."), 254); - }); - #endif - } else { - editHandler = &server.on(F("/edit"), HTTP_ANY, [](AsyncWebServerRequest *request){ + + editHandler = &server.on(F("/edit"), static_cast(HTTP_GET), [](AsyncWebServerRequest *request) { + // PIN check for GET/DELETE, for POST it is done in handleUpload() + if (!correctPIN) { serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254); - }); - } + return; + } + const String& func = request->arg(FPSTR(s_func)); + + if(func.length() == 0) { + // default: serve the editor page + handleStaticContent(request, FPSTR(_edit_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_edit, PAGE_edit_length); + return; + } + + if (func == "list") { + bool first = true; + AsyncResponseStream* response = request->beginResponseStream(FPSTR(CONTENT_TYPE_JSON)); + response->addHeader(FPSTR(s_cache_control), FPSTR(s_no_store)); + response->addHeader(FPSTR(s_expires), F("0")); + response->write('['); + + File rootdir = WLED_FS.open("/", "r"); + File rootfile = rootdir.openNextFile(); + while (rootfile) { + String name = rootfile.name(); + if (name.indexOf(FPSTR(s_wsec)) >= 0) { + rootfile = rootdir.openNextFile(); // skip wsec.json + continue; + } + if (!first) response->write(','); + first = false; + response->printf_P(PSTR("{\"name\":\"%s\",\"type\":\"file\",\"size\":%u}"), name.c_str(), rootfile.size()); + rootfile = rootdir.openNextFile(); + } + rootfile.close(); + rootdir.close(); + response->write(']'); + request->send(response); + return; + } + + String path = request->arg(FPSTR(s_path)); // remaining functions expect a path + + if (path.length() == 0) { + request->send(400, FPSTR(CONTENT_TYPE_PLAIN), F("Missing path")); + return; + } + + if (path.charAt(0) != '/') { + path = '/' + path; // prepend slash if missing + } + + if (!WLED_FS.exists(path)) { + request->send(404, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_not_found)); + return; + } + + if (path.indexOf(FPSTR(s_wsec)) >= 0) { + request->send(403, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied)); // skip wsec.json + return; + } + + if (func == "edit") { + request->send(WLED_FS, path); + return; + } + + if (func == "download") { + request->send(WLED_FS, path, String(), true); + return; + } + + if (func == "delete") { + if (!WLED_FS.remove(path)) + request->send(500, FPSTR(CONTENT_TYPE_PLAIN), F("Delete failed")); + else + request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("File deleted")); + return; + } + + // unrecognized func + request->send(400, FPSTR(CONTENT_TYPE_PLAIN), F("Invalid function")); + }); } static bool captivePortal(AsyncWebServerRequest *request) @@ -391,7 +468,7 @@ void initServer() size_t len, bool isFinal) {handleUpload(request, filename, index, data, len, isFinal);} ); - createEditHandler(correctPIN); + createEditHandler(); // initialize "/edit" handler, access is protected by "correctPIN" static const char _update[] PROGMEM = "/update"; #ifndef WLED_DISABLE_OTA @@ -569,8 +646,8 @@ void serveSettingsJS(AsyncWebServerRequest* request) } AsyncResponseStream *response = request->beginResponseStream(FPSTR(CONTENT_TYPE_JAVASCRIPT)); - response->addHeader(F("Cache-Control"), F("no-store")); - response->addHeader(F("Expires"), F("0")); + response->addHeader(FPSTR(s_cache_control), FPSTR(s_no_store)); + response->addHeader(FPSTR(s_expires), F("0")); response->print(F("function GetV(){var d=document;")); getSettingsJS(subPage, *response); @@ -694,7 +771,6 @@ void serveSettings(AsyncWebServerRequest* request, bool post) { #endif case SUBPAGE_LOCK : { correctPIN = !strlen(settingsPIN); // lock if a pin is set - createEditHandler(correctPIN); serveMessage(request, 200, strlen(settingsPIN) > 0 ? PSTR("Settings locked") : PSTR("No PIN set"), FPSTR(s_redirecting), 1); return; }