// ========== DOCKER COMPOSE IMPORT ========== (function() { injectModal('compose-import-modal', `

📦 Import Docker Compose

`); const modal = document.getElementById('compose-import-modal'); const openBtn = document.getElementById('compose-import-btn'); const cancelBtn = document.getElementById('compose-cancel'); wireModal(modal, cancelBtn); let parsedData = null; function showStep(step) { document.getElementById('compose-step-paste').style.display = step === 'paste' ? '' : 'none'; document.getElementById('compose-step-preview').style.display = step === 'preview' ? '' : 'none'; document.getElementById('compose-step-progress').style.display = step === 'progress' ? '' : 'none'; } openBtn?.addEventListener('click', () => { showStep('paste'); parsedData = null; document.getElementById('compose-yaml').value = ''; document.getElementById('compose-stack-name').value = ''; modal?.classList.add('show'); }); // File upload document.getElementById('compose-file-upload')?.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = () => { document.getElementById('compose-yaml').value = reader.result; }; reader.readAsText(file); }); // Parse document.getElementById('compose-parse-btn')?.addEventListener('click', async () => { const yamlStr = document.getElementById('compose-yaml').value.trim(); const stackName = document.getElementById('compose-stack-name').value.trim() || 'stack'; if (!yamlStr) { showNotification('Paste a docker-compose.yml', 'warning'); return; } const btn = document.getElementById('compose-parse-btn'); const origText = btn.textContent; btn.textContent = 'Parsing...'; btn.disabled = true; try { const data = await postJSON('/api/v1/apps/import-compose', { yaml: yamlStr, stackName }); parsedData = data; parsedData.stackName = stackName; renderPreview(data); showStep('preview'); } catch (e) { showNotification('Parse failed: ' + e.message, 'error'); } finally { btn.textContent = origText; btn.disabled = false; } }); function renderPreview(data) { const container = document.getElementById('compose-preview-content'); let html = ''; if (data.networks && data.networks.length > 0) { html += `
Networks: ${data.networks.map(n => `${escapeHtml(n)}`).join(', ')}
`; } if (data.volumes && data.volumes.length > 0) { html += `
Volumes: ${data.volumes.map(v => `${escapeHtml(v)}`).join(', ')}
`; } html += `
${data.services.length} service(s)
`; html += '
'; for (const svc of data.services) { const borderColor = svc.skip ? 'var(--bad-fg)' : 'var(--border)'; html += `
`; html += `
${escapeHtml(svc.name)}`; if (svc.skip) html += ` — skipped: ${escapeHtml(svc.reason)}`; html += `
`; if (!svc.skip) { html += `
Image: ${escapeHtml(svc.image)}
`; if (svc.ports?.length) html += `
Ports: ${svc.ports.map(p => `${p.host}:${p.container}`).join(', ')}
`; if (svc.volumes?.length) html += `
Volumes: ${svc.volumes.length}
`; if (Object.keys(svc.environment || {}).length) html += `
Env vars: ${Object.keys(svc.environment).length}
`; if (svc.envFileWarning) html += `
⚠ ${escapeHtml(svc.envFileWarning)}
`; if (svc.resources?.cpus || svc.resources?.memory) { const parts = []; if (svc.resources.cpus) parts.push(`CPU: ${svc.resources.cpus}`); if (svc.resources.memory) parts.push(`Mem: ${svc.resources.memory}MB`); html += `
Limits: ${parts.join(', ')}
`; } } html += '
'; } html += '
'; container.innerHTML = html; } // Back button document.getElementById('compose-back-btn')?.addEventListener('click', () => showStep('paste')); // Deploy document.getElementById('compose-deploy-btn')?.addEventListener('click', async () => { if (!parsedData) return; const btn = document.getElementById('compose-deploy-btn'); btn.textContent = 'Deploying...'; btn.disabled = true; showStep('progress'); const progressEl = document.getElementById('compose-progress-content'); progressEl.innerHTML = '
Deploying services...
'; try { const result = await postJSON('/api/v1/apps/deploy-compose', { services: parsedData.services, networks: parsedData.networks, stackName: parsedData.stackName }); let html = `
Stack "${escapeHtml(result.stackName)}" — Deployment Complete
`; html += '
'; for (const r of result.results) { const icon = r.status === 'deployed' || r.status === 'created' ? '✅' : r.status === 'exists' ? '⚡' : r.status === 'skipped' ? '⏭' : '❌'; html += `
`; html += `${icon} ${escapeHtml(r.name)} (${r.type}) — ${escapeHtml(r.status)}`; if (r.error) html += ` ${escapeHtml(r.error)}`; if (r.subdomain) html += ` → ${escapeHtml(r.subdomain)}`; if (r.reason) html += ` (${escapeHtml(r.reason)})`; html += '
'; } html += '
'; html += ''; progressEl.innerHTML = html; document.getElementById('compose-done-btn')?.addEventListener('click', () => { modal?.classList.remove('show'); if (typeof window.loadServices === 'function') window.loadServices().then(() => { if (typeof window.buildGrid === 'function') window.buildGrid(); }); }); showNotification(`Stack "${result.stackName}" deployed`, 'success'); } catch (e) { progressEl.innerHTML = `
Deployment failed: ${escapeHtml(e.message)}
`; document.getElementById('compose-retry-btn')?.addEventListener('click', () => showStep('paste')); } finally { btn.textContent = 'Deploy All'; btn.disabled = false; } }); })();