// ========== DOCKER RESOURCES (Volumes, Networks, Disk Usage) ========== (function() { injectModal('docker-resources-modal', `

🐳 Docker Resources

Loading...
Loading...
Loading...
`); const modal = document.getElementById('docker-resources-modal'); const openBtn = document.getElementById('docker-resources-btn'); const closeBtn = document.getElementById('dr-close'); function fmtBytes(bytes) { if (!bytes || bytes === 0) return '0 B'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(1024)); return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + units[i]; } // ===== VOLUMES ===== async function loadVolumes() { const container = document.getElementById('dr-vol-list'); try { const data = await getJSON('/api/v1/docker/volumes'); const vols = data.volumes || []; if (vols.length === 0) { container.innerHTML = '
📦No volumes found.
'; return; } let html = ''; html += ''; for (const v of vols) { const isSystem = v.name === 'buildkit' || v.name.length === 64; html += ``; html += ``; html += ``; html += ``; html += ``; } html += '
NameDriverScopeActions
${escapeHtml(v.name.length > 40 ? v.name.substring(0, 37) + '...' : v.name)}${escapeHtml(v.driver)}${escapeHtml(v.scope)}`; if (!isSystem) { html += ``; } html += `
'; container.innerHTML = html; container.querySelectorAll('.dr-vol-del').forEach(btn => { btn.addEventListener('click', async () => { if (!confirm(`Delete volume "${btn.dataset.name}"? Data will be lost.`)) return; btn.textContent = '...'; btn.disabled = true; try { await deleteAPI(`/api/v1/docker/volumes/${encodeURIComponent(btn.dataset.name)}?force=true`); loadVolumes(); } catch (e) { showNotification('Delete failed: ' + e.message, 'error'); btn.textContent = 'Delete'; btn.disabled = false; } }); }); } catch (e) { container.innerHTML = `
Failed: ${escapeHtml(e.message)}
`; } } document.getElementById('dr-vol-create')?.addEventListener('click', async () => { const nameInput = document.getElementById('dr-vol-name'); const name = nameInput.value.trim(); if (!name) { showNotification('Enter a volume name', 'warning'); return; } try { await postJSON('/api/v1/docker/volumes', { name }); nameInput.value = ''; showNotification(`Volume "${name}" created`, 'success'); loadVolumes(); } catch (e) { showNotification('Create failed: ' + e.message, 'error'); } }); // ===== NETWORKS ===== async function loadNetworks() { const container = document.getElementById('dr-net-list'); try { const data = await getJSON('/api/v1/docker/networks'); const nets = data.networks || []; if (nets.length === 0) { container.innerHTML = '
🌐No networks found.
'; return; } let html = ''; html += ''; for (const n of nets) { const isSystem = ['bridge', 'host', 'none'].includes(n.name); html += ``; html += ``; html += ``; html += ``; html += ``; html += ``; } html += '
NameDriverScopeContainersActions
${escapeHtml(n.name)}${escapeHtml(n.driver)}${escapeHtml(n.scope)}${n.containers}`; if (!isSystem) { html += ``; } html += `
'; container.innerHTML = html; container.querySelectorAll('.dr-net-del').forEach(btn => { btn.addEventListener('click', async () => { if (!confirm(`Delete network "${btn.dataset.name}"?`)) return; btn.textContent = '...'; btn.disabled = true; try { await deleteAPI(`/api/v1/docker/networks/${encodeURIComponent(btn.dataset.id)}`); loadNetworks(); } catch (e) { showNotification('Delete failed: ' + e.message, 'error'); btn.textContent = 'Delete'; btn.disabled = false; } }); }); } catch (e) { container.innerHTML = `
Failed: ${escapeHtml(e.message)}
`; } } document.getElementById('dr-net-create')?.addEventListener('click', async () => { const nameInput = document.getElementById('dr-net-name'); const driverSelect = document.getElementById('dr-net-driver'); const name = nameInput.value.trim(); if (!name) { showNotification('Enter a network name', 'warning'); return; } try { await postJSON('/api/v1/docker/networks', { name, driver: driverSelect.value }); nameInput.value = ''; showNotification(`Network "${name}" created`, 'success'); loadNetworks(); } catch (e) { showNotification('Create failed: ' + e.message, 'error'); } }); // ===== DISK USAGE ===== async function loadDiskUsage() { const container = document.getElementById('dr-disk-content'); try { const data = await getJSON('/api/v1/docker/disk-usage'); const sections = [ { label: 'Images', icon: '📀', count: data.images.count, size: data.images.size, reclaimable: data.images.reclaimable }, { label: 'Containers', icon: '📦', count: data.containers.count, size: data.containers.size, extra: `${data.containers.running} running` }, { label: 'Volumes', icon: '💾', count: data.volumes.count, size: data.volumes.size, reclaimable: data.volumes.reclaimable }, { label: 'Build Cache', icon: '🔧', count: data.buildCache.count, size: data.buildCache.size, reclaimable: data.buildCache.reclaimable }, ]; let html = `
Total: ${fmtBytes(data.totalSize)}
`; html += '
'; for (const s of sections) { html += `
`; html += `
${s.icon} ${s.label} (${s.count})
`; html += `
${fmtBytes(s.size)}
`; if (s.reclaimable > 0) html += `
Reclaimable: ${fmtBytes(s.reclaimable)}
`; if (s.extra) html += `
${s.extra}
`; html += '
'; } html += '
'; container.innerHTML = html; } catch (e) { container.innerHTML = `
Failed: ${escapeHtml(e.message)}
`; } } // Modal events openBtn?.addEventListener('click', () => { modal?.classList.add('show'); loadVolumes(); }); wireModal(modal, closeBtn); // Lazy-load tabs document.querySelector('[data-panel="dr-networks"]')?.addEventListener('click', loadNetworks); document.querySelector('[data-panel="dr-disk"]')?.addEventListener('click', loadDiskUsage); })();