feat: add Camera node type + flag RTSP/camera ports during scan
- New 'camera' node type with Camera icon (frontend + types) - Scanner fingerprint: camera ports (554, 8554, 37777, 34567, 2020) now suggest 'camera' instead of 'iot' - service_signatures.json: all camera/NVR entries updated to suggested_node_type=camera - 'camera' added to priority list in suggest_node_type (above iot)
This commit is contained in:
@@ -66,12 +66,12 @@ _PORT_TYPE_HINTS: dict[int, str] = {
|
||||
# Routers / network devices
|
||||
8291: "router", # MikroTik Winbox
|
||||
179: "router", # BGP
|
||||
# Cameras / RTSP → iot
|
||||
554: "iot",
|
||||
8554: "iot",
|
||||
37777: "iot", # Dahua
|
||||
34567: "iot", # Amcrest
|
||||
2020: "iot", # Tapo
|
||||
# Cameras / RTSP
|
||||
554: "camera",
|
||||
8554: "camera",
|
||||
37777: "camera", # Dahua
|
||||
34567: "camera", # Amcrest
|
||||
2020: "camera", # Tapo
|
||||
# Smart-home / MQTT → iot
|
||||
1883: "iot",
|
||||
8883: "iot",
|
||||
@@ -87,7 +87,7 @@ _PORT_TYPE_HINTS: dict[int, str] = {
|
||||
|
||||
def suggest_node_type(open_ports: list[dict[str, Any]]) -> str:
|
||||
"""Suggest a node type based on the most specific matched signature."""
|
||||
priority = ["proxmox", "nas", "router", "lxc", "vm", "server", "ap", "iot", "switch"]
|
||||
priority = ["proxmox", "nas", "router", "lxc", "vm", "server", "ap", "camera", "iot", "switch"]
|
||||
found: set[str] = set()
|
||||
for p in open_ports:
|
||||
port = p["port"]
|
||||
|
||||
@@ -45,20 +45,20 @@
|
||||
{"port": 8883, "protocol": "tcp", "banner_regex": null, "service_name": "MQTT Broker TLS", "icon": "radio", "category": "iot", "suggested_node_type": "iot"},
|
||||
{"port": 6052, "protocol": "tcp", "banner_regex": null, "service_name": "ESPHome", "icon": "cpu", "category": "iot", "suggested_node_type": "iot"},
|
||||
{"port": 1880, "protocol": "tcp", "banner_regex": null, "service_name": "Node-RED", "icon": "git-branch", "category": "automation", "suggested_node_type": "iot"},
|
||||
{"port": 8971, "protocol": "tcp", "banner_regex": null, "service_name": "Frigate NVR", "icon": "camera", "category": "nvr", "suggested_node_type": "iot"},
|
||||
{"port": 10443, "protocol": "tcp", "banner_regex": null, "service_name": "Scrypted", "icon": "camera", "category": "nvr", "suggested_node_type": "iot"},
|
||||
{"port": 5000, "protocol": "tcp", "banner_regex": "frigate", "service_name": "Frigate NVR", "icon": "camera", "category": "nvr", "suggested_node_type": "iot"},
|
||||
{"port": 8971, "protocol": "tcp", "banner_regex": null, "service_name": "Frigate NVR", "icon": "camera", "category": "nvr", "suggested_node_type": "camera"},
|
||||
{"port": 10443, "protocol": "tcp", "banner_regex": null, "service_name": "Scrypted", "icon": "camera", "category": "nvr", "suggested_node_type": "camera"},
|
||||
{"port": 5000, "protocol": "tcp", "banner_regex": "frigate", "service_name": "Frigate NVR", "icon": "camera", "category": "nvr", "suggested_node_type": "camera"},
|
||||
{"port": 8081, "protocol": "tcp", "banner_regex": "iobroker|ioBroker", "service_name": "ioBroker", "icon": "cpu", "category": "automation", "suggested_node_type": "iot"},
|
||||
{"port": 8080, "protocol": "tcp", "banner_regex": "Domoticz|domoticz", "service_name": "Domoticz", "icon": "home", "category": "automation", "suggested_node_type": "iot"},
|
||||
{"port": 5683, "protocol": "udp", "banner_regex": null, "service_name": "CoAP (IoT)", "icon": "radio", "category": "iot", "suggested_node_type": "iot"},
|
||||
|
||||
{"port": 554, "protocol": "tcp", "banner_regex": null, "service_name": "RTSP (Camera)", "icon": "camera", "category": "camera", "suggested_node_type": "iot"},
|
||||
{"port": 8554, "protocol": "tcp", "banner_regex": null, "service_name": "RTSP Alt (Camera)", "icon": "camera", "category": "camera", "suggested_node_type": "iot"},
|
||||
{"port": 37777, "protocol": "tcp", "banner_regex": null, "service_name": "Dahua Camera SDK", "icon": "camera", "category": "camera", "suggested_node_type": "iot"},
|
||||
{"port": 34567, "protocol": "tcp", "banner_regex": null, "service_name": "Amcrest / Dahua Camera", "icon": "camera", "category": "camera", "suggested_node_type": "iot"},
|
||||
{"port": 8000, "protocol": "tcp", "banner_regex": "[Hh]ikvision|[Dd]ahua", "service_name": "IP Camera SDK", "icon": "camera", "category": "camera", "suggested_node_type": "iot"},
|
||||
{"port": 2020, "protocol": "tcp", "banner_regex": null, "service_name": "TP-Link Tapo Camera", "icon": "camera", "category": "camera", "suggested_node_type": "iot"},
|
||||
{"port": 9000, "protocol": "tcp", "banner_regex": "[Rr]eolink", "service_name": "Reolink Camera", "icon": "camera", "category": "camera", "suggested_node_type": "iot"},
|
||||
{"port": 554, "protocol": "tcp", "banner_regex": null, "service_name": "RTSP (Camera)", "icon": "camera", "category": "camera", "suggested_node_type": "camera"},
|
||||
{"port": 8554, "protocol": "tcp", "banner_regex": null, "service_name": "RTSP Alt (Camera)", "icon": "camera", "category": "camera", "suggested_node_type": "camera"},
|
||||
{"port": 37777, "protocol": "tcp", "banner_regex": null, "service_name": "Dahua Camera SDK", "icon": "camera", "category": "camera", "suggested_node_type": "camera"},
|
||||
{"port": 34567, "protocol": "tcp", "banner_regex": null, "service_name": "Amcrest / Dahua Camera", "icon": "camera", "category": "camera", "suggested_node_type": "camera"},
|
||||
{"port": 8000, "protocol": "tcp", "banner_regex": "[Hh]ikvision|[Dd]ahua", "service_name": "IP Camera SDK", "icon": "camera", "category": "camera", "suggested_node_type": "camera"},
|
||||
{"port": 2020, "protocol": "tcp", "banner_regex": null, "service_name": "TP-Link Tapo Camera", "icon": "camera", "category": "camera", "suggested_node_type": "camera"},
|
||||
{"port": 9000, "protocol": "tcp", "banner_regex": "[Rr]eolink", "service_name": "Reolink Camera", "icon": "camera", "category": "camera", "suggested_node_type": "camera"},
|
||||
|
||||
{"port": 8291, "protocol": "tcp", "banner_regex": null, "service_name": "MikroTik Winbox", "icon": "router", "category": "network", "suggested_node_type": "router"},
|
||||
{"port": 8880, "protocol": "tcp", "banner_regex": null, "service_name": "UniFi HTTP Portal", "icon": "wifi", "category": "network", "suggested_node_type": "ap"},
|
||||
|
||||
@@ -116,3 +116,18 @@ def test_suggest_node_type_priority_proxmox_over_server():
|
||||
# both proxmox and server ports open → proxmox wins (higher priority)
|
||||
result = suggest_node_type([{"port": 80, "protocol": "tcp"}, {"port": 8006, "protocol": "tcp"}])
|
||||
assert result == "proxmox"
|
||||
|
||||
|
||||
def test_suggest_node_type_camera_from_rtsp_port():
|
||||
# RTSP port 554 → camera (via _PORT_TYPE_HINTS)
|
||||
result = suggest_node_type([{"port": 554, "protocol": "tcp"}])
|
||||
assert result == "camera"
|
||||
|
||||
|
||||
def test_suggest_node_type_camera_from_signature():
|
||||
# patch _load to return a camera sig so we test the sig path too
|
||||
with patch("app.services.fingerprint._load", return_value=[
|
||||
{"port": 554, "protocol": "tcp", "banner_regex": None, "service_name": "RTSP", "icon": "camera", "category": "camera", "suggested_node_type": "camera"}
|
||||
]):
|
||||
result = suggest_node_type([{"port": 554, "protocol": "tcp"}])
|
||||
assert result == "camera"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type NodeProps, type Node } from '@xyflow/react'
|
||||
import {
|
||||
Globe, Router, Network, Server, Layers, Box, Container,
|
||||
HardDrive, Cpu, Wifi, Circle,
|
||||
HardDrive, Cpu, Wifi, Circle, Camera,
|
||||
} from 'lucide-react'
|
||||
import { BaseNode } from './BaseNode'
|
||||
import type { NodeData } from '@/types'
|
||||
@@ -18,4 +18,5 @@ export const LxcNode = (props: N) => <BaseNode {...props} icon={Container} /
|
||||
export const NasNode = (props: N) => <BaseNode {...props} icon={HardDrive} />
|
||||
export const IotNode = (props: N) => <BaseNode {...props} icon={Cpu} />
|
||||
export const ApNode = (props: N) => <BaseNode {...props} icon={Wifi} />
|
||||
export const CameraNode = (props: N) => <BaseNode {...props} icon={Camera} />
|
||||
export const GenericNode = (props: N) => <BaseNode {...props} icon={Circle} />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IspNode, RouterNode, SwitchNode, ServerNode, VmNode, LxcNode, NasNode, IotNode, ApNode, GenericNode } from './index'
|
||||
import { IspNode, RouterNode, SwitchNode, ServerNode, VmNode, LxcNode, NasNode, IotNode, ApNode, CameraNode, GenericNode } from './index'
|
||||
import { ProxmoxGroupNode } from './ProxmoxGroupNode'
|
||||
|
||||
export const nodeTypes = {
|
||||
@@ -12,5 +12,6 @@ export const nodeTypes = {
|
||||
nas: NasNode,
|
||||
iot: IotNode,
|
||||
ap: ApNode,
|
||||
camera: CameraNode,
|
||||
generic: GenericNode,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { CheckMethod } from '@/types'
|
||||
|
||||
describe('NODE_TYPE_LABELS', () => {
|
||||
it('has an entry for every node type', () => {
|
||||
const expectedTypes = ['isp', 'router', 'switch', 'server', 'proxmox', 'vm', 'lxc', 'nas', 'iot', 'ap', 'generic']
|
||||
const expectedTypes = ['isp', 'router', 'switch', 'server', 'proxmox', 'vm', 'lxc', 'nas', 'iot', 'ap', 'camera', 'generic']
|
||||
expectedTypes.forEach((t) => {
|
||||
expect(NODE_TYPE_LABELS).toHaveProperty(t)
|
||||
expect(typeof NODE_TYPE_LABELS[t as keyof typeof NODE_TYPE_LABELS]).toBe('string')
|
||||
|
||||
@@ -9,6 +9,7 @@ export type NodeType =
|
||||
| 'nas'
|
||||
| 'iot'
|
||||
| 'ap'
|
||||
| 'camera'
|
||||
| 'generic'
|
||||
|
||||
export type EdgeType = 'ethernet' | 'wifi' | 'iot' | 'vlan' | 'virtual' | 'cluster'
|
||||
@@ -67,6 +68,7 @@ export const NODE_TYPE_LABELS: Record<NodeType, string> = {
|
||||
nas: 'NAS',
|
||||
iot: 'IoT Device',
|
||||
ap: 'Access Point',
|
||||
camera: 'Camera',
|
||||
generic: 'Generic Device',
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user