// webui/html/assets/js/tabs/networks.js (function () { // Gebruik gedeelde helpers uit index.html (staan op window in non-module scripts) const apiGet = window.apiGet || (async (path) => { const r = await fetch('/api' + path, { headers: { 'Accept': 'application/json' } }); if (!r.ok) { const t = await r.text().catch(() => ''); throw new Error(`HTTP ${r.status} ${path}: ${t}`); } return r.json(); }); const esc = window.esc || ((s) => String(s) .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", ''') ); function cssId(s) { return String(s).replaceAll(/[^a-zA-Z0-9_-]/g, '_'); } async function fetchNetworksUsage() { return apiGet('/networks/usage'); } async function fetchNetworksList() { // returns { networks: [...] } return apiGet('/networks'); } async function fetchNetworkInspect(name) { return apiGet('/networks/' + encodeURIComponent(name)); } const state = { expanded: new Set(), usage: null, list: null, inspectCache: new Map(), }; function toggleNetworkRow(name) { if (state.expanded.has(name)) state.expanded.delete(name); else state.expanded.add(name); renderNetworks(); } async function refresh() { const statusEl = document.getElementById('networksStatus'); if (statusEl) statusEl.textContent = 'Bezig met laden...'; try { const [usage, list] = await Promise.all([ fetchNetworksUsage(), fetchNetworksList(), ]); state.usage = usage; state.list = list; if (statusEl) statusEl.textContent = `Laatst geladen: ${new Date().toLocaleString()}`; renderNetworks(); } catch (e) { console.error(e); if (statusEl) statusEl.textContent = 'Fout: ' + (e?.message || e); } } function renderNetworkUsersListHTML(netName, containers) { if (!containers.length) { return `
Geen containers gevonden op dit netwerk.
`; } const rows = containers.map(c => { const cname = c.name || '?'; const pod = c.pod || ''; const shared = c.networkMode && String(c.networkMode).startsWith('container:'); const via = c.networkOwnerName ? `via ${c.networkOwnerName}` : (c.networkOwnerId ? `via ${c.networkOwnerId}` : ''); const badges = []; if (pod) badges.push(`pod: ${esc(pod)}`); if (shared) badges.push(`shared netns`); if (via) badges.push(`${esc(via)}`); return `
  • ${esc(cname)} ${badges.length ? `
    ${badges.join('')}
    ` : ``}
  • `; }).join(''); return `
    Containers op ${esc(netName)}:
    `; } function renderNetworksRelationsHTML(usage) { const byContainer = usage.byContainer || {}; const byMeta = usage.byContainerMeta || {}; const multi = Object.entries(byContainer) .filter(([_, nets]) => Array.isArray(nets) && nets.length > 1) .sort((a, b) => a[0].localeCompare(b[0])); const shared = Object.entries(byMeta) .filter(([_, meta]) => meta && meta.networkMode && String(meta.networkMode).startsWith('container:')) .sort((a, b) => a[0].localeCompare(b[0])); let html = ''; html += `

    Containers met meerdere netwerken

    `; if (!multi.length) { html += `
    Geen.
    `; } else { html += ``; } html += `

    Shared network namespace

    `; if (!shared.length) { html += `
    Geen.
    `; } else { html += ``; } return html; } async function onInspectNetwork(name) { const box = document.getElementById('inspectBox-' + cssId(name)); const status = document.getElementById('inspectStatus-' + cssId(name)); if (status) status.textContent = 'Inspect laden...'; try { let data = state.inspectCache.get(name); if (!data) { data = await fetchNetworkInspect(name); state.inspectCache.set(name, data); } if (box) { box.style.display = ''; box.textContent = JSON.stringify(data, null, 2); } if (status) status.textContent = ''; } catch (e) { console.error(e); if (status) status.textContent = 'Fout: ' + (e?.message || e); } } function renderNetworks() { const tbody = document.getElementById('networksTbody'); const rel = document.getElementById('networksRelations'); if (!tbody || !rel) return; tbody.innerHTML = ''; rel.innerHTML = ''; const usage = state.usage; if (!usage || !usage.byNetwork) { tbody.innerHTML = `Geen data. Klik op Vernieuwen.`; rel.innerHTML = `
    Geen data.
    `; return; } const byNetwork = usage.byNetwork; const names = Object.keys(byNetwork).sort((a, b) => a.localeCompare(b)); for (const netName of names) { const slot = byNetwork[netName] || {}; const containers = slot.containers || []; const isOpen = state.expanded.has(netName); const arrow = isOpen ? '▾' : '▸'; const tr = document.createElement('tr'); tr.innerHTML = ` ${esc(netName)} ${containers.length} `; tr.querySelector('button').addEventListener('click', () => toggleNetworkRow(netName)); tbody.appendChild(tr); if (isOpen) { const tr2 = document.createElement('tr'); tr2.innerHTML = `
    ${renderNetworkUsersListHTML(netName, containers)}
    `; tbody.appendChild(tr2); const btn = tr2.querySelector('button[data-inspect]'); btn.addEventListener('click', async () => { await onInspectNetwork(netName); }); } } rel.innerHTML = renderNetworksRelationsHTML(usage); } function bindUiOnce() { const btn = document.getElementById('networksRefreshBtn'); if (btn && !btn.dataset.bound) { btn.dataset.bound = '1'; btn.addEventListener('click', refresh); } } // Expose minimal API window.mvpNetworks = { refresh, state, bindUiOnce, }; // Bind when script loads (DOM is already mostly there because script is at end of body) bindUiOnce(); })();