import { forwardRef, useImperativeHandle, useState, useEffect } from "react"; import { useLockFn } from "ahooks"; import { useTranslation } from "react-i18next"; import { Box, Button, FormControl, List, ListItem, ListItemText, MenuItem, Select, styled, Switch, TextField, Typography, } from "@mui/material"; import { RestartAltRounded } from "@mui/icons-material"; import { useClash } from "@/hooks/use-clash"; import { BaseDialog, DialogRef } from "@/components/base"; import yaml from "js-yaml"; import MonacoEditor from "react-monaco-editor"; import { useThemeMode } from "@/services/states"; import getSystem from "@/utils/get-system"; import { invoke } from "@tauri-apps/api/core"; import { showNotice } from "@/services/noticeService"; const Item = styled(ListItem)(({ theme }) => ({ padding: "8px 0", borderBottom: `1px solid ${theme.palette.divider}`, "& textarea": { lineHeight: 1.5, fontSize: 14, resize: "vertical", }, })); // 默认DNS配置 const DEFAULT_DNS_CONFIG = { enable: true, listen: ":53", "enhanced-mode": "fake-ip" as "fake-ip" | "redir-host", "fake-ip-range": "198.18.0.1/16", "fake-ip-filter-mode": "blacklist" as "blacklist" | "whitelist", "prefer-h3": false, "respect-rules": false, "use-hosts": false, "use-system-hosts": false, ipv6: true, "fake-ip-filter": [ "*.lan", "*.local", "*.arpa", "time.*.com", "ntp.*.com", "time.*.com", "+.market.xiaomi.com", "localhost.ptlogin2.qq.com", "*.msftncsi.com", "www.msftconnecttest.com", ], "default-nameserver": [ "system", "223.6.6.6", "8.8.8.8", "2400:3200::1", "2001:4860:4860::8888", ], nameserver: [ "8.8.8.8", "https://doh.pub/dns-query", "https://dns.alidns.com/dns-query", ], fallback: [], "nameserver-policy": {}, "proxy-server-nameserver": [ "https://doh.pub/dns-query", "https://dns.alidns.com/dns-query", "tls://223.5.5.5", ], "direct-nameserver": [], "direct-nameserver-follow-policy": false, "fallback-filter": { geoip: true, "geoip-code": "CN", ipcidr: ["240.0.0.0/4", "0.0.0.0/32"], domain: ["+.google.com", "+.facebook.com", "+.youtube.com"], }, }; export const DnsViewer = forwardRef((props, ref) => { const { t } = useTranslation(); const { clash, mutateClash, patchClash } = useClash(); const themeMode = useThemeMode(); const [open, setOpen] = useState(false); const [visualization, setVisualization] = useState(true); const [values, setValues] = useState<{ enable: boolean; listen: string; enhancedMode: "fake-ip" | "redir-host"; fakeIpRange: string; fakeIpFilterMode: "blacklist" | "whitelist"; preferH3: boolean; respectRules: boolean; useHosts: boolean; useSystemHosts: boolean; ipv6: boolean; fakeIpFilter: string; nameserver: string; fallback: string; defaultNameserver: string; proxyServerNameserver: string; directNameserver: string; directNameserverFollowPolicy: boolean; fallbackGeoip: boolean; fallbackGeoipCode: string; fallbackIpcidr: string; fallbackDomain: string; nameserverPolicy: string; hosts: string; // hosts设置,独立于dns }>({ enable: DEFAULT_DNS_CONFIG.enable, listen: DEFAULT_DNS_CONFIG.listen, enhancedMode: DEFAULT_DNS_CONFIG["enhanced-mode"], fakeIpRange: DEFAULT_DNS_CONFIG["fake-ip-range"], fakeIpFilterMode: DEFAULT_DNS_CONFIG["fake-ip-filter-mode"], preferH3: DEFAULT_DNS_CONFIG["prefer-h3"], respectRules: DEFAULT_DNS_CONFIG["respect-rules"], useHosts: DEFAULT_DNS_CONFIG["use-hosts"], useSystemHosts: DEFAULT_DNS_CONFIG["use-system-hosts"], ipv6: DEFAULT_DNS_CONFIG.ipv6, fakeIpFilter: DEFAULT_DNS_CONFIG["fake-ip-filter"].join(", "), defaultNameserver: DEFAULT_DNS_CONFIG["default-nameserver"].join(", "), nameserver: DEFAULT_DNS_CONFIG.nameserver.join(", "), fallback: DEFAULT_DNS_CONFIG.fallback.join(", "), proxyServerNameserver: DEFAULT_DNS_CONFIG["proxy-server-nameserver"]?.join(", ") || "", directNameserver: DEFAULT_DNS_CONFIG["direct-nameserver"]?.join(", ") || "", directNameserverFollowPolicy: DEFAULT_DNS_CONFIG["direct-nameserver-follow-policy"] || false, fallbackGeoip: DEFAULT_DNS_CONFIG["fallback-filter"].geoip, fallbackGeoipCode: DEFAULT_DNS_CONFIG["fallback-filter"]["geoip-code"], fallbackIpcidr: DEFAULT_DNS_CONFIG["fallback-filter"].ipcidr?.join(", ") || "", fallbackDomain: DEFAULT_DNS_CONFIG["fallback-filter"].domain?.join(", ") || "", nameserverPolicy: "", hosts: "", }); // 用于YAML编辑模式 const [yamlContent, setYamlContent] = useState(""); useImperativeHandle(ref, () => ({ open: () => { setOpen(true); // 获取DNS配置文件并初始化表单 initDnsConfig(); }, close: () => setOpen(false), })); // 初始化DNS配置 const initDnsConfig = async () => { try { // 尝试从dns_config.yaml文件读取配置 const dnsConfigExists = await invoke( "check_dns_config_exists", {}, ); if (dnsConfigExists) { // 如果存在配置文件,加载其内容 const dnsConfig = await invoke("get_dns_config_content", {}); const config = yaml.load(dnsConfig) as any; // 更新表单数据 updateValuesFromConfig(config); // 更新YAML编辑器内容 setYamlContent(dnsConfig); } else { // 如果不存在配置文件,使用默认值 resetToDefaults(); } } catch (err) { console.error("Failed to initialize DNS config", err); resetToDefaults(); } }; // 从配置对象更新表单值 const updateValuesFromConfig = (config: any) => { if (!config) return; // 提取dns配置 const dnsConfig = config.dns || {}; // 提取hosts配置(与dns同级) const hostsConfig = config.hosts || {}; const enhancedMode = dnsConfig["enhanced-mode"] || DEFAULT_DNS_CONFIG["enhanced-mode"]; const validEnhancedMode = enhancedMode === "fake-ip" || enhancedMode === "redir-host" ? enhancedMode : DEFAULT_DNS_CONFIG["enhanced-mode"]; const fakeIpFilterMode = dnsConfig["fake-ip-filter-mode"] || DEFAULT_DNS_CONFIG["fake-ip-filter-mode"]; const validFakeIpFilterMode = fakeIpFilterMode === "blacklist" || fakeIpFilterMode === "whitelist" ? fakeIpFilterMode : DEFAULT_DNS_CONFIG["fake-ip-filter-mode"]; setValues({ enable: dnsConfig.enable ?? DEFAULT_DNS_CONFIG.enable, listen: dnsConfig.listen ?? DEFAULT_DNS_CONFIG.listen, enhancedMode: validEnhancedMode, fakeIpRange: dnsConfig["fake-ip-range"] ?? DEFAULT_DNS_CONFIG["fake-ip-range"], fakeIpFilterMode: validFakeIpFilterMode, preferH3: dnsConfig["prefer-h3"] ?? DEFAULT_DNS_CONFIG["prefer-h3"], respectRules: dnsConfig["respect-rules"] ?? DEFAULT_DNS_CONFIG["respect-rules"], useHosts: dnsConfig["use-hosts"] ?? DEFAULT_DNS_CONFIG["use-hosts"], useSystemHosts: dnsConfig["use-system-hosts"] ?? DEFAULT_DNS_CONFIG["use-system-hosts"], ipv6: dnsConfig.ipv6 ?? DEFAULT_DNS_CONFIG.ipv6, fakeIpFilter: dnsConfig["fake-ip-filter"]?.join(", ") ?? DEFAULT_DNS_CONFIG["fake-ip-filter"].join(", "), nameserver: dnsConfig.nameserver?.join(", ") ?? DEFAULT_DNS_CONFIG.nameserver.join(", "), fallback: dnsConfig.fallback?.join(", ") ?? DEFAULT_DNS_CONFIG.fallback.join(", "), defaultNameserver: dnsConfig["default-nameserver"]?.join(", ") ?? DEFAULT_DNS_CONFIG["default-nameserver"].join(", "), proxyServerNameserver: dnsConfig["proxy-server-nameserver"]?.join(", ") ?? (DEFAULT_DNS_CONFIG["proxy-server-nameserver"]?.join(", ") || ""), directNameserver: dnsConfig["direct-nameserver"]?.join(", ") ?? (DEFAULT_DNS_CONFIG["direct-nameserver"]?.join(", ") || ""), directNameserverFollowPolicy: dnsConfig["direct-nameserver-follow-policy"] ?? DEFAULT_DNS_CONFIG["direct-nameserver-follow-policy"], fallbackGeoip: dnsConfig["fallback-filter"]?.geoip ?? DEFAULT_DNS_CONFIG["fallback-filter"].geoip, fallbackGeoipCode: dnsConfig["fallback-filter"]?.["geoip-code"] ?? DEFAULT_DNS_CONFIG["fallback-filter"]["geoip-code"], fallbackIpcidr: dnsConfig["fallback-filter"]?.ipcidr?.join(", ") ?? DEFAULT_DNS_CONFIG["fallback-filter"].ipcidr.join(", "), fallbackDomain: dnsConfig["fallback-filter"]?.domain?.join(", ") ?? DEFAULT_DNS_CONFIG["fallback-filter"].domain.join(", "), nameserverPolicy: formatNameserverPolicy(dnsConfig["nameserver-policy"]) || "", hosts: formatHosts(hostsConfig) || "", }); }; // 重置为默认值 const resetToDefaults = () => { setValues({ enable: DEFAULT_DNS_CONFIG.enable, listen: DEFAULT_DNS_CONFIG.listen, enhancedMode: DEFAULT_DNS_CONFIG["enhanced-mode"], fakeIpRange: DEFAULT_DNS_CONFIG["fake-ip-range"], fakeIpFilterMode: DEFAULT_DNS_CONFIG["fake-ip-filter-mode"], preferH3: DEFAULT_DNS_CONFIG["prefer-h3"], respectRules: DEFAULT_DNS_CONFIG["respect-rules"], useHosts: DEFAULT_DNS_CONFIG["use-hosts"], useSystemHosts: DEFAULT_DNS_CONFIG["use-system-hosts"], ipv6: DEFAULT_DNS_CONFIG.ipv6, fakeIpFilter: DEFAULT_DNS_CONFIG["fake-ip-filter"].join(", "), defaultNameserver: DEFAULT_DNS_CONFIG["default-nameserver"].join(", "), nameserver: DEFAULT_DNS_CONFIG.nameserver.join(", "), fallback: DEFAULT_DNS_CONFIG.fallback.join(", "), proxyServerNameserver: DEFAULT_DNS_CONFIG["proxy-server-nameserver"]?.join(", ") || "", directNameserver: DEFAULT_DNS_CONFIG["direct-nameserver"]?.join(", ") || "", directNameserverFollowPolicy: DEFAULT_DNS_CONFIG["direct-nameserver-follow-policy"] || false, fallbackGeoip: DEFAULT_DNS_CONFIG["fallback-filter"].geoip, fallbackGeoipCode: DEFAULT_DNS_CONFIG["fallback-filter"]["geoip-code"], fallbackIpcidr: DEFAULT_DNS_CONFIG["fallback-filter"].ipcidr?.join(", ") || "", fallbackDomain: DEFAULT_DNS_CONFIG["fallback-filter"].domain?.join(", ") || "", nameserverPolicy: "", hosts: "", }); // 更新YAML编辑器内容 updateYamlFromValues(); }; // 从表单值更新YAML内容 const updateYamlFromValues = () => { const config: Record = {}; const dnsConfig = generateDnsConfig(); if (Object.keys(dnsConfig).length > 0) { config.dns = dnsConfig; } const hosts = parseHosts(values.hosts); if (Object.keys(hosts).length > 0) { config.hosts = hosts; } setYamlContent(yaml.dump(config, { forceQuotes: true })); }; // 从YAML更新表单值 const updateValuesFromYaml = () => { try { const parsedYaml = yaml.load(yamlContent) as any; if (!parsedYaml) return; updateValuesFromConfig(parsedYaml); } catch (err: any) { showNotice("error", t("Invalid YAML format")); } }; // 格式化nameserver-policy为字符串 const formatNameserverPolicy = (policy: any): string => { if (!policy) return ""; let result: string[] = []; Object.entries(policy).forEach(([domain, servers]) => { if (Array.isArray(servers)) { // 处理数组格式的服务器 const serversStr = servers.join(";"); result.push(`${domain}=${serversStr}`); } else { // 处理单个服务器 result.push(`${domain}=${servers}`); } }); return result.join(", "); }; // 解析nameserver-policy为对象 const parseNameserverPolicy = (str: string): Record => { const result: Record = {}; if (!str) return result; str.split(",").forEach((item) => { const parts = item.trim().split("="); if (parts.length < 2) return; const domain = parts[0].trim(); const serversStr = parts.slice(1).join("=").trim(); // 检查是否包含多个分号分隔的服务器 if (serversStr.includes(";")) { // 多个服务器,作为数组处理 result[domain] = serversStr .split(";") .map((s) => s.trim()) .filter(Boolean); } else { // 单个服务器 result[domain] = serversStr; } }); return result; }; // 格式化hosts为字符串 const formatHosts = (hosts: any): string => { if (!hosts || typeof hosts !== "object") return ""; let result: string[] = []; Object.entries(hosts).forEach(([domain, value]) => { if (Array.isArray(value)) { // 处理数组格式的IP const ipsStr = value.join(";"); result.push(`${domain}=${ipsStr}`); } else { // 处理单个IP或域名 result.push(`${domain}=${value}`); } }); return result.join(", "); }; // 解析hosts字符串为对象 const parseHosts = (str: string): Record => { const result: Record = {}; if (!str) return result; str.split(",").forEach((item) => { const parts = item.trim().split("="); if (parts.length < 2) return; const domain = parts[0].trim(); const valueStr = parts.slice(1).join("=").trim(); // 检查是否包含多个分号分隔的IP if (valueStr.includes(";")) { result[domain] = valueStr .split(";") .map((s) => s.trim()) .filter(Boolean); } else { result[domain] = valueStr; } }); return result; }; // 初始化时设置默认YAML useEffect(() => { updateYamlFromValues(); }, []); // 切换编辑模式时的处理 useEffect(() => { if (visualization) { updateValuesFromYaml(); } else { updateYamlFromValues(); } }, [visualization]); // 解析列表字符串为数组 const parseList = (str: string): string[] => { if (!str) return []; return str .split(",") .map((item) => item.trim()) .filter(Boolean); }; // 生成DNS配置对象 const generateDnsConfig = () => { const dnsConfig: any = { enable: values.enable, listen: values.listen, "enhanced-mode": values.enhancedMode, "fake-ip-range": values.fakeIpRange, "fake-ip-filter-mode": values.fakeIpFilterMode, "prefer-h3": values.preferH3, "respect-rules": values.respectRules, "use-hosts": values.useHosts, "use-system-hosts": values.useSystemHosts, ipv6: values.ipv6, "fake-ip-filter": parseList(values.fakeIpFilter), "default-nameserver": parseList(values.defaultNameserver), nameserver: parseList(values.nameserver), "direct-nameserver-follow-policy": values.directNameserverFollowPolicy, "fallback-filter": { geoip: values.fallbackGeoip, "geoip-code": values.fallbackGeoipCode, ipcidr: parseList(values.fallbackIpcidr), domain: parseList(values.fallbackDomain), }, }; // 只在有值时添加其他可选字段 if (values.fallback) { dnsConfig["fallback"] = parseList(values.fallback); } const policy = parseNameserverPolicy(values.nameserverPolicy); if (Object.keys(policy).length > 0) { dnsConfig["nameserver-policy"] = policy; } if (values.proxyServerNameserver) { dnsConfig["proxy-server-nameserver"] = parseList( values.proxyServerNameserver, ); } if (values.directNameserver) { dnsConfig["direct-nameserver"] = parseList(values.directNameserver); } return dnsConfig; }; // 处理保存操作 const onSave = useLockFn(async () => { try { let config: Record; if (visualization) { // 使用表单值生成配置 config = {}; const dnsConfig = generateDnsConfig(); if (Object.keys(dnsConfig).length > 0) { config.dns = dnsConfig; } const hosts = parseHosts(values.hosts); if (Object.keys(hosts).length > 0) { config.hosts = hosts; } } else { // 使用YAML编辑器的值 const parsedConfig = yaml.load(yamlContent); if (typeof parsedConfig !== "object" || parsedConfig === null) { throw new Error(t("Invalid configuration")); } config = parsedConfig as Record; } // 保存配置 await invoke("save_dns_config", { dnsConfig: config }); // 验证配置 const [isValid, errorMsg] = await invoke<[boolean, string]>( "validate_dns_config", {}, ); if (!isValid) { let cleanErrorMsg = errorMsg; // 提取关键错误信息 if (errorMsg.includes("level=error")) { const errorLines = errorMsg .split("\n") .filter( (line) => line.includes("level=error") || line.includes("level=fatal") || line.includes("failed"), ); if (errorLines.length > 0) { cleanErrorMsg = errorLines .map((line) => { const msgMatch = line.match(/msg="([^"]+)"/); return msgMatch ? msgMatch[1] : line; }) .join(", "); } } showNotice( "error", t("DNS configuration error") + ": " + cleanErrorMsg, ); return; } // 如果DNS开关当前是打开的,则需要应用新的DNS配置 if (clash?.dns?.enable) { await invoke("apply_dns_config", { apply: true }); mutateClash(); } setOpen(false); showNotice("success", t("DNS settings saved")); } catch (err: any) { showNotice("error", err.message || err.toString()); } }); // YAML编辑器内容变更处理 const handleYamlChange = (value: string) => { setYamlContent(value || ""); // 允许YAML编辑后立即分析和更新表单值 try { const config = yaml.load(value) as any; if (config && typeof config === "object") { setTimeout(() => { updateValuesFromConfig(config); }, 300); } } catch (err) { console.log("YAML解析错误,忽略自动更新", err); } }; // 处理表单值变化 const handleChange = (field: string) => (event: any) => { const value = event.target.type === "checkbox" ? event.target.checked : event.target.value; setValues((prev) => { const newValues = { ...prev, [field]: value, }; // 当可视化编辑模式下的值变化时,自动更新YAML if (visualization) { setTimeout(() => { updateYamlFromValues(); }, 0); } return newValues; }); }; return ( {t("DNS Overwrite")} } contentSx={{ width: 550, overflow: "auto", ...(visualization ? {} : { padding: "0 24px", display: "flex", flexDirection: "column" }), }} okBtn={t("Save")} cancelBtn={t("Cancel")} onClose={() => setOpen(false)} onCancel={() => setOpen(false)} onOk={onSave} > {/* Warning message */} {t("DNS Settings Warning")} {visualization ? ( {t("DNS Settings")} {t("Fallback Filter Settings")} {/* Hosts 配置部分 */} {t("Hosts Settings")} ) : ( = 1500, }, mouseWheelZoom: true, quickSuggestions: { strings: true, comments: true, other: true, }, padding: { top: 33, }, fontFamily: `Fira Code, JetBrains Mono, Roboto Mono, "Source Code Pro", Consolas, Menlo, Monaco, monospace, "Courier New", "Apple Color Emoji"${ getSystem() === "windows" ? ", twemoji mozilla" : "" }`, fontLigatures: false, smoothScrolling: true, }} onChange={handleYamlChange} /> )} ); });