Merge remote-tracking branch 'origin/main' into feat/electrical
This commit is contained in:
Generated
+14
-14
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frontend",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.2.0",
|
||||
"@dagrejs/dagre": "^2.0.4",
|
||||
@@ -3237,9 +3237,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ts-morph/common/node_modules/brace-expansion": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
@@ -3651,9 +3651,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4250,9 +4250,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
||||
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8040,9 +8040,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
|
||||
"integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
|
||||
"version": "6.15.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
|
||||
"integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -10,6 +10,7 @@ import { toast } from 'sonner'
|
||||
import { PendingDeviceModal, type PendingDevice } from '@/components/modals/PendingDeviceModal'
|
||||
import type { NodeType, ServiceInfo } from '@/types'
|
||||
import { buildZigbeeProperties, isZigbeeType } from '@/utils/zigbeeProperties'
|
||||
import { buildMacProperty } from '@/utils/macProperty'
|
||||
|
||||
interface PendingDevicesModalProps {
|
||||
open: boolean
|
||||
@@ -255,11 +256,12 @@ export function PendingDevicesModal({ open, onClose, highlightId, initialStatus
|
||||
const fallbackLabel = deviceLabel(device)
|
||||
const type = (device.suggested_type ?? 'generic') as NodeType
|
||||
const zigbee = isZigbeeType(type)
|
||||
const properties = zigbee ? buildZigbeeProperties(device) : []
|
||||
const properties = zigbee ? buildZigbeeProperties(device) : buildMacProperty(device.mac)
|
||||
const nodeData = {
|
||||
label: fallbackLabel,
|
||||
type,
|
||||
ip: device.ip ?? undefined,
|
||||
mac: device.mac ?? undefined,
|
||||
hostname: device.hostname ?? undefined,
|
||||
status: zigbee ? 'online' : 'unknown',
|
||||
services: (device.services ?? []) as ServiceInfo[],
|
||||
@@ -325,10 +327,11 @@ export function PendingDevicesModal({ open, onClose, highlightId, initialStatus
|
||||
label: deviceLabel(d),
|
||||
type,
|
||||
ip: d.ip ?? undefined,
|
||||
mac: d.mac ?? undefined,
|
||||
hostname: d.hostname ?? undefined,
|
||||
status: zigbee ? ('online' as const) : ('unknown' as const),
|
||||
services: (d.services ?? []) as ServiceInfo[],
|
||||
properties: zigbee ? buildZigbeeProperties(d) : [],
|
||||
properties: zigbee ? buildZigbeeProperties(d) : buildMacProperty(d.mac),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ const mockApprove = vi.fn()
|
||||
const mockHide = vi.fn()
|
||||
const mockPending = vi.fn()
|
||||
const mockHidden = vi.fn()
|
||||
const mockAddNode = vi.fn()
|
||||
|
||||
vi.mock('@/api/client', () => ({
|
||||
scanApi: {
|
||||
@@ -69,7 +70,7 @@ const DEVICE_ZIGBEE = {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(useCanvasStore).mockReturnValue({
|
||||
addNode: vi.fn(),
|
||||
addNode: mockAddNode,
|
||||
scanEventTs: 0,
|
||||
} as unknown as ReturnType<typeof useCanvasStore>)
|
||||
// setState is used by injectAutoEdges
|
||||
@@ -174,6 +175,34 @@ describe('PendingDevicesModal', () => {
|
||||
await waitFor(() => expect(mockBulkApprove).toHaveBeenCalledWith(['dev-a', 'dev-b']))
|
||||
})
|
||||
|
||||
it('bulk approve carries the scanned MAC onto the canvas node (#168)', async () => {
|
||||
render(<PendingDevicesModal {...baseProps} />)
|
||||
await waitFor(() => expect(screen.getByTestId('pending-card-dev-a')).toBeInTheDocument())
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Select mode' }))
|
||||
fireEvent.click(screen.getByTestId('pending-card-dev-a'))
|
||||
fireEvent.click(screen.getByTestId('pending-card-dev-b'))
|
||||
fireEvent.click(screen.getByRole('button', { name: /Approve \(2\)/ }))
|
||||
await waitFor(() => expect(mockAddNode).toHaveBeenCalledTimes(2))
|
||||
|
||||
// dev-a is an IP device with a MAC → node carries mac + a MAC property row.
|
||||
const ipNode = mockAddNode.mock.calls
|
||||
.map((c) => c[0])
|
||||
.find((n) => n.id === 'n1')
|
||||
expect(ipNode.data.mac).toBe('aa:bb:cc:dd:ee:01')
|
||||
expect(ipNode.data.properties).toContainEqual({
|
||||
key: 'MAC',
|
||||
value: 'aa:bb:cc:dd:ee:01',
|
||||
icon: null,
|
||||
visible: false,
|
||||
})
|
||||
|
||||
// dev-b is zigbee with no MAC → no MAC property row.
|
||||
const zbNode = mockAddNode.mock.calls
|
||||
.map((c) => c[0])
|
||||
.find((n) => n.id === 'n2')
|
||||
expect(zbNode.data.properties.some((p: { key: string }) => p.key === 'MAC')).toBe(false)
|
||||
})
|
||||
|
||||
it('bulk hide calls API with selected ids', async () => {
|
||||
render(<PendingDevicesModal {...baseProps} />)
|
||||
await waitFor(() => expect(screen.getByTestId('pending-card-dev-a')).toBeInTheDocument())
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { buildMacProperty } from '../macProperty'
|
||||
|
||||
describe('buildMacProperty', () => {
|
||||
it('returns a hidden MAC property row for a MAC', () => {
|
||||
expect(buildMacProperty('aa:bb:cc:dd:ee:ff')).toEqual([
|
||||
{ key: 'MAC', value: 'aa:bb:cc:dd:ee:ff', icon: null, visible: false },
|
||||
])
|
||||
})
|
||||
|
||||
it('returns an empty array when MAC is null/undefined/empty', () => {
|
||||
expect(buildMacProperty(null)).toEqual([])
|
||||
expect(buildMacProperty(undefined)).toEqual([])
|
||||
expect(buildMacProperty('')).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { NodeProperty } from '@/types'
|
||||
|
||||
/** Build the MAC address property row shown in the right panel.
|
||||
* Hidden by default — the user opts in to showing it on the canvas card.
|
||||
* Matches backend `build_mac_property`. Returns an empty array when no MAC. */
|
||||
export function buildMacProperty(mac?: string | null): NodeProperty[] {
|
||||
if (!mac) return []
|
||||
return [{ key: 'MAC', value: mac, icon: null, visible: false }]
|
||||
}
|
||||
Reference in New Issue
Block a user