fix(nodes): persist show_port_numbers across reloads

The Show Port Numbers toggle reset on every reload because the backend
never stored it: the column was missing from the Node model and the field
was stripped by NodeSave/NodeBase/NodeUpdate schemas, so canvas/save
silently dropped it.

Add the show_port_numbers column (idempotent migration), and the field to
the node schemas so it round-trips through save/load.

Add regression tests covering persistence and the default-false case.

Fixes #184

ha-relevant: yes
This commit is contained in:
Pouzor
2026-06-05 15:53:58 +02:00
parent 0a4bac30ac
commit 2f3e986d4a
5 changed files with 24 additions and 0 deletions
+2
View File
@@ -91,6 +91,8 @@ async def init_db() -> None:
await conn.exec_driver_sql("ALTER TABLE nodes ADD COLUMN disk_gb REAL") await conn.exec_driver_sql("ALTER TABLE nodes ADD COLUMN disk_gb REAL")
with suppress(OperationalError): with suppress(OperationalError):
await conn.exec_driver_sql("ALTER TABLE nodes ADD COLUMN show_hardware BOOLEAN NOT NULL DEFAULT 0") await conn.exec_driver_sql("ALTER TABLE nodes ADD COLUMN show_hardware BOOLEAN NOT NULL DEFAULT 0")
with suppress(OperationalError):
await conn.exec_driver_sql("ALTER TABLE nodes ADD COLUMN show_port_numbers BOOLEAN NOT NULL DEFAULT 0")
with suppress(OperationalError): with suppress(OperationalError):
await conn.exec_driver_sql("ALTER TABLE nodes ADD COLUMN width REAL") await conn.exec_driver_sql("ALTER TABLE nodes ADD COLUMN width REAL")
with suppress(OperationalError): with suppress(OperationalError):
+1
View File
@@ -54,6 +54,7 @@ class Node(Base):
ram_gb: Mapped[float | None] = mapped_column(Float, nullable=True) ram_gb: Mapped[float | None] = mapped_column(Float, nullable=True)
disk_gb: Mapped[float | None] = mapped_column(Float, nullable=True) disk_gb: Mapped[float | None] = mapped_column(Float, nullable=True)
show_hardware: Mapped[bool] = mapped_column(Boolean, default=False) show_hardware: Mapped[bool] = mapped_column(Boolean, default=False)
show_port_numbers: Mapped[bool] = mapped_column(Boolean, default=False)
properties: Mapped[list[Any]] = mapped_column(JSON, default=list) properties: Mapped[list[Any]] = mapped_column(JSON, default=list)
width: Mapped[float | None] = mapped_column(Float, nullable=True) width: Mapped[float | None] = mapped_column(Float, nullable=True)
height: Mapped[float | None] = mapped_column(Float, nullable=True) height: Mapped[float | None] = mapped_column(Float, nullable=True)
+1
View File
@@ -29,6 +29,7 @@ class NodeSave(BaseModel):
ram_gb: float | None = None ram_gb: float | None = None
disk_gb: float | None = None disk_gb: float | None = None
show_hardware: bool = False show_hardware: bool = False
show_port_numbers: bool = False
properties: list[Any] = [] properties: list[Any] = []
width: float | None = None width: float | None = None
height: float | None = None height: float | None = None
+2
View File
@@ -27,6 +27,7 @@ class NodeBase(BaseModel):
ram_gb: float | None = None ram_gb: float | None = None
disk_gb: float | None = None disk_gb: float | None = None
show_hardware: bool = False show_hardware: bool = False
show_port_numbers: bool = False
properties: list[dict[str, Any]] = [] properties: list[dict[str, Any]] = []
width: float | None = None width: float | None = None
height: float | None = None height: float | None = None
@@ -60,6 +61,7 @@ class NodeUpdate(BaseModel):
ram_gb: float | None = None ram_gb: float | None = None
disk_gb: float | None = None disk_gb: float | None = None
show_hardware: bool | None = None show_hardware: bool | None = None
show_port_numbers: bool | None = None
properties: list[dict[str, Any]] | None = None properties: list[dict[str, Any]] | None = None
width: float | None = None width: float | None = None
height: float | None = None height: float | None = None
+18
View File
@@ -199,6 +199,24 @@ async def test_save_canvas_show_hardware_defaults_false(client: AsyncClient, hea
assert canvas["nodes"][0]["show_hardware"] is False assert canvas["nodes"][0]["show_hardware"] is False
# Regression (#184): show_port_numbers was dropped by the save schema, so the
# toggle reset on every reload.
async def test_save_canvas_persists_show_port_numbers(client: AsyncClient, headers: dict):
n1 = node_payload(show_port_numbers=True)
await client.post("/api/v1/canvas/save", json={"nodes": [n1], "edges": [], "viewport": {}}, headers=headers)
canvas = (await client.get("/api/v1/canvas", headers=headers)).json()
assert canvas["nodes"][0]["show_port_numbers"] is True
async def test_save_canvas_show_port_numbers_defaults_false(client: AsyncClient, headers: dict):
n1 = node_payload()
await client.post("/api/v1/canvas/save", json={"nodes": [n1], "edges": [], "viewport": {}}, headers=headers)
canvas = (await client.get("/api/v1/canvas", headers=headers)).json()
assert canvas["nodes"][0]["show_port_numbers"] is False
async def test_save_canvas_hardware_fields_cleared_on_update(client: AsyncClient, headers: dict): async def test_save_canvas_hardware_fields_cleared_on_update(client: AsyncClient, headers: dict):
n1 = node_payload(cpu_count=8, ram_gb=32.0) n1 = node_payload(cpu_count=8, ram_gb=32.0)
await client.post("/api/v1/canvas/save", json={"nodes": [n1], "edges": [], "viewport": {}}, headers=headers) await client.post("/api/v1/canvas/save", json={"nodes": [n1], "edges": [], "viewport": {}}, headers=headers)