From 2f3e986d4a9dcfbac5f0a943b330dcab0434a5bf Mon Sep 17 00:00:00 2001 From: Pouzor Date: Fri, 5 Jun 2026 15:53:58 +0200 Subject: [PATCH] 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 --- backend/app/db/database.py | 2 ++ backend/app/db/models.py | 1 + backend/app/schemas/canvas.py | 1 + backend/app/schemas/nodes.py | 2 ++ backend/tests/test_canvas.py | 18 ++++++++++++++++++ 5 files changed, 24 insertions(+) diff --git a/backend/app/db/database.py b/backend/app/db/database.py index 3da2212..a95258a 100644 --- a/backend/app/db/database.py +++ b/backend/app/db/database.py @@ -91,6 +91,8 @@ async def init_db() -> None: await conn.exec_driver_sql("ALTER TABLE nodes ADD COLUMN disk_gb REAL") with suppress(OperationalError): 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): await conn.exec_driver_sql("ALTER TABLE nodes ADD COLUMN width REAL") with suppress(OperationalError): diff --git a/backend/app/db/models.py b/backend/app/db/models.py index caccdc0..bdcd6d2 100644 --- a/backend/app/db/models.py +++ b/backend/app/db/models.py @@ -54,6 +54,7 @@ class Node(Base): ram_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_port_numbers: Mapped[bool] = mapped_column(Boolean, default=False) properties: Mapped[list[Any]] = mapped_column(JSON, default=list) width: Mapped[float | None] = mapped_column(Float, nullable=True) height: Mapped[float | None] = mapped_column(Float, nullable=True) diff --git a/backend/app/schemas/canvas.py b/backend/app/schemas/canvas.py index 6e8b9f0..580daba 100644 --- a/backend/app/schemas/canvas.py +++ b/backend/app/schemas/canvas.py @@ -29,6 +29,7 @@ class NodeSave(BaseModel): 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 diff --git a/backend/app/schemas/nodes.py b/backend/app/schemas/nodes.py index 5683486..5c220ed 100644 --- a/backend/app/schemas/nodes.py +++ b/backend/app/schemas/nodes.py @@ -27,6 +27,7 @@ class NodeBase(BaseModel): ram_gb: float | None = None disk_gb: float | None = None show_hardware: bool = False + show_port_numbers: bool = False properties: list[dict[str, Any]] = [] width: float | None = None height: float | None = None @@ -60,6 +61,7 @@ class NodeUpdate(BaseModel): ram_gb: float | None = None disk_gb: float | None = None show_hardware: bool | None = None + show_port_numbers: bool | None = None properties: list[dict[str, Any]] | None = None width: float | None = None height: float | None = None diff --git a/backend/tests/test_canvas.py b/backend/tests/test_canvas.py index ac0ce1e..19202b6 100644 --- a/backend/tests/test_canvas.py +++ b/backend/tests/test_canvas.py @@ -199,6 +199,24 @@ async def test_save_canvas_show_hardware_defaults_false(client: AsyncClient, hea 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): n1 = node_payload(cpu_count=8, ram_gb=32.0) await client.post("/api/v1/canvas/save", json={"nodes": [n1], "edges": [], "viewport": {}}, headers=headers)