From 5822a1483a18d61862cdc40c521c91b46742e965 Mon Sep 17 00:00:00 2001 From: Pouzor Date: Thu, 14 May 2026 00:45:58 +0200 Subject: [PATCH] fix(zigbee): inject IEEE/Vendor/Model/LQI props into approved nodes client-side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend already writes the properties on approve, but the frontend's local addNode() call rebuilt NodeData without them — so newly approved zigbee nodes showed an empty right panel until a full canvas reload. Build the property list on the client from the PendingDevice fields so the canvas state matches the DB row immediately. Single + bulk approve flows updated. --- .../components/modals/PendingDevicesModal.tsx | 20 +++++++++++----- frontend/src/utils/zigbeeProperties.ts | 23 +++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 frontend/src/utils/zigbeeProperties.ts diff --git a/frontend/src/components/modals/PendingDevicesModal.tsx b/frontend/src/components/modals/PendingDevicesModal.tsx index 41a86ee..973b492 100644 --- a/frontend/src/components/modals/PendingDevicesModal.tsx +++ b/frontend/src/components/modals/PendingDevicesModal.tsx @@ -9,6 +9,7 @@ import { useCanvasStore } from '@/stores/canvasStore' import { toast } from 'sonner' import { PendingDeviceModal, type PendingDevice } from '@/components/modals/PendingDeviceModal' import type { NodeType, ServiceInfo } from '@/types' +import { buildZigbeeProperties, isZigbeeType } from '@/utils/zigbeeProperties' interface PendingDevicesModalProps { open: boolean @@ -252,13 +253,17 @@ export function PendingDevicesModal({ open, onClose, highlightId, initialStatus const handleApprove = async (device: PendingDevice) => { try { const fallbackLabel = deviceLabel(device) + const type = (device.suggested_type ?? 'generic') as NodeType + const zigbee = isZigbeeType(type) + const properties = zigbee ? buildZigbeeProperties(device) : [] const nodeData = { label: fallbackLabel, - type: (device.suggested_type ?? 'generic') as NodeType, + type, ip: device.ip ?? undefined, hostname: device.hostname ?? undefined, - status: 'unknown', + status: zigbee ? 'online' : 'unknown', services: (device.services ?? []) as ServiceInfo[], + properties, } const res = await scanApi.approve(device.id, nodeData) const nodeId = res.data.node_id @@ -266,7 +271,7 @@ export function PendingDevicesModal({ open, onClose, highlightId, initialStatus id: nodeId, type: nodeData.type, position: { x: 400, y: 300 }, - data: { ...nodeData, status: 'unknown' as const }, + data: { ...nodeData, status: zigbee ? ('online' as const) : ('unknown' as const) }, }) injectAutoEdges(res.data.edges) const extra = res.data.edges_created > 0 ? ` (+${res.data.edges_created} link${res.data.edges_created !== 1 ? 's' : ''})` : '' @@ -310,17 +315,20 @@ export function PendingDevicesModal({ open, onClose, highlightId, initialStatus approvedDevices.forEach((d, i) => { const nodeId = deviceToNode[d.id] if (!nodeId) return + const type = (d.suggested_type ?? 'generic') as NodeType + const zigbee = isZigbeeType(type) addNode({ id: nodeId, - type: (d.suggested_type ?? 'generic') as NodeType, + type, position: { x: 400 + (i % 4) * 160, y: 300 + Math.floor(i / 4) * 100 }, data: { label: deviceLabel(d), - type: (d.suggested_type ?? 'generic') as NodeType, + type, ip: d.ip ?? undefined, hostname: d.hostname ?? undefined, - status: 'unknown' as const, + status: zigbee ? ('online' as const) : ('unknown' as const), services: (d.services ?? []) as ServiceInfo[], + properties: zigbee ? buildZigbeeProperties(d) : [], }, }) }) diff --git a/frontend/src/utils/zigbeeProperties.ts b/frontend/src/utils/zigbeeProperties.ts new file mode 100644 index 0000000..6fdc4ef --- /dev/null +++ b/frontend/src/utils/zigbeeProperties.ts @@ -0,0 +1,23 @@ +import type { NodeProperty, NodeType } from '@/types' + +const ZIGBEE_TYPES: NodeType[] = ['zigbee_coordinator', 'zigbee_router', 'zigbee_enddevice'] + +export function isZigbeeType(type: NodeType | string | undefined | null): boolean { + return !!type && (ZIGBEE_TYPES as string[]).includes(type) +} + +/** Build the IEEE/Vendor/Model/LQI property rows shown in the right panel. + * Matches backend `build_zigbee_properties`. */ +export function buildZigbeeProperties(input: { + ieee_address?: string | null + vendor?: string | null + model?: string | null + lqi?: number | null +}): NodeProperty[] { + const props: NodeProperty[] = [] + if (input.ieee_address) props.push({ key: 'IEEE', value: input.ieee_address, icon: null, visible: true }) + if (input.vendor) props.push({ key: 'Vendor', value: input.vendor, icon: null, visible: true }) + if (input.model) props.push({ key: 'Model', value: input.model, icon: null, visible: true }) + if (input.lqi != null) props.push({ key: 'LQI', value: String(input.lqi), icon: null, visible: true }) + return props +}