Conversation
…showing Compose Down action in context menu while the stack is up/partial
… save order feature
There was a problem hiding this comment.
Pull request overview
Implements persistent drag-and-drop ordering for Compose stacks, gated behind Unraid’s “Lock” (sortable) mode, so users can reorder stacks and have that order persist across refreshes.
Changes:
- Added server-side persistence for stack order (
stack-order.json) and applied it when enumerating stacks. - Added client-side sortable behavior (jQuery UI Sortable) + UI affordances (move handle, lock button label sync).
- Enabled the Unraid Lock button on relevant Compose pages to expose the sortable mode.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| source/compose.manager/php/util.php | Adds stack-order read/write helpers and applies saved ordering in StackInfo::listProjectFolders(). |
| source/compose.manager/php/exec.php | Adds saveStackOrder AJAX action to validate and persist the client-provided order. |
| source/compose.manager/php/defines.php | Introduces COMPOSE_STACK_ORDER_FILE constant. |
| source/compose.manager/php/compose_manager_main.php | Loads sortable JS, adds CSS, syncs sort-mode UI, and disables row interactions during sort mode. |
| source/compose.manager/php/compose_list.php | Adds a hidden “move” icon per stack row (shown in sort mode). |
| source/compose.manager/javascript/composeSortable.js | New drag-and-drop + lock button chaining + persistence client logic. |
| source/compose.manager/Compose.page | Enables Unraid Lock button in header-menu entry. |
| source/compose.manager/compose.manager.settings.page | Enables Lock button and adds sortable behavior for the Updates table. |
| source/compose.manager/compose.manager.page | Enables Unraid Lock button on the Compose page under the Docker menu. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $map[getComposeStackOrderKey($composeRoot)] = array_values($projects); | ||
|
|
||
| $dir = dirname(COMPOSE_STACK_ORDER_FILE); | ||
| if (!is_dir($dir) && !@mkdir($dir, 0777, true) && !is_dir($dir)) { |
There was a problem hiding this comment.
saveComposeStackOrder() creates the config directory with mode 0777, which is overly permissive and inconsistent with other persistent-file helpers in this file (e.g., getAutoUpdateConfigFilePath() uses 0755). Use a tighter default (typically 0755) and rely on umask, or match the existing convention used elsewhere in the plugin.
| if (!is_dir($dir) && !@mkdir($dir, 0777, true) && !is_dir($dir)) { | |
| if (!is_dir($dir) && !@mkdir($dir, 0755, true) && !is_dir($dir)) { |
| function initComposeSortable() { | ||
| var $tbody = $('#compose_list'); | ||
| if (!$tbody.length) { | ||
| return; | ||
| } | ||
|
|
||
| if ($tbody.hasClass('ui-sortable')) { | ||
| $tbody.sortable('destroy'); | ||
| } | ||
|
|
There was a problem hiding this comment.
initComposeSortable() calls sortable() unconditionally. In other plugin code (e.g. settings page) there’s an explicit guard for typeof $.fn.sortable === 'function', implying jQuery UI Sortable may not always be available. Add the same guard here to prevent runtime JS errors on pages where the sortable plugin isn’t loaded.
| function saveComposeStackOrder(string $composeRoot, array $projects): bool | ||
| { | ||
| $map = getComposeStackOrderMap(); | ||
| $map[getComposeStackOrderKey($composeRoot)] = array_values($projects); |
There was a problem hiding this comment.
saveComposeStackOrder() persists whatever is in $projects without validating element types. Since getComposeStackOrder() later filters to strings, a malformed payload could silently discard the saved order. Consider filtering $projects to strings (and optionally basename() each entry) within saveComposeStackOrder() as a defensive measure.
| $map[getComposeStackOrderKey($composeRoot)] = array_values($projects); | |
| $map[getComposeStackOrderKey($composeRoot)] = array_values(array_filter( | |
| $projects, | |
| static function ($project): bool { | |
| return is_string($project); | |
| } | |
| )); |
| $savedOrder = getComposeStackOrder($composeRoot); | ||
| if ($savedOrder) { | ||
| $positions = array_flip($savedOrder); | ||
| usort($result, function ($left, $right) use ($positions) { | ||
| $leftPos = $positions[$left] ?? PHP_INT_MAX; | ||
| $rightPos = $positions[$right] ?? PHP_INT_MAX; |
There was a problem hiding this comment.
This new saved-order sorting changes the ordering contract of listProjectFolders(). The method’s docblock above currently states it returns “unsorted, filesystem order”, which is no longer true when a saved order exists. Please update the docblock to match the new behavior so callers don’t make incorrect assumptions.
What changed
Implemented sortable stacks with a lock feature and a save order capability, improving user experience in managing stack order.
Related issues
N/A
Checklist
Testing notes
Test the sortable functionality by attempting to reorder stacks and verify that the order persists after refresh.
Notes
No migration steps required. Ensure backward compatibility with existing stack configurations.