feat (ui): netwerk map functionaliteit verder uitgebreid en polish
This commit is contained in:
@@ -22,6 +22,14 @@ DirectoryIndex index.html
|
|||||||
ErrorLog /proc/self/fd/2
|
ErrorLog /proc/self/fd/2
|
||||||
CustomLog /proc/self/fd/1 combined
|
CustomLog /proc/self/fd/1 combined
|
||||||
|
|
||||||
|
#ProxyPreserveHost On
|
||||||
|
#ProxyPass "/api/" "http://127.0.0.1:8000/api/"
|
||||||
|
#ProxyPassReverse "/api/" "http://127.0.0.1:8000/api/"
|
||||||
|
|
||||||
|
# allow long-running upstream requests (image builds)
|
||||||
|
Timeout 600
|
||||||
|
ProxyTimeout 600
|
||||||
|
|
||||||
ProxyPreserveHost On
|
ProxyPreserveHost On
|
||||||
ProxyPass "/api/" "http://127.0.0.1:8000/api/"
|
ProxyPass "/api/" "http://127.0.0.1:8000/api/" connectiontimeout=5 timeout=600 retry=0
|
||||||
ProxyPassReverse "/api/" "http://127.0.0.1:8000/api/"
|
ProxyPassReverse "/api/" "http://127.0.0.1:8000/api/"
|
||||||
|
|||||||
@@ -165,7 +165,15 @@ async function buildImage() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await res.json();
|
const ct = (res.headers.get("content-type") || "").toLowerCase();
|
||||||
|
let data;
|
||||||
|
|
||||||
|
if (ct.includes("application/json")) {
|
||||||
|
data = await res.json();
|
||||||
|
} else {
|
||||||
|
const text = await res.text();
|
||||||
|
data = { ok: res.ok, status: res.status, non_json: true, body: text.slice(0, 4000) };
|
||||||
|
}
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
outputBox.value += "\nERROR:\n" + JSON.stringify(data, null, 2);
|
outputBox.value += "\nERROR:\n" + JSON.stringify(data, null, 2);
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
connectedOnly: false,
|
connectedOnly: false,
|
||||||
hideDefaults: true,
|
hideDefaults: true,
|
||||||
sharedOnly: false,
|
sharedOnly: false,
|
||||||
|
showModes: true,
|
||||||
sort: 'name_asc',
|
sort: 'name_asc',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -126,6 +127,18 @@
|
|||||||
return { totalNetworks, usedNetworks, unusedNetworks, connectedContainers, sharedNetns };
|
return { totalNetworks, usedNetworks, unusedNetworks, connectedContainers, sharedNetns };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildMapStatus(label, nodesCount, linksCount) {
|
||||||
|
const f = state.filters || {};
|
||||||
|
const flags = [];
|
||||||
|
if (f.connectedOnly) flags.push('connected');
|
||||||
|
if (f.hideDefaults) flags.push('hide-defaults');
|
||||||
|
if (f.sharedOnly) flags.push('shared');
|
||||||
|
if (f.showModes === false) flags.push('modes-off');
|
||||||
|
|
||||||
|
const flagTxt = flags.length ? ` | ${flags.join(', ')}` : '';
|
||||||
|
return `${label} | ${nodesCount} nodes, ${linksCount} links${flagTxt}`;
|
||||||
|
}
|
||||||
|
|
||||||
function renderNetworksSummary() {
|
function renderNetworksSummary() {
|
||||||
const host = document.getElementById('networksSummary');
|
const host = document.getElementById('networksSummary');
|
||||||
if (!host) return;
|
if (!host) return;
|
||||||
@@ -345,6 +358,7 @@
|
|||||||
const connectedOnly = !!state.filters.connectedOnly;
|
const connectedOnly = !!state.filters.connectedOnly;
|
||||||
const hideDefaults = !!state.filters.hideDefaults;
|
const hideDefaults = !!state.filters.hideDefaults;
|
||||||
const sharedOnly = !!state.filters.sharedOnly;
|
const sharedOnly = !!state.filters.sharedOnly;
|
||||||
|
const showModes = state.filters.showModes !== false;
|
||||||
|
|
||||||
const byMeta = state.usage?.byContainerMeta || {};
|
const byMeta = state.usage?.byContainerMeta || {};
|
||||||
|
|
||||||
@@ -353,6 +367,9 @@
|
|||||||
if (hideDefaults) {
|
if (hideDefaults) {
|
||||||
out = out.filter(n => !isDefaultNetworkName(n.name));
|
out = out.filter(n => !isDefaultNetworkName(n.name));
|
||||||
}
|
}
|
||||||
|
if (!showModes) {
|
||||||
|
out = out.filter(n => String(n.meta?.driver || '') !== 'mode');
|
||||||
|
}
|
||||||
|
|
||||||
if (connectedOnly) {
|
if (connectedOnly) {
|
||||||
out = out.filter(n => n.containerCount > 0);
|
out = out.filter(n => n.containerCount > 0);
|
||||||
@@ -696,8 +713,8 @@
|
|||||||
|
|
||||||
// simulation
|
// simulation
|
||||||
const sim = d3.forceSimulation(model.nodes)
|
const sim = d3.forceSimulation(model.nodes)
|
||||||
.force('link', d3.forceLink(model.links).id(d => d.id).distance(45))
|
.force('link', d3.forceLink(model.links).id(d => d.id).distance(80))
|
||||||
.force('charge', d3.forceManyBody().strength(-40))
|
.force('charge', d3.forceManyBody().strength(-30))
|
||||||
.force('center', d3.forceCenter(w / 2, h / 2).strength(0.15))
|
.force('center', d3.forceCenter(w / 2, h / 2).strength(0.15))
|
||||||
.force('collide', d3.forceCollide().radius(d => d.type === 'network' ? 18 : 16))
|
.force('collide', d3.forceCollide().radius(d => d.type === 'network' ? 18 : 16))
|
||||||
.on('tick', () => {
|
.on('tick', () => {
|
||||||
@@ -731,7 +748,7 @@
|
|||||||
showDetailPanel(networkName);
|
showDetailPanel(networkName);
|
||||||
|
|
||||||
const s = document.getElementById('networksMapStatus');
|
const s = document.getElementById('networksMapStatus');
|
||||||
if (s) s.textContent = `Detail: ${networkName}`;
|
if (s) s.textContent = buildMapStatus(`Detail: ${networkName}`, model.nodes.length, model.links.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showDetailPanel(networkName) {
|
function showDetailPanel(networkName) {
|
||||||
@@ -792,7 +809,7 @@
|
|||||||
hideDetailPanel();
|
hideDetailPanel();
|
||||||
|
|
||||||
const s = document.getElementById('networksMapStatus');
|
const s = document.getElementById('networksMapStatus');
|
||||||
if (s) s.textContent = `Kaart: ${model.meta.nodes} nodes, ${model.meta.links} links`;
|
if (s) s.textContent = buildMapStatus('Global', model.nodes.length, model.links.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNetworks() {
|
function renderNetworks() {
|
||||||
@@ -927,6 +944,10 @@
|
|||||||
resetBtn.dataset.bound = '1';
|
resetBtn.dataset.bound = '1';
|
||||||
resetBtn.addEventListener('click', () => {
|
resetBtn.addEventListener('click', () => {
|
||||||
if (!graphCtx) return;
|
if (!graphCtx) return;
|
||||||
|
|
||||||
|
// wis highlight/dim
|
||||||
|
graphCtx.g.selectAll('.graphNode').classed('graphDim', false);
|
||||||
|
graphCtx.g.selectAll('.graphLink').classed('graphDim', false).classed('graphActive', false);
|
||||||
|
|
||||||
// reset zoom
|
// reset zoom
|
||||||
graphCtx.svg
|
graphCtx.svg
|
||||||
@@ -990,7 +1011,13 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const s = document.getElementById('networksMapStatus');
|
const s = document.getElementById('networksMapStatus');
|
||||||
if (s) s.textContent = `Container: ${id.slice(0, 12)}…`;
|
if (s) {
|
||||||
|
const label = (state.mapMode === 'detail' && state.selectedNetwork)
|
||||||
|
? `Detail: ${state.selectedNetwork}`
|
||||||
|
: 'Global';
|
||||||
|
|
||||||
|
s.textContent = buildMapStatus(label, graphCtx.model.nodes.length, graphCtx.model.links.length) + ` | container: ${id.slice(0, 12)}…`;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -999,13 +1026,24 @@
|
|||||||
const fHideDefaults = document.getElementById('networksFilterHideDefaults');
|
const fHideDefaults = document.getElementById('networksFilterHideDefaults');
|
||||||
const fShared = document.getElementById('networksFilterShared');
|
const fShared = document.getElementById('networksFilterShared');
|
||||||
const sort = document.getElementById('networksSort');
|
const sort = document.getElementById('networksSort');
|
||||||
|
const mapShowModes = document.getElementById('networksMapShowModes');
|
||||||
|
const mapConnectedOnly = document.getElementById('networksMapConnectedOnly');
|
||||||
|
|
||||||
function rerender() {
|
function rerender() {
|
||||||
renderNetworks();
|
renderNetworks();
|
||||||
|
|
||||||
if (state.view === 'map') {
|
if (state.view === 'map') {
|
||||||
|
|
||||||
|
// als we in detail zitten → detail opnieuw renderen
|
||||||
|
if (state.mapMode === 'detail' && state.selectedNetwork) {
|
||||||
|
openNetworkDetail(state.selectedNetwork);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// anders global map
|
||||||
const model = buildGlobalGraphModel();
|
const model = buildGlobalGraphModel();
|
||||||
const s = document.getElementById('networksMapStatus');
|
const s = document.getElementById('networksMapStatus');
|
||||||
if (s) s.textContent = `Kaart: ${model.meta.nodes} nodes, ${model.meta.links} links`;
|
if (s) s.textContent = buildMapStatus('Global', model.nodes.length, model.links.length);
|
||||||
renderGraph(model);
|
renderGraph(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1051,8 +1089,36 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mapConnectedOnly && !mapConnectedOnly.dataset.bound) {
|
||||||
|
mapConnectedOnly.dataset.bound = '1';
|
||||||
|
mapConnectedOnly.checked = !!state.filters.connectedOnly;
|
||||||
|
|
||||||
|
mapConnectedOnly.addEventListener('change', () => {
|
||||||
|
state.filters.connectedOnly = !!mapConnectedOnly.checked;
|
||||||
|
|
||||||
|
// sync ook de tabel checkbox (zodat alles hetzelfde blijft)
|
||||||
|
const tableChk = document.getElementById('networksFilterConnected');
|
||||||
|
if (tableChk) tableChk.checked = !!state.filters.connectedOnly;
|
||||||
|
|
||||||
|
rerender();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapShowModes && !mapShowModes.dataset.bound) {
|
||||||
|
mapShowModes.dataset.bound = '1';
|
||||||
|
mapShowModes.checked = state.filters.showModes !== false;
|
||||||
|
mapShowModes.addEventListener('change', () => {
|
||||||
|
state.filters.showModes = !!mapShowModes.checked;
|
||||||
|
rerender();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
renderNetworksSummary();
|
renderNetworksSummary();
|
||||||
setNetworksView(state.view);
|
setNetworksView(state.view);
|
||||||
|
// sync kaart-checkbox bij init
|
||||||
|
if (mapConnectedOnly) {
|
||||||
|
mapConnectedOnly.checked = !!state.filters.connectedOnly;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose minimal API
|
// Expose minimal API
|
||||||
@@ -1066,7 +1132,7 @@
|
|||||||
console.table(model.nodes.slice(0, 12));
|
console.table(model.nodes.slice(0, 12));
|
||||||
console.table(model.links.slice(0, 12));
|
console.table(model.links.slice(0, 12));
|
||||||
return model;
|
return model;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bind when script loads (DOM is already mostly there because script is at end of body)
|
// Bind when script loads (DOM is already mostly there because script is at end of body)
|
||||||
|
|||||||
@@ -162,14 +162,19 @@
|
|||||||
<button class="btn small" type="button" id="networksMapResetBtn">Reset view</button>
|
<button class="btn small" type="button" id="networksMapResetBtn">Reset view</button>
|
||||||
<button class="btn small" type="button" id="networksMapLayoutBtn">Auto-layout</button>
|
<button class="btn small" type="button" id="networksMapLayoutBtn">Auto-layout</button>
|
||||||
<button class="btn small ghost" type="button" id="networksMapLegendBtn">Legenda</button>
|
<button class="btn small ghost" type="button" id="networksMapLegendBtn">Legenda</button>
|
||||||
<span class="muted" id="networksMapStatus" style="margin-left:auto;">Kaartweergave (placeholder)</span>
|
<label class="chk" style="display:flex; align-items:center; gap:8px; margin-left:10px;">
|
||||||
|
<input type="checkbox" id="networksMapShowModes" checked>
|
||||||
|
<span class="muted">Toon modes</span>
|
||||||
|
</label>
|
||||||
|
<label class="chk" style="display:flex; align-items:center; gap:8px; margin-left:10px;">
|
||||||
|
<input type="checkbox" id="networksMapConnectedOnly">
|
||||||
|
<span class="muted">Alleen verbonden</span>
|
||||||
|
</label>
|
||||||
|
<span class="muted" id="networksMapStatus" style="margin-left:auto;">Kaartweergave (placeholder)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="networksMapHost" class="mapHost">
|
<div id="networksMapHost" class="mapHost">
|
||||||
<div class="muted" style="padding:12px;">
|
|
||||||
Kaartweergave is actief. (STAP 3A-1: alleen layout/controls. D3 rendering komt in 3C.)
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="networksDetailPanel" class="mapDetail" style="display:none;">
|
<div id="networksDetailPanel" class="mapDetail" style="display:none;">
|
||||||
<div class="mapDetailHeader">
|
<div class="mapDetailHeader">
|
||||||
|
|||||||
Reference in New Issue
Block a user