feat: prompt on duplicate device instead of silently blocking/merging approve
Single-device approve now guards duplicates per-design the same way bulk approve does, and asks the user instead of failing or silently merging: - create_node and approve_device reject a same-design duplicate (ieee, ip or mac) with 409 + the existing node; a force flag creates it anyway. - Frontend shows a confirm dialog: go to existing node, add duplicate anyway, or cancel. - approve_device no longer rejects a device already on another canvas (status is global, canvas membership is per-design) — it can be placed on a new design, matching bulk approve. - IEEE (Zigbee/Z-Wave) devices now use the same prompt as ip/mac instead of auto-merging into the existing node. - bulk approve reports which devices it skipped as duplicates. Closes #260 ha-relevant: maybe
This commit is contained in:
@@ -77,6 +77,26 @@ export interface DeepScanConfig {
|
||||
|
||||
export type ScanConfigData = { ranges: string[] } & DeepScanConfig
|
||||
|
||||
// A device the backend refused to place because an equivalent node already
|
||||
// exists on the target design (same ip/mac/ieee). `existing_node_id` points at
|
||||
// the node already there so the UI can link to it.
|
||||
export interface SkippedDevice {
|
||||
device_id: string
|
||||
label: string
|
||||
match: 'ip' | 'mac' | 'ieee'
|
||||
value: string
|
||||
existing_node_id: string | null
|
||||
}
|
||||
|
||||
// 409 body from single approve / create when a same-design duplicate is found.
|
||||
export interface DuplicateNodeConflict {
|
||||
duplicate: true
|
||||
existing_node_id: string
|
||||
existing_label: string
|
||||
match: 'ip' | 'mac' | 'ieee'
|
||||
value: string
|
||||
}
|
||||
|
||||
export const scanApi = {
|
||||
trigger: (deepScan?: Partial<DeepScanConfig>) => api.post('/scan/trigger', deepScan ?? {}),
|
||||
pending: () => api.get('/scan/pending'),
|
||||
@@ -100,6 +120,7 @@ export const scanApi = {
|
||||
edges_created: number
|
||||
edges: { id: string; source: string; target: string; type?: string; source_handle?: string | null; target_handle?: string | null }[]
|
||||
skipped: number
|
||||
skipped_devices: SkippedDevice[]
|
||||
}>('/scan/pending/bulk-approve', { device_ids: ids, design_id: designId ?? undefined }),
|
||||
bulkHide: (ids: string[]) => api.post<{ hidden: number; skipped: number }>('/scan/pending/bulk-hide', { device_ids: ids }),
|
||||
restore: (id: string) => api.post<{ restored: boolean; device_id: string }>(`/scan/pending/${id}/restore`),
|
||||
|
||||
Reference in New Issue
Block a user