mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 21:20:29 +08:00
Compare commits
6 Commits
d6f0d30f9a
...
45484ffff2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45484ffff2 | ||
|
|
73161d0cc2 | ||
|
|
578a8a559f | ||
|
|
470adeb519 | ||
|
|
58e0925c5b | ||
|
|
0a064bdbb8 |
@ -23,6 +23,7 @@ package() {
|
|||||||
chmod +x ${pkgdir}/opt/mihomo-party/mihomo-party
|
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
|
||||||
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
|
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 -Dm755 "${srcdir}/${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
|
||||||
sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${_pkgname}.desktop"
|
sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${_pkgname}.desktop"
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ package() {
|
|||||||
cp -r $srcdir/opt/mihomo-party/resources/files ${pkgdir}/opt/mihomo-party/resources/
|
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
|
||||||
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
|
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 -Dm755 "${srcdir}/${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
|
||||||
install -Dm644 "${_pkgname}.desktop" "${pkgdir}/usr/share/applications/${_pkgname}.desktop"
|
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"
|
install -Dm644 "${pkgdir}/opt/mihomo-party/resources/icon.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/${_pkgname}.png"
|
||||||
|
|||||||
@ -39,6 +39,7 @@ package() {
|
|||||||
cp -r $srcdir/${_pkgname}-${pkgver}/extra/files ${pkgdir}/opt/mihomo-party/resources/
|
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
|
||||||
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
|
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 -Dm755 "${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
|
||||||
install -Dm644 "${_pkgname}.desktop" "${pkgdir}/usr/share/applications/${_pkgname}.desktop"
|
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"
|
install -Dm644 "${pkgdir}/opt/mihomo-party/resources/icon.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/${_pkgname}.png"
|
||||||
|
|||||||
@ -41,6 +41,7 @@ package() {
|
|||||||
chmod +x ${pkgdir}/opt/mihomo-party/mihomo-party
|
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
|
||||||
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
|
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 -Dm755 "${srcdir}/../${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
|
||||||
sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${_pkgname}.desktop"
|
sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${_pkgname}.desktop"
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@ package() {
|
|||||||
chmod +x ${pkgdir}/opt/mihomo-party/mihomo-party
|
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
|
||||||
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
|
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 -Dm755 "${srcdir}/../${pkgname}.sh" "${pkgdir}/usr/bin/${pkgname}"
|
||||||
sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${pkgname}.desktop"
|
sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${pkgname}.desktop"
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ fi
|
|||||||
chmod 4755 '/opt/mihomo-party/chrome-sandbox' || true
|
chmod 4755 '/opt/mihomo-party/chrome-sandbox' || true
|
||||||
chmod +sx /opt/mihomo-party/resources/sidecar/mihomo
|
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-alpha
|
||||||
|
chmod +sx /opt/mihomo-party/resources/sidecar/mihomo-smart
|
||||||
|
|
||||||
if hash update-mime-database 2>/dev/null; then
|
if hash update-mime-database 2>/dev/null; then
|
||||||
update-mime-database /usr/share/mime || true
|
update-mime-database /usr/share/mime || true
|
||||||
|
|||||||
19
changelog.md
19
changelog.md
@ -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
|
## 1.8.1
|
||||||
|
|
||||||
### 新功能 (Feat)
|
### 新功能 (Feat)
|
||||||
- 重构 DNS 控制模块,改为“覆写”逻辑,当开关打开后,使用DNS 设置中的配置覆盖订阅原始配置,关闭开关恢复订阅原始配置
|
- 重构 DNS 卡片模块,改为“覆写”逻辑,当开关打开后,使用DNS 设置中的配置覆盖订阅原始配置,关闭开关恢复订阅原始配置
|
||||||
|
|
||||||
### 性能提升(Perf)
|
### 性能提升(Perf)
|
||||||
- 更新依赖,提升页面响应性
|
- 更新依赖,提升页面响应性
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mihomo-party",
|
"name": "mihomo-party",
|
||||||
"version": "1.8.1",
|
"version": "1.8.2",
|
||||||
"description": "Mihomo Party",
|
"description": "Mihomo Party",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "mihomo-party-org",
|
"author": "mihomo-party-org",
|
||||||
|
|||||||
@ -20,23 +20,7 @@ export async function getControledMihomoConfig(force = false): Promise<Partial<I
|
|||||||
|
|
||||||
export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
|
export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
|
||||||
const { useNameserverPolicy, controlDns = true, controlSniff = true } = await getAppConfig()
|
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) {
|
if (patch.hosts) {
|
||||||
controledMihomoConfig.hosts = 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.dns['nameserver-policy'] = patch.dns['nameserver-policy']
|
||||||
}
|
}
|
||||||
controledMihomoConfig = deepMerge(controledMihomoConfig, patch)
|
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) {
|
if (!useNameserverPolicy) {
|
||||||
delete controledMihomoConfig?.dns?.['nameserver-policy']
|
delete configForProfile?.dns?.['nameserver-policy']
|
||||||
}
|
}
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
delete controledMihomoConfig?.tun?.device
|
delete configForProfile?.tun?.device
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const originalConfig = controledMihomoConfig
|
||||||
|
controledMihomoConfig = configForProfile
|
||||||
await generateProfile()
|
await generateProfile()
|
||||||
|
controledMihomoConfig = originalConfig
|
||||||
|
|
||||||
await writeFile(controledMihomoConfigPath(), yaml.stringify(controledMihomoConfig), 'utf-8')
|
await writeFile(controledMihomoConfigPath(), yaml.stringify(controledMihomoConfig), 'utf-8')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import os from 'os'
|
|||||||
import { createWriteStream, existsSync } from 'fs'
|
import { createWriteStream, existsSync } from 'fs'
|
||||||
import { uploadRuntimeConfig } from '../resolve/gistApi'
|
import { uploadRuntimeConfig } from '../resolve/gistApi'
|
||||||
import { startMonitor } from '../resolve/trafficMonitor'
|
import { startMonitor } from '../resolve/trafficMonitor'
|
||||||
|
import { safeShowErrorBox } from '../utils/init'
|
||||||
import i18next from '../../shared/i18n'
|
import i18next from '../../shared/i18n'
|
||||||
|
|
||||||
chokidar.watch(path.join(mihomoCoreDir(), 'meta-update'), {}).on('unlinkDir', async () => {
|
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 stopCore(true)
|
||||||
await startCore()
|
await startCore()
|
||||||
} catch (e) {
|
} 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())
|
await writeFile(path.join(dataDir(), 'core.pid'), child.pid.toString())
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dialog.showErrorBox(i18next.t('mihomo.error.coreStartFailed'), `${e}`)
|
safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,12 +18,31 @@ import { initProfileUpdater } from './core/profileUpdater'
|
|||||||
import { existsSync, writeFileSync } from 'fs'
|
import { existsSync, writeFileSync } from 'fs'
|
||||||
import { exePath, taskDir } from './utils/dirs'
|
import { exePath, taskDir } from './utils/dirs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import iconv from 'iconv-lite'
|
||||||
import { startMonitor } from './resolve/trafficMonitor'
|
import { startMonitor } from './resolve/trafficMonitor'
|
||||||
import { showFloatingWindow } from './resolve/floatingWindow'
|
import { showFloatingWindow } from './resolve/floatingWindow'
|
||||||
import iconv from 'iconv-lite'
|
|
||||||
import { initI18n } from '../shared/i18n'
|
import { initI18n } from '../shared/i18n'
|
||||||
import i18next from 'i18next'
|
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> {
|
async function fixUserDataPermissions(): Promise<void> {
|
||||||
if (process.platform !== 'darwin') return
|
if (process.platform !== 'darwin') return
|
||||||
|
|
||||||
@ -50,6 +69,7 @@ async function fixUserDataPermissions(): Promise<void> {
|
|||||||
let quitTimeout: NodeJS.Timeout | null = null
|
let quitTimeout: NodeJS.Timeout | null = null
|
||||||
export let mainWindow: BrowserWindow | null = null
|
export let mainWindow: BrowserWindow | null = null
|
||||||
|
|
||||||
|
// Windows 管理员权限检查(仅在生产模式下)
|
||||||
if (process.platform === 'win32' && !is.dev && !process.argv.includes('noadmin')) {
|
if (process.platform === 'win32' && !is.dev && !process.argv.includes('noadmin')) {
|
||||||
try {
|
try {
|
||||||
createElevateTask()
|
createElevateTask()
|
||||||
@ -74,10 +94,7 @@ if (process.platform === 'win32' && !is.dev && !process.argv.includes('noadmin')
|
|||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
dialog.showErrorBox(
|
showSafeErrorBox('common.error.adminRequired', `${createErrorStr}\n${eStr}`)
|
||||||
i18next.t('common.error.adminRequired'),
|
|
||||||
`${i18next.t('common.error.adminRequired')}\n${createErrorStr}\n${eStr}`
|
|
||||||
)
|
|
||||||
} finally {
|
} finally {
|
||||||
app.exit()
|
app.exit()
|
||||||
}
|
}
|
||||||
@ -171,6 +188,9 @@ app.whenReady().then(async () => {
|
|||||||
electronApp.setAppUserModelId('party.mihomo.app')
|
electronApp.setAppUserModelId('party.mihomo.app')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 首先等待初始化完成,确保配置文件和目录都已创建
|
||||||
|
await initPromise
|
||||||
|
|
||||||
const appConfig = await getAppConfig()
|
const appConfig = await getAppConfig()
|
||||||
// 如果配置中没有语言设置,则使用系统语言
|
// 如果配置中没有语言设置,则使用系统语言
|
||||||
if (!appConfig.language) {
|
if (!appConfig.language) {
|
||||||
@ -179,9 +199,8 @@ app.whenReady().then(async () => {
|
|||||||
appConfig.language = systemLanguage
|
appConfig.language = systemLanguage
|
||||||
}
|
}
|
||||||
await initI18n({ lng: appConfig.language })
|
await initI18n({ lng: appConfig.language })
|
||||||
await initPromise
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dialog.showErrorBox(i18next.t('common.error.initFailed'), `${e}`)
|
showSafeErrorBox('common.error.initFailed', `${e}`)
|
||||||
app.quit()
|
app.quit()
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -190,7 +209,7 @@ app.whenReady().then(async () => {
|
|||||||
await initProfileUpdater()
|
await initProfileUpdater()
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dialog.showErrorBox(i18next.t('mihomo.error.coreStartFailed'), `${e}`)
|
showSafeErrorBox('mihomo.error.coreStartFailed', `${e}`)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await startMonitor()
|
await startMonitor()
|
||||||
@ -242,7 +261,7 @@ async function handleDeepLink(url: string): Promise<void> {
|
|||||||
new Notification({ title: i18next.t('profiles.notification.importSuccess') }).show()
|
new Notification({ title: i18next.t('profiles.notification.importSuccess') }).show()
|
||||||
break
|
break
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dialog.showErrorBox(i18next.t('profiles.error.importFailed'), `${url}\n${e}`)
|
showSafeErrorBox('profiles.error.importFailed', `${url}\n${e}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,8 +39,25 @@ import {
|
|||||||
patchAppConfig,
|
patchAppConfig,
|
||||||
patchControledMihomoConfig
|
patchControledMihomoConfig
|
||||||
} from '../config'
|
} from '../config'
|
||||||
import { app } from 'electron'
|
import { app, dialog } from 'electron'
|
||||||
import { startSSIDCheck } from '../sys/ssid'
|
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> {
|
async function fixDataDirPermissions(): Promise<void> {
|
||||||
if (process.platform !== 'darwin') return
|
if (process.platform !== 'darwin') return
|
||||||
@ -68,47 +85,48 @@ async function fixDataDirPermissions(): Promise<void> {
|
|||||||
async function initDirs(): Promise<void> {
|
async function initDirs(): Promise<void> {
|
||||||
await fixDataDirPermissions()
|
await fixDataDirPermissions()
|
||||||
|
|
||||||
if (!existsSync(dataDir())) {
|
// 按依赖顺序创建目录
|
||||||
await mkdir(dataDir())
|
const dirsToCreate = [
|
||||||
}
|
dataDir(),
|
||||||
if (!existsSync(themesDir())) {
|
themesDir(),
|
||||||
await mkdir(themesDir())
|
profilesDir(),
|
||||||
}
|
overrideDir(),
|
||||||
if (!existsSync(profilesDir())) {
|
mihomoWorkDir(),
|
||||||
await mkdir(profilesDir())
|
logDir(),
|
||||||
}
|
mihomoTestDir(),
|
||||||
if (!existsSync(overrideDir())) {
|
subStoreDir()
|
||||||
await mkdir(overrideDir())
|
]
|
||||||
}
|
|
||||||
if (!existsSync(mihomoWorkDir())) {
|
for (const dir of dirsToCreate) {
|
||||||
await mkdir(mihomoWorkDir())
|
try {
|
||||||
}
|
if (!existsSync(dir)) {
|
||||||
if (!existsSync(logDir())) {
|
await mkdir(dir, { recursive: true })
|
||||||
await mkdir(logDir())
|
}
|
||||||
}
|
} catch (error) {
|
||||||
if (!existsSync(mihomoTestDir())) {
|
console.error(`Failed to create directory ${dir}:`, error)
|
||||||
await mkdir(mihomoTestDir())
|
throw new Error(`Failed to create directory ${dir}: ${error}`)
|
||||||
}
|
}
|
||||||
if (!existsSync(subStoreDir())) {
|
|
||||||
await mkdir(subStoreDir())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initConfig(): Promise<void> {
|
async function initConfig(): Promise<void> {
|
||||||
if (!existsSync(appConfigPath())) {
|
const configs = [
|
||||||
await writeFile(appConfigPath(), yaml.stringify(defaultConfig))
|
{ path: appConfigPath(), content: defaultConfig, name: 'app config' },
|
||||||
}
|
{ path: profileConfigPath(), content: defaultProfileConfig, name: 'profile config' },
|
||||||
if (!existsSync(profileConfigPath())) {
|
{ path: overrideConfigPath(), content: defaultOverrideConfig, name: 'override config' },
|
||||||
await writeFile(profileConfigPath(), yaml.stringify(defaultProfileConfig))
|
{ path: profilePath('default'), content: defaultProfile, name: 'default profile' },
|
||||||
}
|
{ path: controledMihomoConfigPath(), content: defaultControledMihomoConfig, name: 'mihomo config' }
|
||||||
if (!existsSync(overrideConfigPath())) {
|
]
|
||||||
await writeFile(overrideConfigPath(), yaml.stringify(defaultOverrideConfig))
|
|
||||||
}
|
for (const config of configs) {
|
||||||
if (!existsSync(profilePath('default'))) {
|
try {
|
||||||
await writeFile(profilePath('default'), yaml.stringify(defaultProfile))
|
if (!existsSync(config.path)) {
|
||||||
}
|
await writeFile(config.path, yaml.stringify(config.content))
|
||||||
if (!existsSync(controledMihomoConfigPath())) {
|
}
|
||||||
await writeFile(controledMihomoConfigPath(), yaml.stringify(defaultControledMihomoConfig))
|
} 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 targetPath = path.join(mihomoWorkDir(), file)
|
||||||
const testTargetPath = path.join(mihomoTestDir(), file)
|
const testTargetPath = path.join(mihomoTestDir(), file)
|
||||||
const sourcePath = path.join(resourcesFilesDir(), file)
|
const sourcePath = path.join(resourcesFilesDir(), file)
|
||||||
if (!existsSync(targetPath) && existsSync(sourcePath)) {
|
|
||||||
await cp(sourcePath, targetPath, { recursive: true })
|
try {
|
||||||
}
|
if (!existsSync(targetPath) && existsSync(sourcePath)) {
|
||||||
if (!existsSync(testTargetPath) && existsSync(sourcePath)) {
|
await cp(sourcePath, targetPath, { recursive: true })
|
||||||
await cp(sourcePath, testTargetPath, { 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([
|
await Promise.all([
|
||||||
copy('country.mmdb'),
|
copy('country.mmdb'),
|
||||||
copy('geoip.metadb'),
|
copy('geoip.metadb'),
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
} from '@heroui/react'
|
} from '@heroui/react'
|
||||||
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
|
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
|
||||||
import dayjs from '@renderer/utils/dayjs'
|
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 EditFileModal from './edit-file-modal'
|
||||||
import EditInfoModal from './edit-info-modal'
|
import EditInfoModal from './edit-info-modal'
|
||||||
import { useSortable } from '@dnd-kit/sortable'
|
import { useSortable } from '@dnd-kit/sortable'
|
||||||
@ -54,7 +54,7 @@ const OverrideItem: React.FC<Props> = (props) => {
|
|||||||
id: info.id
|
id: info.id
|
||||||
})
|
})
|
||||||
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
|
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 menuItems: MenuItem[] = useMemo(() => {
|
||||||
const list = [
|
const list = [
|
||||||
{
|
{
|
||||||
@ -124,17 +124,13 @@ const OverrideItem: React.FC<Props> = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isDragging) {
|
|
||||||
setTimeout(() => {
|
const handleContextMenu = (e: React.MouseEvent) => {
|
||||||
setDisableOpen(true)
|
e.preventDefault()
|
||||||
}, 200)
|
e.stopPropagation()
|
||||||
} else {
|
setDropdownOpen(true)
|
||||||
setTimeout(() => {
|
}
|
||||||
setDisableOpen(false)
|
|
||||||
}, 200)
|
|
||||||
}
|
|
||||||
}, [isDragging])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -164,13 +160,21 @@ const OverrideItem: React.FC<Props> = (props) => {
|
|||||||
<Card
|
<Card
|
||||||
as="div"
|
as="div"
|
||||||
fullWidth
|
fullWidth
|
||||||
isPressable
|
className="cursor-pointer"
|
||||||
onPress={() => {
|
onContextMenu={handleContextMenu}
|
||||||
if (disableOpen) return
|
onDoubleClick={(e) => {
|
||||||
|
if ((e.target as Element)?.closest('button, [role="menu"], [role="menuitem"]')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
setOpenFileEditor(true)
|
setOpenFileEditor(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div ref={setNodeRef} {...attributes} {...listeners} className="h-full w-full">
|
<div
|
||||||
|
ref={setNodeRef}
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
className="h-full w-full"
|
||||||
|
>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<div className="flex justify-between h-[32px]">
|
<div className="flex justify-between h-[32px]">
|
||||||
<h3
|
<h3
|
||||||
@ -206,7 +210,10 @@ const OverrideItem: React.FC<Props> = (props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Dropdown>
|
<Dropdown
|
||||||
|
isOpen={dropdownOpen}
|
||||||
|
onOpenChange={setDropdownOpen}
|
||||||
|
>
|
||||||
<DropdownTrigger>
|
<DropdownTrigger>
|
||||||
<Button isIconOnly size="sm" variant="light" color="default">
|
<Button isIconOnly size="sm" variant="light" color="default">
|
||||||
<IoMdMore color="default" className={`text-[24px]`} />
|
<IoMdMore color="default" className={`text-[24px]`} />
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import SettingItem from '../base/base-setting-item'
|
|||||||
import { Button, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react'
|
import { Button, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react'
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
import debounce from '@renderer/utils/debounce'
|
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 { MdDeleteForever } from 'react-icons/md'
|
||||||
import { BiCopy } from 'react-icons/bi'
|
import { BiCopy } from 'react-icons/bi'
|
||||||
import { IoIosHelpCircle } from 'react-icons/io'
|
import { IoIosHelpCircle } from 'react-icons/io'
|
||||||
@ -16,7 +16,6 @@ const MihomoConfig: React.FC = () => {
|
|||||||
const { appConfig, patchAppConfig } = useAppConfig()
|
const { appConfig, patchAppConfig } = useAppConfig()
|
||||||
const {
|
const {
|
||||||
diffWorkDir = false,
|
diffWorkDir = false,
|
||||||
controlSniff = true,
|
|
||||||
delayTestConcurrency,
|
delayTestConcurrency,
|
||||||
delayTestTimeout,
|
delayTestTimeout,
|
||||||
githubToken = '',
|
githubToken = '',
|
||||||
@ -193,21 +192,7 @@ const MihomoConfig: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</SettingItem>
|
</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>
|
<SettingItem title={t('mihomo.autoCloseConnection')} divider>
|
||||||
<Switch
|
<Switch
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
|
|||||||
import BorderSwitch from '@renderer/components/base/border-swtich'
|
import BorderSwitch from '@renderer/components/base/border-swtich'
|
||||||
import { RiScan2Fill } from 'react-icons/ri'
|
import { RiScan2Fill } from 'react-icons/ri'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
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 { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||||
import { useSortable } from '@dnd-kit/sortable'
|
import { useSortable } from '@dnd-kit/sortable'
|
||||||
import { CSS } from '@dnd-kit/utilities'
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
@ -15,15 +15,13 @@ interface Props {
|
|||||||
}
|
}
|
||||||
const SniffCard: React.FC<Props> = (props) => {
|
const SniffCard: React.FC<Props> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { appConfig } = useAppConfig()
|
const { appConfig, patchAppConfig } = useAppConfig()
|
||||||
const { iconOnly } = props
|
const { iconOnly } = props
|
||||||
const { sniffCardStatus = 'col-span-1', controlSniff = true } = appConfig || {}
|
const { sniffCardStatus = 'col-span-1', controlSniff = true } = appConfig || {}
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const match = location.pathname.includes('/sniffer')
|
const match = location.pathname.includes('/sniffer')
|
||||||
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
|
const { patchControledMihomoConfig } = useControledMihomoConfig()
|
||||||
const { sniffer } = controledMihomoConfig || {}
|
|
||||||
const { enable } = sniffer || {}
|
|
||||||
const {
|
const {
|
||||||
attributes,
|
attributes,
|
||||||
listeners,
|
listeners,
|
||||||
@ -35,14 +33,19 @@ const SniffCard: React.FC<Props> = (props) => {
|
|||||||
id: 'sniff'
|
id: 'sniff'
|
||||||
})
|
})
|
||||||
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
|
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
|
||||||
const onChange = async (enable: boolean): Promise<void> => {
|
const onChange = async (controlSniff: boolean): Promise<void> => {
|
||||||
await patchControledMihomoConfig({ sniffer: { enable } })
|
try {
|
||||||
await patchMihomoConfig({ sniffer: { enable } })
|
await patchAppConfig({ controlSniff })
|
||||||
|
await patchControledMihomoConfig({})
|
||||||
|
await restartCore()
|
||||||
|
} catch (e) {
|
||||||
|
alert(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iconOnly) {
|
if (iconOnly) {
|
||||||
return (
|
return (
|
||||||
<div className={`${sniffCardStatus} ${!controlSniff ? 'hidden' : ''} flex justify-center`}>
|
<div className={`${sniffCardStatus} flex justify-center`}>
|
||||||
<Tooltip content={t('sider.cards.sniff')} placement="right">
|
<Tooltip content={t('sider.cards.sniff')} placement="right">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -68,7 +71,7 @@ const SniffCard: React.FC<Props> = (props) => {
|
|||||||
transition,
|
transition,
|
||||||
zIndex: isDragging ? 'calc(infinity)' : undefined
|
zIndex: isDragging ? 'calc(infinity)' : undefined
|
||||||
}}
|
}}
|
||||||
className={`${sniffCardStatus} ${!controlSniff ? 'hidden' : ''} sniff-card`}
|
className={`${sniffCardStatus} sniff-card`}
|
||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
fullWidth
|
fullWidth
|
||||||
@ -91,8 +94,8 @@ const SniffCard: React.FC<Props> = (props) => {
|
|||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
<BorderSwitch
|
<BorderSwitch
|
||||||
isShowBorder={match && enable}
|
isShowBorder={match && controlSniff}
|
||||||
isSelected={enable}
|
isSelected={controlSniff}
|
||||||
onValueChange={onChange}
|
onValueChange={onChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -216,7 +216,7 @@
|
|||||||
"sider.cards.connections": "Connections",
|
"sider.cards.connections": "Connections",
|
||||||
"sider.cards.core": "Core Settings",
|
"sider.cards.core": "Core Settings",
|
||||||
"sider.cards.dns": "DNS Override",
|
"sider.cards.dns": "DNS Override",
|
||||||
"sider.cards.sniff": "Sniffing",
|
"sider.cards.sniff": "Sniff OVRD",
|
||||||
"sider.cards.logs": "Logs",
|
"sider.cards.logs": "Logs",
|
||||||
"sider.cards.substore": "Sub-Store",
|
"sider.cards.substore": "Sub-Store",
|
||||||
"sider.cards.config": "Runtime Config",
|
"sider.cards.config": "Runtime Config",
|
||||||
@ -269,6 +269,7 @@
|
|||||||
"proxies.search.placeholder": "Search Proxies",
|
"proxies.search.placeholder": "Search Proxies",
|
||||||
"proxies.locate": "Locate Current Proxy",
|
"proxies.locate": "Locate Current Proxy",
|
||||||
"sniffer.title": "Domain Sniffing Settings",
|
"sniffer.title": "Domain Sniffing Settings",
|
||||||
|
"sniffer.enable": "Enable Domain Sniffing",
|
||||||
"sniffer.parsePureIP": "Sniff Unmapped IP Addresses",
|
"sniffer.parsePureIP": "Sniff Unmapped IP Addresses",
|
||||||
"sniffer.forceDNSMapping": "Sniff Real IP Mappings",
|
"sniffer.forceDNSMapping": "Sniff Real IP Mappings",
|
||||||
"sniffer.overrideDestination": "Override Connection Address",
|
"sniffer.overrideDestination": "Override Connection Address",
|
||||||
@ -284,6 +285,7 @@
|
|||||||
"sniffer.skipDstAddress.placeholder": "Example: 1.1.1.1/32",
|
"sniffer.skipDstAddress.placeholder": "Example: 1.1.1.1/32",
|
||||||
"sniffer.skipSrcAddress.title": "Skip Source Address Sniffing",
|
"sniffer.skipSrcAddress.title": "Skip Source Address Sniffing",
|
||||||
"sniffer.skipSrcAddress.placeholder": "Example: 192.168.1.1/24",
|
"sniffer.skipSrcAddress.placeholder": "Example: 192.168.1.1/24",
|
||||||
|
"sniffer.saveOnly": "Save Only",
|
||||||
"sysproxy.title": "System Proxy",
|
"sysproxy.title": "System Proxy",
|
||||||
"sysproxy.host.title": "Proxy Host",
|
"sysproxy.host.title": "Proxy Host",
|
||||||
"sysproxy.host.placeholder": "Default 127.0.0.1, do not modify unless necessary",
|
"sysproxy.host.placeholder": "Default 127.0.0.1, do not modify unless necessary",
|
||||||
@ -342,6 +344,7 @@
|
|||||||
"dns.customHosts.list": "Hosts List",
|
"dns.customHosts.list": "Hosts List",
|
||||||
"dns.customHosts.domainPlaceholder": "Domain",
|
"dns.customHosts.domainPlaceholder": "Domain",
|
||||||
"dns.customHosts.valuePlaceholder": "Domain or IP",
|
"dns.customHosts.valuePlaceholder": "Domain or IP",
|
||||||
|
"dns.saveOnly": "Save Only",
|
||||||
"profiles.title": "Profile Management",
|
"profiles.title": "Profile Management",
|
||||||
"profiles.updateAll": "Update All Profiles",
|
"profiles.updateAll": "Update All Profiles",
|
||||||
"profiles.useProxy": "Proxy",
|
"profiles.useProxy": "Proxy",
|
||||||
|
|||||||
@ -207,7 +207,7 @@
|
|||||||
"sider.cards.connections": "اتصالات",
|
"sider.cards.connections": "اتصالات",
|
||||||
"sider.cards.core": "تنظیمات هسته",
|
"sider.cards.core": "تنظیمات هسته",
|
||||||
"sider.cards.dns": "بازنویسی دیاناس",
|
"sider.cards.dns": "بازنویسی دیاناس",
|
||||||
"sider.cards.sniff": "تشخیص دامنه",
|
"sider.cards.sniff": "لغو بو کشیدن",
|
||||||
"sider.cards.logs": "گزارشها",
|
"sider.cards.logs": "گزارشها",
|
||||||
"sider.cards.substore": "ساب استور",
|
"sider.cards.substore": "ساب استور",
|
||||||
"sider.cards.config": "پیکربندی اجرا",
|
"sider.cards.config": "پیکربندی اجرا",
|
||||||
@ -260,6 +260,7 @@
|
|||||||
"proxies.search.placeholder": "جستجوی پراکسیها",
|
"proxies.search.placeholder": "جستجوی پراکسیها",
|
||||||
"proxies.locate": "یافتن پراکسی فعلی",
|
"proxies.locate": "یافتن پراکسی فعلی",
|
||||||
"sniffer.title": "تنظیمات تشخیص دامنه",
|
"sniffer.title": "تنظیمات تشخیص دامنه",
|
||||||
|
"sniffer.enable": "فعال کردن قابلیت شنود دامنه",
|
||||||
"sniffer.parsePureIP": "تشخیص آدرسهای IP بدون نگاشت",
|
"sniffer.parsePureIP": "تشخیص آدرسهای IP بدون نگاشت",
|
||||||
"sniffer.forceDNSMapping": "تشخیص نگاشتهای IP واقعی",
|
"sniffer.forceDNSMapping": "تشخیص نگاشتهای IP واقعی",
|
||||||
"sniffer.overrideDestination": "جایگزینی آدرس اتصال",
|
"sniffer.overrideDestination": "جایگزینی آدرس اتصال",
|
||||||
@ -275,6 +276,7 @@
|
|||||||
"sniffer.skipDstAddress.placeholder": "مثال: 1.1.1.1/32",
|
"sniffer.skipDstAddress.placeholder": "مثال: 1.1.1.1/32",
|
||||||
"sniffer.skipSrcAddress.title": "رد کردن تشخیص آدرس مبدا",
|
"sniffer.skipSrcAddress.title": "رد کردن تشخیص آدرس مبدا",
|
||||||
"sniffer.skipSrcAddress.placeholder": "مثال: 192.168.1.1/24",
|
"sniffer.skipSrcAddress.placeholder": "مثال: 192.168.1.1/24",
|
||||||
|
"sniffer.saveOnly": "فقط ذخیره",
|
||||||
"sysproxy.title": "پراکسی سیستم",
|
"sysproxy.title": "پراکسی سیستم",
|
||||||
"sysproxy.host.title": "میزبان پراکسی",
|
"sysproxy.host.title": "میزبان پراکسی",
|
||||||
"sysproxy.host.placeholder": "پیشفرض 127.0.0.1، در صورت عدم نیاز تغییر ندهید",
|
"sysproxy.host.placeholder": "پیشفرض 127.0.0.1، در صورت عدم نیاز تغییر ندهید",
|
||||||
@ -333,6 +335,7 @@
|
|||||||
"dns.customHosts.list": "لیست Hosts",
|
"dns.customHosts.list": "لیست Hosts",
|
||||||
"dns.customHosts.domainPlaceholder": "دامنه",
|
"dns.customHosts.domainPlaceholder": "دامنه",
|
||||||
"dns.customHosts.valuePlaceholder": "دامنه یا IP",
|
"dns.customHosts.valuePlaceholder": "دامنه یا IP",
|
||||||
|
"dns.saveOnly": "فقط ذخیره",
|
||||||
"profiles.title": "مدیریت پروفایل",
|
"profiles.title": "مدیریت پروفایل",
|
||||||
"profiles.updateAll": "بهروزرسانی همه پروفایلها",
|
"profiles.updateAll": "بهروزرسانی همه پروفایلها",
|
||||||
"profiles.useProxy": "پراکسی",
|
"profiles.useProxy": "پراکسی",
|
||||||
|
|||||||
@ -207,7 +207,7 @@
|
|||||||
"sider.cards.connections": "Подключения",
|
"sider.cards.connections": "Подключения",
|
||||||
"sider.cards.core": "Настройки ядра",
|
"sider.cards.core": "Настройки ядра",
|
||||||
"sider.cards.dns": "Переопределение DNS",
|
"sider.cards.dns": "Переопределение DNS",
|
||||||
"sider.cards.sniff": "Анализ трафика",
|
"sider.cards.sniff": "переопределение сниффинга",
|
||||||
"sider.cards.logs": "Журналы",
|
"sider.cards.logs": "Журналы",
|
||||||
"sider.cards.substore": "Sub-Store",
|
"sider.cards.substore": "Sub-Store",
|
||||||
"sider.cards.config": "Конфигурация",
|
"sider.cards.config": "Конфигурация",
|
||||||
@ -260,6 +260,7 @@
|
|||||||
"proxies.search.placeholder": "Поиск прокси",
|
"proxies.search.placeholder": "Поиск прокси",
|
||||||
"proxies.locate": "Найти текущий прокси",
|
"proxies.locate": "Найти текущий прокси",
|
||||||
"sniffer.title": "Настройки анализа доменов",
|
"sniffer.title": "Настройки анализа доменов",
|
||||||
|
"sniffer.enable": "Включить анализ домена",
|
||||||
"sniffer.parsePureIP": "Анализировать немаппированные IP-адреса",
|
"sniffer.parsePureIP": "Анализировать немаппированные IP-адреса",
|
||||||
"sniffer.forceDNSMapping": "Анализировать реальные IP-маппинги",
|
"sniffer.forceDNSMapping": "Анализировать реальные IP-маппинги",
|
||||||
"sniffer.overrideDestination": "Переопределить адрес подключения",
|
"sniffer.overrideDestination": "Переопределить адрес подключения",
|
||||||
@ -275,6 +276,7 @@
|
|||||||
"sniffer.skipDstAddress.placeholder": "Пример: 1.1.1.1/32",
|
"sniffer.skipDstAddress.placeholder": "Пример: 1.1.1.1/32",
|
||||||
"sniffer.skipSrcAddress.title": "Пропустить анализ исходных адресов",
|
"sniffer.skipSrcAddress.title": "Пропустить анализ исходных адресов",
|
||||||
"sniffer.skipSrcAddress.placeholder": "Пример: 192.168.1.1/24",
|
"sniffer.skipSrcAddress.placeholder": "Пример: 192.168.1.1/24",
|
||||||
|
"sniffer.saveOnly": "Только сохранить",
|
||||||
"sysproxy.title": "Системный прокси",
|
"sysproxy.title": "Системный прокси",
|
||||||
"sysproxy.host.title": "Хост прокси",
|
"sysproxy.host.title": "Хост прокси",
|
||||||
"sysproxy.host.placeholder": "По умолчанию 127.0.0.1, не изменяйте без необходимости",
|
"sysproxy.host.placeholder": "По умолчанию 127.0.0.1, не изменяйте без необходимости",
|
||||||
@ -333,6 +335,7 @@
|
|||||||
"dns.customHosts.list": "Список Hosts",
|
"dns.customHosts.list": "Список Hosts",
|
||||||
"dns.customHosts.domainPlaceholder": "Домен",
|
"dns.customHosts.domainPlaceholder": "Домен",
|
||||||
"dns.customHosts.valuePlaceholder": "Домен или IP",
|
"dns.customHosts.valuePlaceholder": "Домен или IP",
|
||||||
|
"dns.saveOnly": "Только сохранить",
|
||||||
"profiles.title": "Управление профилями",
|
"profiles.title": "Управление профилями",
|
||||||
"profiles.updateAll": "Обновить все профили",
|
"profiles.updateAll": "Обновить все профили",
|
||||||
"profiles.useProxy": "Прокси",
|
"profiles.useProxy": "Прокси",
|
||||||
|
|||||||
@ -216,7 +216,7 @@
|
|||||||
"sider.cards.connections": "连接",
|
"sider.cards.connections": "连接",
|
||||||
"sider.cards.core": "内核设置",
|
"sider.cards.core": "内核设置",
|
||||||
"sider.cards.dns": "DNS覆写",
|
"sider.cards.dns": "DNS覆写",
|
||||||
"sider.cards.sniff": "域名嗅探",
|
"sider.cards.sniff": "嗅探覆写",
|
||||||
"sider.cards.logs": "日志",
|
"sider.cards.logs": "日志",
|
||||||
"sider.cards.substore": "Sub-Store",
|
"sider.cards.substore": "Sub-Store",
|
||||||
"sider.cards.config": "运行时配置",
|
"sider.cards.config": "运行时配置",
|
||||||
@ -269,6 +269,7 @@
|
|||||||
"proxies.search.placeholder": "搜索节点",
|
"proxies.search.placeholder": "搜索节点",
|
||||||
"proxies.locate": "定位到当前节点",
|
"proxies.locate": "定位到当前节点",
|
||||||
"sniffer.title": "域名嗅探设置",
|
"sniffer.title": "域名嗅探设置",
|
||||||
|
"sniffer.enable": "启用域名嗅探",
|
||||||
"sniffer.parsePureIP": "对未映射 IP 地址嗅探",
|
"sniffer.parsePureIP": "对未映射 IP 地址嗅探",
|
||||||
"sniffer.forceDNSMapping": "对真实 IP 映射嗅探",
|
"sniffer.forceDNSMapping": "对真实 IP 映射嗅探",
|
||||||
"sniffer.overrideDestination": "覆盖连接地址",
|
"sniffer.overrideDestination": "覆盖连接地址",
|
||||||
@ -284,6 +285,7 @@
|
|||||||
"sniffer.skipDstAddress.placeholder": "例:1.1.1.1/32",
|
"sniffer.skipDstAddress.placeholder": "例:1.1.1.1/32",
|
||||||
"sniffer.skipSrcAddress.title": "跳过来源地址嗅探",
|
"sniffer.skipSrcAddress.title": "跳过来源地址嗅探",
|
||||||
"sniffer.skipSrcAddress.placeholder": "例:192.168.1.1/24",
|
"sniffer.skipSrcAddress.placeholder": "例:192.168.1.1/24",
|
||||||
|
"sniffer.saveOnly": "仅保存",
|
||||||
"sysproxy.title": "系统代理",
|
"sysproxy.title": "系统代理",
|
||||||
"sysproxy.host.title": "代理主机",
|
"sysproxy.host.title": "代理主机",
|
||||||
"sysproxy.host.placeholder": "默认 127.0.0.1 若无特殊需求请勿修改",
|
"sysproxy.host.placeholder": "默认 127.0.0.1 若无特殊需求请勿修改",
|
||||||
@ -342,6 +344,7 @@
|
|||||||
"dns.customHosts.list": "Hosts 列表",
|
"dns.customHosts.list": "Hosts 列表",
|
||||||
"dns.customHosts.domainPlaceholder": "域名",
|
"dns.customHosts.domainPlaceholder": "域名",
|
||||||
"dns.customHosts.valuePlaceholder": "域名或 IP",
|
"dns.customHosts.valuePlaceholder": "域名或 IP",
|
||||||
|
"dns.saveOnly": "仅保存",
|
||||||
"profiles.title": "订阅管理",
|
"profiles.title": "订阅管理",
|
||||||
"profiles.updateAll": "更新全部订阅",
|
"profiles.updateAll": "更新全部订阅",
|
||||||
"profiles.useProxy": "代理",
|
"profiles.useProxy": "代理",
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const DNS: React.FC = () => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
|
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
|
||||||
const { appConfig, patchAppConfig } = useAppConfig()
|
const { appConfig, patchAppConfig } = useAppConfig()
|
||||||
const { nameserverPolicy, useNameserverPolicy } = appConfig || {}
|
const { nameserverPolicy, useNameserverPolicy, controlDns = true } = appConfig || {}
|
||||||
const { dns, hosts } = controledMihomoConfig || {}
|
const { dns, hosts } = controledMihomoConfig || {}
|
||||||
const {
|
const {
|
||||||
enable = true,
|
enable = true,
|
||||||
@ -128,8 +128,10 @@ const DNS: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
setChanged(false)
|
setChanged(false)
|
||||||
await patchControledMihomoConfig(patch)
|
await patchControledMihomoConfig(patch)
|
||||||
await patchMihomoConfig(patch)
|
if (controlDns) {
|
||||||
await restartCore()
|
await patchMihomoConfig(patch)
|
||||||
|
await restartCore()
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e)
|
alert(e)
|
||||||
}
|
}
|
||||||
@ -175,7 +177,7 @@ const DNS: React.FC = () => {
|
|||||||
onSave(result)
|
onSave(result)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('common.save')}
|
{controlDns ? t('common.save') : t('dns.saveOnly')}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,8 @@ import BasePage from '@renderer/components/base/base-page'
|
|||||||
import SettingCard from '@renderer/components/base/base-setting-card'
|
import SettingCard from '@renderer/components/base/base-setting-card'
|
||||||
import SettingItem from '@renderer/components/base/base-setting-item'
|
import SettingItem from '@renderer/components/base/base-setting-item'
|
||||||
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
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 React, { ReactNode, useState } from 'react'
|
||||||
import { MdDeleteForever } from 'react-icons/md'
|
import { MdDeleteForever } from 'react-icons/md'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -11,8 +12,11 @@ import { useTranslation } from 'react-i18next'
|
|||||||
const Sniffer: React.FC = () => {
|
const Sniffer: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
|
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
|
||||||
|
const { appConfig } = useAppConfig()
|
||||||
|
const { controlSniff = true } = appConfig || {}
|
||||||
const { sniffer } = controledMihomoConfig || {}
|
const { sniffer } = controledMihomoConfig || {}
|
||||||
const {
|
const {
|
||||||
|
enable = true,
|
||||||
'parse-pure-ip': parsePureIP = true,
|
'parse-pure-ip': parsePureIP = true,
|
||||||
'force-dns-mapping': forceDNSMapping = true,
|
'force-dns-mapping': forceDNSMapping = true,
|
||||||
'override-destination': overrideDestination = false,
|
'override-destination': overrideDestination = false,
|
||||||
@ -41,6 +45,7 @@ const Sniffer: React.FC = () => {
|
|||||||
} = sniffer || {}
|
} = sniffer || {}
|
||||||
const [changed, setChanged] = useState(false)
|
const [changed, setChanged] = useState(false)
|
||||||
const [values, originSetValues] = useState({
|
const [values, originSetValues] = useState({
|
||||||
|
enable,
|
||||||
parsePureIP,
|
parsePureIP,
|
||||||
forceDNSMapping,
|
forceDNSMapping,
|
||||||
overrideDestination,
|
overrideDestination,
|
||||||
@ -59,7 +64,11 @@ const Sniffer: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
setChanged(false)
|
setChanged(false)
|
||||||
await patchControledMihomoConfig(patch)
|
await patchControledMihomoConfig(patch)
|
||||||
await restartCore()
|
|
||||||
|
if (controlSniff) {
|
||||||
|
await patchMihomoConfig(patch)
|
||||||
|
await restartCore()
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e)
|
alert(e)
|
||||||
}
|
}
|
||||||
@ -130,22 +139,34 @@ const Sniffer: React.FC = () => {
|
|||||||
onPress={() =>
|
onPress={() =>
|
||||||
onSave({
|
onSave({
|
||||||
sniffer: {
|
sniffer: {
|
||||||
|
enable: values.enable,
|
||||||
'parse-pure-ip': values.parsePureIP,
|
'parse-pure-ip': values.parsePureIP,
|
||||||
'force-dns-mapping': values.forceDNSMapping,
|
'force-dns-mapping': values.forceDNSMapping,
|
||||||
'override-destination': values.overrideDestination,
|
'override-destination': values.overrideDestination,
|
||||||
sniff: values.sniff,
|
sniff: values.sniff,
|
||||||
'skip-domain': values.skipDomain,
|
'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>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SettingCard>
|
<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>
|
<SettingItem title={t('sniffer.overrideDestination')} divider>
|
||||||
<Switch
|
<Switch
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user