mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 05:30:46 +08:00
Fix topology truncate false positives and reduce graph/discover load.
Batch ManagedNE lookups, correct project-neighbors truncation/frozen signals, page discover polls, and delete both nodes and edges when multi-selected. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
03ef9eb39a
commit
2aafb72676
7 changed files with 186 additions and 25 deletions
|
|
@ -1413,9 +1413,13 @@ const en = {
|
|||
edgeDeleted: "Deleted {{count}} link(s)",
|
||||
discoverCancelled: "Discovery cancelled",
|
||||
truncatedMembership: "Membership cap reached; some neighbors were not placed. Use classify/slices or a new map.",
|
||||
truncatedFrozen: "This map membership is frozen; neighbors cannot be projected. Unfreeze in classify/slices or create a new map.",
|
||||
truncatedNodes: "Too many view nodes; display truncated. Narrow membership or create another map.",
|
||||
truncatedEdges: "Too many edges; display truncated. Filter status or create another map.",
|
||||
truncatedGeneric: "Graph data truncated. Narrow scope or create another map.",
|
||||
deleteSelectionConfirm:
|
||||
"Remove {{nodes}} NE(s) from this map and delete {{edges}} Fabric link(s)?",
|
||||
selectionDeleted: "Removed {{nodes}} NE(s) and deleted {{edges}} link(s)",
|
||||
newMap: "New",
|
||||
newMapName: "New topology",
|
||||
rename: "Rename",
|
||||
|
|
|
|||
|
|
@ -1404,9 +1404,12 @@ const zh = {
|
|||
edgeDeleted: "已删除 {{count}} 条链路",
|
||||
discoverCancelled: "发现已取消",
|
||||
truncatedMembership: "视图已达成员上限,部分邻居未上图。可去分类/切片或新建视图。",
|
||||
truncatedFrozen: "当前视图已冻结成员,无法投影邻居。请在分类/切片中解冻或新建视图。",
|
||||
truncatedNodes: "画布节点过多已截断显示。建议新建视图或收紧成员范围。",
|
||||
truncatedEdges: "画布链路过多已截断显示。建议筛选状态或新建视图。",
|
||||
truncatedGeneric: "图数据已截断显示。建议收紧范围或新建视图。",
|
||||
deleteSelectionConfirm: "删除选中的 {{nodes}} 个网元(移出画布)和 {{edges}} 条链路(从 Fabric 删除)?",
|
||||
selectionDeleted: "已移除 {{nodes}} 个网元、删除 {{edges}} 条链路",
|
||||
newMap: "新建",
|
||||
newMapName: "新拓扑图",
|
||||
rename: "重命名",
|
||||
|
|
|
|||
|
|
@ -1580,7 +1580,7 @@ export function TopologyPage() {
|
|||
cancelled = true;
|
||||
break;
|
||||
}
|
||||
job = await fetchLldpDiscoverJob(jobStart.id);
|
||||
job = await fetchLldpDiscoverJob(jobStart.id, { page: 1, pageSize: 5 });
|
||||
setDiscoverProgress((p) => ({
|
||||
...p,
|
||||
index: job.done,
|
||||
|
|
@ -1619,12 +1619,21 @@ export function TopologyPage() {
|
|||
if (job.status === "failed") {
|
||||
throw new Error(job.error || "discover_failed");
|
||||
}
|
||||
// Final page: pull a fuller item slice for the summary panel.
|
||||
try {
|
||||
job = await fetchLldpDiscoverJob(jobStart.id, { page: 1, pageSize: 100 });
|
||||
} catch {
|
||||
/* keep last polled job */
|
||||
}
|
||||
const projected = discoverProjectNeighbors
|
||||
? await projectTopologyNeighbors(mapId, {
|
||||
seed_fabric_node_ids: scoped.map((n) => n.id),
|
||||
})
|
||||
: await fetchTopologyGraph(mapId);
|
||||
queryClient.setQueryData(queryKeys.topologyGraph(mapId), projected);
|
||||
if (discoverProjectNeighbors && projected.truncate_reason === "membership_frozen") {
|
||||
showError(t("topology.truncatedFrozen"));
|
||||
}
|
||||
appliedMapIdRef.current = mapId;
|
||||
let { rfNodes, rfEdges } = graphToFlow(projected.nodes, projected.edges, edgeDefaults);
|
||||
// Keep existing node positions when we did not auto-layout.
|
||||
|
|
@ -2021,10 +2030,15 @@ export function TopologyPage() {
|
|||
}, [setNodes, setEdges]);
|
||||
|
||||
const persistDeleteEdges = useCallback(
|
||||
async (edgeIds: string[], opts?: { confirmKey?: string; okKey?: string }) => {
|
||||
async (
|
||||
edgeIds: string[],
|
||||
opts?: { confirmKey?: string; okKey?: string; skipConfirm?: boolean },
|
||||
) => {
|
||||
if (!mapId || !edgeIds.length) return false;
|
||||
const confirmMsg = t(opts?.confirmKey || "topology.deleteEdgeConfirm");
|
||||
if (!window.confirm(confirmMsg)) return false;
|
||||
if (!opts?.skipConfirm) {
|
||||
const confirmMsg = t(opts?.confirmKey || "topology.deleteEdgeConfirm");
|
||||
if (!window.confirm(confirmMsg)) return false;
|
||||
}
|
||||
pushHistory();
|
||||
try {
|
||||
await deleteFabricEdges(edgeIds);
|
||||
|
|
@ -2061,10 +2075,44 @@ export function TopologyPage() {
|
|||
if (e.selected) edgeIds.add(e.id);
|
||||
}
|
||||
if (!nodeIds.length && !edgeIds.size) return;
|
||||
|
||||
if (nodeIds.length && edgeIds.size) {
|
||||
const msg = t("topology.deleteSelectionConfirm")
|
||||
.replace("{{nodes}}", String(nodeIds.length))
|
||||
.replace("{{edges}}", String(edgeIds.size));
|
||||
if (!window.confirm(msg)) return;
|
||||
pushHistory();
|
||||
try {
|
||||
if (dirtyRef.current) {
|
||||
await patchTopologyPositions(mapId, flowToPositions(nodes));
|
||||
clearDirty();
|
||||
}
|
||||
await deleteFabricEdges([...edgeIds]);
|
||||
const localPos = new Map(nodes.map((n) => [n.id, n.position]));
|
||||
const graph = await removeTopologyViewNodes(mapId, nodeIds);
|
||||
queryClient.setQueryData(queryKeys.topologyGraph(mapId), graph);
|
||||
appliedMapIdRef.current = mapId;
|
||||
historyLockRef.current = true;
|
||||
applyViewGraph(graph, edgeDefaults, setNodes, setEdges, localPos);
|
||||
historyLockRef.current = false;
|
||||
clearDirty();
|
||||
setSelectedEdgeId(null);
|
||||
showOk(
|
||||
t("topology.selectionDeleted")
|
||||
.replace("{{nodes}}", String(nodeIds.length))
|
||||
.replace("{{edges}}", String(edgeIds.size)),
|
||||
);
|
||||
} catch (err) {
|
||||
showError(String(err));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nodeIds.length && edgeIds.size) {
|
||||
await persistDeleteEdges([...edgeIds]);
|
||||
return;
|
||||
}
|
||||
|
||||
pushHistory();
|
||||
try {
|
||||
if (dirtyRef.current) {
|
||||
|
|
@ -2095,7 +2143,9 @@ export function TopologyPage() {
|
|||
edgeDefaults,
|
||||
clearDirty,
|
||||
showError,
|
||||
showOk,
|
||||
persistDeleteEdges,
|
||||
t,
|
||||
]);
|
||||
|
||||
const removeEdgeById = (edgeId: string) => {
|
||||
|
|
@ -2135,6 +2185,10 @@ export function TopologyPage() {
|
|||
applyViewGraph(projected, edgeDefaults, setNodes, setEdges, localPos);
|
||||
historyLockRef.current = false;
|
||||
clearDirty();
|
||||
if (projected.truncate_reason === "membership_frozen") {
|
||||
showError(t("topology.truncatedFrozen"));
|
||||
return;
|
||||
}
|
||||
const added = Math.max(0, projected.nodes.length - before);
|
||||
showOk(t("topology.projectedNeighbors").replace("{{count}}", String(added)));
|
||||
if (added > 0) {
|
||||
|
|
@ -2668,6 +2722,7 @@ export function TopologyPage() {
|
|||
const truncateBannerText = useMemo(() => {
|
||||
if (!graphTruncated) return "";
|
||||
if (truncateReason === "membership_cap") return t("topology.truncatedMembership");
|
||||
if (truncateReason === "membership_frozen") return t("topology.truncatedFrozen");
|
||||
if (truncateReason === "too_many_view_nodes") return t("topology.truncatedNodes");
|
||||
if (truncateReason === "too_many_edges") return t("topology.truncatedEdges");
|
||||
return t("topology.truncatedGeneric");
|
||||
|
|
|
|||
|
|
@ -1225,8 +1225,17 @@ export const startLldpDiscover = (body?: {
|
|||
trigger_mode?: "manual" | "schedule" | "topology";
|
||||
}) => apiPost<TopologyDiscoverJob>("/v1/topology/fabric/discover", body || {});
|
||||
|
||||
export const fetchLldpDiscoverJob = (jobId: string) =>
|
||||
apiGet<TopologyDiscoverJob>(`/v1/topology/fabric/discover/${encodeURIComponent(jobId)}`);
|
||||
export const fetchLldpDiscoverJob = (
|
||||
jobId: string,
|
||||
params?: { page?: number; pageSize?: number },
|
||||
) => {
|
||||
const p = new URLSearchParams();
|
||||
p.set("page", String(Math.max(1, Number(params?.page || 1))));
|
||||
p.set("page_size", String(Math.max(1, Math.min(100, Number(params?.pageSize || 20)))));
|
||||
return apiGet<TopologyDiscoverJob>(
|
||||
`/v1/topology/fabric/discover/${encodeURIComponent(jobId)}?${p.toString()}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchLldpCollectDashboard = () =>
|
||||
apiGet<LldpCollectDashboard>("/v1/topology/lldp-collect/dashboard");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue