fix(zigbee): inject IEEE/Vendor/Model/LQI props into approved nodes client-side
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.
This commit is contained in:
@@ -9,6 +9,7 @@ import { useCanvasStore } from '@/stores/canvasStore'
|
|||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { PendingDeviceModal, type PendingDevice } from '@/components/modals/PendingDeviceModal'
|
import { PendingDeviceModal, type PendingDevice } from '@/components/modals/PendingDeviceModal'
|
||||||
import type { NodeType, ServiceInfo } from '@/types'
|
import type { NodeType, ServiceInfo } from '@/types'
|
||||||
|
import { buildZigbeeProperties, isZigbeeType } from '@/utils/zigbeeProperties'
|
||||||
|
|
||||||
interface PendingDevicesModalProps {
|
interface PendingDevicesModalProps {
|
||||||
open: boolean
|
open: boolean
|
||||||
@@ -252,13 +253,17 @@ export function PendingDevicesModal({ open, onClose, highlightId, initialStatus
|
|||||||
const handleApprove = async (device: PendingDevice) => {
|
const handleApprove = async (device: PendingDevice) => {
|
||||||
try {
|
try {
|
||||||
const fallbackLabel = deviceLabel(device)
|
const fallbackLabel = deviceLabel(device)
|
||||||
|
const type = (device.suggested_type ?? 'generic') as NodeType
|
||||||
|
const zigbee = isZigbeeType(type)
|
||||||
|
const properties = zigbee ? buildZigbeeProperties(device) : []
|
||||||
const nodeData = {
|
const nodeData = {
|
||||||
label: fallbackLabel,
|
label: fallbackLabel,
|
||||||
type: (device.suggested_type ?? 'generic') as NodeType,
|
type,
|
||||||
ip: device.ip ?? undefined,
|
ip: device.ip ?? undefined,
|
||||||
hostname: device.hostname ?? undefined,
|
hostname: device.hostname ?? undefined,
|
||||||
status: 'unknown',
|
status: zigbee ? 'online' : 'unknown',
|
||||||
services: (device.services ?? []) as ServiceInfo[],
|
services: (device.services ?? []) as ServiceInfo[],
|
||||||
|
properties,
|
||||||
}
|
}
|
||||||
const res = await scanApi.approve(device.id, nodeData)
|
const res = await scanApi.approve(device.id, nodeData)
|
||||||
const nodeId = res.data.node_id
|
const nodeId = res.data.node_id
|
||||||
@@ -266,7 +271,7 @@ export function PendingDevicesModal({ open, onClose, highlightId, initialStatus
|
|||||||
id: nodeId,
|
id: nodeId,
|
||||||
type: nodeData.type,
|
type: nodeData.type,
|
||||||
position: { x: 400, y: 300 },
|
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)
|
injectAutoEdges(res.data.edges)
|
||||||
const extra = res.data.edges_created > 0 ? ` (+${res.data.edges_created} link${res.data.edges_created !== 1 ? 's' : ''})` : ''
|
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) => {
|
approvedDevices.forEach((d, i) => {
|
||||||
const nodeId = deviceToNode[d.id]
|
const nodeId = deviceToNode[d.id]
|
||||||
if (!nodeId) return
|
if (!nodeId) return
|
||||||
|
const type = (d.suggested_type ?? 'generic') as NodeType
|
||||||
|
const zigbee = isZigbeeType(type)
|
||||||
addNode({
|
addNode({
|
||||||
id: nodeId,
|
id: nodeId,
|
||||||
type: (d.suggested_type ?? 'generic') as NodeType,
|
type,
|
||||||
position: { x: 400 + (i % 4) * 160, y: 300 + Math.floor(i / 4) * 100 },
|
position: { x: 400 + (i % 4) * 160, y: 300 + Math.floor(i / 4) * 100 },
|
||||||
data: {
|
data: {
|
||||||
label: deviceLabel(d),
|
label: deviceLabel(d),
|
||||||
type: (d.suggested_type ?? 'generic') as NodeType,
|
type,
|
||||||
ip: d.ip ?? undefined,
|
ip: d.ip ?? undefined,
|
||||||
hostname: d.hostname ?? undefined,
|
hostname: d.hostname ?? undefined,
|
||||||
status: 'unknown' as const,
|
status: zigbee ? ('online' as const) : ('unknown' as const),
|
||||||
services: (d.services ?? []) as ServiceInfo[],
|
services: (d.services ?? []) as ServiceInfo[],
|
||||||
|
properties: zigbee ? buildZigbeeProperties(d) : [],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user