Files
homelable/backend/app/schemas/canvas.py
T
Pouzor c95d104245 feat: selectable marker shapes per edge endpoint
Replace the on/off arrowhead toggle with a per-end shape picker. Each end
(start/end) independently selects: none, arrow, arrow-open, circle, diamond,
or square. Markers still recolor live from the resolved stroke color.

Frontend:
- MarkerShape type + edgeMarkers util (normalizeMarker, MARKER_GEOMETRY);
  legacy boolean coerces to 'arrow' on read.
- Per-shape <marker> inner geometry; symmetric shapes use fixed orient.
- MarkerShapePicker reused in EdgeModal and CustomStyleModal.
- Serializer normalizes to shape strings.

Backend:
- Edge marker columns Boolean -> String (default 'none'); TEXT migration.
- normalize_marker() + validators coerce legacy bool / unknown values.

ha-relevant: yes
2026-07-05 11:30:49 +02:00

85 lines
2.3 KiB
Python

from typing import Any
from pydantic import BaseModel, field_validator
from app.schemas.edges import EdgeResponse
from app.schemas.nodes import NodeResponse
from app.schemas.utils import normalize_animated, normalize_marker
class NodeSave(BaseModel):
id: str
type: str
label: str
hostname: str | None = None
ip: str | None = None
mac: str | None = None
os: str | None = None
status: str = "unknown"
check_method: str | None = None
check_target: str | None = None
services: list[Any] = []
notes: str | None = None
parent_id: str | None = None
container_mode: bool = False
custom_colors: dict[str, Any] | None = None
custom_icon: str | None = None
cpu_count: int | None = None
cpu_model: str | None = None
ram_gb: float | None = None
disk_gb: float | None = None
show_hardware: bool = False
show_port_numbers: bool = False
properties: list[Any] = []
width: float | None = None
height: float | None = None
bottom_handles: int = 1
top_handles: int = 1
left_handles: int = 0
right_handles: int = 0
pos_x: float = 0
pos_y: float = 0
class EdgeSave(BaseModel):
id: str
source: str
target: str
type: str = "ethernet"
label: str | None = None
vlan_id: int | None = None
speed: str | None = None
custom_color: str | None = None
path_style: str | None = None
animated: str = 'none'
marker_start: str = 'none'
marker_end: str = 'none'
source_handle: str | None = None
target_handle: str | None = None
waypoints: list[dict[str, float]] | None = None
@field_validator('animated', mode='before')
@classmethod
def validate_animated(cls, v: object) -> str:
return normalize_animated(v)
@field_validator('marker_start', 'marker_end', mode='before')
@classmethod
def validate_marker(cls, v: object) -> str:
return normalize_marker(v)
class CanvasSaveRequest(BaseModel):
nodes: list[NodeSave] = []
edges: list[EdgeSave] = []
viewport: dict[str, Any] = {}
custom_style: dict[str, Any] | None = None
design_id: str | None = None
class CanvasStateResponse(BaseModel):
nodes: list[NodeResponse]
edges: list[EdgeResponse]
viewport: dict[str, Any]
custom_style: dict[str, Any] | None = None