Commit Graph

625 Commits

Author SHA1 Message Date
Pouzor d1c052c9e8 feat: hide header View link in standalone mode
The "View" link opens the read-only live view of the canvas. In frontend-only
standalone mode the editor already renders the only (localStorage) copy, so the
live view adds nothing — hide the link. Kept in full mode.

ha-relevant: no
2026-06-30 00:48:56 +02:00
Pouzor 79cac7d80f feat: hide node stats footer in standalone mode
The Total/Online/Offline counts reflect live status-check results, which
require a backend scanner. In frontend-only standalone mode there is nothing to
populate them, so hide the footer (kept in full mode and live view).

ha-relevant: no
2026-06-30 00:33:42 +02:00
Pouzor c356a65a5f fix: repair live view + settings in standalone multi-canvas mode
Two gaps surfaced after adding per-design localStorage storage:

- Live view (/view) still read the legacy bare `homelable_canvas` key, which
  no longer exists once canvases are keyed per design — the read-only tab
  rendered empty. Now passes the active design id (?design=<id>) and reads that
  design's canvas, falling back to the first design.
- The Settings modal was gated out entirely in standalone, leaving the Settings
  button dead even though canvas prefs (snap, hide-IP) work without a backend.
  Mount it in standalone; only the backend status-check section stays hidden.

ha-relevant: no
2026-06-30 00:05:13 +02:00
Pouzor ca171089c2 feat: support multi-canvas (designs) in frontend-only standalone mode
Standalone mode (VITE_STANDALONE=true) skipped the designs system entirely:
the design list stayed empty (switcher hidden) and all canvases collapsed onto
a single localStorage key. Add a localStorage-backed design layer mirroring the
backend designs API.

- standaloneStorage util: list/create/update/delete designs + per-design canvas
  storage (homelable_designs + homelable_canvas:<id>). ensureSeed migrates a
  legacy single-canvas install into a default design so existing data survives.
- App.tsx: load/seed designs, save + switch canvases per design id in standalone.
- Sidebar.tsx: create/update/delete dispatch to standaloneStorage when standalone.
- Tests for the new storage util.

ha-relevant: no
2026-06-29 23:13:56 +02:00
Rémy c7b4db206b Merge pull request #240 from Pouzor/release/2.6.0
chore: bump version to 2.6.0
v2.6.0
2026-06-29 01:51:18 +02:00
Pouzor a05c82b4f6 chore: bump version to 2.6.0 2026-06-29 01:37:58 +02:00
Rémy 9a066a3d6e Merge pull request #239 from Pouzor/fix/export-printing-165
fix: white background option for export + Firefox download (#165)
2026-06-29 01:17:48 +02:00
Pouzor f0af367c34 fix: force export background on the live react-flow element
The html-to-image style-option override did not reliably beat the
react-flow root's colorMode background, so white exports stayed black.
Set the background colour directly on the live element for the duration
of the capture (then restore it), which html-to-image reads when it
clones computed styles.

Refs #165
2026-06-29 01:13:55 +02:00
Pouzor 46185d187f fix: actually apply white export background over react-flow root
The .react-flow root paints its own opaque background via colorMode,
so html-to-image's backgroundColor option (which only fills transparent
areas) stayed black. Override the root background with an inline style
so the chosen export background is visible.

Refs #165
2026-06-29 01:02:00 +02:00
Pouzor fd2c50c1aa fix: white background option for export + Firefox download
Fixes two issues reported in #165:

- Export always produced a black background, making prints with a
  white page wasteful/unreadable. Adds a Dark/White background choice
  in the export modal, threaded through both PNG and SVG export.
- Firefox refused the download because the programmatic anchor was not
  attached to the document. The anchor is now appended before click and
  removed after, which Firefox requires.

Closes #165

ha-relevant: yes
2026-06-29 00:47:39 +02:00
Rémy 8b9972ca78 Merge pull request #238 from Pouzor/feat/svg-export-211
feat: add SVG export option to canvas export modal
2026-06-29 00:27:11 +02:00
Pouzor 313feb8ceb feat: add SVG export option to canvas export modal
Adds an SVG format choice below the Ultra quality option in the
export modal. PNG quality options still drive raster export; the
new SVG button exports a scalable vector via html-to-image toSvg.

Closes #211

ha-relevant: yes
2026-06-28 23:52:28 +02:00
Rémy 23673eb0ca Merge pull request #237 from Pouzor/fix/mcp-create-design-id
fix: attach MCP-created nodes/edges to a design (#225)
2026-06-28 15:52:39 +02:00
Pouzor cbc2bc03c2 fix: attach MCP-created nodes/edges to a design (#225)
create_node/create_edge persisted rows with design_id=null when the
client omitted it (the MCP write tools), so they existed in the DB but
never rendered on the canvas until a container restart reconciled them.
Both routes now fall back to the first design, matching bulk-approve.

Also fix MCP resource reads (homelable://canvas, homelable://edges):
the framework passes a pydantic AnyUrl, not a str, which raised
"'AnyUrl' object has no attribute 'startswith'". Coerce to str.

EdgeResponse now exposes design_id for symmetry with NodeResponse.

ha-relevant: no
2026-06-28 14:00:59 +02:00
Rémy 7e99d77edc Merge pull request #236 from Pouzor/fix/scan-stop-cancellation
fix: make scan stop interrupt in-flight nmap range (#218)
2026-06-28 11:24:27 +02:00
Pouzor da2c1c356a fix: make scan stop interrupt in-flight nmap range
Stop button had no effect: run_scan only checked the cancel flag between
CIDR ranges and between hosts, never inside the per-range nmap call. For a
single /24 the whole scan is one blocking call, so cancel was ignored for
minutes and the run status stayed 'running'.

- thread run_id into _nmap_scan/_ping_sweep/_nmap_port_scan; check cancel
  before each phase and skip queued hosts once cancelled
- flip ScanRun status to 'cancelled' eagerly in the stop endpoint so the UI
  reacts immediately instead of waiting for a checkpoint

Fixes #218

ha-relevant: yes
2026-06-28 11:13:14 +02:00
Rémy 3cedb40d17 Merge pull request #235 from Pouzor/fix/auto-layout-child-port-order
fix: order auto-layout children by parent port number
2026-06-28 01:09:13 +02:00
Pouzor 7fdce6af37 fix: order auto-layout children by parent port number
Dagre orders sibling nodes by node-insertion order and ignores handle
ids, so after auto-layout a host's children were frequently laid out in
the reverse of the ports they plug into. Add a post-pass that keeps
Dagre's X slots but reassigns which child sits in each, sorted by the
parent's bottom-port index, shifting each child's whole subtree by the
same delta so nested nodes follow their parent. Peer-group members are
skipped so the existing peer layout is untouched.

ha-relevant: yes
2026-06-28 01:03:38 +02:00
Rémy c1b0c42f0c Merge pull request #234 from Pouzor/feat/center-new-nodes-on-screen
feat: drop new nodes at centre of visible canvas
2026-06-28 00:37:51 +02:00
Pouzor 2a8c9d618b feat: drop new nodes at centre of visible canvas
New nodes previously landed at a fixed canvas origin, often off-screen,
forcing the user to drag them into view. Add a projector (registered by
CanvasContainer inside ReactFlowProvider) that maps the visible-canvas
centre into flow coordinates, and use it for add-node, group rect, text,
Zigbee/Z-Wave imports, and pending-device approval (single + bulk).

ha-relevant: yes
2026-06-28 00:27:18 +02:00
Rémy e811d83ceb Merge pull request #233 from Pouzor/feat/inventory-timestamps
feat: surface inventory timestamps on nodes
2026-06-27 23:39:32 +02:00
Pouzor c8d25c2383 fix: bulk-approve places every selected device onto the active canvas
Bulk-approve filtered status=='pending', so a device already approved onto
another canvas (status is global, canvas membership is per-design) — or whose
node was later deleted — was silently skipped. Selecting 64 devices on an
empty canvas produced only the ~28 still pending.

Approve now places a node on the target design for any selected, non-hidden
device that isn't already on that design (deduped by ip/ieee_address, including
within the batch). Returned device_ids/node_ids stay index-aligned so the
client places them all.

ha-relevant: yes
2026-06-27 19:02:28 +02:00
Pouzor 3020b400ed fix: declare transient node timestamp attrs on PendingDevice
mypy flagged the per-request node_* timestamp attributes as undefined on the
model. Declare them as class-level defaults (like canvas_count) and set
__allow_unmapped__ so SQLAlchemy 2.0 doesn't try to map the Optional[datetime]
annotations as columns.
2026-06-27 14:29:22 +02:00
Pouzor 8f8d9fe209 style: fit 3 inventory tiles per row
Move the 3-column grid down to the xl breakpoint (was 2xl), tighten the
inter-tile gap and card padding so three cards fit comfortably per row at
common widths instead of two.
2026-06-27 13:56:27 +02:00
Pouzor 612280e924 feat: show inventory timestamps on Device Inventory tiles
Surface the same lifecycle timestamps on the Device Inventory cards as on the
detail panel. Tiles for devices placed on a canvas show their linked node's
created / last scan / last modified / last seen (correlated by ip or
ieee_address, aggregated across matches: created = oldest, others = newest).
Devices not yet on any canvas fall back to their discovered_at.

Rendered as compact relative times ("2d ago") with the full date on hover, in
a tight two-column footer so the tile keeps its original footprint.

Backend: PendingDeviceResponse gains node_created_at / node_last_scan /
node_last_modified / node_last_seen; the canvas correlation now also pulls node
timestamps in the same single query. Frontend: shared timeFormat util
(absolute + relative), reused by the detail panel.

ha-relevant: maybe
2026-06-27 13:24:29 +02:00
Pouzor 19b7d38ec0 feat: surface inventory timestamps on nodes
Add creation_date, last_scan and last_modify to the node inventory and the
right-hand detail panel (last_seen already shown):

- creation_date  -> existing created_at column
- last_modify    -> existing updated_at column (bumped on any node change)
- last_scan      -> new column, stamped when a scan observes a node by IP/MAC
- last_seen      -> unchanged

Backend: new nodes.last_scan column + idempotent migration, NodeResponse
field, scanner stamps matching canvas nodes per scanned host. Frontend:
NodeData fields + DetailPanel rows with UTC-safe timestamp formatting.

ha-relevant: maybe
2026-06-27 03:13:49 +02:00
Rémy b3f7c38c67 Merge pull request #232 from Pouzor/docs/scanner-http-ranges
docs: document SCANNER_HTTP_RANGES port-spec format
2026-06-27 02:11:42 +02:00
Pouzor 5b20d4fd75 docs: document SCANNER_HTTP_RANGES port-spec format
Closes #229
2026-06-27 02:05:40 +02:00
Rémy 16e2dedab0 Merge pull request #219 from hooli/fix/markdown-copy
Fix: Markdow Copy now works over non-secure HTTP
2026-06-27 01:55:53 +02:00
Pouzor c8ed63712d refactor: extract copyToClipboard util with HTTP fallback + tests 2026-06-26 23:51:23 +02:00
Pouzor 10fdab52af Merge branch 'main' into fix/markdown-copy 2026-06-26 23:28:30 +02:00
Rémy eddf752b50 Merge pull request #224 from Pouzor/feat/zwave-scan
feat: Z-Wave network scan via MQTT
2026-06-26 23:03:49 +02:00
Pouzor 13420bead8 fix: approve devices onto the active design + Z-Wave node fields
Approve (single + bulk) created the canvas Node under the first design
instead of the design the user is viewing, so approved devices were
invisible on the active canvas and got wiped on the next save — and a
re-approve returned "0 approved" because the rows were already approved.

- bulk-approve now accepts design_id; the UI sends the active design.
- single approve already honoured design_id; the UI now sends it too.
- generalize the wireless branch (status=online, mesh props, no ICMP
  check) to Z-Wave as well as Zigbee, using build_zwave_properties.

ha-relevant: yes
2026-06-26 21:58:31 +02:00
Pouzor d41896fadf fix: render styled canvas nodes for zwave types
The zwave_* node types were not registered in the React Flow nodeTypes
map, so imported Z-Wave devices fell back to the default unstyled node
(no icon, no accent). Add ZwaveCoordinator/Router/EndDevice node
components and register them. Adds a registry guard test.

ha-relevant: yes
2026-06-26 20:42:52 +02:00
Pouzor f749b38edc fix: reset Custom Style draft on reopen
The modal is kept mounted by its parent (only `open` toggles), so Radix
onOpenChange never fires for a parent-driven open and the draft-reset
branch was dead code — abandoned edits leaked into the next open. Reset
on the `open` prop edge via effect instead. Also pass radix 10 to
parseInt for the size inputs. Adds a reopen-after-cancel regression test.

ha-relevant: yes
2026-06-26 16:20:30 +02:00
Pouzor ecf3cbdfe4 feat: group node types by category in Custom Style editor
The Custom Style modal listed node types as a flat list. It now groups
them under category headers (Hardware, Virtualization, IoT, Zigbee,
Z-Wave, Personal, Generic) like the Add/Edit Node modal, and exposes the
Z-Wave node types for styling.

ha-relevant: yes
2026-06-26 16:02:23 +02:00
Pouzor 5b08d57124 fix: add zwave node types to canvas theme maps
The 6 per-theme NodeType color maps in themes.ts were missing the three
zwave_* keys, breaking the production build (tsc -b) even though the
dev typecheck passed.

ha-relevant: yes
2026-06-26 13:58:30 +02:00
Pouzor 9b8f15bec3 feat: show Z-Wave scan runs in Scan History
Scan History now recognises kind=zwave: dedicated Z-Wave filter chip,
badge (RadioTower, orange) and import-done toast, instead of falling
back to the generic IP type.

ha-relevant: yes
2026-06-26 13:10:12 +02:00
Pouzor 8faf5c1c79 feat: add Z-Wave network scan via MQTT gateway
Import a Z-Wave JS UI (zwavejs2mqtt) network over the MQTT gateway API,
mirroring the existing Zigbee pipeline:

- New Z-Wave Import modal + sidebar entry (broker, prefix, gateway name)
- coordinator/router/end-device typing with mesh tree from node neighbors
- import to Pending section or straight to canvas
- Pending Devices gains a Z-Wave source filter
- shared mqtt_common helpers extracted from the zigbee service

ha-relevant: yes
2026-06-26 11:19:37 +02:00
Rémy c6076d133a Merge pull request #222 from Pouzor/feat/http-probe-deep-scan
feat: deep-scan HTTP probe + Device Inventory (#195)
2026-06-26 02:12:39 +02:00
Pouzor 662a240b05 fix: collapse duplicate inventory rows for the same device
A re-scan of an already-approved device created a fresh pending row each
time (the upsert only matched status=pending), so one device could show
several times in the inventory. Now the scanner keeps one non-hidden row
per IP, refreshing it in place and preserving an approved status, and
collapses any pre-existing duplicates at scan start.

ha-relevant: maybe
2026-06-26 01:58:53 +02:00
Pouzor dfeb981e40 feat: add 'With services' filter to Device Inventory
ha-relevant: maybe
2026-06-26 01:40:56 +02:00
Pouzor 430c14ed5e fix: rename left-panel nav item to Device Inventory
ha-relevant: maybe
2026-06-26 01:24:04 +02:00
Pouzor 234019c7b7 fix: declare transient canvas_count on PendingDevice for mypy
ha-relevant: maybe
2026-06-25 21:33:05 +02:00
Pouzor d7ab4ba49a feat: Device Inventory — show all scanned devices with canvas-presence
Reworks the "Pending Devices" panel into a "Device Inventory": scanned
devices already placed on a canvas are no longer suppressed — they stay
listed and badged with how many canvases they appear on.

- scanner: stop deleting/skipping on-canvas IPs (hidden still suppressed)
- scan API: /pending returns all non-hidden devices; compute canvas_count
  by correlating ip/ieee_address against nodes grouped by design
- frontend: rename to "Device Inventory", top-right canvas-count corner,
  toggle to show/hide on-canvas devices (default show)

ha-relevant: maybe
2026-06-25 18:05:13 +02:00
Pouzor 96107cc657 feat: seed port-agnostic http_regex signatures for ~50 popular homelab apps
Adds port:null signatures matched by HTTP page title for the most-used
self-hosted and home-automation apps (Jellyfin, Plex, *Arr, Home Assistant,
Node-RED, Zigbee2MQTT, ESPHome, Domoticz, Jeedom, Portainer, Grafana,
Uptime Kuma, Pi-hole, AdGuard Home, Nextcloud, Vaultwarden, Authelia,
OpenMediaVault, Unraid, etc.). These identify a service on any port once the
deep-scan HTTP probe is enabled. Existing port-keyed entries are unchanged.

ha-relevant: yes
2026-06-25 14:47:58 +02:00
Pouzor c68084b751 test: update client trigger assertion for deep-scan body
ha-relevant: yes
2026-06-24 10:40:03 +02:00
Pouzor d01630bf37 feat: add Deep Scan toggle to scan dialog
Collapsible Deep Scan section in ScanConfigModal exposes extra port ranges,
HTTP probe and TLS-verify switches. Pre-filled from saved defaults; edits are
passed to trigger() as a per-scan override and do not change the persisted
defaults (those live in the Options/scan config). scanApi.trigger now accepts
an optional deep-scan body.

ha-relevant: yes
2026-06-24 10:31:32 +02:00
Pouzor b6423c0115 feat: expose deep-scan settings via API and persisted config
Adds scanner_http_ranges / scanner_http_probe_enabled / scanner_http_verify_tls
to Settings (persisted in scan_config.json, Options page defaults). /scan/trigger
accepts an optional body to override these per-scan; /scan/config GET/POST read
and persist the defaults. Port ranges validated at the API boundary.

ha-relevant: yes
2026-06-24 10:04:31 +02:00
Pouzor 9d1ebd6c6e feat: wire deep-scan port ranges and HTTP probe into scanner
run_scan() accepts DeepScanOptions: user port ranges are validated and appended
to the nmap -p list; when http_probe_enabled, open ports are probed for HTTP
signals before fingerprinting. Defaults (no options) reproduce the standard
scan exactly — probe is never called, port list unchanged.

ha-relevant: yes
2026-06-23 14:04:15 +02:00