feat: Phase 3 & 4 — monitoring, discovery, polish, deployment

Phase 3 — Discovery & Monitoring:
- Network scanner: nmap wrapper + mock fallback, fingerprint service (35 signatures)
- Status checker: ping/http/https/tcp/ssh/prometheus/health per-node checks
- APScheduler: status checks every 60s, WebSocket broadcast
- WebSocket /ws/status: live node status updates to frontend
- Sidebar panels: Pending Devices, Hidden Devices, Scan History
- Auth token persisted to localStorage (survive page refresh)
- 24 new backend tests (scan flow + status_checker)

Phase 4 — Polish & Deployment:
- Auto-layout: Dagre hierarchical TB via Toolbar button
- Export PNG: html-to-image download via Toolbar button
- Scan config modal: CIDR ranges + check interval, GET/POST /api/v1/scan/config
- Dockerfile.backend (Python 3.13 slim + nmap), Dockerfile.frontend (nginx)
- docker-compose.yml with data volume and NET_RAW cap for ping
- scripts/lxc-install.sh: Proxmox VE systemd bootstrap
- README.md: quick-start, config reference, stack overview
This commit is contained in:
Pouzor
2026-03-07 00:45:50 +01:00
parent 44a448e26d
commit 0bd714a68b
26 changed files with 1778 additions and 25 deletions
+49
View File
@@ -0,0 +1,49 @@
import { useEffect, useRef } from 'react'
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
}
export function useStatusPolling() {
const wsRef = useRef<WebSocket | null>(null)
const { updateNode } = useCanvasStore()
const { isAuthenticated, token } = useAuthStore()
useEffect(() => {
if (!isAuthenticated || !token) return
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'
const host = window.location.hostname
const url = `${protocol}://${host}:8000/api/v1/status/ws/status`
const ws = new WebSocket(url)
wsRef.current = ws
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,
})
} catch {
// ignore malformed messages
}
}
ws.onerror = () => {
// silently ignore — backend may not be running in dev
}
return () => {
ws.close()
wsRef.current = null
}
}, [isAuthenticated, token, updateNode])
}