Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8541922386 | |||
| 3ed9cb0d4f | |||
| fff11a4b6a |
@@ -225,18 +225,18 @@ async def approve_device(
|
||||
if device.status != "pending":
|
||||
raise HTTPException(status_code=409, detail="Device already processed")
|
||||
device.status = "approved"
|
||||
_zigbee_types = {"zigbee_coordinator", "zigbee_router", "zigbee_enddevice"}
|
||||
_is_zigbee = node_data.type in _zigbee_types
|
||||
node = Node(
|
||||
label=node_data.label,
|
||||
type=node_data.type,
|
||||
ip=node_data.ip,
|
||||
hostname=node_data.hostname,
|
||||
status=node_data.status,
|
||||
status="online" if _is_zigbee else node_data.status,
|
||||
services=node_data.services or [],
|
||||
ieee_address=device.ieee_address,
|
||||
# Honour caller-supplied check_method, else default to ping when an IP exists
|
||||
# so the scheduler doesn't silently skip the new node.
|
||||
check_method=node_data.check_method or ("ping" if node_data.ip else None),
|
||||
check_target=node_data.check_target,
|
||||
check_method="none" if _is_zigbee else (node_data.check_method or ("ping" if node_data.ip else None)),
|
||||
check_target=None if _is_zigbee else node_data.check_target,
|
||||
)
|
||||
db.add(node)
|
||||
await db.flush()
|
||||
|
||||
@@ -157,7 +157,8 @@ async def _persist_pending_import(
|
||||
node = Node(
|
||||
label=label,
|
||||
type=n.get("type") or "zigbee_coordinator",
|
||||
status="unknown",
|
||||
status="online",
|
||||
check_method="none",
|
||||
ieee_address=ieee,
|
||||
services=[],
|
||||
)
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "2.0.2",
|
||||
"version": "2.0.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frontend",
|
||||
"version": "1.13.0",
|
||||
"version": "2.0.3",
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.2.0",
|
||||
"@dagrejs/dagre": "^2.0.4",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "2.0.2",
|
||||
"version": "2.0.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -22,6 +22,7 @@ const NODE_TYPE_GROUPS: { label: string; types: NodeType[] }[] = [
|
||||
|
||||
const CHECK_METHODS: CheckMethod[] = ['none', 'ping', 'http', 'https', 'tcp', 'ssh', 'prometheus', 'health']
|
||||
const CONTAINER_MODE_TYPES: NodeType[] = ['proxmox', 'vm', 'lxc', 'docker_host']
|
||||
const ZIGBEE_TYPES: NodeType[] = ['zigbee_coordinator', 'zigbee_router', 'zigbee_enddevice']
|
||||
|
||||
const CHECK_METHOD_LABELS: Record<CheckMethod, string> = {
|
||||
none: 'None',
|
||||
@@ -59,7 +60,9 @@ interface NodeModalProps {
|
||||
// NodeModal is always mounted with a key that changes on open/edit, so useState
|
||||
// initial value is enough - no need for a reset effect.
|
||||
export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node', parentContainerNodes = [] }: NodeModalProps) {
|
||||
const [form, setForm] = useState<Partial<NodeData>>({ ...DEFAULT_DATA, ...initial })
|
||||
const merged = { ...DEFAULT_DATA, ...initial }
|
||||
if (ZIGBEE_TYPES.includes((merged.type ?? '') as NodeType)) merged.check_method = 'none'
|
||||
const [form, setForm] = useState<Partial<NodeData>>(merged)
|
||||
const [iconSearch, setIconSearch] = useState('')
|
||||
const [iconPickerOpen, setIconPickerOpen] = useState(false)
|
||||
const [iconTab, setIconTab] = useState<'generic' | 'brand'>(isBrandIconKey(initial?.custom_icon) ? 'brand' : 'generic')
|
||||
@@ -107,7 +110,10 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
||||
{/* Type + Icon on the same row */}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label className="text-xs text-muted-foreground">Type</Label>
|
||||
<Select value={form.type} onValueChange={(v) => set('type', v as NodeType)}>
|
||||
<Select value={form.type} onValueChange={(v) => {
|
||||
const t = v as NodeType
|
||||
setForm((f) => ({ ...f, type: t, ...(ZIGBEE_TYPES.includes(t) ? { check_method: 'none' as CheckMethod } : {}) }))
|
||||
}}>
|
||||
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 w-full cursor-pointer ${modalStyles['modal-interactive']} ${modalStyles['modal-radius']}`} aria-label="Node type selector">
|
||||
<SelectValue>{NODE_TYPE_LABELS[(form.type ?? 'server') as NodeType]}</SelectValue>
|
||||
</SelectTrigger>
|
||||
@@ -289,31 +295,35 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
||||
<span className="text-[10px] text-muted-foreground/50">comma-separated</span>
|
||||
</div>
|
||||
|
||||
{/* Check method */}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label className="text-xs text-muted-foreground">Check Method</Label>
|
||||
<Select value={form.check_method ?? 'ping'} onValueChange={(v) => set('check_method', v as CheckMethod)}>
|
||||
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 cursor-pointer ${modalStyles['modal-interactive']} ${modalStyles['modal-radius']}`} aria-label="Check method selector">
|
||||
<SelectValue>{CHECK_METHOD_LABELS[(form.check_method ?? 'ping') as CheckMethod]}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
||||
{CHECK_METHODS.map((m) => (
|
||||
<SelectItem key={m} value={m} className="text-sm">{CHECK_METHOD_LABELS[m]}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{/* Check method — hidden for zigbee nodes (always none/online) */}
|
||||
{!ZIGBEE_TYPES.includes((form.type ?? '') as NodeType) && (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label className="text-xs text-muted-foreground">Check Method</Label>
|
||||
<Select value={form.check_method ?? 'ping'} onValueChange={(v) => set('check_method', v as CheckMethod)}>
|
||||
<SelectTrigger className={`bg-[#21262d] border-[#30363d] text-sm h-8 cursor-pointer ${modalStyles['modal-interactive']} ${modalStyles['modal-radius']}`} aria-label="Check method selector">
|
||||
<SelectValue>{CHECK_METHOD_LABELS[(form.check_method ?? 'ping') as CheckMethod]}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-[#21262d] border-[#30363d]">
|
||||
{CHECK_METHODS.map((m) => (
|
||||
<SelectItem key={m} value={m} className="text-sm">{CHECK_METHOD_LABELS[m]}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Check target */}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label className="text-xs text-muted-foreground">Check Target</Label>
|
||||
<Input
|
||||
value={form.check_target ?? ''}
|
||||
onChange={(e) => set('check_target', e.target.value)}
|
||||
placeholder="http://..."
|
||||
className={`bg-[#21262d] border-[#30363d] font-mono text-sm h-8 ${modalStyles['modal-radius']}`}
|
||||
/>
|
||||
</div>
|
||||
{/* Check target — hidden for zigbee nodes */}
|
||||
{!ZIGBEE_TYPES.includes((form.type ?? '') as NodeType) && (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label className="text-xs text-muted-foreground">Check Target</Label>
|
||||
<Input
|
||||
value={form.check_target ?? ''}
|
||||
onChange={(e) => set('check_target', e.target.value)}
|
||||
placeholder="http://..."
|
||||
className={`bg-[#21262d] border-[#30363d] font-mono text-sm h-8 ${modalStyles['modal-radius']}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Parent container */}
|
||||
{form.type !== 'groupRect' && form.type !== 'group' && filteredParentNodes.length > 0 && (
|
||||
|
||||
@@ -433,4 +433,24 @@ describe('NodeModal', () => {
|
||||
const slider = screen.getByLabelText('Bottom connection points slider') as HTMLInputElement
|
||||
expect(slider.value).toBe('48')
|
||||
})
|
||||
|
||||
// ── Zigbee nodes ──────────────────────────────────────────────────────
|
||||
|
||||
const zigbeeTypes = ['zigbee_coordinator', 'zigbee_router', 'zigbee_enddevice'] as const
|
||||
|
||||
it.each(zigbeeTypes)('hides Check Method for %s type', (type) => {
|
||||
renderModal({ initial: { ...BASE, type } })
|
||||
expect(screen.queryByText('Check Method')).toBeNull()
|
||||
})
|
||||
|
||||
it.each(zigbeeTypes)('hides Check Target for %s type', (type) => {
|
||||
renderModal({ initial: { ...BASE, type } })
|
||||
expect(screen.queryByText('Check Target')).toBeNull()
|
||||
})
|
||||
|
||||
it.each(zigbeeTypes)('submits check_method=none for %s type', (type) => {
|
||||
const { onSubmit } = renderModal({ initial: { ...BASE, type, label: 'Zigbee Node' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Add' }))
|
||||
expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).check_method).toBe('none')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user