Skip to content

Conversation

@mgutt
Copy link
Contributor

@mgutt mgutt commented Jan 15, 2026

This PR completes the migration from HTML string responses to a unified JSON API format for all file_manager operations, building on the work started in PR #2429.

Backend changes (nchan/file_manager):

  • Migrate remaining actions to JSON format:
    • case 0 (create folder): {action: 0, text: ['Creating...']}
    • case 2/7 (rename): {action: 2/7, text: ['Renaming...']}
    • case 11 (chown): {action: 11, text: ['Updating... filename']}
    • case 12 (chmod): {action: 12, text: ['Updating... filename']}
    • case 15 (search): {action: 15, text: ['Searching... count']}
    • completion: {action: X, text: ['Done']} for all non-search operations
  • Convert search results to JSON structure:
    • {action: 15, results: [{location, path}, ...]}
    • Replaces legacy #cat# string format with structured data
  • Convert cat() function to return array of objects instead of string
  • Send errors as plain text (not HTML) for safer frontend handling

Frontend changes (BrowseButton.page):

  • Implement unified JSON parser for all status updates
  • Show footer only for progress-tracking operations (delete, copy, move, search)
  • No footer for quick operations (create, rename, chown, chmod)
  • Parse search results from JSON structure:
    • Display as properly paired dt/dd elements
    • Clear previous results before showing new ones
    • Show 'No files found' for empty results
  • Remove all legacy string parsing code
  • Use .text() with white-space:pre-line for error display (prevents XSS)
  • Add error logging for JSON parse failures

Frontend changes (Browse.page):

  • Add try-catch for Control.php JSON parsing
  • Handle empty responses gracefully

All file_manager operations now use a consistent, type-safe JSON API with proper error handling and no legacy code paths remaining.

Fixes #2514

Summary by CodeRabbit

  • Bug Fixes

    • More robust JSON parsing and safer error handling to prevent failures when processing responses.
    • Improved multiline error display for clearer messages.
  • New Features

    • Search results now show location and file-path pairs and return accurate file counts.
  • Improvements

    • File operations (copy/move/create/delete/rename/permissions/owner/search) emit richer, structured JSON status and progress updates.
    • Footer and result-listing behavior refined for clearer UX during searches and bulk actions.

✏️ Tip: You can customize this high-level summary in your review settings.

…ations

This PR completes the migration from HTML string responses to a unified
JSON API format for all file_manager operations, building on the work
started in PR unraid#2429.

Backend changes (nchan/file_manager):
- Migrate remaining actions to JSON format:
  * case 0 (create folder): {action: 0, text: ['Creating...']}
  * case 2/7 (rename): {action: 2/7, text: ['Renaming...']}
  * case 11 (chown): {action: 11, text: ['Updating... filename']}
  * case 12 (chmod): {action: 12, text: ['Updating... filename']}
  * case 15 (search): {action: 15, text: ['Searching... count']}
  * completion: {action: X, text: ['Done']} for all non-search operations
- Convert search results to JSON structure:
  * {action: 15, results: [{location, path}, ...]}
  * Replaces legacy #cat# string format with structured data
- Convert cat() function to return array of objects instead of string
- Send errors as plain text (not HTML) for safer frontend handling

Frontend changes (BrowseButton.page):
- Implement unified JSON parser for all status updates
- Show footer only for progress-tracking operations (delete, copy, move, search)
- No footer for quick operations (create, rename, chown, chmod)
- Parse search results from JSON structure:
  * Display as properly paired dt/dd elements
  * Clear previous results before showing new ones
  * Show 'No files found' for empty results
- Remove all legacy string parsing code
- Use .text() with white-space:pre-line for error display (prevents XSS)
- Add error logging for JSON parse failures

Frontend changes (Browse.page):
- Add try-catch for Control.php JSON parsing
- Handle empty responses gracefully

All file_manager operations now use a consistent, type-safe JSON API
with proper error handling and no legacy code paths remaining.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

Walkthrough

Browse-related JavaScript now parses file-manager responses defensively; BrowseButton handles action 15 search results as JSON-first and improves multiline error display; file_manager PHP responses (including cat()) were migrated from plain/HTML text to structured JSON objects and arrays.

Changes

Cohort / File(s) Summary
JSON Parsing Hardening
emhttp/plugins/dynamix/Browse.page
Wrapped JSON.parse in try/catch at both doAction and doActions call sites; on failure dfm_read becomes {} and a parse error is logged.
Search Results & Error Display
emhttp/plugins/dynamix/BrowseButton.page
Added JSON-first handling for action 15 (search): renders location/path pairs, updates file count, clears stale progress; extended footer logic to include action 15; removed legacy non-JSON parsing path; WebSocket error text uses white-space: pre-line for multiline errors.
Response Format Migration
emhttp/plugins/dynamix/nchan/file_manager
Converted many action handlers to emit JSON objects (action + text or results); cat() now returns an array of {location, path} entries instead of a string; progress and final statuses standardized to JSON; trimmed/plain error texts returned in JSON.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant BrowseJS as "Browse.page / BrowseButton.page (JS)"
  participant WebSocket as "nchan WebSocket"
  participant FileManager as "file_manager (PHP)"

  Browser->>BrowseJS: trigger action or search
  BrowseJS->>WebSocket: send request (action id, payload)
  WebSocket->>FileManager: forward request
  FileManager-->>WebSocket: stream JSON messages (progress/results/errors)
  WebSocket-->>BrowseJS: deliver JSON payloads
  BrowseJS->>BrowseJS: try { parsed = JSON.parse(data) } catch
  alt parse succeeds
    BrowseJS->>Browser: render using parsed.action / parsed.text / parsed.results
  else parse fails
    BrowseJS->>Console: log parse error
    BrowseJS->>Browser: clear/replace progress UI (empty object handling)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped through streams of text and code,
Found tidy JSON on the road,
Paths and results now neatly paired,
Progress and errors gently shared,
A carrot-toast for migrations bold! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective: completing the migration to JSON API for file_manager operations, directly addressing issue #2514.
Linked Issues check ✅ Passed All coding requirements from issue #2514 are met: remaining actions converted to JSON, cat() returns structured arrays, unified JSON parser with error handling, progress footer adjusted per operation type, and legacy code removed.
Out of Scope Changes check ✅ Passed All changes are directly related to completing the JSON migration as specified in issue #2514; no extraneous modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


🧹 Recent nitpick comments
emhttp/plugins/dynamix/nchan/file_manager (1)

298-331: Clean migration to structured return type.

The refactored cat() function correctly returns an array of objects with location and path keys instead of a concatenated string. The logic for handling user vs non-user paths and the getfattr batching is preserved correctly.

One optional clarity improvement: the $set variable serves two distinct purposes (first accumulating getfattr strings, then holding parsed locations). Consider using a separate variable name after the transformation to make the code more self-documenting.

♻️ Optional: use distinct variable names for clarity
       if (count($user) > 0) {
+        $getfattr_output = [];
         for ($n=0; $n < count($user); $n+=100) {
           $name = array_slice($user, $n, 100);
-          $set[] = exec("getfattr --no-dereference --absolute-names --only-values -n system.LOCATIONS ".quoted($name)." 2>$null");
+          $getfattr_output[] = exec("getfattr --no-dereference --absolute-names --only-values -n system.LOCATIONS ".quoted($name)." 2>$null");
         }
         $disks = parse_ini_file('state/disks.ini', true);
         $tag = implode('|',array_merge(['disk'],fm_pools_filter($disks)));
-        $set = explode(';',str_replace(',;',',',preg_replace("/($tag)/", ';$1', implode($set))));
+        $locations = explode(';',str_replace(',;',',',preg_replace("/($tag)/", ';$1', implode($getfattr_output))));
       }

Then update the reference on line 325 to use $locations[++$i].


📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between acf0e64 and f5d032f.

📒 Files selected for processing (1)
  • emhttp/plugins/dynamix/nchan/file_manager
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: mgutt
Repo: unraid/webgui PR: 2501
File: emhttp/plugins/dynamix/Browse.page:1282-1339
Timestamp: 2025-12-30T20:28:06.818Z
Learning: In emhttp/plugins/dynamix/Browse.page, the xlink() function's clipboard functionality is intentionally designed to silently fail on HTTP connections without showing warnings. The path text is pre-selected to allow manual copying, which is preferred over showing repetitive error messages that would annoy HTTP users.
🔇 Additional comments (10)
emhttp/plugins/dynamix/nchan/file_manager (10)

262-262: LGTM!

The removal of htmlspecialchars() is correct since the frontend now uses createTextNode() for XSS-safe rendering. The mb_strimhalf() truncation provides a clean middle-ellipsis display for long filenames.


399-414: LGTM!

The create folder action now returns a properly structured JSON response. The format {action: 0, text: ['Creating...']} is consistent with the unified API design.


419-423: LGTM!

The delete operation status correctly uses mb_strimhalf() for path truncation while relying on json_encode() for proper character escaping.


430-445: LGTM!

The rename operation correctly uses a simple status message without progress tracking, as it's typically a quick operation. The JSON structure is consistent with the unified API.


486-490: LGTM!

The move operation's cleanup phase (empty directory deletion) correctly shows status using the same truncation pattern as other file operations.


630-644: LGTM!

The change owner operation correctly extracts the filename from chown -v output using the Perl regex lookbehind pattern. The JSON response structure is consistent with other operations.


645-659: LGTM!

The change permission operation mirrors the change owner implementation correctly. Both use the same extraction pattern since chmod -v and chown -v have similar output formats.


660-674: LGTM!

The search operation provides helpful real-time feedback by showing the current result count. The regex extraction from wc -l output correctly handles the standard output format.


697-709: Well-structured completion handling.

The done status correctly differentiates between regular operations (returning text: ['Done']) and search operations (returning results: [...] with structured data). The done flag values (1 vs 2) enable appropriate frontend handling.


718-718: LGTM - Proper XSS-safe error handling.

Returning plain text (with trim()) instead of HTML-encoded strings is the correct approach since the frontend uses .text() for rendering, which inherently prevents XSS. The white-space: pre-line CSS on the frontend preserves newlines without requiring HTML conversion.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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.

@github-actions
Copy link

github-actions bot commented Jan 15, 2026

🔧 PR Test Plugin Available

A test plugin has been generated for this PR that includes the modified files.

Version: 2026.01.15.2202
Build: View Workflow Run

📥 Installation Instructions:

Install via Unraid Web UI:

  1. Go to Plugins → Install Plugin
  2. Copy and paste this URL:
https://preview.dl.unraid.net/pr-plugins/pr-2521/webgui-pr-2521.plg
  1. Click Install

Alternative: Direct Download

⚠️ Important Notes:

  • Testing only: This plugin is for testing PR changes
  • Backup included: Original files are automatically backed up
  • Easy removal: Files are restored when plugin is removed
  • Conflicts: Remove this plugin before installing production updates

📝 Modified Files:

Click to expand file list
emhttp/plugins/dynamix/Browse.page
emhttp/plugins/dynamix/BrowseButton.page
emhttp/plugins/dynamix/nchan/file_manager

🔄 To Remove:

Navigate to Plugins → Installed Plugins and remove webgui-pr-2521, or run:

plugin remove webgui-pr-2521

🤖 This comment is automatically generated and will be updated with each new push to this PR.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
emhttp/plugins/dynamix/nchan/file_manager (1)

298-331: Guard against missing location entries when building search results.
If getfattr returns fewer entries than $user, $set[++$i] can be undefined, yielding notices and null locations in JSON.

💡 Suggested fix
-    foreach ($user as $row) {
-      $results[] = [
-        'location' => $set[++$i],
-        'path' => $row
-      ];
-    }
+    foreach ($user as $row) {
+      $results[] = [
+        'location' => $set[++$i] ?? '---',
+        'path' => $row
+      ];
+    }
🤖 Fix all issues with AI agents
In `@emhttp/plugins/dynamix/BrowseButton.page`:
- Around line 155-175: When handling search results in the block where
parsed.action === 15 and parsed.results.length > 0, clear the progress text so
the previous "Searching…" message doesn't remain; call
dfm.window.find('.dfm_text').html('') (and optionally
dfm.window.find('.dfm_loc').html('') if appropriate) before or immediately after
appending the new results and updating `#dfm_files`, so the UI shows only the
fresh results; locate this change inside the same result-handling branch that
uses parsed.results, $dl, and dfm.window.
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa5fb3d and f7be13e.

📒 Files selected for processing (3)
  • emhttp/plugins/dynamix/Browse.page
  • emhttp/plugins/dynamix/BrowseButton.page
  • emhttp/plugins/dynamix/nchan/file_manager
🔇 Additional comments (5)
emhttp/plugins/dynamix/Browse.page (1)

877-883: Defensive JSON parsing is a solid safeguard.
This avoids hard failures on malformed Control.php responses and keeps the UI resilient.

emhttp/plugins/dynamix/BrowseButton.page (3)

238-245: Footer display rules look consistent with the new action set.


251-253: JSON parse failure logging is a good safety net.


411-416: white-space: pre-line improves multi-line error readability.

emhttp/plugins/dynamix/nchan/file_manager (1)

400-407: Unified JSON status payloads and plain-text error output fit the new API contract.

Also applies to: 432-438, 632-638, 647-653, 662-668, 698-701, 704-707, 718-718

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

- Guard against undefined array access in cat() using null coalescing operator
- Clear stale 'Searching...' text after search results render
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
emhttp/plugins/dynamix/nchan/file_manager (1)

419-429: The frontend incorrectly displays HTML-escaped text as literal entity codes.

The backend applies htmlspecialchars() to exec output (lines 422, 489, 637, 652, 262), encoding special characters into HTML entities. However, the frontend's dfm_showProgress() function (BrowseButton.page:208, 215, 219, 225, 229) inserts this content using document.createTextNode(), which treats the text as plain content without decoding HTML entities. Users will see literal &lt;, &amp;, etc. instead of the intended <, & characters.

Either remove htmlspecialchars() from the backend and rely on document.createTextNode() for safe plain-text insertion, or switch the frontend to use .innerHTML to properly decode the pre-escaped content.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7be13e and acf0e64.

📒 Files selected for processing (2)
  • emhttp/plugins/dynamix/BrowseButton.page
  • emhttp/plugins/dynamix/nchan/file_manager
🚧 Files skipped from review as they are similar to previous changes (1)
  • emhttp/plugins/dynamix/BrowseButton.page
🔇 Additional comments (6)
emhttp/plugins/dynamix/nchan/file_manager (6)

298-331: LGTM! Good defensive coding with null coalescing.

The cat() function correctly returns a structured array of objects. The use of $set[++$i] ?? '---' on line 325 properly guards against undefined array access when the xattr output doesn't contain the expected number of elements.


399-414: LGTM!

The JSON response structure { action, text } is consistent with other actions, and the implementation correctly handles the quick create operation without progress tracking.


430-445: LGTM!

The rename operation JSON response follows the established pattern consistently.


630-659: LGTM!

The chown/chmod operations correctly extract filenames from verbose output and apply htmlspecialchars() for XSS protection. The JSON response structure is consistent with other actions.


660-674: LGTM!

The search progress feedback correctly extracts and displays the result count. The JSON structure is consistent with the established pattern.


697-718: LGTM! Dual completion structures for regular vs. search operations.

The implementation correctly differentiates between:

  • Regular operations: { action, text: ['Done'] }
  • Search operations: { action, results: [...] } with structured data from cat()

The error handling on line 718 correctly outputs plain text (trimmed), aligning with the PR objective to send errors as plain text rather than HTML.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Since the frontend uses createTextNode() which is already XSS-safe and doesn't decode HTML entities, the backend htmlspecialchars() encoding is unnecessary and causes literal entity codes to appear in the UI.
@mgutt mgutt marked this pull request as ready for review January 15, 2026 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

File Manager: Finalize migration to JSON (file_manager API)

1 participant