mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 16:43:18 +08:00
Fix fabric path search, read scope, orbit total ranking, and edge menu clamp.
Prefer BFS shortest paths, allow ne:read on path find, honor objective=total clearance trades, and keep context menus on-screen without a11y clock spam. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
298859e608
commit
e4a135ec16
13 changed files with 163 additions and 30 deletions
|
|
@ -141,6 +141,9 @@ def required_scope_for_request(method: str, path: str) -> str | None:
|
|||
return SCOPE_NE_READ
|
||||
|
||||
if p.startswith("/v1/topology"):
|
||||
# Read-only path search uses POST for a structured body.
|
||||
if m == "POST" and p.rstrip("/").endswith("/fabric/paths"):
|
||||
return SCOPE_NE_READ
|
||||
if m in ("POST", "PUT", "PATCH", "DELETE"):
|
||||
return SCOPE_NE_WRITE
|
||||
return SCOPE_NE_READ
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ def validate_select_sql(sql: str, *, allowed_tables: set[str] | None = None) ->
|
|||
low = cleaned.lower().lstrip()
|
||||
if not low.startswith(("select", "with")):
|
||||
raise HTTPException(status_code=400, detail="select_only")
|
||||
# Plain WITH CTE is allowed; RECURSIVE can explode into DoS.
|
||||
if re.search(r"\bwith\s+recursive\b", cleaned, flags=re.IGNORECASE):
|
||||
raise HTTPException(status_code=400, detail="with_recursive_not_allowed")
|
||||
if _SQL_FORBIDDEN_RE.search(cleaned):
|
||||
raise HTTPException(status_code=400, detail="forbidden_keyword")
|
||||
# Block obvious catalog / other-schema probes in the text.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections import deque
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
|
@ -509,25 +510,26 @@ def find_fabric_paths(
|
|||
adj.setdefault(e.a_node_id, []).append((e.b_node_id, e.id))
|
||||
adj.setdefault(e.b_node_id, []).append((e.a_node_id, e.id))
|
||||
|
||||
# DFS for simple paths (no repeated nodes), capped to avoid blow-up on large graphs.
|
||||
_EXPLORE_CAP = 100
|
||||
all_paths: list[list[str]] = []
|
||||
stack: list[tuple[str, list[str], set[str]]] = [(from_id, [], {from_id})]
|
||||
while stack and len(all_paths) < _EXPLORE_CAP:
|
||||
node, edge_path, visited = stack.pop()
|
||||
# BFS for simple paths so shorter hops are found first; cap expansions on dense graphs.
|
||||
_EXPLORE_CAP = 5000
|
||||
found: list[list[str]] = []
|
||||
queue: deque[tuple[str, list[str], set[str]]] = deque([(from_id, [], {from_id})])
|
||||
explored = 0
|
||||
while queue and len(found) < max_paths and explored < _EXPLORE_CAP:
|
||||
node, edge_path, visited = queue.popleft()
|
||||
if len(edge_path) >= max_hops:
|
||||
continue
|
||||
for nbr, eid in adj.get(node, []):
|
||||
if nbr in visited:
|
||||
continue
|
||||
explored += 1
|
||||
new_path = edge_path + [eid]
|
||||
if nbr == to_id:
|
||||
all_paths.append(new_path)
|
||||
found.append(new_path)
|
||||
if len(found) >= max_paths:
|
||||
break
|
||||
continue
|
||||
stack.append((nbr, new_path, visited | {nbr}))
|
||||
|
||||
all_paths.sort(key=len)
|
||||
found = all_paths[:max_paths]
|
||||
queue.append((nbr, new_path, visited | {nbr}))
|
||||
|
||||
node_ids = {from_id, to_id}
|
||||
for p in found:
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from .topology_fabric import (
|
|||
ensure_fabric_node_for_managed,
|
||||
ensure_fabric_node_for_ume,
|
||||
ensure_lldp_discovered_managed_ne,
|
||||
find_fabric_paths,
|
||||
get_fabric_neighborhood,
|
||||
get_fabric_summary,
|
||||
list_fabric_edges,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue