Compare commits

..

6 Commits

Author SHA1 Message Date
ezequielnick
45484ffff2 fix: dns/sniffer override logic 2025-08-06 17:21:51 +08:00
ezequielnick
73161d0cc2 1.8.2 Released 2025-08-06 14:45:04 +08:00
ezequielnick
578a8a559f fix: missing Smart core permission setup on Linux 2025-08-06 14:44:43 +08:00
ezequielnick
470adeb519 fix: double-click to edit override & add right-click menu support 2025-08-06 13:54:56 +08:00
ezequielnick
58e0925c5b refactor: Sniffing module with override logic 2025-08-06 13:25:57 +08:00
ezequielnick
0a064bdbb8 fix: windows first startup issue 2025-08-06 10:46:21 +08:00
21 changed files with 251 additions and 134 deletions

View File

@ -23,6 +23,7 @@ package() {
chmod +x ${pkgdir}/opt/mihomo-party/mihomo-party
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-smart
install -Dm755 "${srcdir}/${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${_pkgname}.desktop"

View File

@ -29,6 +29,7 @@ package() {
cp -r $srcdir/opt/mihomo-party/resources/files ${pkgdir}/opt/mihomo-party/resources/
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-smart
install -Dm755 "${srcdir}/${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
install -Dm644 "${_pkgname}.desktop" "${pkgdir}/usr/share/applications/${_pkgname}.desktop"
install -Dm644 "${pkgdir}/opt/mihomo-party/resources/icon.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/${_pkgname}.png"

View File

@ -39,6 +39,7 @@ package() {
cp -r $srcdir/${_pkgname}-${pkgver}/extra/files ${pkgdir}/opt/mihomo-party/resources/
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-smart
install -Dm755 "${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
install -Dm644 "${_pkgname}.desktop" "${pkgdir}/usr/share/applications/${_pkgname}.desktop"
install -Dm644 "${pkgdir}/opt/mihomo-party/resources/icon.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/${_pkgname}.png"

View File

@ -41,6 +41,7 @@ package() {
chmod +x ${pkgdir}/opt/mihomo-party/mihomo-party
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-smart
install -Dm755 "${srcdir}/../${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${_pkgname}.desktop"

View File

@ -36,6 +36,7 @@ package() {
chmod +x ${pkgdir}/opt/mihomo-party/mihomo-party
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-smart
install -Dm755 "${srcdir}/../${pkgname}.sh" "${pkgdir}/usr/bin/${pkgname}"
sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${pkgname}.desktop"

View File

@ -13,6 +13,7 @@ fi
chmod 4755 '/opt/mihomo-party/chrome-sandbox' || true
chmod +sx /opt/mihomo-party/resources/sidecar/mihomo
chmod +sx /opt/mihomo-party/resources/sidecar/mihomo-alpha
chmod +sx /opt/mihomo-party/resources/sidecar/mihomo-smart
if hash update-mime-database 2>/dev/null; then
update-mime-database /usr/share/mime || true

View File

@ -1,7 +1,24 @@
## 1.8.2
**本次更新主要集中在重大内核更新和依赖升级后所产生的 bug 修复解决了自1.7版以后首次安装无法启动的问题,推荐更新**
### 新功能 (Feat)
- 重构 域名嗅探 卡片模块,改为“覆写”逻辑,当开关打开后,使用 嗅探覆写 设置中的配置覆盖订阅原始配置,关闭开关恢复订阅原始配置
- 订阅/覆写卡片可右键呼出菜单
- MacOS 下“轻触(tap)”触控板可进行开关操作(之前必须“按下(click)”)
### 修复 (Fix)
- **因多国语言带来的在 Windows 下首次安装无法启动的问题**
- 1.8.1升级依赖导致的节点圆角显示失效
- DNS 覆写模块的逻辑冲突
- 点击订阅卡片功能区导致的选中订阅问题
- 覆写卡片可以双击编辑
- 因代码不规范导致的控制台警告
- Linux 下没有设置 Smart 内核权限导致的“外部控制监听错误”
## 1.8.1
### 新功能 (Feat)
- 重构 DNS 控制模块改为“覆写”逻辑当开关打开后使用DNS 设置中的配置覆盖订阅原始配置,关闭开关恢复订阅原始配置
- 重构 DNS 卡片模块改为“覆写”逻辑当开关打开后使用DNS 设置中的配置覆盖订阅原始配置,关闭开关恢复订阅原始配置
### 性能提升(Perf)
- 更新依赖,提升页面响应性

View File

@ -1,6 +1,6 @@
{
"name": "mihomo-party",
"version": "1.8.1",
"version": "1.8.2",
"description": "Mihomo Party",
"main": "./out/main/index.js",
"author": "mihomo-party-org",

View File

@ -20,23 +20,7 @@ export async function getControledMihomoConfig(force = false): Promise<Partial<I
export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
const { useNameserverPolicy, controlDns = true, controlSniff = true } = await getAppConfig()
if (!controlDns) {
delete controledMihomoConfig.dns
delete controledMihomoConfig.hosts
} else {
// 从不接管状态恢复
if (controledMihomoConfig.dns?.ipv6 === undefined) {
controledMihomoConfig.dns = defaultControledMihomoConfig.dns
}
}
if (!controlSniff) {
delete controledMihomoConfig.sniffer
} else {
// 从不接管状态恢复
if (!controledMihomoConfig.sniffer) {
controledMihomoConfig.sniffer = defaultControledMihomoConfig.sniffer
}
}
if (patch.hosts) {
controledMihomoConfig.hosts = patch.hosts
}
@ -45,12 +29,37 @@ export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>):
controledMihomoConfig.dns['nameserver-policy'] = patch.dns['nameserver-policy']
}
controledMihomoConfig = deepMerge(controledMihomoConfig, patch)
// 覆写开关控制
let configForProfile = { ...controledMihomoConfig }
if (!controlDns) {
delete configForProfile.dns
delete configForProfile.hosts
} else {
if (configForProfile.dns?.ipv6 === undefined) {
configForProfile.dns = defaultControledMihomoConfig.dns
}
}
if (!controlSniff) {
delete configForProfile.sniffer
} else {
if (!configForProfile.sniffer) {
configForProfile.sniffer = defaultControledMihomoConfig.sniffer
}
}
if (!useNameserverPolicy) {
delete controledMihomoConfig?.dns?.['nameserver-policy']
delete configForProfile?.dns?.['nameserver-policy']
}
if (process.platform === 'darwin') {
delete controledMihomoConfig?.tun?.device
delete configForProfile?.tun?.device
}
const originalConfig = controledMihomoConfig
controledMihomoConfig = configForProfile
await generateProfile()
controledMihomoConfig = originalConfig
await writeFile(controledMihomoConfigPath(), yaml.stringify(controledMihomoConfig), 'utf-8')
}

View File

@ -39,6 +39,7 @@ import os from 'os'
import { createWriteStream, existsSync } from 'fs'
import { uploadRuntimeConfig } from '../resolve/gistApi'
import { startMonitor } from '../resolve/trafficMonitor'
import { safeShowErrorBox } from '../utils/init'
import i18next from '../../shared/i18n'
chokidar.watch(path.join(mihomoCoreDir(), 'meta-update'), {}).on('unlinkDir', async () => {
@ -46,7 +47,7 @@ chokidar.watch(path.join(mihomoCoreDir(), 'meta-update'), {}).on('unlinkDir', as
await stopCore(true)
await startCore()
} catch (e) {
dialog.showErrorBox(i18next.t('mihomo.error.coreStartFailed'), `${e}`)
safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
}
})
@ -228,7 +229,7 @@ export async function keepCoreAlive(): Promise<void> {
await writeFile(path.join(dataDir(), 'core.pid'), child.pid.toString())
}
} catch (e) {
dialog.showErrorBox(i18next.t('mihomo.error.coreStartFailed'), `${e}`)
safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
}
}

View File

@ -18,12 +18,31 @@ import { initProfileUpdater } from './core/profileUpdater'
import { existsSync, writeFileSync } from 'fs'
import { exePath, taskDir } from './utils/dirs'
import path from 'path'
import iconv from 'iconv-lite'
import { startMonitor } from './resolve/trafficMonitor'
import { showFloatingWindow } from './resolve/floatingWindow'
import iconv from 'iconv-lite'
import { initI18n } from '../shared/i18n'
import i18next from 'i18next'
// 错误处理
function showSafeErrorBox(titleKey: string, message: string): void {
let title: string
try {
title = i18next.t(titleKey)
if (!title || title === titleKey) throw new Error('Translation not ready')
} catch {
const isZh = app.getLocale().startsWith('zh')
const fallbacks: Record<string, { zh: string; en: string }> = {
'common.error.initFailed': { zh: '应用初始化失败', en: 'Application initialization failed' },
'mihomo.error.coreStartFailed': { zh: '内核启动出错', en: 'Core start failed' },
'profiles.error.importFailed': { zh: '配置导入失败', en: 'Profile import failed' },
'common.error.adminRequired': { zh: '需要管理员权限', en: 'Administrator privileges required' }
}
title = fallbacks[titleKey] ? (isZh ? fallbacks[titleKey].zh : fallbacks[titleKey].en) : (isZh ? '错误' : 'Error')
}
dialog.showErrorBox(title, message)
}
async function fixUserDataPermissions(): Promise<void> {
if (process.platform !== 'darwin') return
@ -50,6 +69,7 @@ async function fixUserDataPermissions(): Promise<void> {
let quitTimeout: NodeJS.Timeout | null = null
export let mainWindow: BrowserWindow | null = null
// Windows 管理员权限检查(仅在生产模式下)
if (process.platform === 'win32' && !is.dev && !process.argv.includes('noadmin')) {
try {
createElevateTask()
@ -74,10 +94,7 @@ if (process.platform === 'win32' && !is.dev && !process.argv.includes('noadmin')
} catch {
// ignore
}
dialog.showErrorBox(
i18next.t('common.error.adminRequired'),
`${i18next.t('common.error.adminRequired')}\n${createErrorStr}\n${eStr}`
)
showSafeErrorBox('common.error.adminRequired', `${createErrorStr}\n${eStr}`)
} finally {
app.exit()
}
@ -171,6 +188,9 @@ app.whenReady().then(async () => {
electronApp.setAppUserModelId('party.mihomo.app')
try {
// 首先等待初始化完成,确保配置文件和目录都已创建
await initPromise
const appConfig = await getAppConfig()
// 如果配置中没有语言设置,则使用系统语言
if (!appConfig.language) {
@ -179,9 +199,8 @@ app.whenReady().then(async () => {
appConfig.language = systemLanguage
}
await initI18n({ lng: appConfig.language })
await initPromise
} catch (e) {
dialog.showErrorBox(i18next.t('common.error.initFailed'), `${e}`)
showSafeErrorBox('common.error.initFailed', `${e}`)
app.quit()
}
try {
@ -190,7 +209,7 @@ app.whenReady().then(async () => {
await initProfileUpdater()
})
} catch (e) {
dialog.showErrorBox(i18next.t('mihomo.error.coreStartFailed'), `${e}`)
showSafeErrorBox('mihomo.error.coreStartFailed', `${e}`)
}
try {
await startMonitor()
@ -242,7 +261,7 @@ async function handleDeepLink(url: string): Promise<void> {
new Notification({ title: i18next.t('profiles.notification.importSuccess') }).show()
break
} catch (e) {
dialog.showErrorBox(i18next.t('profiles.error.importFailed'), `${url}\n${e}`)
showSafeErrorBox('profiles.error.importFailed', `${url}\n${e}`)
}
}
}

View File

@ -39,8 +39,25 @@ import {
patchAppConfig,
patchControledMihomoConfig
} from '../config'
import { app } from 'electron'
import { app, dialog } from 'electron'
import { startSSIDCheck } from '../sys/ssid'
import i18next from '../../shared/i18n'
// 安全错误处理
export function safeShowErrorBox(titleKey: string, message: string): void {
let title: string
try {
title = i18next.t(titleKey)
if (!title || title === titleKey) throw new Error('Translation not ready')
} catch {
const isZh = process.env.LANG?.startsWith('zh') || process.env.LC_ALL?.startsWith('zh')
const fallbacks: Record<string, { zh: string; en: string }> = {
'mihomo.error.coreStartFailed': { zh: '内核启动出错', en: 'Core start failed' }
}
title = fallbacks[titleKey] ? (isZh ? fallbacks[titleKey].zh : fallbacks[titleKey].en) : (isZh ? '错误' : 'Error')
}
dialog.showErrorBox(title, message)
}
async function fixDataDirPermissions(): Promise<void> {
if (process.platform !== 'darwin') return
@ -68,47 +85,48 @@ async function fixDataDirPermissions(): Promise<void> {
async function initDirs(): Promise<void> {
await fixDataDirPermissions()
if (!existsSync(dataDir())) {
await mkdir(dataDir())
}
if (!existsSync(themesDir())) {
await mkdir(themesDir())
}
if (!existsSync(profilesDir())) {
await mkdir(profilesDir())
}
if (!existsSync(overrideDir())) {
await mkdir(overrideDir())
}
if (!existsSync(mihomoWorkDir())) {
await mkdir(mihomoWorkDir())
}
if (!existsSync(logDir())) {
await mkdir(logDir())
}
if (!existsSync(mihomoTestDir())) {
await mkdir(mihomoTestDir())
}
if (!existsSync(subStoreDir())) {
await mkdir(subStoreDir())
// 按依赖顺序创建目录
const dirsToCreate = [
dataDir(),
themesDir(),
profilesDir(),
overrideDir(),
mihomoWorkDir(),
logDir(),
mihomoTestDir(),
subStoreDir()
]
for (const dir of dirsToCreate) {
try {
if (!existsSync(dir)) {
await mkdir(dir, { recursive: true })
}
} catch (error) {
console.error(`Failed to create directory ${dir}:`, error)
throw new Error(`Failed to create directory ${dir}: ${error}`)
}
}
}
async function initConfig(): Promise<void> {
if (!existsSync(appConfigPath())) {
await writeFile(appConfigPath(), yaml.stringify(defaultConfig))
}
if (!existsSync(profileConfigPath())) {
await writeFile(profileConfigPath(), yaml.stringify(defaultProfileConfig))
}
if (!existsSync(overrideConfigPath())) {
await writeFile(overrideConfigPath(), yaml.stringify(defaultOverrideConfig))
}
if (!existsSync(profilePath('default'))) {
await writeFile(profilePath('default'), yaml.stringify(defaultProfile))
}
if (!existsSync(controledMihomoConfigPath())) {
await writeFile(controledMihomoConfigPath(), yaml.stringify(defaultControledMihomoConfig))
const configs = [
{ path: appConfigPath(), content: defaultConfig, name: 'app config' },
{ path: profileConfigPath(), content: defaultProfileConfig, name: 'profile config' },
{ path: overrideConfigPath(), content: defaultOverrideConfig, name: 'override config' },
{ path: profilePath('default'), content: defaultProfile, name: 'default profile' },
{ path: controledMihomoConfigPath(), content: defaultControledMihomoConfig, name: 'mihomo config' }
]
for (const config of configs) {
try {
if (!existsSync(config.path)) {
await writeFile(config.path, yaml.stringify(config.content))
}
} catch (error) {
console.error(`Failed to create ${config.name} at ${config.path}:`, error)
throw new Error(`Failed to create ${config.name}: ${error}`)
}
}
}
@ -117,13 +135,30 @@ async function initFiles(): Promise<void> {
const targetPath = path.join(mihomoWorkDir(), file)
const testTargetPath = path.join(mihomoTestDir(), file)
const sourcePath = path.join(resourcesFilesDir(), file)
if (!existsSync(targetPath) && existsSync(sourcePath)) {
await cp(sourcePath, targetPath, { recursive: true })
}
if (!existsSync(testTargetPath) && existsSync(sourcePath)) {
await cp(sourcePath, testTargetPath, { recursive: true })
try {
if (!existsSync(targetPath) && existsSync(sourcePath)) {
await cp(sourcePath, targetPath, { recursive: true })
}
if (!existsSync(testTargetPath) && existsSync(sourcePath)) {
await cp(sourcePath, testTargetPath, { recursive: true })
}
} catch (error) {
console.error(`Failed to copy ${file}:`, error)
if (['country.mmdb', 'geoip.dat', 'geosite.dat'].includes(file)) {
throw new Error(`Failed to copy critical file ${file}: ${error}`)
}
}
}
// 确保工作目录存在
if (!existsSync(mihomoWorkDir())) {
await mkdir(mihomoWorkDir(), { recursive: true })
}
if (!existsSync(mihomoTestDir())) {
await mkdir(mihomoTestDir(), { recursive: true })
}
await Promise.all([
copy('country.mmdb'),
copy('geoip.metadb'),

View File

@ -10,7 +10,7 @@ import {
} from '@heroui/react'
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
import dayjs from '@renderer/utils/dayjs'
import React, { Key, useEffect, useMemo, useState } from 'react'
import React, { Key, useMemo, useState } from 'react'
import EditFileModal from './edit-file-modal'
import EditInfoModal from './edit-info-modal'
import { useSortable } from '@dnd-kit/sortable'
@ -54,7 +54,7 @@ const OverrideItem: React.FC<Props> = (props) => {
id: info.id
})
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
const [disableOpen, setDisableOpen] = useState(false)
const [dropdownOpen, setDropdownOpen] = useState(false)
const menuItems: MenuItem[] = useMemo(() => {
const list = [
{
@ -124,17 +124,13 @@ const OverrideItem: React.FC<Props> = (props) => {
}
}
useEffect(() => {
if (isDragging) {
setTimeout(() => {
setDisableOpen(true)
}, 200)
} else {
setTimeout(() => {
setDisableOpen(false)
}, 200)
}
}, [isDragging])
const handleContextMenu = (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
setDropdownOpen(true)
}
return (
<div
@ -164,13 +160,21 @@ const OverrideItem: React.FC<Props> = (props) => {
<Card
as="div"
fullWidth
isPressable
onPress={() => {
if (disableOpen) return
className="cursor-pointer"
onContextMenu={handleContextMenu}
onDoubleClick={(e) => {
if ((e.target as Element)?.closest('button, [role="menu"], [role="menuitem"]')) {
return
}
setOpenFileEditor(true)
}}
>
<div ref={setNodeRef} {...attributes} {...listeners} className="h-full w-full">
<div
ref={setNodeRef}
{...attributes}
{...listeners}
className="h-full w-full"
>
<CardBody>
<div className="flex justify-between h-[32px]">
<h3
@ -206,7 +210,10 @@ const OverrideItem: React.FC<Props> = (props) => {
</Button>
)}
<Dropdown>
<Dropdown
isOpen={dropdownOpen}
onOpenChange={setDropdownOpen}
>
<DropdownTrigger>
<Button isIconOnly size="sm" variant="light" color="default">
<IoMdMore color="default" className={`text-[24px]`} />

View File

@ -4,7 +4,7 @@ import SettingItem from '../base/base-setting-item'
import { Button, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react'
import { useAppConfig } from '@renderer/hooks/use-app-config'
import debounce from '@renderer/utils/debounce'
import { getGistUrl, patchControledMihomoConfig, restartCore } from '@renderer/utils/ipc'
import { getGistUrl, restartCore } from '@renderer/utils/ipc'
import { MdDeleteForever } from 'react-icons/md'
import { BiCopy } from 'react-icons/bi'
import { IoIosHelpCircle } from 'react-icons/io'
@ -16,7 +16,6 @@ const MihomoConfig: React.FC = () => {
const { appConfig, patchAppConfig } = useAppConfig()
const {
diffWorkDir = false,
controlSniff = true,
delayTestConcurrency,
delayTestTimeout,
githubToken = '',
@ -193,21 +192,7 @@ const MihomoConfig: React.FC = () => {
/>
</SettingItem>
<SettingItem title={t('mihomo.controlSniff')} divider>
<Switch
size="sm"
isSelected={controlSniff}
onValueChange={async (v) => {
try {
await patchAppConfig({ controlSniff: v })
await patchControledMihomoConfig({})
await restartCore()
} catch (e) {
alert(e)
}
}}
/>
</SettingItem>
<SettingItem title={t('mihomo.autoCloseConnection')} divider>
<Switch
size="sm"

View File

@ -2,7 +2,7 @@ import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
import BorderSwitch from '@renderer/components/base/border-swtich'
import { RiScan2Fill } from 'react-icons/ri'
import { useLocation, useNavigate } from 'react-router-dom'
import { patchMihomoConfig } from '@renderer/utils/ipc'
import { restartCore } from '@renderer/utils/ipc'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
@ -15,15 +15,13 @@ interface Props {
}
const SniffCard: React.FC<Props> = (props) => {
const { t } = useTranslation()
const { appConfig } = useAppConfig()
const { appConfig, patchAppConfig } = useAppConfig()
const { iconOnly } = props
const { sniffCardStatus = 'col-span-1', controlSniff = true } = appConfig || {}
const location = useLocation()
const navigate = useNavigate()
const match = location.pathname.includes('/sniffer')
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
const { sniffer } = controledMihomoConfig || {}
const { enable } = sniffer || {}
const { patchControledMihomoConfig } = useControledMihomoConfig()
const {
attributes,
listeners,
@ -35,14 +33,19 @@ const SniffCard: React.FC<Props> = (props) => {
id: 'sniff'
})
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
const onChange = async (enable: boolean): Promise<void> => {
await patchControledMihomoConfig({ sniffer: { enable } })
await patchMihomoConfig({ sniffer: { enable } })
const onChange = async (controlSniff: boolean): Promise<void> => {
try {
await patchAppConfig({ controlSniff })
await patchControledMihomoConfig({})
await restartCore()
} catch (e) {
alert(e)
}
}
if (iconOnly) {
return (
<div className={`${sniffCardStatus} ${!controlSniff ? 'hidden' : ''} flex justify-center`}>
<div className={`${sniffCardStatus} flex justify-center`}>
<Tooltip content={t('sider.cards.sniff')} placement="right">
<Button
size="sm"
@ -68,7 +71,7 @@ const SniffCard: React.FC<Props> = (props) => {
transition,
zIndex: isDragging ? 'calc(infinity)' : undefined
}}
className={`${sniffCardStatus} ${!controlSniff ? 'hidden' : ''} sniff-card`}
className={`${sniffCardStatus} sniff-card`}
>
<Card
fullWidth
@ -91,8 +94,8 @@ const SniffCard: React.FC<Props> = (props) => {
/>
</Button>
<BorderSwitch
isShowBorder={match && enable}
isSelected={enable}
isShowBorder={match && controlSniff}
isSelected={controlSniff}
onValueChange={onChange}
/>
</div>

View File

@ -216,7 +216,7 @@
"sider.cards.connections": "Connections",
"sider.cards.core": "Core Settings",
"sider.cards.dns": "DNS Override",
"sider.cards.sniff": "Sniffing",
"sider.cards.sniff": "Sniff OVRD",
"sider.cards.logs": "Logs",
"sider.cards.substore": "Sub-Store",
"sider.cards.config": "Runtime Config",
@ -269,6 +269,7 @@
"proxies.search.placeholder": "Search Proxies",
"proxies.locate": "Locate Current Proxy",
"sniffer.title": "Domain Sniffing Settings",
"sniffer.enable": "Enable Domain Sniffing",
"sniffer.parsePureIP": "Sniff Unmapped IP Addresses",
"sniffer.forceDNSMapping": "Sniff Real IP Mappings",
"sniffer.overrideDestination": "Override Connection Address",
@ -284,6 +285,7 @@
"sniffer.skipDstAddress.placeholder": "Example: 1.1.1.1/32",
"sniffer.skipSrcAddress.title": "Skip Source Address Sniffing",
"sniffer.skipSrcAddress.placeholder": "Example: 192.168.1.1/24",
"sniffer.saveOnly": "Save Only",
"sysproxy.title": "System Proxy",
"sysproxy.host.title": "Proxy Host",
"sysproxy.host.placeholder": "Default 127.0.0.1, do not modify unless necessary",
@ -342,6 +344,7 @@
"dns.customHosts.list": "Hosts List",
"dns.customHosts.domainPlaceholder": "Domain",
"dns.customHosts.valuePlaceholder": "Domain or IP",
"dns.saveOnly": "Save Only",
"profiles.title": "Profile Management",
"profiles.updateAll": "Update All Profiles",
"profiles.useProxy": "Proxy",

View File

@ -207,7 +207,7 @@
"sider.cards.connections": "اتصالات",
"sider.cards.core": "تنظیمات هسته",
"sider.cards.dns": "بازنویسی دی‌ان‌اس",
"sider.cards.sniff": "تشخیص دامنه",
"sider.cards.sniff": "لغو بو کشیدن",
"sider.cards.logs": "گزارش‌ها",
"sider.cards.substore": "ساب استور",
"sider.cards.config": "پیکربندی اجرا",
@ -260,6 +260,7 @@
"proxies.search.placeholder": "جستجوی پراکسی‌ها",
"proxies.locate": "یافتن پراکسی فعلی",
"sniffer.title": "تنظیمات تشخیص دامنه",
"sniffer.enable": "فعال کردن قابلیت شنود دامنه",
"sniffer.parsePureIP": "تشخیص آدرس‌های IP بدون نگاشت",
"sniffer.forceDNSMapping": "تشخیص نگاشت‌های IP واقعی",
"sniffer.overrideDestination": "جایگزینی آدرس اتصال",
@ -275,6 +276,7 @@
"sniffer.skipDstAddress.placeholder": "مثال: 1.1.1.1/32",
"sniffer.skipSrcAddress.title": "رد کردن تشخیص آدرس مبدا",
"sniffer.skipSrcAddress.placeholder": "مثال: 192.168.1.1/24",
"sniffer.saveOnly": "فقط ذخیره",
"sysproxy.title": "پراکسی سیستم",
"sysproxy.host.title": "میزبان پراکسی",
"sysproxy.host.placeholder": "پیش‌فرض 127.0.0.1، در صورت عدم نیاز تغییر ندهید",
@ -333,6 +335,7 @@
"dns.customHosts.list": "لیست Hosts",
"dns.customHosts.domainPlaceholder": "دامنه",
"dns.customHosts.valuePlaceholder": "دامنه یا IP",
"dns.saveOnly": "فقط ذخیره",
"profiles.title": "مدیریت پروفایل",
"profiles.updateAll": "به‌روزرسانی همه پروفایل‌ها",
"profiles.useProxy": "پراکسی",

View File

@ -207,7 +207,7 @@
"sider.cards.connections": "Подключения",
"sider.cards.core": "Настройки ядра",
"sider.cards.dns": "Переопределение DNS",
"sider.cards.sniff": "Анализ трафика",
"sider.cards.sniff": "переопределение сниффинга",
"sider.cards.logs": "Журналы",
"sider.cards.substore": "Sub-Store",
"sider.cards.config": "Конфигурация",
@ -260,6 +260,7 @@
"proxies.search.placeholder": "Поиск прокси",
"proxies.locate": "Найти текущий прокси",
"sniffer.title": "Настройки анализа доменов",
"sniffer.enable": "Включить анализ домена",
"sniffer.parsePureIP": "Анализировать немаппированные IP-адреса",
"sniffer.forceDNSMapping": "Анализировать реальные IP-маппинги",
"sniffer.overrideDestination": "Переопределить адрес подключения",
@ -275,6 +276,7 @@
"sniffer.skipDstAddress.placeholder": "Пример: 1.1.1.1/32",
"sniffer.skipSrcAddress.title": "Пропустить анализ исходных адресов",
"sniffer.skipSrcAddress.placeholder": "Пример: 192.168.1.1/24",
"sniffer.saveOnly": "Только сохранить",
"sysproxy.title": "Системный прокси",
"sysproxy.host.title": "Хост прокси",
"sysproxy.host.placeholder": "По умолчанию 127.0.0.1, не изменяйте без необходимости",
@ -333,6 +335,7 @@
"dns.customHosts.list": "Список Hosts",
"dns.customHosts.domainPlaceholder": "Домен",
"dns.customHosts.valuePlaceholder": "Домен или IP",
"dns.saveOnly": "Только сохранить",
"profiles.title": "Управление профилями",
"profiles.updateAll": "Обновить все профили",
"profiles.useProxy": "Прокси",

View File

@ -216,7 +216,7 @@
"sider.cards.connections": "连接",
"sider.cards.core": "内核设置",
"sider.cards.dns": "DNS覆写",
"sider.cards.sniff": "域名嗅探",
"sider.cards.sniff": "嗅探覆写",
"sider.cards.logs": "日志",
"sider.cards.substore": "Sub-Store",
"sider.cards.config": "运行时配置",
@ -269,6 +269,7 @@
"proxies.search.placeholder": "搜索节点",
"proxies.locate": "定位到当前节点",
"sniffer.title": "域名嗅探设置",
"sniffer.enable": "启用域名嗅探",
"sniffer.parsePureIP": "对未映射 IP 地址嗅探",
"sniffer.forceDNSMapping": "对真实 IP 映射嗅探",
"sniffer.overrideDestination": "覆盖连接地址",
@ -284,6 +285,7 @@
"sniffer.skipDstAddress.placeholder": "例1.1.1.1/32",
"sniffer.skipSrcAddress.title": "跳过来源地址嗅探",
"sniffer.skipSrcAddress.placeholder": "例192.168.1.1/24",
"sniffer.saveOnly": "仅保存",
"sysproxy.title": "系统代理",
"sysproxy.host.title": "代理主机",
"sysproxy.host.placeholder": "默认 127.0.0.1 若无特殊需求请勿修改",
@ -342,6 +344,7 @@
"dns.customHosts.list": "Hosts 列表",
"dns.customHosts.domainPlaceholder": "域名",
"dns.customHosts.valuePlaceholder": "域名或 IP",
"dns.saveOnly": "仅保存",
"profiles.title": "订阅管理",
"profiles.updateAll": "更新全部订阅",
"profiles.useProxy": "代理",

View File

@ -13,7 +13,7 @@ const DNS: React.FC = () => {
const { t } = useTranslation()
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
const { appConfig, patchAppConfig } = useAppConfig()
const { nameserverPolicy, useNameserverPolicy } = appConfig || {}
const { nameserverPolicy, useNameserverPolicy, controlDns = true } = appConfig || {}
const { dns, hosts } = controledMihomoConfig || {}
const {
enable = true,
@ -128,8 +128,10 @@ const DNS: React.FC = () => {
try {
setChanged(false)
await patchControledMihomoConfig(patch)
await patchMihomoConfig(patch)
await restartCore()
if (controlDns) {
await patchMihomoConfig(patch)
await restartCore()
}
} catch (e) {
alert(e)
}
@ -175,7 +177,7 @@ const DNS: React.FC = () => {
onSave(result)
}}
>
{t('common.save')}
{controlDns ? t('common.save') : t('dns.saveOnly')}
</Button>
)
}

View File

@ -3,7 +3,8 @@ import BasePage from '@renderer/components/base/base-page'
import SettingCard from '@renderer/components/base/base-setting-card'
import SettingItem from '@renderer/components/base/base-setting-item'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
import { restartCore } from '@renderer/utils/ipc'
import { useAppConfig } from '@renderer/hooks/use-app-config'
import { restartCore, patchMihomoConfig } from '@renderer/utils/ipc'
import React, { ReactNode, useState } from 'react'
import { MdDeleteForever } from 'react-icons/md'
import { useTranslation } from 'react-i18next'
@ -11,8 +12,11 @@ import { useTranslation } from 'react-i18next'
const Sniffer: React.FC = () => {
const { t } = useTranslation()
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
const { appConfig } = useAppConfig()
const { controlSniff = true } = appConfig || {}
const { sniffer } = controledMihomoConfig || {}
const {
enable = true,
'parse-pure-ip': parsePureIP = true,
'force-dns-mapping': forceDNSMapping = true,
'override-destination': overrideDestination = false,
@ -41,6 +45,7 @@ const Sniffer: React.FC = () => {
} = sniffer || {}
const [changed, setChanged] = useState(false)
const [values, originSetValues] = useState({
enable,
parsePureIP,
forceDNSMapping,
overrideDestination,
@ -59,7 +64,11 @@ const Sniffer: React.FC = () => {
try {
setChanged(false)
await patchControledMihomoConfig(patch)
await restartCore()
if (controlSniff) {
await patchMihomoConfig(patch)
await restartCore()
}
} catch (e) {
alert(e)
}
@ -130,22 +139,34 @@ const Sniffer: React.FC = () => {
onPress={() =>
onSave({
sniffer: {
enable: values.enable,
'parse-pure-ip': values.parsePureIP,
'force-dns-mapping': values.forceDNSMapping,
'override-destination': values.overrideDestination,
sniff: values.sniff,
'skip-domain': values.skipDomain,
'force-domain': values.forceDomain
'force-domain': values.forceDomain,
'skip-dst-address': values.skipDstAddress,
'skip-src-address': values.skipSrcAddress
}
})
}
>
{t('common.save')}
{controlSniff ? t('common.save') : t('sniffer.saveOnly')}
</Button>
)
}
>
<SettingCard>
<SettingItem title={t('sniffer.enable')} divider>
<Switch
size="sm"
isSelected={values.enable}
onValueChange={(v) => {
setValues({ ...values, enable: v })
}}
/>
</SettingItem>
<SettingItem title={t('sniffer.overrideDestination')} divider>
<Switch
size="sm"