mirror of
https://github.com/hansjone/netx.git
synced 2026-10-10 04:40:45 +08:00
Add fabric link browsing, conservative job retention, hour-based intervals, multi-worker start locking, and trim canvas discover UI in favor of the LLDP page. Co-authored-by: Cursor <cursoragent@cursor.com>
230 lines
6.2 KiB
Python
230 lines
6.2 KiB
Python
"""Pydantic schemas for fabric topology + views (final model)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fabric
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class FabricNodeOut(BaseModel):
|
|
id: str
|
|
managed_ne_id: str = ""
|
|
ume_ne_id: str = ""
|
|
name: str = ""
|
|
ip: str = ""
|
|
vendor: str = ""
|
|
device_type: str = ""
|
|
attrs: dict[str, Any] = Field(default_factory=dict)
|
|
last_seen_at: datetime | None = None
|
|
|
|
|
|
class FabricEdgeOut(BaseModel):
|
|
id: str
|
|
layer: str = "physical"
|
|
a_node_id: str
|
|
b_node_id: str
|
|
a_port: str = ""
|
|
b_port: str = ""
|
|
a_name: str = ""
|
|
b_name: str = ""
|
|
a_ip: str = ""
|
|
b_ip: str = ""
|
|
source: str = "lldp"
|
|
status: str = "active"
|
|
attrs: dict[str, Any] = Field(default_factory=dict)
|
|
discovered_at: datetime | None = None
|
|
last_seen_at: datetime | None = None
|
|
updated_at: datetime | None = None
|
|
|
|
|
|
class FabricSummaryOut(BaseModel):
|
|
node_count: int = 0
|
|
edge_count: int = 0
|
|
edge_active: int = 0
|
|
edge_stale: int = 0 # legacy alias of edge_missing
|
|
edge_missing: int = 0
|
|
last_discover_at: datetime | None = None
|
|
updated_at: datetime | None = None
|
|
|
|
|
|
class FabricNeighborhoodOut(BaseModel):
|
|
center_node_id: str
|
|
depth: int = 1
|
|
nodes: list[FabricNodeOut] = Field(default_factory=list)
|
|
edges: list[FabricEdgeOut] = Field(default_factory=list)
|
|
|
|
|
|
class FabricDiscoverRequest(BaseModel):
|
|
"""Start LLDP discovery into fabric (no CDP)."""
|
|
|
|
scope: str = Field(default="ne_ids", description="all_inventory | ne_ids")
|
|
ne_ids: list[str] = Field(
|
|
default_factory=list,
|
|
description="Legacy mixed ids (managed first, then ume). Prefer managed_ne_ids/ume_ne_ids.",
|
|
)
|
|
managed_ne_ids: list[str] = Field(default_factory=list)
|
|
ume_ne_ids: list[str] = Field(default_factory=list)
|
|
auto_add_unmatched: bool = Field(
|
|
default=True,
|
|
description="Create SSH placeholder ManagedNEs for LLDP neighbors not in inventory",
|
|
)
|
|
concurrency: int = Field(default=4, ge=1, le=32)
|
|
trigger_mode: str = Field(default="manual", description="manual | schedule | topology")
|
|
|
|
|
|
class FabricDiscoverUnmatched(BaseModel):
|
|
remote_name: str = ""
|
|
remote_ip: str = ""
|
|
local_port: str = ""
|
|
remote_port: str = ""
|
|
|
|
|
|
class FabricDiscoverJobItemOut(BaseModel):
|
|
id: str
|
|
job_id: str
|
|
ne_id: str = ""
|
|
ume_ne_id: str = ""
|
|
fabric_node_id: str = ""
|
|
ne_name: str = ""
|
|
ne_ip: str = ""
|
|
ok: bool = False
|
|
command: str = ""
|
|
neighbors: int = 0
|
|
edges_added: int = 0
|
|
edges_updated: int = 0
|
|
unmatched_count: int = 0
|
|
unmatched: list[FabricDiscoverUnmatched] = Field(default_factory=list)
|
|
parser_key: str = ""
|
|
parser_stub: bool = False
|
|
error: str = ""
|
|
raw_preview: str = ""
|
|
|
|
|
|
class FabricDiscoverJobOut(BaseModel):
|
|
id: str
|
|
scope: str
|
|
trigger_mode: str = "manual"
|
|
status: str
|
|
total: int = 0
|
|
done: int = 0
|
|
edges_added: int = 0
|
|
edges_updated: int = 0
|
|
edges_stale: int = 0 # legacy alias of edges_missing
|
|
edges_missing: int = 0
|
|
error: str = ""
|
|
started_at: datetime | None = None
|
|
ended_at: datetime | None = None
|
|
items: list[FabricDiscoverJobItemOut] = Field(default_factory=list)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Views
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TopologyViewCreate(BaseModel):
|
|
name: str = Field(min_length=1, max_length=256)
|
|
remark: str = Field(default="", max_length=1024)
|
|
filter: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class TopologyViewUpdate(BaseModel):
|
|
name: str | None = Field(default=None, min_length=1, max_length=256)
|
|
remark: str | None = Field(default=None, max_length=1024)
|
|
filter: dict[str, Any] | None = None
|
|
viewport: dict[str, Any] | None = None
|
|
|
|
|
|
class TopologyViewOut(BaseModel):
|
|
id: str
|
|
name: str
|
|
remark: str = ""
|
|
filter: dict[str, Any] = Field(default_factory=dict)
|
|
viewport: dict[str, Any] = Field(default_factory=dict)
|
|
node_count: int = 0
|
|
created_at: datetime | None = None
|
|
updated_at: datetime | None = None
|
|
|
|
|
|
class ViewNodeIn(BaseModel):
|
|
fabric_node_id: str = Field(min_length=1, max_length=64)
|
|
x: float = 0.0
|
|
y: float = 0.0
|
|
label: str = ""
|
|
locked: bool = False
|
|
|
|
|
|
class ViewNodeOut(BaseModel):
|
|
fabric_node_id: str
|
|
managed_ne_id: str = ""
|
|
ume_ne_id: str = ""
|
|
label: str = ""
|
|
x: float = 0.0
|
|
y: float = 0.0
|
|
locked: bool = False
|
|
name: str = ""
|
|
ip: str = ""
|
|
vendor: str = ""
|
|
device_type: str = ""
|
|
connect_status: str = ""
|
|
|
|
|
|
class ViewEdgeOut(BaseModel):
|
|
id: str
|
|
a_node_id: str
|
|
b_node_id: str
|
|
a_port: str = ""
|
|
b_port: str = ""
|
|
source: str = "lldp"
|
|
status: str = "active"
|
|
layer: str = "physical"
|
|
stroke_color: str = ""
|
|
stroke_width: int = 0
|
|
line_style: str = ""
|
|
discovered_at: datetime | None = None
|
|
|
|
|
|
class TopologyViewGraphOut(BaseModel):
|
|
view: TopologyViewOut
|
|
nodes: list[ViewNodeOut]
|
|
edges: list[ViewEdgeOut]
|
|
truncated: bool = False
|
|
truncate_reason: str = ""
|
|
|
|
|
|
class ViewPositionsPatch(BaseModel):
|
|
positions: list[ViewNodeIn] = Field(default_factory=list)
|
|
|
|
|
|
class ViewNodesAdd(BaseModel):
|
|
"""Add inventory NEs onto a view (creates fabric nodes as needed)."""
|
|
|
|
managed_ne_ids: list[str] = Field(default_factory=list)
|
|
ume_ne_ids: list[str] = Field(default_factory=list)
|
|
fabric_node_ids: list[str] = Field(
|
|
default_factory=list,
|
|
description="Place existing fabric nodes onto the view",
|
|
)
|
|
# Optional initial positions keyed by managed/ume id
|
|
layout: str = Field(default="grid", description="grid | keep")
|
|
|
|
|
|
class ViewEdgeStylePatch(BaseModel):
|
|
fabric_edge_id: str
|
|
stroke_color: str = ""
|
|
stroke_width: int = Field(default=0, ge=0, le=12)
|
|
line_style: str = Field(default="", max_length=16)
|
|
|
|
|
|
class FabricManualEdgeIn(BaseModel):
|
|
a_node_id: str = Field(min_length=1, max_length=64)
|
|
b_node_id: str = Field(min_length=1, max_length=64)
|
|
a_port: str = ""
|
|
b_port: str = ""
|