From b2024c1a37aee2933d225b0fb2b28dd6eb7b616a Mon Sep 17 00:00:00 2001 From: Nick Szittai Date: Fri, 20 Mar 2026 22:09:29 -0400 Subject: [PATCH 01/40] feat(import): add Docker Manager container import functionality and preview generation --- .../include/ComposeManager.php | 151 +++++++++ source/compose.manager/include/Exec.php | 138 ++++++++ source/compose.manager/include/Util.php | 313 ++++++++++++++++++ 3 files changed, 602 insertions(+) diff --git a/source/compose.manager/include/ComposeManager.php b/source/compose.manager/include/ComposeManager.php index e28d34a..6f4ff52 100755 --- a/source/compose.manager/include/ComposeManager.php +++ b/source/compose.manager/include/ComposeManager.php @@ -2208,6 +2208,156 @@ function(data) { + function importFromDockerManager() { + var modalHtml = ` +
+ +
+ `; + + var existing = document.getElementById('compose-import-modal-overlay'); + if (existing) existing.remove(); + document.body.insertAdjacentHTML('beforeend', modalHtml); + + window.closeComposeImportModal = function() { + var overlay = document.getElementById('compose-import-modal-overlay'); + if (overlay) overlay.remove(); + }; + + $.post(caURL, {action: 'getDockerContainersForImport'}, function(data) { + var response; + try { response = JSON.parse(data); } catch (e) { response = { result:'error', message: 'Invalid response' }; } + if (response.result !== 'success') { + $('#compose-import-modal-body').html('
Failed to load containers.
'); + return; + } + var containers = response.containers || []; + if (!containers.length) { + $('#compose-import-modal-body').html('
No Docker Manager containers found.
'); + return; + } + var rows = containers.map(function(ct) { + return '' + + '' + + '' + escapeHtml(ct.Name) + '' + + '' + escapeHtml(ct.Image) + '' + + '' + escapeHtml(ct.Status) + '' + + '' + (ct.Icon ? '' : '') + '' + + '' + (ct.Url ? 'Link' : '') + '' + + ''; + }).join(''); + var table = '
' + rows + '
NameImageStatusIconWebUI
'; + $('#compose-import-modal-body').html(table); + $('#compose-import-modal-footer').html(''); + }).fail(function() { + $('#compose-import-modal-body').html('
Failed to contact server.
'); + }); + } + + function composeImportPreview() { + var selected = []; + $('.cm-import-container:checked').each(function() { + selected.push(this.value); + }); + if (!selected.length) { + swal('No containers selected', 'Please select at least one container to import.', 'warning'); + return; + } + + $('#compose-import-modal-body').html('
Generating import preview...
'); + $('#compose-import-modal-footer').empty(); + + $.post(caURL, {action: 'generateImportPreview', containerIds: JSON.stringify(selected)}, function(data) { + var response; + try { response = JSON.parse(data); } catch (e) { response = { result:'error', message: 'Invalid response' }; } + if (response.result !== 'success') { + $('#compose-import-modal-body').html('
' + (response.message || 'Failed to generate preview') + '
'); + return; + } + var preview = '
Compose YAML preview
' + + '
' + escapeHtml(response.composeYml) + '
' + + '
.env preview (optional)
' + + '
' + escapeHtml(response.env) + '
' + + '
Override preview (icons/webui)
' + + '
' + escapeHtml(response.override) + '
'; + + preview += '
Stack name
' + + '' + + '
Actions
' + + '' + + '' + + ''; + + $('#compose-import-modal-body').html(preview); + $('#compose-import-modal-footer').html(''); + + $('#cm-import-remove-containers').on('change', function() { + if ($(this).is(':checked')) { + $('#cm-import-stop-containers').prop('checked', true); + } + }); + $('#cm-import-stop-containers').on('change', function() { + if (!$(this).is(':checked')) { + $('#cm-import-remove-containers').prop('checked', false); + } + }); + + window.__composeImportPayload = {composeYml: response.composeYml, env: response.env, override: response.override, containerIds: selected}; + }).fail(function() { + $('#compose-import-modal-body').html('
Failed to contact server for preview.
'); + }); + } + + function composeImportPerform() { + var payload = window.__composeImportPayload || {}; + var name = document.getElementById('cm-import-stack-name').value.trim(); + if (!name) { + swal('Stack name required', 'Please provide a stack name to continue.', 'warning'); + return; + } + + var stopContainers = document.getElementById('cm-import-stop-containers').checked ? '1' : '0'; + var removeContainers = document.getElementById('cm-import-remove-containers').checked ? '1' : '0'; + var startStack = document.getElementById('cm-import-start-stack').checked ? '1' : '0'; + + $.post(caURL, { + action: 'performImportTransfer', + stackName: name, + stopContainers: stopContainers, + removeContainers: removeContainers, + startStack: startStack, + composeYml: payload.composeYml || '', + env: payload.env || '', + override: payload.override || '', + containerIds: JSON.stringify(payload.containerIds || []) + }, function(data) { + var response; + try { response = JSON.parse(data); } catch (e) { response = { result:'error', message: 'Invalid response' }; } + if (response.result === 'success') { + closeComposeImportModal(); + composeLoadlist(); + openEditorModalByProject(response.project, response.projectName); + + if (response.startStack === 1 && response.projectPath) { + ComposeUp(response.projectPath); + } + + swal('Imported', 'Stack imported successfully.', 'success'); + } else { + swal('Import failed', response.message || 'Unknown error', 'error'); + } + }).fail(function() { + swal('Import failed', 'Communication error', 'error'); + }); + } + function stripTags(string) { return string.replace(/(<([^>]+)>)/ig, ""); } @@ -6090,6 +6240,7 @@ function addComposeStackContext(elementId) {