import { useState } from 'react'; import { NavLink } from 'react-router'; import { Card, CardHeader, CardTitle, CardContent } from './ui/card'; import { Button } from './ui/button'; import { Input, Label } from './ui'; import { Textarea } from './ui/textarea'; import { Alert, AlertDescription } from './ui/alert'; import { Router, Globe, Loader2, AlertCircle, CheckCircle, XCircle, Play, RotateCw, Copy, ChevronDown, ChevronUp, Edit2, RefreshCw, ExternalLink, TestTube2, } from 'lucide-react'; import { Badge } from './ui/badge'; import { useConfig } from '../hooks'; import { useCloudflare } from '../hooks/useCloudflare'; import { useDnsmasq } from '../hooks/useDnsmasq'; import { useDdns } from '../hooks/useDdns'; import { apiService } from '../services/api-legacy'; import type { DdnsRecordStatus } from '../services/api/networking'; export function DnsComponent() { const { config: globalConfig, updateConfig, isUpdating } = useConfig(); // ── LAN DNS (dnsmasq) ── const { status: dnsStatus, isLoadingStatus: isDnsStatusLoading, config: dnsConfig, fetchConfig: fetchDnsConfig, generateConfig: generateDnsConfig, isGenerating: isDnsGenerating, generateData: dnsGenerateData, restart: restartDns, isRestarting: isDnsRestarting, restartData: dnsRestartData, generateError: dnsGenerateError, restartError: dnsRestartError, } = useDnsmasq(); const [showDnsAdvanced, setShowDnsAdvanced] = useState(false); const [editedDnsConfig, setEditedDnsConfig] = useState(''); const [originalDnsConfig, setOriginalDnsConfig] = useState(''); const [copiedDnsIp, setCopiedDnsIp] = useState(false); const isDnsRunning = dnsStatus?.status === 'active'; const dnsIp = dnsStatus?.ip; const handleCopyDnsIp = () => { if (dnsIp) { navigator.clipboard.writeText(dnsIp); setCopiedDnsIp(true); setTimeout(() => setCopiedDnsIp(false), 2000); } }; const handleShowDnsAdvanced = async () => { if (!showDnsAdvanced) { const result = await fetchDnsConfig(); const content = result.data?.content || dnsConfig?.content || ''; setEditedDnsConfig(content); setOriginalDnsConfig(content); } setShowDnsAdvanced(!showDnsAdvanced); }; const handleSaveDnsConfig = async () => { try { await apiService.writeDnsmasqConfig(editedDnsConfig); fetchDnsConfig(); } catch (error) { console.error('Failed to save config:', error); } }; const handleSaveAndRestartDns = async () => { try { await apiService.writeDnsmasqConfig(editedDnsConfig); await apiService.restartDnsmasq(); fetchDnsConfig(); } catch (error) { console.error('Failed to save config and restart:', error); } }; // ── Dynamic DNS ── const { status: ddnsStatus, isLoadingStatus: isDdnsLoading, trigger: triggerDdns, isTriggering: isDdnsTriggering } = useDdns(); const { verification: cfVerification } = useCloudflare(); const [editingDdnsConfig, setEditingDdnsConfig] = useState(false); const [ddnsConfigForm, setDdnsConfigForm] = useState({ enabled: false, records: '', intervalMinutes: 5 }); const handleDdnsConfigEdit = () => { setDdnsConfigForm({ enabled: globalConfig?.cloud?.ddns?.enabled ?? false, records: (globalConfig?.cloud?.ddns?.records ?? []).join('\n'), intervalMinutes: globalConfig?.cloud?.ddns?.intervalMinutes ?? 5, }); setEditingDdnsConfig(true); }; const handleDdnsConfigSave = async () => { if (!globalConfig) return; const records = ddnsConfigForm.records.split('\n').map(r => r.trim()).filter(Boolean); await updateConfig({ ...globalConfig, cloud: { ...globalConfig.cloud, ddns: { enabled: ddnsConfigForm.enabled, provider: 'cloudflare', records, intervalMinutes: ddnsConfigForm.intervalMinutes, }, }, }); setEditingDdnsConfig(false); }; // ── DNS Lookup ── const [lookupDomain, setLookupDomain] = useState(''); const [lookupTesting, setLookupTesting] = useState<'external' | 'internal' | null>(null); const [lookupResult, setLookupResult] = useState<{ type: string; success: boolean; message: string } | null>(null); const handleLookup = async (type: 'external' | 'internal') => { const domain = lookupDomain.trim(); if (!domain) return; setLookupTesting(type); setLookupResult(null); try { if (type === 'external') { const response = await fetch(`https://cloudflare-dns.com/dns-query?name=${domain}&type=A`, { headers: { accept: 'application/dns-json' }, }); const data = await response.json(); if (data.Status === 0 && data.Answer?.length > 0) { const aRecord = data.Answer.find((a: { type: number; data: string }) => a.type === 1); const ip = aRecord ? aRecord.data : data.Answer[data.Answer.length - 1].data; setLookupResult({ type: 'External', success: true, message: `Resolves to ${ip}` }); } else { setLookupResult({ type: 'External', success: false, message: 'No public DNS record found' }); } } else { const response = await fetch(`/api/v1/network/resolve?domain=${domain}`); const data = await response.json(); if (data.success && data.ip) { setLookupResult({ type: 'LAN', success: true, message: `Resolves to ${data.ip}` }); } else { setLookupResult({ type: 'LAN', success: false, message: data.error || 'Not resolved on LAN' }); } } } catch { setLookupResult({ type: type === 'external' ? 'External' : 'LAN', success: false, message: 'Lookup failed' }); } finally { setLookupTesting(null); } }; return (

DNS

LAN name resolution and public DNS record management

{/* ── LAN DNS ── */}
LAN DNS
{isDnsStatusLoading ? ( ) : isDnsRunning ? ( ) : null} {isDnsStatusLoading ? 'Checking...' : isDnsRunning ? 'Running' : 'Stopped'}
Set as primary DNS in your router
{dnsIp || 'Auto-detecting...'}
{globalConfig?.cloud?.router?.ip && (
Router: {globalConfig.cloud.router.ip}
)}
{!isDnsRunning ? ( ) : ( )}
{(dnsGenerateData || dnsRestartData) && ( {dnsGenerateData?.message || dnsRestartData?.message} )} {(dnsGenerateError || dnsRestartError) && ( {(dnsGenerateError || dnsRestartError)?.toString()} )} {showDnsAdvanced && (
Config file