Add topology legend and node visibility filters.
Show color legend and allow filtering graph nodes by all/new/missing states after scan diff analysis. Made-with: Cursor
This commit is contained in:
+40
-4
@@ -31,6 +31,18 @@
|
||||
<button id="diffBtn">Сравнить</button>
|
||||
</div>
|
||||
<div id="status">Ожидание данных...</div>
|
||||
<div style="margin: 6px 0 10px 0; font-size: 13px;">
|
||||
Легенда:
|
||||
<span style="display:inline-block;margin-left:8px;color:#2563eb;">● синий - обычный узел</span>
|
||||
<span style="display:inline-block;margin-left:8px;color:#16a34a;">● зеленый - новый</span>
|
||||
<span style="display:inline-block;margin-left:8px;color:#dc2626;">● красный - пропавший</span>
|
||||
<span style="display:inline-block;margin-left:12px;">Фильтр:</span>
|
||||
<select id="nodeFilter">
|
||||
<option value="all">все</option>
|
||||
<option value="new">только новые</option>
|
||||
<option value="missing">только пропавшие</option>
|
||||
</select>
|
||||
</div>
|
||||
<pre id="diffBox" style="background:#f8fafc;border:1px solid #e5e7eb;border-radius:8px;padding:10px;white-space:pre-wrap;"></pre>
|
||||
<svg id="canvas" viewBox="0 0 1200 620"></svg>
|
||||
|
||||
@@ -45,10 +57,13 @@
|
||||
const latestBtn = document.getElementById("latestBtn");
|
||||
const diffBtn = document.getElementById("diffBtn");
|
||||
const fitBtn = document.getElementById("fitBtn");
|
||||
const nodeFilter = document.getElementById("nodeFilter");
|
||||
const statusEl = document.getElementById("status");
|
||||
const diffBox = document.getElementById("diffBox");
|
||||
const svg = document.getElementById("canvas");
|
||||
const NS = "http://www.w3.org/2000/svg";
|
||||
let lastTopology = null;
|
||||
let lastHighlight = null;
|
||||
|
||||
function setStatus(text) { statusEl.textContent = text; }
|
||||
function setDiff(text) { diffBox.textContent = text; }
|
||||
@@ -59,8 +74,17 @@
|
||||
|
||||
function drawTopology(data, highlight = null) {
|
||||
clearSVG();
|
||||
const nodes = data.nodes || [];
|
||||
let nodes = data.nodes || [];
|
||||
const edges = data.edges || [];
|
||||
const filter = nodeFilter.value || "all";
|
||||
if (highlight && (filter === "new" || filter === "missing")) {
|
||||
nodes = nodes.filter(n => {
|
||||
const isNew = highlight.newHosts && highlight.newHosts.has(n.ip);
|
||||
const isMissing = highlight.missingHosts && highlight.missingHosts.has(n.ip);
|
||||
return filter === "new" ? isNew : isMissing;
|
||||
});
|
||||
}
|
||||
const visibleIPs = new Set(nodes.map(n => n.ip));
|
||||
const byIP = new Map();
|
||||
nodes.forEach(n => byIP.set(n.ip, n));
|
||||
|
||||
@@ -79,6 +103,8 @@
|
||||
edges.forEach(e => {
|
||||
const s = pos.get(e.source_ip);
|
||||
if (!s) return;
|
||||
if (!visibleIPs.has(e.source_ip)) return;
|
||||
if (e.target_ip && !visibleIPs.has(e.target_ip)) return;
|
||||
const t = pos.get(e.target_ip) || { x: s.x + 30, y: s.y + 30 };
|
||||
|
||||
const line = document.createElementNS(NS, "line");
|
||||
@@ -165,7 +191,9 @@
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
drawTopology(data);
|
||||
lastTopology = data;
|
||||
lastHighlight = null;
|
||||
drawTopology(data, null);
|
||||
setStatus(`Готово. Узлов: ${data.nodes?.length || 0}, связей: ${data.edges?.length || 0}`);
|
||||
} catch (e) {
|
||||
setStatus(`Ошибка загрузки: ${e.message}`);
|
||||
@@ -216,10 +244,13 @@
|
||||
const topoRes = await fetch(`/api/scans/${encodeURIComponent(toId)}/topology?limit=5000`);
|
||||
if (topoRes.ok) {
|
||||
const topo = await topoRes.json();
|
||||
drawTopology(topo, {
|
||||
const highlight = {
|
||||
newHosts: new Set(d.new_hosts || []),
|
||||
missingHosts: new Set(d.missing_hosts || []),
|
||||
});
|
||||
};
|
||||
lastTopology = topo;
|
||||
lastHighlight = highlight;
|
||||
drawTopology(topo, highlight);
|
||||
}
|
||||
setStatus(
|
||||
`Diff готов: +hosts ${d.new_hosts?.length || 0}, -hosts ${d.missing_hosts?.length || 0}, +links ${d.new_links?.length || 0}, -links ${d.missing_links?.length || 0}`
|
||||
@@ -244,6 +275,11 @@
|
||||
fitBtn.addEventListener("click", () => {
|
||||
svg.setAttribute("viewBox", "0 0 1200 620");
|
||||
});
|
||||
nodeFilter.addEventListener("change", () => {
|
||||
if (lastTopology) {
|
||||
drawTopology(lastTopology, lastHighlight);
|
||||
}
|
||||
});
|
||||
setDiff("Diff будет показан здесь...");
|
||||
loadScanOptions();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user