feat: non-blocking scan with progressive device discovery

Backend:
- asyncio.to_thread(_nmap_scan) — nmap no longer blocks the event loop
- Commit each discovered device immediately (previously one bulk commit at end)
- Update ScanRun.devices_found after each device so history panel shows live count
- broadcast_scan_update() pushes {type: scan_device_found} WS event per device
- Refactor broadcast_status to shared _broadcast() helper, adds type: "status" field

Frontend:
- useStatusPolling handles both WS message types (status / scan_device_found)
- canvasStore: scanEventTs + notifyScanDeviceFound() action
- PendingDevicesPanel auto-refreshes when WS scan event is received
This commit is contained in:
Pouzor
2026-03-07 16:06:44 +01:00
parent 974e782057
commit bec699ba93
5 changed files with 72 additions and 25 deletions
+18 -11
View File
@@ -3,15 +3,18 @@ import { useCanvasStore } from '@/stores/canvasStore'
import { useAuthStore } from '@/stores/authStore'
interface StatusMessage {
node_id: string
status: 'online' | 'offline' | 'pending' | 'unknown'
checked_at: string
response_time_ms: number | null
type?: string
node_id?: string
status?: 'online' | 'offline' | 'pending' | 'unknown'
checked_at?: string
response_time_ms?: number | null
run_id?: string
devices_found?: number
}
export function useStatusPolling() {
const wsRef = useRef<WebSocket | null>(null)
const { updateNode } = useCanvasStore()
const { updateNode, notifyScanDeviceFound } = useCanvasStore()
const { isAuthenticated, token } = useAuthStore()
useEffect(() => {
@@ -27,11 +30,15 @@ export function useStatusPolling() {
ws.onmessage = (event) => {
try {
const msg: StatusMessage = JSON.parse(event.data)
updateNode(msg.node_id, {
status: msg.status,
response_time_ms: msg.response_time_ms ?? undefined,
last_seen: msg.status === 'online' ? msg.checked_at : undefined,
})
if (msg.type === 'scan_device_found') {
notifyScanDeviceFound()
} else if (msg.node_id && msg.status) {
updateNode(msg.node_id, {
status: msg.status,
response_time_ms: msg.response_time_ms ?? undefined,
last_seen: msg.status === 'online' ? msg.checked_at : undefined,
})
}
} catch {
// ignore malformed messages
}
@@ -45,5 +52,5 @@ export function useStatusPolling() {
ws.close()
wsRef.current = null
}
}, [isAuthenticated, token, updateNode])
}, [isAuthenticated, token, updateNode, notifyScanDeviceFound])
}