'use client'; import { useState, useCallback } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Textarea } from '@/components/ui/textarea'; import { Checkbox } from '@/components/ui/checkbox'; import { Loader2, ChevronDown, ChevronRight, RefreshCw, Pencil, Trash2, CheckCircle2, AlertTriangle, MinusCircle, Search, List, Plus, X, } from 'lucide-react'; import { toast } from 'sonner'; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- interface ZabbixTag { tag: string; value: string; } interface ZabbixMacro { macro: string; value: string; description?: string; } interface ZabbixGroup { groupid: string; name: string; } interface ZabbixInterface { type: number; main: number; useip: number; ip: string; dns: string; port: string; } interface ZabbixHostRow { hostid: string; host: string; name: string; status: string; description?: string; interfaces?: ZabbixInterface[]; groups?: ZabbixGroup[]; macros?: ZabbixMacro[]; tags?: ZabbixTag[]; rmmMatched: boolean | null; sourceTag: string | null; } interface Company { id: number; name: string; } interface HostManagerProps { companies: Company[]; } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function primaryIp(host: ZabbixHostRow): string { const iface = host.interfaces?.find((i) => i.main === 1 || (i.main as any) === '1'); return iface?.ip ?? '—'; } function tagValue(host: ZabbixHostRow, key: string): string | null { return host.tags?.find((t) => t.tag === key)?.value ?? null; } function macroValue(host: ZabbixHostRow, key: string): string | null { return host.macros?.find((m) => m.macro === key)?.value ?? null; } function clientLabel(host: ZabbixHostRow): string | null { return tagValue(host, 'client') ?? macroValue(host, '{$AUTOTASK_COMPANY_NAME}'); } // --------------------------------------------------------------------------- // Edit Modal // --------------------------------------------------------------------------- interface EditModalProps { host: ZabbixHostRow; companies: Company[]; onClose: () => void; onSaved: (updated: ZabbixHostRow) => void; } function EditModal({ host, companies, onClose, onSaved }: EditModalProps) { const [name, setName] = useState(host.name); const [ip, setIp] = useState(primaryIp(host)); const [description, setDescription] = useState(host.description ?? ''); const [companyId, setCompanyId] = useState(() => { const id = macroValue(host, '{$AUTOTASK_COMPANY_ID}'); return id ?? 'none'; }); const [tags, setTags] = useState(() => host.tags ? [...host.tags] : []); const [macros, setMacros] = useState(() => host.macros ? [...host.macros] : []); const [saving, setSaving] = useState(false); const [rebuildFromClient, setRebuildFromClient] = useState(false); const ipv4Valid = /^(\d{1,3}\.){3}\d{1,3}$/.test(ip); const addTag = () => setTags((t) => [...t, { tag: '', value: '' }]); const removeTag = (i: number) => setTags((t) => t.filter((_, idx) => idx !== i)); const updateTag = (i: number, field: 'tag' | 'value', val: string) => setTags((t) => t.map((item, idx) => idx === i ? { ...item, [field]: val } : item)); const addMacro = () => setMacros((m) => [...m, { macro: '{$}', value: '', description: '' }]); const removeMacro = (i: number) => setMacros((m) => m.filter((_, idx) => idx !== i)); const updateMacro = (i: number, field: keyof ZabbixMacro, val: string) => setMacros((m) => m.map((item, idx) => idx === i ? { ...item, [field]: val } : item)); const handleSave = async () => { setSaving(true); try { const resp = await fetch(`/api/zabbix/hosts/${host.hostid}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name.trim(), ip: ip.trim(), description, companyId: companyId && companyId !== 'none' ? Number(companyId) : null, tags, macros, rebuildFromClient, }), }); const data = await resp.json(); if (!resp.ok) { toast.error(data.error ?? 'Save failed'); return; } toast.success(`Host "${name}" saved`); // Return updated row (optimistic — caller will refresh) onSaved({ ...host, name, description, tags, macros }); } catch (err) { toast.error('Save failed: ' + String(err)); } finally { setSaving(false); } }; return ( { if (!o) onClose(); }}> Edit Host {host.hostid}
{/* Name + IP */}
setName(e.target.value)} />
setIp(e.target.value)} className={`font-mono ${ip && !ipv4Valid ? 'border-destructive' : ''}`} /> {ip && !ipv4Valid &&

Invalid IPv4

}
{/* Description */}