Skip to content

Conversation

DedeHai
Copy link
Collaborator

@DedeHai DedeHai commented Sep 23, 2025

I made a new file editor with the help of our AI friends (I do suck at JS and HTML) so someone with more knowledge in web creation please check for potential issues.

This replaces the default SPIFFS-editor from the Async Webserver library.

Features:

  • no mendatory external JS dependency, works in offline mode
  • optional external dependency is used for highlighting JSON, plain text edit is used if not available
  • WLED styling (dark mode only)
  • JSON files are displayed "prettyfied" and saved "minified"
  • JSON color highlighting (if available)
  • JSON verification during edit and on saving both in online and offline mode
  • special treatment for ledmap files: displayed in aligned columns (2D) or as lines (1D), saved as minified json: no more white-space problems

removed restrictions to the editor in offline mode and mobile.

tested and working in chrome (PC and mobile), firefox and safari. Also checked ESP32 and ESP8266.

Bonus: saves 2k of flash

JSON file editing:
image

JSON error:
image

JSON error on saving:
image

Ledmap display:
image

image preview:
image

Edit: UI buttons updated:
image

Summary by CodeRabbit

  • New Features

    • Full web-based file editor at /edit: ACE-powered editor with uploader, file tree, image preview, direct download/delete, and live JSON validation.
  • Improvements

    • Consolidated upload/save flow, pretty-print/minify on save, streamlined request/loader UX, and improved dark-themed editor UI.
  • Behavior Changes

    • Editor is PIN-protected; editor visibility no longer tied to PC/AP state; caching and editor routing updated.

Copy link
Contributor

coderabbitai bot commented Sep 23, 2025

Walkthrough

Adds a rebuilt web editor page (edit.htm) into the build and emits wled00/html_edit.h; replaces the client editor UI with an ACE-based editor and new uploader/tree UI; removes conditional visibility toggles for #edit; and wires a PIN‑protected /edit HTTP handler supporting list/view/download, DELETE, and uploads.

Changes

Cohort / File(s) Summary
Build pipeline for editor assets
tools/cdata.js
Generates wled00/html_edit.h from wled00/data/edit.htm (gzip + html-minify), registers PAGE_edit, and re-enables emitting the edit page in the build output.
Editor UI implementation
wled00/data/edit.htm
Replaces iframe/inline editor with an ACE-based editor, adds top uploader/filepath controls, file tree with per-item actions (Download/Delete), preview pane (images), live JSON validation/pretty-printing/ledmap formatting, simplified request queue/loader, DOM/layout and style updates, and changes createEditor signature to createEditor(element, file) returning an API (save(), loadText(), clearEditor()). Also adds createTop(element, editor) and updates tree/uploader logic.
Client index adjustments
wled00/data/index.js
Removes conditional show/hide of the #edit element based on pcMode and Wi‑Fi AP status in parseInfo and togglePcMode.
Server editor routing and handlers
wled00/wled_server.cpp
Includes html_edit.h and _edit_htm; reworks createEditHandler to enable a PIN‑protected /edit handler supporting GET (list, view, download, default editor page), DELETE (remove files), and upload handling; adds editor-related flash strings, protects specific files (e.g., wsec.json), updates cache header constants, and changes cfg.json restore message to "Config restore ok...".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • willmmiles
  • blazoncek

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly identifies the main change of introducing a new file editor, directly reflecting the core objective of the pull request without unnecessary detail or noise.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
wled00/data/edit.htm (4)

10-12: Add defer (and optionally SRI) to CDN script tags to avoid render‑blocking and improve security.

These scripts are optional; deferring them prevents blocking HTML parsing. If you keep CDN usage, consider SRI + crossorigin too.

-<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/ace.min.js"></script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/mode-json.min.js"></script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/theme-monokai.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/ace.min.js" defer></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/mode-json.min.js" defer></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/theme-monokai.min.js" defer></script>

136-146: Add XHR timeout/error handlers to prevent a stuck loader.

Without timeout/error hooks, the spinner can remain visible indefinitely.

-    this.x=new XMLHttpRequest();
-    this.x.onreadystatechange=cb(this.x,req);
+    this.x=new XMLHttpRequest();
+    this.x.timeout = 10000;
+    this.x.onreadystatechange=cb(this.x,req);
+    this.x.onerror = () => { ge("loader").style.display="none"; this.r=false; alert("Network error"); };
+    this.x.ontimeout = () => { ge("loader").style.display="none"; this.r=false; alert("Request timed out"); };

200-204: Don't require a file extension for new files.

Extensions aren’t mandatory in WLED data (e.g., user files). Allow any non‑empty name.

-    if(!newPath || newPath.indexOf(".") == -1) {
-      alert("Please specify a filename with extension (e.g., newfile.txt)");
+    if(!newPath) {
+      alert("Please specify a filename");

662-662: Remove unused hidden iframe to save bytes.

Downloads use window.open; the iframe isn’t referenced elsewhere.

-  <iframe id="download-frame" style="display:none;"></iframe>
wled00/wled_server.cpp (2)

331-340: POST /edit path: consider early 401 if PIN is not correct to avoid needless upload callbacks.

Minor: you rely on handleUpload() to reject when !correctPIN. You could short‑circuit in the request handler for clarity and to reduce callback invocations.

-  server.on(F("/edit"), HTTP_POST,
-    [](AsyncWebServerRequest *request) { 
-      // Don't send any response here - let the upload callback handle it
-      DEBUG_PRINTLN("POST handler called - upload should be handled by callback");
-    },
+  server.on(F("/edit"), HTTP_POST,
+    [](AsyncWebServerRequest *request) {
+      if (!correctPIN) { request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg)); return; }
+      DEBUG_PRINTLN("POST handler called - upload should be handled by callback");
+    },

232-235: Remove no‑op parameter loop (dead code).

The loop reads params then discards them. Safe to drop.

-      for (int i = 0; i < request->params(); i++) {
-        AsyncWebParameter* p = request->getParam(i);
-      }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b39757 and 75e6a15.

📒 Files selected for processing (4)
  • tools/cdata.js (2 hunks)
  • wled00/data/edit.htm (1 hunks)
  • wled00/data/index.js (0 hunks)
  • wled00/wled_server.cpp (3 hunks)
💤 Files with no reviewable changes (1)
  • wled00/data/index.js
🧰 Additional context used
📓 Path-based instructions (2)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for all C++ source and header files

Files:

  • wled00/wled_server.cpp
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/edit.htm
🧠 Learnings (2)
📚 Learning: 2025-09-20T09:00:08.933Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T09:00:08.933Z
Learning: Applies to wled00/html_*.h : DO NOT edit generated headers wled00/html_*.h

Applied to files:

  • wled00/wled_server.cpp
  • tools/cdata.js
📚 Learning: 2025-09-20T09:00:08.933Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T09:00:08.933Z
Learning: Always commit both the web source files and the generated wled00/html_*.h files

Applied to files:

  • tools/cdata.js
🧬 Code graph analysis (1)
wled00/wled_server.cpp (1)
tools/cdata.js (2)
  • i (63-63)
  • output (29-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: wled_build / Build Environments (usermods)
🔇 Additional comments (3)
wled00/data/edit.htm (1)

400-431: Confirm ledmap pretty‑printer supports actual ledmap schema(s).

If map is nested or structured differently, alignment logic may misformat. Verify against typical 1D/2D ledmap files.

Would you like me to adapt prettyLedmap() to detect and format nested arrays if present?

tools/cdata.js (2)

29-29: LGTM: added html_edit.h to outputs.

This ensures rebuild detection includes the new page header.


252-263: LGTM — generated header missing; verify wled00/html_edit.h contains PAGE_edit symbols

wled00/wled_server.cpp:307 references PAGE_edit and tools/cdata.js:257 registers name: "PAGE_edit", but wled00/html_edit.h is not present in the repo — confirm the build step generates this header or commit it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
wled00/data/edit.htm (4)

246-276: DELETE uses multipart FormData; prefer URL-encoded body to maximize server parsing compatibility.

AsyncWebServer reliably parses x-www-form-urlencoded; multipart on DELETE can be flaky across stacks.

Apply this diff:

-  function deleteFile(p, tree) {
+  function deleteFile(p, tree) {
     if (!confirm("Are you sure you want to delete " + p + "?")) return;
-    var fd = new FormData();
-    fd.append("path", p);
-    req.add("DELETE", "/edit", fd, function(st, resp) {
+    var body = "path=" + encodeURIComponent(p);
+    req.add("DELETE", "/edit", body, function(st, resp) {
       if (st != 200) alert("ERROR " + st + ": " + resp);
       else tree.refreshPath("/");
     });
   }

136-146: Add a request timeout to prevent a stuck loader on network issues.

Without a timeout, loader can remain visible indefinitely.

     this.x=new XMLHttpRequest();
     this.x.onreadystatechange=cb(this.x,req);
+    this.x.timeout = 10000; // 10s
+    this.x.ontimeout = () => {
+      ge("loader").style.display="none";
+      this.r = false;
+      alert("Request timed out");
+    };

461-495: Initialize Ace robustly and add live JSON validation parity with textarea.

Current 100ms probe may miss deferred CDN load; no live validation in Ace path.

-  // Try to initialize Ace editor if available
-  setTimeout(function() {
-    if(typeof ace !== 'undefined') {
+  // Try to initialize Ace editor if available (poll a few times)
+  let aceTries = 0;
+  function tryInitAce() {
+    if (typeof ace !== 'undefined') {
       try {
         aceEditor = ace.edit(editorDiv);
         aceEditor.setTheme("ace/theme/monokai");
         aceEditor.session.setMode("ace/mode/text");
         aceEditor.setOptions({
           fontSize: "13px",
           fontFamily: "monospace",
           showPrintMargin: false,
           wrap: true
         });
         useAce = true;
         console.log("Ace editor loaded successfully");
 
         // Always switch to Ace if available (even with no file)
         if(useAce && aceEditor) {
           switchToAce();
         }
 
+        // Live JSON validation in Ace (offline-friendly)
+        aceEditor.session.on("change", function() {
+          const pathField = ge("upload-path");
+          const filename = (pathField && pathField.value) ? pathField.value : currentFile;
+          if (filename && filename.toLowerCase().endsWith(".json")) {
+            try {
+              JSON.parse(aceEditor.getValue());
+              editorDiv.style.border = "2px solid #333";
+            } catch(e) {
+              editorDiv.style.border = "2px solid #f00";
+            }
+          } else {
+            editorDiv.style.border = "2px solid #333";
+          }
+        });
+
         // Monitor filename input for JSON highlighting
         var pathField = ge("upload-path");
         if(pathField) {
           pathField.oninput = function() {
             updateEditorMode();
           };
         }
       } catch(e) {
         console.log("Ace editor failed to load:", e);
         useAce = false;
       }
-    }
-  }, 100);
+      return;
+    }
+    if (++aceTries < 30) setTimeout(tryInitAce, 100); // retry up to ~3s
+  }
+  setTimeout(tryInitAce, 100);

656-663: Remove unused download iframe to save bytes.

No longer used after switching to window.open().

  <div id="preview"></div>
- <iframe id="download-frame" style="display:none;"></iframe>
wled00/wled_server.cpp (1)

319-334: Harden DELETE: accept path from query or body; forbid deleting wsec.json.

Improves robustness (client can send either) and prevents accidental credential wipes.

-    if (request->method() == HTTP_DELETE) {
-      if (!request->hasParam(FPSTR(s_path), true)) {
+    if (request->method() == HTTP_DELETE) {
+      if (!request->hasParam(FPSTR(s_path), true) && !request->hasParam(FPSTR(s_path))) {
         request->send(400, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_not_found));
         return;
       }
 
-      String path = request->getParam(FPSTR(s_path), true)->value();
+      String path = request->hasParam(FPSTR(s_path), true)
+        ? request->getParam(FPSTR(s_path), true)->value()
+        : request->getParam(FPSTR(s_path))->value();
       if (!path.startsWith("/")) path = "/" + path;
 
+      // disallow deleting WiFi secrets
+      if (path.indexOf(FPSTR(s_wsec)) >= 0) {
+        request->send(403, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied));
+        return;
+      }
+
       if (!WLED_FS.remove(path)) {
         request->send(500, FPSTR(CONTENT_TYPE_PLAIN), F("Delete failed"));
         return;
       }
 
       request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("File deleted"));
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75e6a15 and e6fa691.

📒 Files selected for processing (2)
  • wled00/data/edit.htm (1 hunks)
  • wled00/wled_server.cpp (5 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for all C++ source and header files

Files:

  • wled00/wled_server.cpp
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/edit.htm
🧠 Learnings (2)
📚 Learning: 2025-09-20T09:00:08.933Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T09:00:08.933Z
Learning: Applies to wled00/html_*.h : DO NOT edit generated headers wled00/html_*.h

Applied to files:

  • wled00/wled_server.cpp
📚 Learning: 2025-09-20T09:00:08.933Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T09:00:08.933Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Applied to files:

  • wled00/data/edit.htm
🧬 Code graph analysis (1)
wled00/wled_server.cpp (2)
tools/cdata.js (2)
  • path (19-19)
  • output (29-29)
wled00/data/common.js (1)
  • path (85-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_compat)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
  • GitHub Check: wled_build / Build Environments (nodemcuv2)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_compat)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_160)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (esp8266_2m)
🔇 Additional comments (5)
wled00/data/edit.htm (2)

528-543: Textarea JSON validation is good. Mirror the same border behavior for Ace editor (see above).

Ensures “validation during editing” claim holds with and without CDN.


14-110: Indentation complies with repo rule (tabs in wled00/data).

Thanks for aligning to tabs throughout.

Also applies to: 112-154, 156-184, 186-246, 248-276, 279-398, 400-431, 432-642, 656-663

wled00/wled_server.cpp (3)

296-312: Good: downloading wsec.json is blocked.

This closes the secret leakage hole.


770-773: Enabling editor handler immediately after PIN acceptance: LGTM.

Fixes the “stay locked until visiting Lock page” issue.


229-336: Overall /edit routing is solid (PIN-gated GET/DELETE, POST via upload callback).

Directory listing excludes wsec.json; edit/download paths normalized; default serves editor page with cache headers via handleStaticContent.

Please confirm AsyncWebServer on both ESP8266 and ESP32 parses multipart bodies for DELETE. If not, the paired client refactor (URL-encoded body) plus the server query/body handling above will fully cover it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
wled00/data/edit.htm (6)

349-356: Optional: Show directories in the tree.

Currently only files are listed. If the backend returns type: "dir", users can't navigate into folders.

Apply this minimal enhancement:

   function addList(path,items){
     for(var i=0;i<items.length;i++){
-      if(items[i].type==="file" && items[i].name !== "wsec.json") { // hide wsec.json, this is redundant on purpose (done in C code too), just in case...
+      if(items[i].type==="file" && items[i].name !== "wsec.json") { // hide wsec.json, this is redundant on purpose (done in C code too), just in case...
         var fullPath = path === "/" ? "/" + items[i].name : path + "/" + items[i].name;
         createLeaf(fullPath, items[i].name);
+      } else if (items[i].type==="dir") {
+        // Simple folder entry; click to load its contents
+        (function(dirName){
+          var fullPath = path === "/" ? "/" + dirName : path + "/" + dirName;
+          var folder = ce("div");
+          folder.style.cssText="cursor:pointer;padding:2px 4px;border-radius:2px;position:relative;color:#8cf";
+          folder.textContent = dirName + "/";
+          folder.onclick = function(){
+            treeRoot.innerHTML = "";
+            httpGet(fullPath);
+          };
+          folder.onmouseover=function(){ folder.style.background="#333"; };
+          folder.onmouseout=function(){ folder.style.background=""; };
+          treeRoot.appendChild(folder);
+        })(items[i].name);
       }
     }
   }

165-174: Keep editor mode in sync with filename input.

Tie the filename input’s oninput to the editor’s mode switch so JSON highlighting updates as users type.

Apply this diff:

   var path = ce("input");
   path.id = "upload-path";
   path.type = "text";
   path.value = "";
   path.placeholder = "Filename (e.g. config.json)";
+  // Update editor mode when filename changes (if editor exposes updateMode)
+  path.oninput = function(){
+    if (editor && typeof editor.updateMode === "function") editor.updateMode();
+  };

And expose updateMode on the editor object (see next comment).


583-624: Expose an updateMode() method on the editor API.

Lets the uploader filename field trigger JSON/text mode updates.

Apply this diff:

   var ed={
     save:function(){
@@
       httpPost(filename, getCurrentContent());
     },
     loadUrl:function(fn){
@@
       httpGet(fn);
     },
     loadNewFile:function(fn, content){
@@
       updateEditorMode(); // Update editor mode after loading new file
-    }
+    },
+    updateMode:function(){
+      updateEditorMode();
+    }
   };

447-480: Make Ace initialization robust (don’t rely on a 100ms race).

A single 100ms timeout can miss the CDN load. Initialize Ace on load and retry if needed.

Apply this diff:

-  // Try to initialize Ace editor if available
-  setTimeout(function() {
-    if(typeof ace !== 'undefined') {
-      try {
-        aceEditor = ace.edit(editorDiv);
-        aceEditor.setTheme("ace/theme/monokai");
-        aceEditor.session.setMode("ace/mode/text");
-        aceEditor.setOptions({
-          fontSize: "13px",
-          fontFamily: "monospace",
-          showPrintMargin: false,
-          wrap: true
-        });
-        useAce = true;
-        console.log("Use Ace editor");
-
-        // Always switch to Ace if available (even with no file)
-        if(useAce && aceEditor) {
-          switchToAce();
-        }
-
-        // Monitor filename input for JSON highlighting
-        var pathField = ge("upload-path");
-        if(pathField) {
-          pathField.oninput = function() {
-            updateEditorMode();
-          };
-        }
-      } catch(e) {
-        console.log("Ace load failed:", e);
-        useAce = false;
-      }
-    }
-  }, 100);
+  function initAce(){
+    if (useAce || typeof ace === 'undefined') return;
+    try {
+      aceEditor = ace.edit(editorDiv);
+      aceEditor.setTheme("ace/theme/monokai");
+      aceEditor.session.setMode("ace/mode/text");
+      aceEditor.setOptions({ fontSize:"13px", fontFamily:"monospace", showPrintMargin:false, wrap:true });
+      useAce = true;
+      console.log("Use Ace editor");
+      switchToAce();
+      updateEditorMode();
+    } catch(e) {
+      console.log("Ace load failed:", e);
+      useAce = false;
+    }
+  }
+  // Try now, then on window load as a fallback
+  setTimeout(initAce, 100);
+  window.addEventListener('load', initAce);

115-152: Add timeout/error handling to XHR queue to avoid stuck loader and dead requests.

Currently no onerror/ontimeout. The spinner can get stuck and the queue wedged.

Apply this diff:

     this.x=new XMLHttpRequest();
-    this.x.onreadystatechange=cb(this.x,req);
+    this.x.onreadystatechange=cb(this.x,req);
+    // Basic timeout and error handling
+    this.x.timeout = 10000;
+    var fail = function(msg){
+      ge("loader").style.display="none";
+      try { req.callback(0, msg); } catch(_) {}
+      if(that.q.length===0) that.r=false;
+      if(that.r) that._request(that.q.shift());
+    };
+    this.x.onerror   = function(){ fail("Network error"); };
+    this.x.ontimeout = function(){ fail("Request timeout"); };

385-415: Safer width handling for ledmap pretty-printing.

If obj.width is missing/invalid or doesn’t divide map.length, current logic can misalign rows.

Apply this diff:

-    let width = obj.width || obj.map.length;
+    let width = Number.isInteger(obj.width) && obj.width > 0 ? obj.width : obj.map.length;
+    if (obj.map.length % width !== 0) width = obj.map.length; // fallback to single row if not rectangular
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6fa691 and 4d008f9.

📒 Files selected for processing (1)
  • wled00/data/edit.htm (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/edit.htm
🧠 Learnings (1)
📚 Learning: 2025-09-20T09:00:08.942Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T09:00:08.942Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Applied to files:

  • wled00/data/edit.htm
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_compat)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
🔇 Additional comments (2)
wled00/data/edit.htm (2)

629-638: Overall wiring LGTM; order is fine given the new updateMode() hook.

With updateMode() exposed and bound from the uploader, editor mode stays in sync even though editor is created before uploader.

Please verify that JSON files still validate/red-border in textarea fallback and Ace’s JSON worker loads when online (check Network tab for worker-json.min.js).


9-12: Harden ACE script includes: add defer, crossorigin="anonymous", and SRI
Replace lines 9–12 in wled00/data/edit.htm:

-<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/ace.min.js"></script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/mode-json.min.js"></script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/theme-monokai.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/ace.min.js" defer crossorigin="anonymous" integrity="sha384/…"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/mode-json.min.js" defer crossorigin="anonymous" integrity="sha384/…"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/theme-monokai.min.js" defer crossorigin="anonymous" integrity="sha384/…"></script>

Compute the correct SRI hashes (e.g. via openssl dgst -sha384 -binary | openssl base64 -A) and replace each .

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d008f9 and ead7105.

📒 Files selected for processing (1)
  • wled00/data/edit.htm (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/edit.htm
🧠 Learnings (4)
📚 Learning: 2025-09-20T09:00:08.942Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T09:00:08.942Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-24T18:52:06.803Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:376-379
Timestamp: 2025-09-24T18:52:06.803Z
Learning: In WLED firmware, all files reside in the root directory ("/"), so file tree refresh operations should always target the root path regardless of the parameter passed to refreshPath().

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-25T03:51:29.583Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:213-238
Timestamp: 2025-09-25T03:51:29.583Z
Learning: For WLED file editor: Path handling can be simplified since the system operates in root directory context, complex path traversal protection is not needed.

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-24T18:52:34.090Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:295-303
Timestamp: 2025-09-24T18:52:34.090Z
Learning: WLED file system stores all files in the root directory only (flat structure), so file paths don't have subdirectories. When working with WLED file editors, using just the filename (name) rather than full path is correct since there are no nested directories.

Applied to files:

  • wled00/data/edit.htm
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (esp8266_2m)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (nodemcuv2)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_compat)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_compat)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_160)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
wled00/wled_server.cpp (2)

319-334: Consider blocking deletion of wsec.json to align with other protections.

List/edit/download already hide/forbid wsec.json. Deleting it via DELETE is still allowed; confirm policy or block it.

Apply if you want to forbid deletion:

       String path = request->getParam(FPSTR(s_path), true)->value();
       if (!path.startsWith("/")) path = "/" + path;
 
+      // prevent deleting WiFi/security secrets
+      if (path.indexOf(FPSTR(s_wsec)) >= 0) {
+        request->send(403, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied));
+        return;
+      }
+
       if (!WLED_FS.remove(path)) {
         request->send(500, FPSTR(CONTENT_TYPE_PLAIN), F("Delete failed"));
         return;
       }

239-277: Build JSON with proper escaping.

Manually concatenating names risks malformed JSON if a filename contains quotes or backslashes. Prefer AsyncJsonResponse (or escape names).

Example (ESP32 branch sketch):

AsyncJsonResponse* res = new AsyncJsonResponse(false);
JsonArray arr = res->getRoot().to<JsonArray>();
// ... for each file:
JsonObject o = arr.createNestedObject();
o["name"] = name;   // safely escaped
o["type"] = "file";
o["size"] = file.size();
res->setLength();
request->send(res);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ead7105 and 5495d12.

📒 Files selected for processing (2)
  • wled00/data/edit.htm (1 hunks)
  • wled00/wled_server.cpp (5 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
wled00/**/*.{cpp,h}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use spaces (2 per level) for all C++ source and header files

Files:

  • wled00/wled_server.cpp
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/edit.htm
🧠 Learnings (6)
📚 Learning: 2025-09-20T09:00:08.942Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T09:00:08.942Z
Learning: Applies to wled00/html_*.h : DO NOT edit generated headers wled00/html_*.h

Applied to files:

  • wled00/wled_server.cpp
📚 Learning: 2025-09-25T03:51:29.583Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:213-238
Timestamp: 2025-09-25T03:51:29.583Z
Learning: For WLED file editor: Path handling can be simplified since the system operates in root directory context, complex path traversal protection is not needed.

Applied to files:

  • wled00/wled_server.cpp
  • wled00/data/edit.htm
📚 Learning: 2025-09-20T09:00:08.942Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T09:00:08.942Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-24T18:52:06.803Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:376-379
Timestamp: 2025-09-24T18:52:06.803Z
Learning: In WLED firmware, all files reside in the root directory ("/"), so file tree refresh operations should always target the root path regardless of the parameter passed to refreshPath().

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-24T18:52:34.090Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:295-303
Timestamp: 2025-09-24T18:52:34.090Z
Learning: WLED file system stores all files in the root directory only (flat structure), so file paths don't have subdirectories. When working with WLED file editors, using just the filename (name) rather than full path is correct since there are no nested directories.

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-25T05:52:09.019Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:213-222
Timestamp: 2025-09-25T05:52:09.019Z
Learning: In the WLED file editor (wled00/data/edit.htm), file renaming upon upload is not supported. The filename field is used for other purposes like creating new files or saving edited content, but uploaded files keep their original names.

Applied to files:

  • wled00/data/edit.htm
🧬 Code graph analysis (1)
wled00/wled_server.cpp (2)
tools/cdata.js (2)
  • path (19-19)
  • output (29-29)
wled00/data/common.js (1)
  • path (85-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (nodemcuv2)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
🔇 Additional comments (4)
wled00/wled_server.cpp (1)

770-772: Enable editor after PIN acceptance: LGTM.

This addresses the earlier 401-after-PIN issue.

wled00/data/edit.htm (3)

211-224: Upload flow: using original filename only is consistent with design.

Renaming on upload is intentionally unsupported; implementation matches that.


255-263: Image preview relies on server MIME; verify after server fix.

With /edit?edit now serving correct image content types, preview should render reliably across browsers.


359-365: Always refreshing root is fine given flat FS.

Matches WLED’s root-only file layout.

if (request->hasParam(FPSTR(s_list))) {
String path = request->getParam(FPSTR(s_list))->value();
if (path.isEmpty()) path = "/";
String output = "[";
Copy link
Member

@willmmiles willmmiles Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😬 Arduino String concatenation is still one of the worst ways to abuse your allocator ....

Instead use AsyncResponseStream which is an Arduino Print type that will build up a buffer in packet sized blocks:

AsyncResponseStream* response = request->beginResponseStream(FPSTR(CONTENT_TYPE_JSON));
response->write('{');   // character literal can be inlined
for_each(file) {
   if (!first) response->write(',');
   response->printf_P(PSTR({\"name\":\"%s\",\"type\":\"file\",\"size\":%d}"), file.name.c_str(), file.size());
}
response->write('}');
request->send(response);

(also you might want to send a please-don't-cache header, like serveSettingsJS(); also an example of AsyncResponseStream.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, that is helpful. I looked at some examples but completely missed out on AsyncResponseStream. I also found there are other deficiencies in the code, should not have relied so heavily on AI to get it right :)
Rewrote the whole section but found some other things I want to update, the editor UI is a bit convoluted and can be simplified.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Sep 27, 2025

@coderabbitai please review latest commits

Copy link
Contributor

coderabbitai bot commented Sep 27, 2025

@DedeHai I'll review the latest commits in your new file editor PR. This looks like a comprehensive replacement of the existing editor with modern functionality.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5495d12 and a171755.

📒 Files selected for processing (2)
  • wled00/data/edit.htm (1 hunks)
  • wled00/wled_server.cpp (7 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-20T09:00:08.942Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T09:00:08.942Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-24T18:52:06.803Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:376-379
Timestamp: 2025-09-24T18:52:06.803Z
Learning: In WLED firmware, all files reside in the root directory ("/"), so file tree refresh operations should always target the root path regardless of the parameter passed to refreshPath().

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-25T03:51:29.583Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:213-238
Timestamp: 2025-09-25T03:51:29.583Z
Learning: For WLED file editor: Path handling can be simplified since the system operates in root directory context, complex path traversal protection is not needed.

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-24T18:52:34.090Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:295-303
Timestamp: 2025-09-24T18:52:34.090Z
Learning: WLED file system stores all files in the root directory only (flat structure), so file paths don't have subdirectories. When working with WLED file editors, using just the filename (name) rather than full path is correct since there are no nested directories.

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-25T05:52:09.019Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:213-222
Timestamp: 2025-09-25T05:52:09.019Z
Learning: In the WLED file editor (wled00/data/edit.htm), file renaming upon upload is not supported. The filename field is used for other purposes like creating new files or saving edited content, but uploaded files keep their original names.

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-20T09:00:08.942Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-20T09:00:08.942Z
Learning: Applies to wled00/html_*.h : DO NOT edit generated headers wled00/html_*.h

Applied to files:

  • wled00/wled_server.cpp

@DedeHai
Copy link
Collaborator Author

DedeHai commented Sep 27, 2025

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Sep 27, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Sep 27, 2025

Fixed all issues hopefully. @willmmiles can you glance at the updated handler code? if you approve, this is ready to merge.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
wled00/data/edit.htm (4)

279-279: Eliminate code duplication by using the tree parameter.

An inline object with a refreshPath method is created here, duplicating logic that already exists in the tree object passed to delFile. This violates DRY principles and makes the code harder to maintain.

Apply this diff:

-			deleteOption.onclick = function() { delFile(path, {refreshPath: function() { treeRoot.innerHTML=""; httpGet("/"); }}); menu.remove(); };
+			deleteOption.onclick = function() { delFile(path, tree); menu.remove(); };

Note: This requires capturing the tree object in the createLeaf function scope. Update the createTree function to pass a reference:

 function createTree(element, editor){
 	var treeRoot=cE("div");
 	gId(element).appendChild(treeRoot);
+	var tree = { refreshPath: function(p) { treeRoot.innerHTML=""; httpGet("/"); } };
 
 	// ... (downlFile and delFile stay the same)
 
 	function createLeaf(path,name){
 		// ... existing code ...
-		deleteOption.onclick = function() { delFile(path, {refreshPath: function() { treeRoot.innerHTML=""; httpGet("/"); }}); menu.remove(); };
+		deleteOption.onclick = function() { delFile(path, tree); menu.remove(); };
 		// ... rest of createLeaf ...
 	}
 
 	// ... rest of createTree ...
 
-	this.refreshPath=function(p){
+	tree.refreshPath=function(p){
 		treeRoot.innerHTML="";
 		httpGet("/");
 	};
 
 	httpGet("/");
-	return this;
+	return tree;
 }

565-565: Consider passing tree reference explicitly instead of using global state.

Storing the tree reference in window.fileTree creates a global dependency that makes the code harder to test and maintain. The editor already receives the tree indirectly through callbacks.

Consider refactoring to pass the tree reference explicitly:

-function createEditor(element,file){
+function createEditor(element,file,tree){
 	if (!file) file="";
 	// ... existing code ...
 
 	function httpPost(filename,data){
 		// ... existing code ...
 		req.add("POST","/upload",fd,function(st,resp){
 			if (st!=200) alert("ERROR "+st+": "+resp);
 			else {
 				alert("File saved successfully!");
-				if (window.fileTree) window.fileTree.refreshPath("/");
+				if (tree) tree.refreshPath("/");
 			}
 		});
 	}
 	// ... rest of function ...
 }
 
 function onBodyLoad(){
 	var vars={};
 	window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi,function(m,k,v){
 		vars[decodeURIComponent(k)]=decodeURIComponent(v);
 	});
 
-	var editor=createEditor("editor",vars.file);
 	var tree=createTree("tree",editor);
-	window.fileTree = tree;
+	var editor=createEditor("editor",vars.file,tree);
 	createTop("top",tree,editor);
 }

Note: This requires restructuring the initialization order or using a forward reference pattern since createTree needs the editor and createEditor would need the tree.


148-158: Consider using createElement for DOM manipulation instead of innerHTML.

While the current implementation is safe due to encodeURIComponent, using createElement is more idiomatic and avoids any theoretical string injection risks.

Apply this diff:

 function loadPreview(filename, editor) {
 	var pathField = gId("filepath");
 	pathField.value = filename;
 	if (/\.(png|jpg|jpeg|gif|bmp|webp)$/i.test(filename)) {
 		gId("editor").style.display="none";
 		gId("preview").style.display="block";
-		gId("preview").innerHTML = '<img src="/edit?func=edit&path=' + encodeURIComponent(filename) + '&_cb=' + Date.now() + '">';
+		var img = cE("img");
+		img.src = "/edit?func=edit&path=" + encodeURIComponent(filename) + "&_cb=" + Date.now();
+		var preview = gId("preview");
+		preview.innerHTML = "";
+		preview.appendChild(img);
 	} else {
 		editor.loadText(filename);
 	}
 }

333-363: Consider performance impact for very large LED maps.

The prettyLedmap function performs string padding for each number in the map, which could be slow for maps with thousands of LEDs. For typical use cases this is fine, but consider adding a size check for very large maps.

Add an optional size threshold:

 function prettyLedmap(json){
 	try {
 		let obj = JSON.parse(json);
 		if (!obj.map || !Array.isArray(obj.map)) return JSON.stringify(obj, null, 2);
+		
+		// For very large maps, skip pretty-printing to avoid performance issues
+		if (obj.map.length > 10000) {
+			return JSON.stringify(obj, null, 2);
+		}
+		
 		let width = obj.width || obj.map.length;
 		let maxLen = Math.max(...obj.map.map(n => String(n).length));
 		// ... rest of function ...
 	} catch (e) {
 		return json;
 	}
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1517982 and 749c329.

📒 Files selected for processing (1)
  • wled00/data/edit.htm (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/edit.htm
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/edit.htm
🧠 Learnings (5)
📚 Learning: 2025-10-05T15:24:05.545Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-24T18:52:06.843Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:376-379
Timestamp: 2025-09-24T18:52:06.843Z
Learning: In WLED firmware, all files reside in the root directory ("/"), so file tree refresh operations should always target the root path regardless of the parameter passed to refreshPath().

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-24T18:52:34.117Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:295-303
Timestamp: 2025-09-24T18:52:34.117Z
Learning: WLED file system stores all files in the root directory only (flat structure), so file paths don't have subdirectories. When working with WLED file editors, using just the filename (name) rather than full path is correct since there are no nested directories.

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-25T03:51:29.606Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:213-238
Timestamp: 2025-09-25T03:51:29.606Z
Learning: For WLED file editor: Path handling can be simplified since the system operates in root directory context, complex path traversal protection is not needed.

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-25T05:52:09.044Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:213-222
Timestamp: 2025-09-25T05:52:09.044Z
Learning: In the WLED file editor (wled00/data/edit.htm), file renaming upon upload is not supported. The filename field is used for other purposes like creating new files or saving edited content, but uploaded files keep their original names.

Applied to files:

  • wled00/data/edit.htm
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
🔇 Additional comments (4)
wled00/data/edit.htm (4)

420-422: LGTM: Robust ACE editor initialization with fallback.

The dual initialization strategy (immediate timeout + window load event) ensures ACE loads even if the CDN is slow, while the guard clause in initAce prevents duplicate initialization. This is a solid defensive pattern.


519-531: LGTM: Save-then-reload sequence is safe due to request queuing.

The httpGet(fn) call after httpPost is protected from race conditions by the QueuedRequester mechanism, which ensures requests execute sequentially. The reload will only happen after the save completes successfully.


449-463: LGTM: Clear visual feedback for JSON validation.

The live JSON validation with border color feedback provides immediate user feedback without being intrusive. The filename-based detection ensures validation only runs for JSON files.


216-228: LGTM: File upload implementation is correct.

The upload flow properly handles file selection, FormData construction, error handling, and post-upload cleanup. Using file.name directly aligns with the design decision that file renaming on upload is not supported. Based on learnings

Comment on lines +10 to +12
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/ace.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/mode-json.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/theme-monokai.min.js"></script>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add Subresource Integrity (SRI) hashes for CDN resources.

Loading scripts from external CDNs without SRI attributes creates a security posture gap. If the CDN is compromised or serves malicious content, the editor could execute untrusted code.

Add integrity and crossorigin attributes:

-<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/ace.min.js"></script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/mode-json.min.js"></script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/theme-monokai.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/ace.min.js" 
+        integrity="sha512-<hash>" crossorigin="anonymous"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/mode-json.min.js" 
+        integrity="sha512-<hash>" crossorigin="anonymous"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/theme-monokai.min.js" 
+        integrity="sha512-<hash>" crossorigin="anonymous"></script>

Generate the SRI hashes using:

#!/bin/bash
# Generate SRI hashes for ACE editor CDN resources
for url in \
  "https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/ace.min.js" \
  "https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/mode-json.min.js" \
  "https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/theme-monokai.min.js"
do
  echo "URL: $url"
  curl -s "$url" | openssl dgst -sha512 -binary | openssl base64 -A | sed 's/^/sha512-/'
  echo -e "\n"
done
🤖 Prompt for AI Agents
In wled00/data/edit.htm around lines 10 to 12, the three ACE CDN script tags
lack Subresource Integrity (SRI) and crossorigin attributes; generate SHA-512
SRI hashes for each URL (e.g., using the provided curl + openssl script), then
add integrity="sha512-..." and crossorigin="anonymous" attributes to each script
tag so the browser can verify the resources before execution.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
wled00/data/edit.htm (1)

10-12: Add Subresource Integrity (SRI) hashes for CDN resources.

Loading scripts from external CDNs without SRI attributes creates a security posture gap. If the CDN is compromised or serves malicious content, the editor could execute untrusted code.

Generate the SRI hashes and add them:

#!/bin/bash
# Generate SRI hashes for ACE editor CDN resources
for url in \
  "https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/ace.min.js" \
  "https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/mode-json.min.js" \
  "https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/theme-monokai.min.js"
do
  echo "URL: $url"
  curl -s "$url" | openssl dgst -sha512 -binary | openssl base64 -A | sed 's/^/sha512-/'
  echo -e "\n"
done

Then apply this pattern to each script tag:

-<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/ace.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.4/ace.min.js" 
+        integrity="sha512-GENERATED_HASH" crossorigin="anonymous"></script>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 749c329 and 6055ff0.

📒 Files selected for processing (1)
  • wled00/data/edit.htm (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
wled00/data/**/*.{htm,html,css,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Files:

  • wled00/data/edit.htm
wled00/data/**

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

wled00/data/**: When modifying web UI files, run npm run build to regenerate embedded headers before any firmware build
For web UI changes, edit files only under wled00/data (not firmware or generated files)

Files:

  • wled00/data/edit.htm
🧠 Learnings (5)
📚 Learning: 2025-10-05T15:24:05.545Z
Learnt from: CR
PR: wled/WLED#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-05T15:24:05.545Z
Learning: Applies to wled00/data/**/*.{htm,html,css,js} : Use tabs for indentation in web files (.htm/.html/.css/.js) under wled00/data

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-24T18:52:06.843Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:376-379
Timestamp: 2025-09-24T18:52:06.843Z
Learning: In WLED firmware, all files reside in the root directory ("/"), so file tree refresh operations should always target the root path regardless of the parameter passed to refreshPath().

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-24T18:52:34.117Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:295-303
Timestamp: 2025-09-24T18:52:34.117Z
Learning: WLED file system stores all files in the root directory only (flat structure), so file paths don't have subdirectories. When working with WLED file editors, using just the filename (name) rather than full path is correct since there are no nested directories.

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-25T03:51:29.606Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:213-238
Timestamp: 2025-09-25T03:51:29.606Z
Learning: For WLED file editor: Path handling can be simplified since the system operates in root directory context, complex path traversal protection is not needed.

Applied to files:

  • wled00/data/edit.htm
📚 Learning: 2025-09-25T05:52:09.044Z
Learnt from: DedeHai
PR: wled/WLED#4956
File: wled00/data/edit.htm:213-222
Timestamp: 2025-09-25T05:52:09.044Z
Learning: In the WLED file editor (wled00/data/edit.htm), file renaming upon upload is not supported. The filename field is used for other purposes like creating new files or saving edited content, but uploaded files keep their original names.

Applied to files:

  • wled00/data/edit.htm
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_compat)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_compat)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (nodemcuv2)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
  • GitHub Check: wled_build / Build Environments (esp8266_2m)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_160)
🔇 Additional comments (14)
wled00/data/edit.htm (14)

14-114: LGTM! CSS styling is well-structured.

The CSS uses tabs for indentation as per coding guidelines, defines clear component layouts, and customizes ACE editor colors to match WLED style.


116-154: LGTM! Request queuing implementation is solid.

The queued request handler properly serializes requests, handles both GET and POST methods, supports FormData, and manages loader visibility. The implementation prevents race conditions and handles different parameter types correctly.


157-167: LGTM! Preview loading is correctly implemented.

The function properly handles image vs. text file detection and uses encodeURIComponent to safely embed filenames in URLs. The cache-busting timestamp prevents stale image displays.


169-171: LGTM! Tree refresh helper is straightforward.

Simple wrapper that calls the global tree's refresh method.


173-241: LGTM! Top control bar implementation is well-structured.

The upload/save/clear/back button handlers are correctly wired. File uploads use the original filename (as intended per WLED design), and success callbacks properly refresh the tree and load previews.


306-340: LGTM! File list rendering and refresh logic is correct.

The tree properly filters sensitive files (wsec.json), constructs file entries, and refreshes to the root directory (as all files are in root per WLED design). The defense-in-depth approach to hiding wsec.json is appropriate.

Based on learnings


342-372: LGTM! Ledmap pretty-printer is well-implemented.

The function correctly formats 2D LED maps with aligned columns while preserving other JSON properties. The fallback to standard formatting on errors is appropriate.


389-421: LGTM! ACE editor initialization is robust.

The editor correctly falls back to textarea if ACE is unavailable, sets up JSON mode detection, and prevents duplicate event listeners. The deferred initialization pattern handles CDN loading timing.


446-459: LGTM! Live JSON validation provides good UX.

The textarea validation provides immediate visual feedback with red borders for invalid JSON, helping users catch syntax errors early.


461-481: LGTM! Save function with JSON minification is appropriate.

The function properly validates JSON before saving and minifies to reduce storage. Error handling with alerts provides clear feedback.


483-520: LGTM! File loading and save handler are correctly implemented.

The load function properly handles JSON pretty-printing (including special ledmap formatting), falls back gracefully on parse errors, and updates editor mode. The save handler validates the filename and updates state appropriately.


521-539: LGTM! Public API methods are clean and functional.

The returned API object provides clear methods for save, loadText, and clearEditor operations with appropriate state management and placeholder text.


542-551: LGTM! Initialization sequence is correct.

The onBodyLoad function properly parses URL parameters and initializes components in the right order (editor → tree → top), allowing dependencies between components.


554-565: LGTM! HTML body structure is clean.

The body structure matches the CSS layout with proper nesting and includes all required containers for the editor, tree, preview, and loader.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants