diff --git a/emhttp/plugins/dynamix/Browse.page b/emhttp/plugins/dynamix/Browse.page index 59802b441..f52893e8b 100644 --- a/emhttp/plugins/dynamix/Browse.page +++ b/emhttp/plugins/dynamix/Browse.page @@ -874,8 +874,13 @@ function doAction(action, title, id) { dfm_fileManager('start'); $.post('/webGui/include/Control.php',{mode:'file',action:action,title:encodeURIComponent(title),source:encodeURIComponent(source),target:encodeURIComponent(target),hdlink:hdlink,sparse:dfm.window.find('#dfm_sparse').val(),exist:dfm.window.find('#dfm_exist').val(),zfs:encodeURIComponent(zfs)},function(){ $.post('/webGui/include/Control.php',{mode:'read'},function(data){ - dfm_read = JSON.parse(data); - dfm_read.action = parseInt(dfm_read.action); + try { + dfm_read = data ? JSON.parse(data) : {}; + dfm_read.action = parseInt(dfm_read.action); + } catch(e) { + console.error('Failed to parse Control.php response:', e, data); + dfm_read = {}; + } }); }); }, @@ -1158,8 +1163,13 @@ function doActions(action, title) { dfm_fileManager('start'); $.post('/webGui/include/Control.php',{mode:'file',action:action,title:encodeURIComponent(title),source:encodeURIComponent(source.join('\r')),target:encodeURIComponent(target),hdlink:hdlink,sparse:dfm.window.find('#dfm_sparse').val(),exist:dfm.window.find('#dfm_exist').val(),zfs:encodeURIComponent(zfs.join('\r'))},function(){ $.post('/webGui/include/Control.php',{mode:'read'},function(data){ - dfm_read = JSON.parse(data); - dfm_read.action = parseInt(dfm_read.action); + try { + dfm_read = data ? JSON.parse(data) : {}; + dfm_read.action = parseInt(dfm_read.action); + } catch(e) { + console.error('Failed to parse Control.php response:', e, data); + dfm_read = {}; + } }); }); }, diff --git a/emhttp/plugins/dynamix/BrowseButton.page b/emhttp/plugins/dynamix/BrowseButton.page index f5d421451..8ba348fdc 100644 --- a/emhttp/plugins/dynamix/BrowseButton.page +++ b/emhttp/plugins/dynamix/BrowseButton.page @@ -152,6 +152,31 @@ function dfm_showProgress(data) { try { let parsed = JSON.parse(data); + // Handle search results (action 15 with results array) + if (parsed.action === 15 && Array.isArray(parsed.results)) { + if (parsed.results.length > 0) { + // Build HTML as pairs of location/path elements + let html = ''; + for (let result of parsed.results) { + html += '
' + dfm_escapeHTML(result.location) + '
'; + html += '
' + dfm_escapeHTML(result.path.dfm_wedge(80)) + '
'; + } + // Remove all existing result dt/dd pairs (keep only the first 2 dt/dd which are the form fields) + let $dl = dfm.window.find('dl'); + $dl.find('dt:gt(1), dd:gt(1)').remove(); + // Append the new results + $dl.append(html); + dfm.window.find('#dfm_files').html(parsed.results.length+" "+"_(files)_"); + // Clear stale progress text + dfm.window.find('.dfm_text').html(''); + } else { + dfm.window.find('.dfm_loc').html(''); + dfm.window.find('.dfm_text').html('_(No files found)_'); + dfm.window.find('#dfm_files').html('0 '+"_(files)_"); + } + return parsed.results.length; + } + // Universal JSON format: {action: int, text: [text0, text1]} // text[0] = file/main text, text[1] = progress info (optional) if (parsed.action !== undefined && Array.isArray(parsed.text)) { @@ -212,54 +237,23 @@ function dfm_showProgress(data) { } // Build footer text (progress info only) - let footerText = parsed.text[1] || parsed.text[0] || ''; - dfm_footer('write', footerText, $icon); - dfm.previous = footerText; + // Show footer for actions with actual progress: delete (1,6), copy (3,8), move (4,9), search (15) + // No footer for quick operations: create (0), rename (2,7), chown (11), chmod (12) + let showFooter = [1, 3, 4, 6, 8, 9, 15].includes(parsed.action); + if (showFooter) { + let footerText = parsed.text[1] || parsed.text[0] || ''; + dfm_footer('write', footerText, $icon); + dfm.previous = footerText; + } + return 0; } } catch(e) { - // Not JSON, fallback to old string parsing - } - - // Legacy string parsing for non-migrated operations - let file = null; - let text = data.split('\n'); - let line = text[0].split('... '); - let strict = /^mnt|^boot/; - let footer = false; - if (text[0] == '#cat#') { - let loc = [], cat = []; - for (let i=1,row; row=text[i]; i++) { - if (!row) continue; - row = row.split('\0'); - loc.push(row[0]); - cat.push(row[1].dfm_wedge(80)); - } - if (cat.length > 0) { - dfm.window.find('.dfm_loc').html(loc.join('
')); - dfm.window.find('.dfm_text').html(cat.join('
')); - dfm.window.find('#dfm_files').html(loc.length+" "+"_(files)_"); - } - return cat.length; - } else if (text.length == 1) { - text = text[0].dfm_wedge(80); - footer = text.indexOf("_(Searching)_") != -1; - } else { - if (strict.test(text[1])) { - file = text[1]; - text = dfm.previous; - } else { - file = line[1]; - text = text[1].split(/\s+/); - text = "_(Completed)_: "+text[1]+",  _(Speed)_: "+text[2]+",  _(ETA)_: "+text[3]; - dfm.previous = text; - footer = true; - } + // Not JSON, log error + console.error('Failed to parse status as JSON:', e, data); + return 0; } - if (file == null || strict.test(file)) dfm.window.find('.dfm_text').html((file?line[0]+'... /'+dfm_escapeHTML(file.dfm_wedge())+'
':'')+text); - if (footer) dfm_footer('write',text); - return 0; } function dfm_fileManager(action) { @@ -420,7 +414,7 @@ nchan_filemanager.on('message', function(msg) { let data = $.parseJSON(msg); if (data.error) { dfm_fileManager('stop'); - dfm.window.find('.dfm_text').addClass('orange-text').html(data.error); + dfm.window.find('.dfm_text').addClass('orange-text').css('white-space', 'pre-line').text(data.error); dfm.window.find('#dfm_target').prop('disabled',false); dfm.window.find('#dfm_sparse').prop('disabled',false); dfm.window.find('#dfm_exist').prop('disabled',false); diff --git a/emhttp/plugins/dynamix/nchan/file_manager b/emhttp/plugins/dynamix/nchan/file_manager index 8ada3fedd..3d1f4e762 100755 --- a/emhttp/plugins/dynamix/nchan/file_manager +++ b/emhttp/plugins/dynamix/nchan/file_manager @@ -259,7 +259,7 @@ function parse_rsync_progress($status, $action_label, $reset = false) { // note: -v is used to obtain the opposite of the progress line regex and to filter out empty lines $file_line = exec("tac $status | grep -m1 -v -E '(^\$|^ .*[0-9]+%.*[0-9]+:[0-9]+:[0-9]+)' | tr -s ' ' || echo ''"); if ($file_line) { - $text[0] .= htmlspecialchars(mb_strimhalf($file_line, 70, '...'), ENT_QUOTES, 'UTF-8'); + $text[0] .= mb_strimhalf($file_line, 70, '...'); } return $text; @@ -297,7 +297,8 @@ function update_translation($locale) { function cat($file) { global $null; - $cat = $set = []; + $results = []; + $set = []; $rows = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); if (count($rows) > 0) { natcasesort($rows); @@ -313,12 +314,20 @@ function cat($file) { } foreach (array_diff($rows, $user) as $row) { [$none, $root, $main] = explode('/',$row,4); - $cat[] = ($root == 'mnt' ? $main : ($root == 'boot' ? 'flash' : '---'))."\0".$row; + $results[] = [ + 'location' => ($root == 'mnt' ? $main : ($root == 'boot' ? 'flash' : '---')), + 'path' => $row + ]; } $i = 0; - foreach ($user as $row) $cat[] = $set[++$i]."\0".$row; + foreach ($user as $row) { + $results[] = [ + 'location' => $set[++$i] ?? '---', + 'path' => $row + ]; + } } - return "#cat#\n".implode("\n",$cat)."\n"; + return $results; } function escape($name) {return escapeshellarg(validname($name));} @@ -388,8 +397,15 @@ while (true) { $source = explode("\r", $source); switch ($action) { case 0: // create folder + + // return status of running action if (!empty($pid)) { - $reply['status'] = ''._('Creating').'...'; + $reply['status'] = json_encode([ + 'action' => $action, + 'text' => [_('Creating').'...'] + ]); + + // start action } else { $dir = $source[0].'/'.$target; exec("mkdir -pm0777 ".quoted($dir)." 1>$null 2>$error & echo $!", $pid); @@ -403,7 +419,7 @@ while (true) { if (!empty($pid)) { $reply['status'] = json_encode([ 'action' => $action, - 'text' => [htmlspecialchars(mb_strimhalf(exec("tail -1 $status"), 70, '...'), ENT_QUOTES, 'UTF-8')] + 'text' => [mb_strimhalf(exec("tail -1 $status"), 70, '...')] ]); // start action @@ -413,8 +429,15 @@ while (true) { break; case 2: // rename folder case 7: // rename file + + // return status of running action if (!empty($pid)) { - $reply['status'] = ''._('Renaming').'...'; + $reply['status'] = json_encode([ + 'action' => $action, + 'text' => [_('Renaming').'...'] + ]); + + // start action } else { $path = dirname($source[0]); exec("mv -f ".quoted($source)." ".quoted("$path/$target")." 1>$null 2>$error & echo \$!", $pid); @@ -463,7 +486,7 @@ while (true) { if ($delete_empty_dirs === false) { $reply['status'] = json_encode([ 'action' => $action, - 'text' => [htmlspecialchars(mb_strimhalf(exec("tail -1 $status"), 70, '...'), ENT_QUOTES, 'UTF-8')] + 'text' => [mb_strimhalf(exec("tail -1 $status"), 70, '...')] ]); // moving: progress @@ -605,22 +628,46 @@ while (true) { } break; case 11: // change owner + + // return status of running action if (!empty($pid)) { - $reply['status'] = ''._('Updating').'... '.exec("tail -2 $status | grep -Pom1 \"^.+ of '\\K[^']+\""); + $file_line = exec("tail -2 $status | grep -Pom1 \"^.+ of '\\K[^']+\""); + $reply['status'] = json_encode([ + 'action' => $action, + 'text' => [_('Updating').'... '.mb_strimhalf($file_line, 70, '...')] + ]); + + // start action } else { exec("chown -Rfv $target ".quoted($source)." 1>$status 2>$error & echo \$!", $pid); } break; case 12: // change permission + + // return status of running action if (!empty($pid)) { - $reply['status'] = ''._('Updating').'... '.exec("tail -2 $status | grep -Pom1 \"^.+ of '\\K[^']+\""); + $file_line = exec("tail -2 $status | grep -Pom1 \"^.+ of '\\K[^']+\""); + $reply['status'] = json_encode([ + 'action' => $action, + 'text' => [_('Updating').'... '.mb_strimhalf($file_line, 70, '...')] + ]); + + // start action } else { exec("chmod -Rfv $target ".quoted($source)." 1>$status 2>$error & echo \$!", $pid); } break; case 15: // search + + // return status of running action if (!empty($pid)) { - $reply['status'] = ''._('Searching').'... '.exec("wc -l $status | grep -Pom1 '^[0-9]+'"); + $count = exec("wc -l $status | grep -Pom1 '^[0-9]+'"); + $reply['status'] = json_encode([ + 'action' => $action, + 'text' => [_('Searching').'... '.$count] + ]); + + // start action } else { exec("find ".source($source)." -iname ".escapeshellarg($target)." 1>$status 2>$null & echo \$!", $pid); } @@ -648,10 +695,16 @@ while (true) { $pid = pid_exists($pid); } else { if ($action != 15) { - $reply['status'] = _('Done'); + $reply['status'] = json_encode([ + 'action' => $action, + 'text' => [_('Done')] + ]); $reply['done'] = 1; } else { - $reply['status'] = cat($status); + $reply['status'] = json_encode([ + 'action' => $action, + 'results' => cat($status) + ]); $reply['done'] = 2; } if ($zfs) { @@ -662,7 +715,7 @@ while (true) { foreach ($datasets as $dataset) if (exec("ls --indicator-style=none /mnt/$dataset|wc -l")==0) exec("zfs destroy $dataset 2>/dev/null"); } } - if (file_exists($error)) $reply['error'] = str_replace("\n","
", trim(file_get_contents($error))); + if (file_exists($error)) $reply['error'] = trim(file_get_contents($error)); delete_file($active, $pid_file, $status, $error); unset($pid); $delete_empty_dirs = null;