diff --git a/docs/user-guide.md b/docs/user-guide.md index 7c67cd5e..a670c1b8 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -41,6 +41,16 @@ Each stack supports the following actions: | **Edit Stack** | Open the stack editor | | **Remove Stack** | Delete the stack configuration | +### Checking for Updates + +Use **Check Updates** on a stack to query the registry for newer image versions. Results are cached until the next manual or scheduled check (see [Update Checking settings](configuration.md#update-checking)). + +When updates are available, clicking **Update Stack** opens a confirmation dialog listing each container alongside its current and incoming image digest. Containers that are already up to date are dimmed. + +#### Changelogs + +If the [docker.versions](https://github.com/phyzical/docker.versions) Unraid plugin is installed, a **Changelog** link appears below the digest for each container with a pending update. Clicking it opens the release notes for that image in a modal. No configuration is required — Compose Manager detects docker.versions automatically. + ## Autostart Enable autostart to have stacks start automatically when the Unraid array starts. diff --git a/source/compose.manager/include/Exec.php b/source/compose.manager/include/Exec.php index 63bcfa9b..f93aee8a 100644 --- a/source/compose.manager/include/Exec.php +++ b/source/compose.manager/include/Exec.php @@ -1504,15 +1504,16 @@ function rejectStaleClientPath(?string $actualPath, string $label): bool case 'getSavedUpdateStatus': // Load saved update status from file $composeUpdateStatusFile = COMPOSE_UPDATE_STATUS_FILE; + $dockerVersionsInstalled = is_dir('/usr/local/emhttp/plugins/docker.versions'); if (is_file($composeUpdateStatusFile)) { $savedStatus = json_decode(file_get_contents($composeUpdateStatusFile), true); if ($savedStatus) { - echo json_encode(['result' => 'success', 'stacks' => $savedStatus]); + echo json_encode(['result' => 'success', 'stacks' => $savedStatus, 'dockerVersionsInstalled' => $dockerVersionsInstalled]); } else { - echo json_encode(['result' => 'success', 'stacks' => []]); + echo json_encode(['result' => 'success', 'stacks' => [], 'dockerVersionsInstalled' => $dockerVersionsInstalled]); } } else { - echo json_encode(['result' => 'success', 'stacks' => []]); + echo json_encode(['result' => 'success', 'stacks' => [], 'dockerVersionsInstalled' => $dockerVersionsInstalled]); } break; case 'getLogs': diff --git a/source/compose.manager/javascript/composeManagerMain.js b/source/compose.manager/javascript/composeManagerMain.js index 707e23ca..2cb3a534 100644 --- a/source/compose.manager/javascript/composeManagerMain.js +++ b/source/compose.manager/javascript/composeManagerMain.js @@ -966,6 +966,9 @@ function updateTabModifiedState() { // Update status cache per stack var stackUpdateStatus = {}; +// Set to true when docker.versions plugin is detected (populated from getSavedUpdateStatus response) +var dockerVersionsInstalled = false; + // Load saved update status from server (called on page load) // If auto-check is enabled and interval has elapsed, trigger a fresh check // Also checks for pending rechecks from recent update operations @@ -978,6 +981,7 @@ function loadSavedUpdateStatus() { var response = JSON.parse(data); if (response.result === 'success' && response.stacks) { stackUpdateStatus = response.stacks; + if (response.dockerVersionsInstalled) dockerVersionsInstalled = true; // Update the UI for each stack with saved status for (var stackName in response.stacks) { @@ -3501,6 +3505,42 @@ function mergeUpdateStatus(containers, project) { return containers; } +// Show docker.versions changelog for a single container. +// Delegates entirely to docker.versions' own showChangeLog() so that display +// logic, Nchan subscription management, and message routing stay in one place. +// +// Coupling point: showChangeLog(containerName) — global function from +// docker.versions/scripts/changelog.js. If that function is renamed or +// removed, this feature silently does nothing. +function showComposeChangelog(containerName, path, profile) { + if (typeof showChangeLog !== 'function') return; + showChangeLog(containerName); + // docker.versions' OK button calls updateContainer(), which bypasses + // compose_plugin's update mechanism. Hide it so the only exit is Cancel. + setTimeout(function() { $('.sweet-alert .confirm').hide(); }, 0); + if (!path) return; + // Reopen the Update Stack dialog when the changelog dialog closes. + // SweetAlert 1.x has no close event; poll the showSweetAlert class instead. + // Cap at 300 ticks (30 s) so the interval self-cleans if the dialog never opens. + var appeared = false; + var ticks = 0; + var poll = setInterval(function() { + if (++ticks > 300) { clearInterval(poll); return; } + var open = $('.sweet-alert').hasClass('showSweetAlert'); + if (!appeared) { if (open) appeared = true; return; } + if (!open) { + clearInterval(poll); + // Skip reopening when DISABLE_ACTION_WARNINGS is true: renderStackActionDialog + // has a fast-path that calls UpdateStackConfirmed directly in that mode, + // so reopening would trigger an immediate update rather than a dialog. + getConfig().then(function(cfg) { + if (cfg && cfg.DISABLE_ACTION_WARNINGS === 'true') return; + showStackActionDialog('update', path, profile || ''); + }); + } + }, 100); +} + // Unified stack action dialog - handles up, down, and update actions function showStackActionDialog(action, path, profile) { var stackName = basename(path); @@ -3753,6 +3793,9 @@ function renderStackActionDialog(action, displayName, path, profile, containers, html += ' '; html += '' + composeEscapeHtml(remoteSha.substring(0, 8)) + ''; html += ''; + if (dockerVersionsInstalled) { + html += '
'; + } } else if (localSha) { // No update - just show current SHA (greyed) html += '