import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Collapse, Dropdown, Empty, Input, InputNumber, Modal, Select, Space, Switch, Table } from 'antd'; import { PlusOutlined, MoreOutlined, EditOutlined, DeleteOutlined, MenuOutlined } from '@ant-design/icons'; import type { ColumnsType } from 'antd/es/table'; import SettingListItem from '@/components/SettingListItem'; import DnsServerModal from './DnsServerModal'; import type { DnsServerValue } from './DnsServerModal'; import DnsPresetsModal from './DnsPresetsModal'; import type { XraySettingsValue, SetTemplate } from '@/hooks/useXraySetting'; import { DnsQueryStrategySchema, type DnsObject } from '@/schemas/dns'; import './DnsTab.css'; interface DnsTabProps { templateSettings: XraySettingsValue | null; setTemplateSettings: SetTemplate; } const STRATEGIES = DnsQueryStrategySchema.options; const DEFAULT_FAKEDNS = () => ({ ipPool: '198.18.0.0/15', poolSize: 65535 }); type DnsConfig = Omit & { servers?: DnsServerValue[] }; interface HostRow { domain: string; values: string[]; } interface FakednsRow { ipPool: string; poolSize: number; } function addrFor(server: DnsServerValue): string { return typeof server === 'string' ? server : server?.address || ''; } function domainsFor(server: DnsServerValue): string { return typeof server === 'object' && server !== null ? (server.domains || []).join(',') : ''; } function expectedIPsFor(server: DnsServerValue): string { if (typeof server !== 'object' || !server) return ''; const list = server.expectedIPs || server.expectIPs || []; return Array.isArray(list) ? list.join(',') : ''; } export default function DnsTab({ templateSettings, setTemplateSettings }: DnsTabProps) { const { t } = useTranslation(); const [modal, modalContextHolder] = Modal.useModal(); const [hostsList, setHostsList] = useState([]); const [serverModalOpen, setServerModalOpen] = useState(false); const [editingServer, setEditingServer] = useState(null); const [editingIndex, setEditingIndex] = useState(null); const [presetsModalOpen, setPresetsModalOpen] = useState(false); const dns = (templateSettings?.dns as DnsConfig | undefined) ?? null; const dnsEnabled = !!dns; const mutate = useCallback( (mutator: (next: XraySettingsValue) => void) => { setTemplateSettings((prev) => { if (!prev) return prev; const clone = JSON.parse(JSON.stringify(prev)) as XraySettingsValue; mutator(clone); return clone; }); }, [setTemplateSettings], ); function toggleDNS(enabled: boolean) { mutate((next) => { if (enabled) { (next as { dns?: DnsConfig }).dns = { tag: 'dns_inbound', queryStrategy: 'UseIP', disableCache: false, disableFallback: false, disableFallbackIfMatch: false, useSystemHosts: false, enableParallelQuery: false, serveStale: false, serveExpiredTTL: 0, hosts: {}, servers: [], }; next.fakedns = null; } else { delete next.dns; delete next.fakedns; } }); } useEffect(() => { if (!dns) { setHostsList([]); return; } const src = dns.hosts || {}; setHostsList( Object.entries(src).map(([domain, val]) => ({ domain, values: Array.isArray(val) ? [...val] : [String(val)], })), ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [dnsEnabled]); function syncHosts(next: HostRow[]) { setHostsList(next); mutate((tt) => { if (!tt.dns) return; const obj: Record = {}; for (const row of next) { if (!row.domain) continue; const vals = (row.values || []).filter(Boolean); if (vals.length === 0) continue; obj[row.domain] = vals.length === 1 ? vals[0] : vals; } if (Object.keys(obj).length > 0) { (tt.dns as DnsConfig).hosts = obj; } else if ('hosts' in (tt.dns as DnsConfig)) { delete (tt.dns as DnsConfig).hosts; } }); } function setDnsField(key: K, value: DnsConfig[K], omit = false) { mutate((tt) => { if (!tt.dns) return; if (omit && (value == null || (typeof value === 'string' && value.trim() === ''))) { delete (tt.dns as Record)[key as string]; } else { (tt.dns as Record)[key as string] = value; } }); } const dnsServers = useMemo(() => { const list = dns?.servers || []; return list.map((server, idx) => ({ key: idx, server })); }, [dns?.servers]); const dnsColumns: ColumnsType<{ key: number; server: DnsServerValue }> = useMemo( () => [ { title: '#', key: 'action', align: 'center', width: 60, render: (_v, _record, index) => ( {index + 1} {t('edit')}, onClick: () => openEditServer(index) }, { key: 'del', danger: true, label: <> {t('delete')}, onClick: () => deleteServer(index) }, ], }} > ) : ( {hostsList.map((row, idx) => (
{ const next = hostsList.map((r, i) => (i === idx ? { ...r, domain: e.target.value } : r)); syncHosts(next); }} />