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
This commit is contained in:
Pouzor
2026-06-25 18:05:13 +02:00
parent 96107cc657
commit d7ab4ba49a
8 changed files with 236 additions and 42 deletions
+6 -3
View File
@@ -457,8 +457,9 @@ async def test_run_scan_mdns_skipped_if_already_in_nmap(mem_db):
@pytest.mark.asyncio
async def test_run_scan_skips_canvas_nodes(mem_db):
"""Hosts already approved onto the canvas must be skipped."""
async def test_run_scan_keeps_canvas_nodes(mem_db):
"""Hosts already on a canvas are NOT suppressed — they stay in the inventory
(badged "In N canvas" via correlation), so a re-scan still records them."""
from app.services.scanner import run_scan
run_id = _make_run_id()
@@ -481,7 +482,9 @@ async def test_run_scan_skips_canvas_nodes(mem_db):
async with mem_db() as session:
result = await session.execute(sa_select(PendingDevice).where(PendingDevice.ip == "192.168.1.100"))
assert result.scalar_one_or_none() is None
device = result.scalar_one_or_none()
assert device is not None
assert device.status == "pending"
@pytest.mark.asyncio