Files
resolutionflow/backend/app/schemas/network_diagram.py
chihlasm c0d8163e98 fix: network diagram team_id guard + multi-style edge routing
Backend:
- Guard create_diagram with 422 if current_user.team_id is None (prevents
  NOT NULL constraint crash for accounts not yet assigned to a team)
- Add routing field to DiagramEdge schema (straight/curved/step)

Frontend:
- ConnectionEdge now supports straight (default), curved (bezier), and
  step (smooth-step) routing per-edge via routing field in edge data
- PropertiesPanel Connection section gets a Line Style toggle:
  Straight | Curved | Step buttons, active state highlights in accent
- handleEdgeUpdate and serializeEdges now propagate the routing field
- DiagramEdge type gets optional routing field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 00:05:12 +00:00

136 lines
3.5 KiB
Python

"""Pydantic schemas for network diagrams."""
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel, Field
class Position(BaseModel):
x: float
y: float
class DeviceProperties(BaseModel):
hostname: str | None = None
ip: str | None = None
subnet: str | None = None
vendor: str | None = None
model: str | None = None
role: str | None = None
vlan: str | None = None
notes: str | None = None
status: str = Field(default="unknown", pattern=r"^(unknown|online|offline|degraded)$")
class DiagramNode(BaseModel):
id: str
type: str
label: str
position: Position
properties: DeviceProperties = Field(default_factory=DeviceProperties)
class DiagramEdge(BaseModel):
id: str
source: str
target: str
label: str | None = None
connectionType: str = "ethernet"
speed: str | None = None
notes: str | None = None
routing: str | None = None
class NetworkDiagramCreate(BaseModel):
name: str = Field(min_length=1, max_length=255)
client_name: str | None = None
asset_name: str | None = None
description: str | None = None
nodes: list[DiagramNode] = Field(default_factory=list)
edges: list[DiagramEdge] = Field(default_factory=list)
class NetworkDiagramUpdate(BaseModel):
name: str | None = Field(default=None, min_length=1, max_length=255)
client_name: str | None = None
asset_name: str | None = None
description: str | None = None
nodes: list[DiagramNode] | None = None
edges: list[DiagramEdge] | None = None
class NetworkDiagramResponse(BaseModel):
id: UUID
team_id: UUID
name: str
client_name: str | None = None
asset_name: str | None = None
description: str | None = None
nodes: list[DiagramNode] = Field(default_factory=list)
edges: list[DiagramEdge] = Field(default_factory=list)
thumbnail_url: str | None = None
is_archived: bool = False
created_by: UUID | None = None
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
class NetworkDiagramListItem(BaseModel):
id: UUID
name: str
client_name: str | None = None
description: str | None = None
node_count: int = 0
created_by: UUID | None = None
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
class ExistingBounds(BaseModel):
minX: float
maxX: float
minY: float
maxY: float
class AIGenerateRequest(BaseModel):
description: str = Field(min_length=1, max_length=5000)
client_name: str | None = None
mode: str = Field(default="replace", pattern=r"^(replace|merge)$")
existingBounds: ExistingBounds | None = None
class AIGenerateResponse(BaseModel):
nodes: list[DiagramNode]
edges: list[DiagramEdge]
suggestedName: str | None = None
notes: str | None = None
class DiagramImportRequest(BaseModel):
schemaVersion: int = Field(ge=1, le=1)
name: str = Field(min_length=1, max_length=255)
client_name: str | None = None
description: str | None = None
nodes: list[DiagramNode] = Field(default_factory=list)
edges: list[DiagramEdge] = Field(default_factory=list)
class DiagramImportResponse(BaseModel):
diagram: NetworkDiagramResponse
warnings: list[str] = Field(default_factory=list)
class DiagramExportResponse(BaseModel):
schemaVersion: int = 1
name: str
client_name: str | None = None
description: str | None = None
nodes: list[DiagramNode]
edges: list[DiagramEdge]
exportedAt: str