import { useState } from 'react'; import { Card, CardHeader, CardTitle, CardContent } from './ui/card'; import { Button } from './ui/button'; import { Input, Label } from './ui'; import { Alert, AlertDescription } from './ui/alert'; import { Wifi, Loader2, Edit2, Check, X, Trash2, Plus, RotateCw, CheckCircle, AlertCircle } from 'lucide-react'; import { Badge } from './ui/badge'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useDhcp } from '../hooks/useDhcp'; import { useDnsmasq } from '../hooks/useDnsmasq'; import { dhcpConfigApi, type DHCPConfig } from '../services/api/settings'; import type { DhcpStaticLease } from '../services/api/networking'; export function DhcpComponent() { const queryClient = useQueryClient(); const { data: dhcpConfig } = useQuery({ queryKey: ['dhcp', 'config'], queryFn: () => dhcpConfigApi.get() }); const dhcpMutation = useMutation({ mutationFn: (data: Partial) => dhcpConfigApi.update(data), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['dhcp', 'config'] }), }); const isUpdating = dhcpMutation.isPending; const { leases, isLoadingLeases, refetchLeases, addStatic, isAddingStatic, deleteStatic } = useDhcp(); const { generateConfig: generateDnsConfig } = useDnsmasq(); const [editingDhcpConfig, setEditingDhcpConfig] = useState(false); const [dhcpConfigForm, setDhcpConfigForm] = useState({ enabled: false, rangeStart: '', rangeEnd: '', leaseTime: '24h', gateway: '' }); const [showAddLease, setShowAddLease] = useState(false); const [newLease, setNewLease] = useState({ mac: '', ip: '', hostname: '' }); const [successMessage, setSuccessMessage] = useState(null); const [errorMessage, setErrorMessage] = useState(null); const showSuccess = (msg: string) => { setSuccessMessage(msg); setErrorMessage(null); setTimeout(() => setSuccessMessage(null), 5000); }; const showError = (msg: string) => { setErrorMessage(msg); setSuccessMessage(null); setTimeout(() => setErrorMessage(null), 8000); }; const handleDhcpConfigEdit = () => { setDhcpConfigForm({ enabled: dhcpConfig?.enabled ?? false, rangeStart: dhcpConfig?.rangeStart ?? '', rangeEnd: dhcpConfig?.rangeEnd ?? '', leaseTime: dhcpConfig?.leaseTime ?? '24h', gateway: dhcpConfig?.gateway ?? '', }); setEditingDhcpConfig(true); }; const handleDhcpConfigSave = async () => { try { await dhcpMutation.mutateAsync({ enabled: dhcpConfigForm.enabled, rangeStart: dhcpConfigForm.rangeStart, rangeEnd: dhcpConfigForm.rangeEnd, leaseTime: dhcpConfigForm.leaseTime, gateway: dhcpConfigForm.gateway || undefined, }); setEditingDhcpConfig(false); if (dhcpConfigForm.enabled) { generateDnsConfig(true); } showSuccess(dhcpConfigForm.enabled ? 'DHCP configuration saved and applied' : 'DHCP disabled'); } catch (e) { showError(`Failed to save DHCP config: ${e instanceof Error ? e.message : String(e)}`); } }; return (

DHCP

Assigns IP addresses to devices on your local network

{successMessage && ( {successMessage} )} {errorMessage && ( {errorMessage} )}
Configuration
{dhcpConfig?.enabled && } {dhcpConfig?.enabled ? 'Enabled' : 'Disabled'} {!editingDhcpConfig && ( )}
{editingDhcpConfig ? (
setDhcpConfigForm(prev => ({ ...prev, enabled: e.target.checked }))} />
{dhcpConfigForm.enabled && ( <>
setDhcpConfigForm(prev => ({ ...prev, rangeStart: e.target.value }))} placeholder="192.168.8.100" className="mt-1 font-mono text-sm" />
setDhcpConfigForm(prev => ({ ...prev, rangeEnd: e.target.value }))} placeholder="192.168.8.200" className="mt-1 font-mono text-sm" />
setDhcpConfigForm(prev => ({ ...prev, leaseTime: e.target.value }))} placeholder="24h" className="mt-1 font-mono text-sm" />
setDhcpConfigForm(prev => ({ ...prev, gateway: e.target.value }))} placeholder="192.168.8.1" className="mt-1 font-mono text-sm" />
)}
) : (
{dhcpConfig?.enabled ? ( <>
Range: {dhcpConfig!.rangeStart} – {dhcpConfig!.rangeEnd}
Lease: {dhcpConfig!.leaseTime || '24h'}
{dhcpConfig!.gateway && (
Gateway: {dhcpConfig!.gateway}
)} ) : (

DHCP is disabled. Enable it to assign IPs to LAN devices.

)}
)}
Leases
{showAddLease && (
setNewLease(l => ({ ...l, mac: e.target.value }))} placeholder="aa:bb:cc:dd:ee:ff" className="mt-1 font-mono text-xs" />
setNewLease(l => ({ ...l, ip: e.target.value }))} placeholder="192.168.8.10" className="mt-1 font-mono text-xs" />
setNewLease(l => ({ ...l, hostname: e.target.value }))} placeholder="node1" className="mt-1 text-xs" />
)} {isLoadingLeases ? (
) : leases.length === 0 ? (

No active leases. DHCP must be enabled in config.

) : (
{leases.map((lease) => ( ))}
MAC IP Hostname Expires
{lease.mac} {lease.ip} {lease.hostname || '—'} {lease.expiry ? new Date(lease.expiry).toLocaleString() : '—'}
)}
); }