mirror of
https://github.com/hansjone/netx.git
synced 2026-10-10 16:50:47 +08:00
Hide TOPO/LLDP placeholder NEs on the canvas by default.
Add a Display toggle to show placeholders; hide their incident edges and skip them in canvas find while hidden. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
6e2c8517cf
commit
73cd2704eb
3 changed files with 69 additions and 8 deletions
|
|
@ -1566,6 +1566,7 @@ const en = {
|
||||||
},
|
},
|
||||||
hideIp: "Hide IP",
|
hideIp: "Hide IP",
|
||||||
hideVendor: "Hide vendor",
|
hideVendor: "Hide vendor",
|
||||||
|
showPlaceholders: "Show placeholders (TOPO/LLDP)",
|
||||||
hidePorts: "Hide ports",
|
hidePorts: "Hide ports",
|
||||||
edgeFlow: "Edge flow",
|
edgeFlow: "Edge flow",
|
||||||
collapseSidebar: "Collapse sidebar",
|
collapseSidebar: "Collapse sidebar",
|
||||||
|
|
|
||||||
|
|
@ -1555,6 +1555,7 @@ const zh = {
|
||||||
},
|
},
|
||||||
hideIp: "隐藏 IP",
|
hideIp: "隐藏 IP",
|
||||||
hideVendor: "隐藏厂商",
|
hideVendor: "隐藏厂商",
|
||||||
|
showPlaceholders: "显示占位网元(TOPO/LLDP)",
|
||||||
hidePorts: "隐藏端口",
|
hidePorts: "隐藏端口",
|
||||||
edgeFlow: "链路流动",
|
edgeFlow: "链路流动",
|
||||||
collapseSidebar: "折叠侧栏",
|
collapseSidebar: "折叠侧栏",
|
||||||
|
|
|
||||||
|
|
@ -484,6 +484,7 @@ const AUTO_LAYOUT_DISCOVER_KEY = "netx.topology.autoLayoutAfterDiscover";
|
||||||
const DISCOVER_AUTO_ADD_KEY = "netx.topology.discoverAutoAddUnmatched.v2";
|
const DISCOVER_AUTO_ADD_KEY = "netx.topology.discoverAutoAddUnmatched.v2";
|
||||||
const DISCOVER_PROJECT_NEIGHBORS_KEY = "netx.topology.discoverProjectNeighbors.v2";
|
const DISCOVER_PROJECT_NEIGHBORS_KEY = "netx.topology.discoverProjectNeighbors.v2";
|
||||||
const SCALE_BUNDLE_WIDTH_KEY = "netx.topology.scaleBundleWidth";
|
const SCALE_BUNDLE_WIDTH_KEY = "netx.topology.scaleBundleWidth";
|
||||||
|
const SHOW_PLACEHOLDERS_KEY = "netx.topology.showPlaceholders";
|
||||||
const CANVAS_BG_KEY = "netx.topology.canvasBg";
|
const CANVAS_BG_KEY = "netx.topology.canvasBg";
|
||||||
const DEFAULT_CANVAS_BG = "#0f172a";
|
const DEFAULT_CANVAS_BG = "#0f172a";
|
||||||
/** Previous light default — migrate so existing sessions pick up dark canvas. */
|
/** Previous light default — migrate so existing sessions pick up dark canvas. */
|
||||||
|
|
@ -860,6 +861,10 @@ export function TopologyPage() {
|
||||||
const [scaleBundleWidth, setScaleBundleWidth] = useState(() =>
|
const [scaleBundleWidth, setScaleBundleWidth] = useState(() =>
|
||||||
loadBoolFlag(SCALE_BUNDLE_WIDTH_KEY, false),
|
loadBoolFlag(SCALE_BUNDLE_WIDTH_KEY, false),
|
||||||
);
|
);
|
||||||
|
/** Topology/LLDP placeholders hidden by default; toggle in Display menu. */
|
||||||
|
const [showPlaceholders, setShowPlaceholders] = useState(() =>
|
||||||
|
loadBoolFlag(SHOW_PLACEHOLDERS_KEY, false),
|
||||||
|
);
|
||||||
const [edgeFlow, setEdgeFlow] = useState(false);
|
const [edgeFlow, setEdgeFlow] = useState(false);
|
||||||
const [edgeDefaults, setEdgeDefaults] = useState<EdgeDefaults>(() => loadEdgeDefaults());
|
const [edgeDefaults, setEdgeDefaults] = useState<EdgeDefaults>(() => loadEdgeDefaults());
|
||||||
const [canvasBg, setCanvasBg] = useState(loadCanvasBg);
|
const [canvasBg, setCanvasBg] = useState(loadCanvasBg);
|
||||||
|
|
@ -914,6 +919,21 @@ export function TopologyPage() {
|
||||||
const findJustLocatedRef = useRef(false);
|
const findJustLocatedRef = useRef(false);
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState<Node<NeNodeData>>([]);
|
const [nodes, setNodes, onNodesChange] = useNodesState<Node<NeNodeData>>([]);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showPlaceholders) return;
|
||||||
|
setNodes((ns) => {
|
||||||
|
let changed = false;
|
||||||
|
const next = ns.map((n) => {
|
||||||
|
if (!n.selected) return n;
|
||||||
|
if (!isPlaceholderSource(n.data.managed_source, n.data.ne_ip)) return n;
|
||||||
|
changed = true;
|
||||||
|
return { ...n, selected: false };
|
||||||
|
});
|
||||||
|
return changed ? next : ns;
|
||||||
|
});
|
||||||
|
}, [showPlaceholders, setNodes]);
|
||||||
|
|
||||||
const rfRef = useRef<ReactFlowInstance<Node<NeNodeData>, Edge> | null>(null);
|
const rfRef = useRef<ReactFlowInstance<Node<NeNodeData>, Edge> | null>(null);
|
||||||
const canvasRef = useRef<HTMLDivElement | null>(null);
|
const canvasRef = useRef<HTMLDivElement | null>(null);
|
||||||
const dirtyRef = useRef(false);
|
const dirtyRef = useRef(false);
|
||||||
|
|
@ -1034,6 +1054,33 @@ export function TopologyPage() {
|
||||||
});
|
});
|
||||||
}, [edges, expandPhysicalLinks, hidePorts, scaleBundleWidth, edgeFlow, edgeDefaults, selectedEdgeId]);
|
}, [edges, expandPhysicalLinks, hidePorts, scaleBundleWidth, edgeFlow, edgeDefaults, selectedEdgeId]);
|
||||||
|
|
||||||
|
const hiddenPlaceholderIds = useMemo(() => {
|
||||||
|
if (showPlaceholders) return new Set<string>();
|
||||||
|
return new Set(
|
||||||
|
nodes
|
||||||
|
.filter((n) => isPlaceholderSource(n.data.managed_source, n.data.ne_ip))
|
||||||
|
.map((n) => n.id),
|
||||||
|
);
|
||||||
|
}, [nodes, showPlaceholders]);
|
||||||
|
|
||||||
|
const flowNodes = useMemo(() => {
|
||||||
|
const list = hiddenPlaceholderIds.size
|
||||||
|
? nodes.filter((n) => !hiddenPlaceholderIds.has(n.id))
|
||||||
|
: nodes;
|
||||||
|
return list.map((n) =>
|
||||||
|
searchHitIds.includes(n.id)
|
||||||
|
? { ...n, className: "is-search-hit" }
|
||||||
|
: { ...n, className: undefined },
|
||||||
|
);
|
||||||
|
}, [nodes, hiddenPlaceholderIds, searchHitIds]);
|
||||||
|
|
||||||
|
const flowEdges = useMemo(() => {
|
||||||
|
if (!hiddenPlaceholderIds.size) return displayEdges;
|
||||||
|
return displayEdges.filter(
|
||||||
|
(e) => !hiddenPlaceholderIds.has(e.source) && !hiddenPlaceholderIds.has(e.target),
|
||||||
|
);
|
||||||
|
}, [displayEdges, hiddenPlaceholderIds]);
|
||||||
|
|
||||||
const treeQuery = useQuery({
|
const treeQuery = useQuery({
|
||||||
queryKey: queryKeys.topologyTree,
|
queryKey: queryKeys.topologyTree,
|
||||||
queryFn: fetchTopologyTree,
|
queryFn: fetchTopologyTree,
|
||||||
|
|
@ -2670,8 +2717,12 @@ export function TopologyPage() {
|
||||||
const canvasHits = useMemo(() => {
|
const canvasHits = useMemo(() => {
|
||||||
const q = canvasQuery.trim();
|
const q = canvasQuery.trim();
|
||||||
if (!q) return [];
|
if (!q) return [];
|
||||||
return nodes.filter((n) => nodeMatchesQuery(n, q));
|
return nodes.filter(
|
||||||
}, [canvasQuery, nodes]);
|
(n) =>
|
||||||
|
nodeMatchesQuery(n, q) &&
|
||||||
|
(showPlaceholders || !isPlaceholderSource(n.data.managed_source, n.data.ne_ip)),
|
||||||
|
);
|
||||||
|
}, [canvasQuery, nodes, showPlaceholders]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const q = canvasQuery.trim();
|
const q = canvasQuery.trim();
|
||||||
|
|
@ -3362,6 +3413,18 @@ export function TopologyPage() {
|
||||||
/>
|
/>
|
||||||
{t("topology.hideVendor")}
|
{t("topology.hideVendor")}
|
||||||
</label>
|
</label>
|
||||||
|
<label className="topo-display-toggles__item">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={showPlaceholders}
|
||||||
|
onChange={(e) => {
|
||||||
|
const next = e.target.checked;
|
||||||
|
setShowPlaceholders(next);
|
||||||
|
persistBoolFlag(SHOW_PLACEHOLDERS_KEY, next);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{t("topology.showPlaceholders")}
|
||||||
|
</label>
|
||||||
<label className="topo-display-toggles__item">
|
<label className="topo-display-toggles__item">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
@ -4094,12 +4157,8 @@ export function TopologyPage() {
|
||||||
{treeRoot ? (
|
{treeRoot ? (
|
||||||
<TopoDisplayContext.Provider value={displayOpts}>
|
<TopoDisplayContext.Provider value={displayOpts}>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes.map((n) =>
|
nodes={flowNodes}
|
||||||
searchHitIds.includes(n.id)
|
edges={flowEdges}
|
||||||
? { ...n, className: "is-search-hit" }
|
|
||||||
: { ...n, className: undefined },
|
|
||||||
)}
|
|
||||||
edges={displayEdges}
|
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
edgeTypes={edgeTypes}
|
edgeTypes={edgeTypes}
|
||||||
onlyRenderVisibleElements
|
onlyRenderVisibleElements
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue