3ae159d8d6
Coordinator auto-approves to a canvas Node; routers/end devices land in pending_devices keyed by IEEE. Discovered parent->child edges are stored in pending_device_links so that approving a pending device later auto-creates the Edge once both endpoints exist as canvas Nodes. - new POST /api/v1/zigbee/import-pending (default mode in modal) - new pending_device_links table; ieee_address on nodes + pending_devices - pending_devices.ip migrated to nullable (table rebuild on existing DBs) - approve / bulk-approve return auto-created edges; sidebar pushes them into the canvas store with bottom -> top-t handles - ZigbeeImportModal: radio toggle pending vs canvas; reset on close - PendingDeviceModal: zigbee badge, IEEE/LQI/vendor/model rows, services hidden for zigbee - Sidebar pending row: ZIG source badge, LQI badge, friendly_name fallback - SearchBar: null-safe IP, also searches friendly_name and ieee_address - Tooltip trigger uses asChild to avoid nested-button hydration error
96 lines
2.8 KiB
Python
96 lines
2.8 KiB
Python
"""Pydantic v2 schemas for Zigbee2MQTT import."""
|
|
|
|
from pydantic import BaseModel, Field, model_validator
|
|
|
|
|
|
class ZigbeeImportRequest(BaseModel):
|
|
mqtt_host: str = Field(..., description="MQTT broker hostname or IP address")
|
|
mqtt_port: int = Field(1883, ge=1, le=65535, description="MQTT broker port")
|
|
mqtt_username: str | None = Field(None, description="MQTT username (optional)")
|
|
mqtt_password: str | None = Field(None, description="MQTT password (optional)")
|
|
base_topic: str = Field("zigbee2mqtt", description="Zigbee2MQTT base topic")
|
|
mqtt_tls: bool = Field(False, description="Enable TLS (typically port 8883)")
|
|
mqtt_tls_insecure: bool = Field(
|
|
False, description="Skip TLS certificate verification (self-signed only)"
|
|
)
|
|
|
|
@model_validator(mode="after")
|
|
def _insecure_requires_tls(self) -> "ZigbeeImportRequest":
|
|
if self.mqtt_tls_insecure and not self.mqtt_tls:
|
|
raise ValueError("mqtt_tls_insecure requires mqtt_tls=true")
|
|
return self
|
|
|
|
|
|
class ZigbeeTestConnectionRequest(BaseModel):
|
|
mqtt_host: str
|
|
mqtt_port: int = Field(1883, ge=1, le=65535)
|
|
mqtt_username: str | None = None
|
|
mqtt_password: str | None = None
|
|
mqtt_tls: bool = False
|
|
mqtt_tls_insecure: bool = False
|
|
|
|
@model_validator(mode="after")
|
|
def _insecure_requires_tls(self) -> "ZigbeeTestConnectionRequest":
|
|
if self.mqtt_tls_insecure and not self.mqtt_tls:
|
|
raise ValueError("mqtt_tls_insecure requires mqtt_tls=true")
|
|
return self
|
|
|
|
|
|
class ZigbeeDeviceData(BaseModel):
|
|
ieee_address: str
|
|
friendly_name: str
|
|
device_type: str # Coordinator, Router, EndDevice
|
|
model: str | None = None
|
|
vendor: str | None = None
|
|
description: str | None = None
|
|
lqi: int | None = None
|
|
last_seen: str | None = None
|
|
|
|
|
|
class ZigbeeNodeOut(BaseModel):
|
|
"""A homelable-ready node representation of a Zigbee device."""
|
|
|
|
id: str
|
|
label: str
|
|
type: str # zigbee_coordinator | zigbee_router | zigbee_enddevice
|
|
ieee_address: str
|
|
friendly_name: str
|
|
device_type: str
|
|
model: str | None = None
|
|
vendor: str | None = None
|
|
lqi: int | None = None
|
|
parent_id: str | None = None
|
|
|
|
|
|
class ZigbeeEdgeOut(BaseModel):
|
|
source: str
|
|
target: str
|
|
|
|
|
|
class ZigbeeImportResponse(BaseModel):
|
|
nodes: list[ZigbeeNodeOut]
|
|
edges: list[ZigbeeEdgeOut]
|
|
device_count: int
|
|
|
|
|
|
class ZigbeeTestConnectionResponse(BaseModel):
|
|
connected: bool
|
|
message: str
|
|
|
|
|
|
class ZigbeeCoordinatorOut(BaseModel):
|
|
id: str
|
|
label: str
|
|
ieee_address: str
|
|
|
|
|
|
class ZigbeeImportPendingResponse(BaseModel):
|
|
"""Result of importing a Z2M network into the pending section."""
|
|
|
|
pending_created: int
|
|
pending_updated: int
|
|
coordinator: ZigbeeCoordinatorOut | None = None
|
|
coordinator_already_existed: bool = False
|
|
links_recorded: int
|
|
device_count: int
|