diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index ef2265a..923a146 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1566,7 +1566,7 @@ const en = { }, hideIp: "Hide IP", hideVendor: "Hide vendor", - showPlaceholders: "Show placeholders (TOPO/LLDP)", + showPlaceholderBadge: "Show placeholder badge (TOPO/LLDP)", hidePorts: "Hide ports", edgeFlow: "Edge flow", collapseSidebar: "Collapse sidebar", diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index de23851..81ee9c9 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1555,7 +1555,7 @@ const zh = { }, hideIp: "隐藏 IP", hideVendor: "隐藏厂商", - showPlaceholders: "显示占位网元(TOPO/LLDP)", + showPlaceholderBadge: "显示占位角标(TOPO/LLDP)", hidePorts: "隐藏端口", edgeFlow: "链路流动", collapseSidebar: "折叠侧栏", diff --git a/web/src/pages/TopologyPage.tsx b/web/src/pages/TopologyPage.tsx index ab718d6..8a3c91a 100644 --- a/web/src/pages/TopologyPage.tsx +++ b/web/src/pages/TopologyPage.tsx @@ -304,6 +304,8 @@ type TopoDisplayOpts = { hideVendor: boolean; hidePorts: boolean; connectMode: boolean; + /** Show TOPO/LLDP placeholder corner badge on nodes. */ + showPlaceholderBadge: boolean; }; type CtxMenu = @@ -317,6 +319,7 @@ const TopoDisplayContext = createContext({ hideVendor: true, hidePorts: true, connectMode: false, + showPlaceholderBadge: false, }); function newId(): string { @@ -418,9 +421,10 @@ function isPlaceholderSource(source: string | undefined, neIp: string): boolean } const NeNode = memo(function NeNode({ data, selected }: NodeProps>) { - const { hideIp, hideVendor, connectMode } = useContext(TopoDisplayContext); + const { hideIp, hideVendor, connectMode, showPlaceholderBadge } = useContext(TopoDisplayContext); const tone = nodeIconTone(data.vendor, data.managed_ne_id, data.ume_ne_id); const placeholder = isPlaceholderSource(data.managed_source, data.ne_ip); + const showBadge = placeholder && showPlaceholderBadge; const name = data.label || (!hideIp ? data.ne_ip : "") || "NE"; const secondary = [ hideIp || !data.ne_ip || data.ne_ip === name ? "" : data.ne_ip, @@ -430,8 +434,8 @@ const NeNode = memo(function NeNode({ data, selected }: NodeProps
- {placeholder ? ( + {showBadge ? ( {String(data.managed_source || "ph").slice(0, 4)} @@ -484,7 +488,7 @@ const AUTO_LAYOUT_DISCOVER_KEY = "netx.topology.autoLayoutAfterDiscover"; const DISCOVER_AUTO_ADD_KEY = "netx.topology.discoverAutoAddUnmatched.v2"; const DISCOVER_PROJECT_NEIGHBORS_KEY = "netx.topology.discoverProjectNeighbors.v2"; const SCALE_BUNDLE_WIDTH_KEY = "netx.topology.scaleBundleWidth"; -const SHOW_PLACEHOLDERS_KEY = "netx.topology.showPlaceholders"; +const SHOW_PLACEHOLDER_BADGE_KEY = "netx.topology.showPlaceholderBadge"; const CANVAS_BG_KEY = "netx.topology.canvasBg"; const DEFAULT_CANVAS_BG = "#0f172a"; /** Previous light default — migrate so existing sessions pick up dark canvas. */ @@ -861,9 +865,9 @@ export function TopologyPage() { const [scaleBundleWidth, setScaleBundleWidth] = useState(() => 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), + /** TOPO/LLDP corner badge hidden by default; toggle in Display menu. */ + const [showPlaceholderBadge, setShowPlaceholderBadge] = useState(() => + loadBoolFlag(SHOW_PLACEHOLDER_BADGE_KEY, false), ); const [edgeFlow, setEdgeFlow] = useState(false); const [edgeDefaults, setEdgeDefaults] = useState(() => loadEdgeDefaults()); @@ -919,21 +923,6 @@ export function TopologyPage() { const findJustLocatedRef = useRef(false); const [nodes, setNodes, onNodesChange] = useNodesState>([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); - - 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, Edge> | null>(null); const canvasRef = useRef(null); const dirtyRef = useRef(false); @@ -1032,8 +1021,14 @@ export function TopologyPage() { const toolBehavior = useMemo(() => behaviorForMode(toolMode), [toolMode]); const displayOpts = useMemo( - () => ({ hideIp, hideVendor, hidePorts, connectMode: toolMode === "connect" }), - [hideIp, hideVendor, hidePorts, toolMode], + () => ({ + hideIp, + hideVendor, + hidePorts, + connectMode: toolMode === "connect", + showPlaceholderBadge, + }), + [hideIp, hideVendor, hidePorts, toolMode, showPlaceholderBadge], ); const displayEdges = useMemo(() => { const built = buildLinkDisplayEdges(edges, expandPhysicalLinks, hidePorts, scaleBundleWidth).map((e) => @@ -1054,33 +1049,6 @@ export function TopologyPage() { }); }, [edges, expandPhysicalLinks, hidePorts, scaleBundleWidth, edgeFlow, edgeDefaults, selectedEdgeId]); - const hiddenPlaceholderIds = useMemo(() => { - if (showPlaceholders) return new Set(); - 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({ queryKey: queryKeys.topologyTree, queryFn: fetchTopologyTree, @@ -2717,12 +2685,8 @@ export function TopologyPage() { const canvasHits = useMemo(() => { const q = canvasQuery.trim(); if (!q) return []; - return nodes.filter( - (n) => - nodeMatchesQuery(n, q) && - (showPlaceholders || !isPlaceholderSource(n.data.managed_source, n.data.ne_ip)), - ); - }, [canvasQuery, nodes, showPlaceholders]); + return nodes.filter((n) => nodeMatchesQuery(n, q)); + }, [canvasQuery, nodes]); useEffect(() => { const q = canvasQuery.trim(); @@ -3416,14 +3380,14 @@ export function TopologyPage() {