Skip to content

Sortable stacks with lock functionality#87

Merged
mstrhakr merged 6 commits intodevfrom
feat/sortable-stacks
Apr 9, 2026
Merged

Sortable stacks with lock functionality#87
mstrhakr merged 6 commits intodevfrom
feat/sortable-stacks

Conversation

@mstrhakr
Copy link
Copy Markdown
Owner

@mstrhakr mstrhakr commented Apr 9, 2026

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

  • I have added/updated tests where applicable
  • I have updated documentation (docs/, README, or plugin docs)
  • Linted/formatted the code
  • Verified on Unraid or CI

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.

Copilot AI review requested due to automatic review settings April 9, 2026 13:15
@mstrhakr mstrhakr merged commit 96a3ffa into dev Apr 9, 2026
7 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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)) {
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
if (!is_dir($dir) && !@mkdir($dir, 0777, true) && !is_dir($dir)) {
if (!is_dir($dir) && !@mkdir($dir, 0755, true) && !is_dir($dir)) {

Copilot uses AI. Check for mistakes.
Comment on lines +80 to +89
function initComposeSortable() {
var $tbody = $('#compose_list');
if (!$tbody.length) {
return;
}

if ($tbody.hasClass('ui-sortable')) {
$tbody.sortable('destroy');
}

Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
function saveComposeStackOrder(string $composeRoot, array $projects): bool
{
$map = getComposeStackOrderMap();
$map[getComposeStackOrderKey($composeRoot)] = array_values($projects);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
$map[getComposeStackOrderKey($composeRoot)] = array_values($projects);
$map[getComposeStackOrderKey($composeRoot)] = array_values(array_filter(
$projects,
static function ($project): bool {
return is_string($project);
}
));

Copilot uses AI. Check for mistakes.
Comment on lines +2035 to +2040
$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;
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
@mstrhakr mstrhakr deleted the feat/sortable-stacks branch April 11, 2026 01:59
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.

2 participants