/** * javascript/networks.js * * Networks panel: table + create/remove, backed by ajax/networks.php. */ (function () { 'use strict'; const P = window.Podman; let networks = []; function rowHtml(n) { const nameCell = n.isDefault ? '' + P.escapeHtml(n.name) + ' (default)' : P.escapeHtml(n.name); const removeDisabled = n.isDefault || n.containers > 0; return '' + '' + '' + nameCell + '' + '' + P.escapeHtml(n.driver) + '' + '' + P.escapeHtml(n.subnet || '—') + '' + '' + P.escapeHtml(n.gateway || '—') + '' + '' + n.containers + '' + '
' + ''; } function render() { const tbody = P.el('networks-tbody'); tbody.innerHTML = networks.length ? networks.map(rowHtml).join('') : 'No networks.'; } function load() { const tbody = P.el('networks-tbody'); tbody.innerHTML = P.loadingRow(6); return P.get('networks', 'list').then(function (data) { networks = data; render(); }).catch(function (err) { tbody.innerHTML = P.errorRow(6, err.message); }); } // Purpose-built modal (not app.js's generic openFormModal, which only // supports flat always-visible text fields) — the parent-interface // dropdown and gateway field only make sense for "macvlan" and need to // show/hide based on the driver choice. function openCreateNetworkModal() { const backdrop = document.createElement('div'); backdrop.className = 'podman-modal-backdrop'; backdrop.innerHTML = '' + ''; (document.querySelector('.podman-plugin') || document.body).appendChild(backdrop); let parentInterfaces = []; P.get('networks', 'list_parent_interfaces').then(function (interfaces) { parentInterfaces = interfaces; const select = backdrop.querySelector('#cn-parent'); select.innerHTML = interfaces.map(function (i) { return ''; }).join(''); }).catch(function () { /* macvlan option just won't have anything to pick if this fails */ }); backdrop.querySelector('#cn-driver').addEventListener('change', function (e) { const isMacvlan = e.target.value === 'macvlan'; backdrop.querySelector('#cn-parent-field').style.display = isMacvlan ? '' : 'none'; backdrop.querySelector('#cn-gateway-field').style.display = isMacvlan ? '' : 'none'; backdrop.querySelector('#cn-subnet-label').textContent = isMacvlan ? 'Subnet' : 'Subnet (optional)'; }); backdrop.querySelector('#cn-name').focus(); function close() { backdrop.remove(); } function showError(message) { let box = backdrop.querySelector('.podman-modal-error'); if (!box) { box = document.createElement('div'); box.className = 'podman-modal-error'; backdrop.querySelector('.podman-modal-body').appendChild(box); } box.textContent = message; } function submit() { const name = backdrop.querySelector('#cn-name').value.trim(); if (!name) { showError('"Network name" is required.'); return; } const driver = backdrop.querySelector('#cn-driver').value; const subnet = backdrop.querySelector('#cn-subnet').value.trim(); const gateway = backdrop.querySelector('#cn-gateway').value.trim(); const parentInterface = backdrop.querySelector('#cn-parent').value; if (driver === 'macvlan') { if (!subnet) { showError('"Subnet" is required for a macvlan network.'); return; } if (!parentInterfaces.length) { showError('No host bridge/VLAN interface available to attach to.'); return; } } const submitBtn = backdrop.querySelector('[data-role="submit"]'); submitBtn.disabled = true; P.post('networks', 'create', { name: name, driver: driver, subnet: subnet || undefined, gateway: gateway || undefined, parentInterface: driver === 'macvlan' ? parentInterface : undefined, }).then(function () { close(); return load(); }).catch(function (err) { submitBtn.disabled = false; showError(err.message); }); } backdrop.querySelector('[data-role="cancel"]').addEventListener('click', close); backdrop.querySelector('[data-role="submit"]').addEventListener('click', submit); backdrop.querySelector('form').addEventListener('submit', function (e) { e.preventDefault(); submit(); }); backdrop.addEventListener('click', function (e) { if (e.target === backdrop) close(); }); document.addEventListener('keydown', function onKey(e) { if (e.key === 'Escape') { close(); document.removeEventListener('keydown', onKey); } }); } function init() { P.el('networks-create-btn').addEventListener('click', openCreateNetworkModal); P.el('networks-tbody').addEventListener('click', function (e) { const btn = e.target.closest('button[data-action="remove"]'); if (!btn || btn.disabled) return; const name = btn.closest('tr').dataset.name; P.confirm('Remove network "' + name + '"?', { danger: true, confirmLabel: 'Remove' }).then(function (ok) { if (!ok) return; btn.disabled = true; P.post('networks', 'remove', { name: name }).then(load).catch(function (err) { P.toast('Remove failed: ' + err.message, 'error'); btn.disabled = false; }); }); }); return load(); } P.registerPanel('networks', { init: init, refresh: load }); })();