From 3d86bc75948dfe69f87cc4ee94767eeb7391049f Mon Sep 17 00:00:00 2001 From: oliver Date: Fri, 7 Aug 2026 18:01:53 +0800 Subject: [PATCH] Count manual canvas NE badges from view membership, not only fabric region ownership. Co-authored-by: Cursor --- netx_api/topology_views_tree.py | 45 +++++++++++++++++++++++++-------- web/src/pages/TopologyPage.tsx | 5 ++-- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/netx_api/topology_views_tree.py b/netx_api/topology_views_tree.py index fa8b34a..e00ef8b 100644 --- a/netx_api/topology_views_tree.py +++ b/netx_api/topology_views_tree.py @@ -757,17 +757,45 @@ def get_topology_tree(db: Session) -> TopologyTreeOut: return is_ume_world_container(folder) or is_world_drill_folder(folder) + views_by_folder: dict[str, list[TopoView]] = {} + for v in views: + views_by_folder.setdefault(str(v.folder_id or ""), []).append(v) + + def _view_membership_ne(v: TopoView) -> int: + """Canvas members that are real NEs (exclude region:* directory icons).""" + raw = int(nc_map.get(v.id, 0) or 0) + return max(0, raw - int(region_icon_map.get(v.id, 0) or 0)) + + def _own_canvas_ne(folder: TopoFolder) -> int: + """Manual canvas: prefer view membership; also honor fabric region ownership.""" + mem = sum( + _view_membership_ne(v) for v in views_by_folder.get(str(folder.id), []) + ) + fab = int(ne_by_folder.get(str(folder.id), 0)) + return max(mem, fab) + def _subtree_inventory(folder_id: str) -> int: - """Directory semantics: NEs owned by this folder + all descendant folders.""" + """UME-synced directory: NEs owned by this folder + all descendant folders.""" total = int(ne_by_folder.get(folder_id, 0)) for child in by_parent.get(folder_id, []): total += _subtree_inventory(str(child.id)) return total + def _is_ume_synced_folder(folder: TopoFolder) -> bool: + return bool(str(getattr(folder, "external_ref", None) or "").strip()) + def _folder_ne_count(folder: TopoFolder) -> int: if _is_whole_network_folder(folder): return int(ume_ne_total or 0) - return _subtree_inventory(str(folder.id)) + # UME SBN / synced dirs: fabric ownership tree (not empty manual canvases). + if _is_ume_synced_folder(folder): + return _subtree_inventory(str(folder.id)) + # Manual 根/根图/子区域: count NEs placed on canvases (+ nested regions). + own = _own_canvas_ne(folder) + nested = sum( + _folder_ne_count(child) for child in by_parent.get(str(folder.id), []) + ) + return own + nested def _view_node_count(v: TopoView) -> int: folder = folder_by_id.get(str(v.folder_id or "")) @@ -784,15 +812,12 @@ def get_topology_tree(db: Session) -> TopologyTreeOut: return int(ume_ne_by_sbn.get(sid, 0)) if str(filt.get("parent") or "") == "md": return int(ume_ne_total or 0) + mem = _view_membership_ne(v) + if mem: + return mem if folder is not None: - return _folder_ne_count(folder) - # Fallback: membership minus region icons (never count region placeholders as NEs). - raw = int(nc_map.get(v.id, 0) or 0) - return max(0, raw - int(region_icon_map.get(v.id, 0) or 0)) - - views_by_folder: dict[str, list[TopoView]] = {} - for v in views: - views_by_folder.setdefault(str(v.folder_id or ""), []).append(v) + return int(ne_by_folder.get(str(folder.id), 0)) + return 0 def _flat_views(folder: TopoFolder, folder_views: list[TopoView]) -> list[TopologyTreeViewOut]: from .ume_topology_world import ( diff --git a/web/src/pages/TopologyPage.tsx b/web/src/pages/TopologyPage.tsx index 8cc009d..63eb629 100644 --- a/web/src/pages/TopologyPage.tsx +++ b/web/src/pages/TopologyPage.tsx @@ -314,9 +314,8 @@ function primaryTreeView( } /** - * Directory NE badge from API: fabric inventory under the folder. - * UME World / World / 世界地图 share the UME total; manual 根/根图 use their own - * subtree (empty → 0). Region folders themselves never count as NEs. + * Directory NE badge from API. + * Manual canvases count view membership (placed NEs); UME World surfaces use inventory. */ function folderNeCount(folder: TopologyTreeFolderItem | null | undefined): number { if (!folder) return 0;