diff --git a/source/compose.manager/Compose.page b/source/compose.manager/Compose.page index aeb2f41..8f81e0e 100644 --- a/source/compose.manager/Compose.page +++ b/source/compose.manager/Compose.page @@ -3,6 +3,7 @@ Type="xmenu" Title="Docker Compose" Tag="fa-cubes" Code="f1b3" +Lock="true" Nchan="docker_load" Cond="$var['fsState'] == 'Started' && exec('/etc/rc.d/rc.docker status | grep -v \"not\"') && exec(\"grep '^SHOW_COMPOSE_IN_HEADER_MENU=' /boot/config/plugins/compose.manager/compose.manager.cfg 2>/dev/null | grep 'true'\")" --- diff --git a/source/compose.manager/compose.manager.page b/source/compose.manager/compose.manager.page index 93856ea..fa48101 100644 --- a/source/compose.manager/compose.manager.page +++ b/source/compose.manager/compose.manager.page @@ -2,6 +2,7 @@ Author="dcflachs" Title="Compose" Type="php" Menu="Docker:2" +Lock="true" Nchan="docker_load" Cond="$var['fsState'] == 'Started' && exec('/etc/rc.d/rc.docker status | grep -v \"not\"') && (!file_exists('/boot/config/plugins/compose.manager/compose.manager.cfg') ? true : exec(\"grep '^SHOW_COMPOSE_IN_HEADER_MENU=' /boot/config/plugins/compose.manager/compose.manager.cfg 2>/dev/null | grep -v 'true'\"))" --- diff --git a/source/compose.manager/compose.manager.settings.page b/source/compose.manager/compose.manager.settings.page index 2b68f9c..8ce82e0 100755 --- a/source/compose.manager/compose.manager.settings.page +++ b/source/compose.manager/compose.manager.settings.page @@ -3,6 +3,7 @@ Icon="cubes" Author="mstrhakr" Title="Compose Manager Plus" Type="xmenu" +Lock="true" --- _(Loading stacks...)_'); $.post('/plugins/compose.manager/php/exec.php', { @@ -1574,6 +1637,8 @@ $acePath = file_exists('/usr/local/emhttp/plugins/dynamix/javascript/ace/ace.js' off_label: '' }); }); + + initUpdatesSortable(); // Sync toggle-all state updateToggleAllState(); }, 'json').fail(function() { @@ -1641,6 +1706,8 @@ $acePath = file_exists('/usr/local/emhttp/plugins/dynamix/javascript/ace/ace.js' // Initialize Updates tab when it's shown $(function() { + updateLockButtonUI(); + // Apply default schedule/time to all rows $('#apply-default').on('click', function() { var s = $('#default-schedule').val(); diff --git a/source/compose.manager/javascript/composeSortable.js b/source/compose.manager/javascript/composeSortable.js new file mode 100644 index 0000000..6094566 --- /dev/null +++ b/source/compose.manager/javascript/composeSortable.js @@ -0,0 +1,162 @@ +/** + * Compose Manager – Stack sortable (drag-and-drop reordering) + * + * Depends on globals provided by the page: + * caURL – AJAX endpoint for exec.php + * composeClientDebug – debug/logging helper (common.js) + * $.cookie / $.removeCookie – jquery.cookie plugin + * $.fn.sortable – jQuery UI Sortable + */ + +// ── Sort-mode state ──────────────────────────────────────────────── + +function isComposeSortModeEnabled() { + return $.cookie('lockbutton') != null; +} + +// ── Lock / Unlock button UI ──────────────────────────────────────── + +function updateComposeLockButtonUI() { + var unlocked = isComposeSortModeEnabled(); + var $button = $('div.nav-item.LockButton'); + if (!$button.length) { + return; + } + + if (unlocked) { + $button.find('a').prop('title', 'Lock sortable items'); + $button.find('b').removeClass('icon-u-lock green-text').addClass('icon-u-lock-open red-text'); + $button.find('span').text('Lock sortable items'); + } else { + $button.find('a').prop('title', 'Unlock sortable items'); + $button.find('b').removeClass('icon-u-lock-open red-text').addClass('icon-u-lock green-text'); + $button.find('span').text('Unlock sortable items'); + } +} + +// ── Persist sort order ───────────────────────────────────────────── + +function saveComposeSortOrder() { + var projects = $('#compose_list > tr.compose-sortable').map(function() { + return $(this).data('project'); + }).get(); + + return $.post(caURL, { + action: 'saveStackOrder', + projects: projects + }).fail(function(xhr) { + composeClientDebug('[saveComposeSortOrder] failed', { + status: xhr.status, + response: xhr.responseText + }, 'daemon', 'error'); + }); +} + +// ── Details-row helpers (detach during drag, reattach on drop) ───── + +function getComposeDetailsRowForItem($item) { + if (!$item || !$item.length) { + return $(); + } + + var rowId = $item.attr('id') || ''; + if (rowId.indexOf('stack-row-') !== 0) { + return $(); + } + + return $('#details-row-' + rowId.replace('stack-row-', '')); +} + +function reattachComposeDetailsRow($item) { + var $detailsRow = $item.data('compose-details-row'); + if ($detailsRow && $detailsRow.length) { + $detailsRow.insertAfter($item); + $item.removeData('compose-details-row'); + } +} + +// ── jQuery UI Sortable initialisation ────────────────────────────── + +function initComposeSortable() { + var $tbody = $('#compose_list'); + if (!$tbody.length) { + return; + } + + if ($tbody.hasClass('ui-sortable')) { + $tbody.sortable('destroy'); + } + + if (!isComposeSortModeEnabled()) { + $tbody.removeClass('compose-sort-enabled'); + return; + } + + $tbody.addClass('compose-sort-enabled'); + $tbody.sortable({ + helper: 'clone', + items: '> tr.compose-sortable', + cursor: 'grab', + axis: 'y', + containment: 'parent', + cancel: '[data-stackid], .compose-updatecolumn a, .compose-updatecolumn .exec, .auto_start, .switchButton, a, button, input', + delay: 100, + opacity: 0.5, + zIndex: 9999, + forcePlaceholderSize: true, + start: function(event, ui) { + var $detailsRow = getComposeDetailsRowForItem(ui.item); + if ($detailsRow.length) { + ui.item.data('compose-details-row', $detailsRow.detach()); + } + }, + update: function() { + saveComposeSortOrder(); + }, + stop: function(event, ui) { + reattachComposeDetailsRow(ui.item); + } + }); +} + +// ── Sync all sort-mode UI (icons, class, sortable instance) ──────── + +function syncComposeSortModeUI() { + var unlocked = isComposeSortModeEnabled(); + $('#compose_stacks tr.compose-sortable').each(function() { + var $row = $(this); + $row.find('.expand-icon').toggle(!unlocked); + $row.find('.mover').toggle(unlocked); + }); + + $('#compose_list').toggleClass('compose-sort-enabled', unlocked); + updateComposeLockButtonUI(); + initComposeSortable(); +} + +// ── Global entry-point called by Unraid navigation lock button ───── +// When embedded in the Docker tab, DockerContainers.page defines its own +// LockButton(). We save the previous implementation (if any) and chain +// to it so both Docker containers AND Compose stacks react to the button. +// IMPORTANT: We use window.LockButton assignment (not a function declaration) +// to avoid hoisting — a hoisted function declaration would overwrite the +// global LockButton before we can capture it. + +var _composePrevLockButton = window.LockButton || null; + +window.LockButton = function LockButton() { + if (_composePrevLockButton) { + // Let the Docker (or other) handler run first — it toggles the + // cookie and manages its own sortable / UI state. + _composePrevLockButton(); + } else { + // Standalone page (header-menu mode) — we own the cookie. + if (isComposeSortModeEnabled()) { + $.removeCookie('lockbutton'); + } else { + $.cookie('lockbutton', 'lockbutton'); + } + } + + syncComposeSortModeUI(); +}; diff --git a/source/compose.manager/php/compose_list.php b/source/compose.manager/php/compose_list.php index 548a4cb..1b537d5 100755 --- a/source/compose.manager/php/compose_list.php +++ b/source/compose.manager/php/compose_list.php @@ -192,6 +192,7 @@ // Arrow column $o .= ""; $o .= ""; + $o .= ""; $o .= ""; // Icon column diff --git a/source/compose.manager/php/compose_manager_main.php b/source/compose.manager/php/compose_manager_main.php index a8f096f..9b4f4cc 100755 --- a/source/compose.manager/php/compose_manager_main.php +++ b/source/compose.manager/php/compose_manager_main.php @@ -100,6 +100,19 @@ function compose_manager_cpu_spec_count($cpuSpec) padding: 0; } + #compose_stacks td.col-arrow { + text-align: center; + white-space: nowrap; + } + + #compose_stacks td.col-arrow i { + vertical-align: middle; + } + + #compose_list.compose-sort-enabled tr.compose-sortable { + cursor: move; + } + #compose_stacks thead th.col-icon { width: 30px; padding: 0; @@ -261,6 +274,7 @@ function compose_manager_cpu_spec_count($cpuSpec) +