From 10576780ed4a11991bf8c03f5c49bbb3cc917a9c Mon Sep 17 00:00:00 2001 From: wonfen Date: Sun, 11 May 2025 22:55:31 +0800 Subject: [PATCH] feat: add hosts settings to DNS settings and enhance DNS config validation --- src-tauri/src/cmd/clash.rs | 23 ++ src-tauri/src/enhance/mod.rs | 21 +- src-tauri/src/lib.rs | 1 + src-tauri/src/utils/init.rs | 10 +- src-tauri/src/utils/tmpl.rs | 3 - src/components/setting/mods/dns-viewer.tsx | 298 ++++++++++++--------- src/locales/en.json | 7 +- src/locales/ru.json | 4 +- src/locales/zh.json | 7 +- 9 files changed, 239 insertions(+), 135 deletions(-) diff --git a/src-tauri/src/cmd/clash.rs b/src-tauri/src/cmd/clash.rs index 852c3151..abaa9514 100644 --- a/src-tauri/src/cmd/clash.rs +++ b/src-tauri/src/cmd/clash.rs @@ -233,3 +233,26 @@ pub async fn get_dns_config_content() -> CmdResult { let content = fs::read_to_string(&dns_path).map_err(|e| e.to_string())?; Ok(content) } + +/// 验证DNS配置文件 +#[tauri::command] +pub async fn validate_dns_config() -> CmdResult<(bool, String)> { + use crate::core::CoreManager; + use crate::utils::dirs; + + let app_dir = dirs::app_home_dir().map_err(|e| e.to_string())?; + let dns_path = app_dir.join("dns_config.yaml"); + let dns_path_str = dns_path.to_str().unwrap_or_default(); + + if !dns_path.exists() { + return Ok((false, "DNS config file not found".to_string())); + } + + match CoreManager::global() + .validate_config_file(dns_path_str, None) + .await + { + Ok(result) => Ok(result), + Err(e) => Err(e.to_string()), + } +} diff --git a/src-tauri/src/enhance/mod.rs b/src-tauri/src/enhance/mod.rs index c3f5f50b..e226880f 100644 --- a/src-tauri/src/enhance/mod.rs +++ b/src-tauri/src/enhance/mod.rs @@ -261,16 +261,29 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { use crate::utils::dirs; use std::fs; - // 尝试读取dns_config.yaml if let Ok(app_dir) = dirs::app_home_dir() { let dns_path = app_dir.join("dns_config.yaml"); if dns_path.exists() { if let Ok(dns_yaml) = fs::read_to_string(&dns_path) { if let Ok(dns_config) = serde_yaml::from_str::(&dns_yaml) { - // 将DNS配置合并到最终配置中 - config.insert("dns".into(), dns_config.into()); - log::info!(target: "app", "apply dns_config.yaml"); + // 处理hosts配置 + if let Some(hosts_value) = dns_config.get("hosts") { + if hosts_value.is_mapping() { + config.insert("hosts".into(), hosts_value.clone()); + log::info!(target: "app", "apply hosts configuration"); + } + } + + if let Some(dns_value) = dns_config.get("dns") { + if let Some(dns_mapping) = dns_value.as_mapping() { + config.insert("dns".into(), dns_mapping.clone().into()); + log::info!(target: "app", "apply dns_config.yaml (dns section)"); + } + } else { + config.insert("dns".into(), dns_config.into()); + log::info!(target: "app", "apply dns_config.yaml"); + } } } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 40a52af2..f905a15c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -252,6 +252,7 @@ pub fn run() { cmd::apply_dns_config, cmd::check_dns_config_exists, cmd::get_dns_config_content, + cmd::validate_dns_config, // verge cmd::get_verge_config, cmd::patch_verge_config, diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs index 887487f5..013f1378 100644 --- a/src-tauri/src/utils/init.rs +++ b/src-tauri/src/utils/init.rs @@ -142,8 +142,8 @@ pub fn delete_log() -> Result<()> { fn init_dns_config() -> Result<()> { use serde_yaml::Value; - // 获取默认DNS配置 - let default_dns_config = serde_yaml::Mapping::from_iter([ + // 创建DNS子配置 + let dns_config = serde_yaml::Mapping::from_iter([ ("enable".into(), Value::Bool(true)), ("listen".into(), Value::String(":53".into())), ("enhanced-mode".into(), Value::String("fake-ip".into())), @@ -231,6 +231,12 @@ fn init_dns_config() -> Result<()> { ), ]); + // 获取默认DNS和host配置 + let default_dns_config = serde_yaml::Mapping::from_iter([ + ("dns".into(), Value::Mapping(dns_config)), + ("hosts".into(), Value::Mapping(serde_yaml::Mapping::new())), + ]); + // 检查DNS配置文件是否存在 let app_dir = dirs::app_home_dir()?; let dns_path = app_dir.join("dns_config.yaml"); diff --git a/src-tauri/src/utils/tmpl.rs b/src-tauri/src/utils/tmpl.rs index 545672de..5d421a8c 100644 --- a/src-tauri/src/utils/tmpl.rs +++ b/src-tauri/src/utils/tmpl.rs @@ -15,9 +15,6 @@ pub const ITEM_MERGE: &str = "# Profile Enhancement Merge Template for Clash Ver profile: store-selected: true - -dns: - use-system-hosts: false "; pub const ITEM_MERGE_EMPTY: &str = "# Profile Enhancement Merge Template for Clash Verge diff --git a/src/components/setting/mods/dns-viewer.tsx b/src/components/setting/mods/dns-viewer.tsx index cbe00c16..cdd9bc3a 100644 --- a/src/components/setting/mods/dns-viewer.tsx +++ b/src/components/setting/mods/dns-viewer.tsx @@ -112,6 +112,7 @@ export const DnsViewer = forwardRef((props, ref) => { fallbackIpcidr: string; fallbackDomain: string; nameserverPolicy: string; + hosts: string; // hosts设置,独立于dns }>({ enable: DEFAULT_DNS_CONFIG.enable, listen: DEFAULT_DNS_CONFIG.listen, @@ -139,6 +140,7 @@ export const DnsViewer = forwardRef((props, ref) => { fallbackDomain: DEFAULT_DNS_CONFIG["fallback-filter"].domain?.join(", ") || "", nameserverPolicy: "", + hosts: "", }); // 用于YAML编辑模式 @@ -185,15 +187,20 @@ export const DnsViewer = forwardRef((props, ref) => { const updateValuesFromConfig = (config: any) => { if (!config) return; + // 提取dns配置 + const dnsConfig = config.dns || {}; + // 提取hosts配置(与dns同级) + const hostsConfig = config.hosts || {}; + const enhancedMode = - config["enhanced-mode"] || DEFAULT_DNS_CONFIG["enhanced-mode"]; + dnsConfig["enhanced-mode"] || DEFAULT_DNS_CONFIG["enhanced-mode"]; const validEnhancedMode = enhancedMode === "fake-ip" || enhancedMode === "redir-host" ? enhancedMode : DEFAULT_DNS_CONFIG["enhanced-mode"]; const fakeIpFilterMode = - config["fake-ip-filter-mode"] || + dnsConfig["fake-ip-filter-mode"] || DEFAULT_DNS_CONFIG["fake-ip-filter-mode"]; const validFakeIpFilterMode = fakeIpFilterMode === "blacklist" || fakeIpFilterMode === "whitelist" @@ -201,53 +208,55 @@ export const DnsViewer = forwardRef((props, ref) => { : DEFAULT_DNS_CONFIG["fake-ip-filter-mode"]; setValues({ - enable: config.enable ?? DEFAULT_DNS_CONFIG.enable, - listen: config.listen ?? DEFAULT_DNS_CONFIG.listen, + enable: dnsConfig.enable ?? DEFAULT_DNS_CONFIG.enable, + listen: dnsConfig.listen ?? DEFAULT_DNS_CONFIG.listen, enhancedMode: validEnhancedMode, fakeIpRange: - config["fake-ip-range"] ?? DEFAULT_DNS_CONFIG["fake-ip-range"], + dnsConfig["fake-ip-range"] ?? DEFAULT_DNS_CONFIG["fake-ip-range"], fakeIpFilterMode: validFakeIpFilterMode, - preferH3: config["prefer-h3"] ?? DEFAULT_DNS_CONFIG["prefer-h3"], + preferH3: dnsConfig["prefer-h3"] ?? DEFAULT_DNS_CONFIG["prefer-h3"], respectRules: - config["respect-rules"] ?? DEFAULT_DNS_CONFIG["respect-rules"], - useHosts: config["use-hosts"] ?? DEFAULT_DNS_CONFIG["use-hosts"], + dnsConfig["respect-rules"] ?? DEFAULT_DNS_CONFIG["respect-rules"], + useHosts: dnsConfig["use-hosts"] ?? DEFAULT_DNS_CONFIG["use-hosts"], useSystemHosts: - config["use-system-hosts"] ?? DEFAULT_DNS_CONFIG["use-system-hosts"], - ipv6: config.ipv6 ?? DEFAULT_DNS_CONFIG.ipv6, + dnsConfig["use-system-hosts"] ?? + DEFAULT_DNS_CONFIG["use-system-hosts"], + ipv6: dnsConfig.ipv6 ?? DEFAULT_DNS_CONFIG.ipv6, fakeIpFilter: - config["fake-ip-filter"]?.join(", ") ?? + dnsConfig["fake-ip-filter"]?.join(", ") ?? DEFAULT_DNS_CONFIG["fake-ip-filter"].join(", "), nameserver: - config.nameserver?.join(", ") ?? + dnsConfig.nameserver?.join(", ") ?? DEFAULT_DNS_CONFIG.nameserver.join(", "), fallback: - config.fallback?.join(", ") ?? DEFAULT_DNS_CONFIG.fallback.join(", "), + dnsConfig.fallback?.join(", ") ?? DEFAULT_DNS_CONFIG.fallback.join(", "), defaultNameserver: - config["default-nameserver"]?.join(", ") ?? + dnsConfig["default-nameserver"]?.join(", ") ?? DEFAULT_DNS_CONFIG["default-nameserver"].join(", "), proxyServerNameserver: - config["proxy-server-nameserver"]?.join(", ") ?? + dnsConfig["proxy-server-nameserver"]?.join(", ") ?? (DEFAULT_DNS_CONFIG["proxy-server-nameserver"]?.join(", ") || ""), directNameserver: - config["direct-nameserver"]?.join(", ") ?? + dnsConfig["direct-nameserver"]?.join(", ") ?? (DEFAULT_DNS_CONFIG["direct-nameserver"]?.join(", ") || ""), directNameserverFollowPolicy: - config["direct-nameserver-follow-policy"] ?? + dnsConfig["direct-nameserver-follow-policy"] ?? DEFAULT_DNS_CONFIG["direct-nameserver-follow-policy"], fallbackGeoip: - config["fallback-filter"]?.geoip ?? + dnsConfig["fallback-filter"]?.geoip ?? DEFAULT_DNS_CONFIG["fallback-filter"].geoip, fallbackGeoipCode: - config["fallback-filter"]?.["geoip-code"] ?? + dnsConfig["fallback-filter"]?.["geoip-code"] ?? DEFAULT_DNS_CONFIG["fallback-filter"]["geoip-code"], fallbackIpcidr: - config["fallback-filter"]?.ipcidr?.join(", ") ?? + dnsConfig["fallback-filter"]?.ipcidr?.join(", ") ?? DEFAULT_DNS_CONFIG["fallback-filter"].ipcidr.join(", "), fallbackDomain: - config["fallback-filter"]?.domain?.join(", ") ?? + dnsConfig["fallback-filter"]?.domain?.join(", ") ?? DEFAULT_DNS_CONFIG["fallback-filter"].domain.join(", "), nameserverPolicy: - formatNameserverPolicy(config["nameserver-policy"]) || "", + formatNameserverPolicy(dnsConfig["nameserver-policy"]) || "", + hosts: formatHosts(hostsConfig) || "", }); }; @@ -281,99 +290,38 @@ export const DnsViewer = forwardRef((props, ref) => { fallbackDomain: DEFAULT_DNS_CONFIG["fallback-filter"].domain?.join(", ") || "", nameserverPolicy: "", + hosts: "", }); // 更新YAML编辑器内容 - updateYamlFromValues(DEFAULT_DNS_CONFIG); + updateYamlFromValues(); }; // 从表单值更新YAML内容 - const updateYamlFromValues = (dnsConfig: any = null) => { - // 如果提供了dnsConfig,直接使用它 - if (dnsConfig) { - setYamlContent(yaml.dump(dnsConfig, { forceQuotes: true })); - return; + const updateYamlFromValues = () => { + + const config: Record = {}; + + const dnsConfig = generateDnsConfig(); + if (Object.keys(dnsConfig).length > 0) { + config.dns = dnsConfig; } - // 否则从当前表单值生成 - const config = generateDnsConfig(); + 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 dnsConfig = yaml.load(yamlContent) as any; - if (!dnsConfig) return; - - const enhancedMode = - dnsConfig["enhanced-mode"] || DEFAULT_DNS_CONFIG["enhanced-mode"]; - // 确保enhancedMode只能是"fake-ip"或"redir-host" - 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"]; - // 确保fakeIpFilterMode只能是"blacklist"或"whitelist" - 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(", "), - defaultNameserver: - dnsConfig["default-nameserver"]?.join(", ") ?? - DEFAULT_DNS_CONFIG["default-nameserver"].join(", "), - nameserver: - dnsConfig.nameserver?.join(", ") ?? - DEFAULT_DNS_CONFIG.nameserver.join(", "), - fallback: - dnsConfig.fallback?.join(", ") ?? - DEFAULT_DNS_CONFIG.fallback.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"]) || "", - }); + const parsedYaml = yaml.load(yamlContent) as any; + if (!parsedYaml) return; + + updateValuesFromConfig(parsedYaml); } catch (err: any) { showNotice('error', t("Invalid YAML format")); } @@ -427,18 +375,62 @@ export const DnsViewer = forwardRef((props, ref) => { 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(DEFAULT_DNS_CONFIG); + updateYamlFromValues(); }, []); // 切换编辑模式时的处理 useEffect(() => { if (visualization) { - // 从YAML更新表单值 updateValuesFromYaml(); } else { - // 从表单值更新YAML updateYamlFromValues(); } }, [visualization]); @@ -503,27 +495,63 @@ export const DnsViewer = forwardRef((props, ref) => { // 处理保存操作 const onSave = useLockFn(async () => { try { - let dnsConfig; + let config: Record; if (visualization) { - // 使用表单值 - dnsConfig = generateDnsConfig(); + // 使用表单值生成配置 + 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 DNS configuration")); + throw new Error(t("Invalid configuration")); } - dnsConfig = parsedConfig; + config = parsedConfig as Record; } - // 不直接应用到clash配置,而是保存到单独文件 - await invoke("save_dns_config", { dnsConfig }); + // 保存配置 + 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(); // 刷新UI + mutateClash(); } setOpen(false); @@ -539,15 +567,13 @@ export const DnsViewer = forwardRef((props, ref) => { // 允许YAML编辑后立即分析和更新表单值 try { - const dnsConfig = yaml.load(value) as any; - if (dnsConfig && typeof dnsConfig === "object") { - // 稍微延迟更新,以避免性能问题 + const config = yaml.load(value) as any; + if (config && typeof config === "object") { setTimeout(() => { - updateValuesFromYaml(); + updateValuesFromConfig(config); }, 300); } } catch (err) { - // 忽略解析错误,只有当YAML有效时才更新表单 console.log("YAML解析错误,忽略自动更新", err); } }; @@ -568,7 +594,7 @@ export const DnsViewer = forwardRef((props, ref) => { // 当可视化编辑模式下的值变化时,自动更新YAML if (visualization) { setTimeout(() => { - updateYamlFromValues(null); + updateYamlFromValues(); }, 0); } @@ -628,6 +654,13 @@ export const DnsViewer = forwardRef((props, ref) => { {visualization ? ( + + {t("DNS Settings")} + + ((props, ref) => { ((props, ref) => { ((props, ref) => { placeholder="+.google.com, +.facebook.com, +.youtube.com" /> + + {/* Hosts 配置部分 */} + + {t("Hosts Settings")} + + + + + + ) : (