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 +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"
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
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
|
||||
|
||||
### 新功能 (Feat)
|
||||
- 重构 DNS 控制模块,改为“覆写”逻辑,当开关打开后,使用DNS 设置中的配置覆盖订阅原始配置,关闭开关恢复订阅原始配置
|
||||
- 重构 DNS 卡片模块,改为“覆写”逻辑,当开关打开后,使用DNS 设置中的配置覆盖订阅原始配置,关闭开关恢复订阅原始配置
|
||||
|
||||
### 性能提升(Perf)
|
||||
- 更新依赖,提升页面响应性
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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')
|
||||
}
|
||||
|
||||
@ -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}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
// 按依赖顺序创建目录
|
||||
const dirsToCreate = [
|
||||
dataDir(),
|
||||
themesDir(),
|
||||
profilesDir(),
|
||||
overrideDir(),
|
||||
mihomoWorkDir(),
|
||||
logDir(),
|
||||
mihomoTestDir(),
|
||||
subStoreDir()
|
||||
]
|
||||
|
||||
for (const dir of dirsToCreate) {
|
||||
try {
|
||||
if (!existsSync(dir)) {
|
||||
await mkdir(dir, { recursive: true })
|
||||
}
|
||||
if (!existsSync(themesDir())) {
|
||||
await mkdir(themesDir())
|
||||
} catch (error) {
|
||||
console.error(`Failed to create directory ${dir}:`, error)
|
||||
throw new Error(`Failed to create directory ${dir}: ${error}`)
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
async function initConfig(): Promise<void> {
|
||||
if (!existsSync(appConfigPath())) {
|
||||
await writeFile(appConfigPath(), yaml.stringify(defaultConfig))
|
||||
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))
|
||||
}
|
||||
if (!existsSync(profileConfigPath())) {
|
||||
await writeFile(profileConfigPath(), yaml.stringify(defaultProfileConfig))
|
||||
} catch (error) {
|
||||
console.error(`Failed to create ${config.name} at ${config.path}:`, error)
|
||||
throw new Error(`Failed to create ${config.name}: ${error}`)
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
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'),
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
const handleContextMenu = (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setDropdownOpen(true)
|
||||
}
|
||||
}, [isDragging])
|
||||
|
||||
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]`} />
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "پراکسی",
|
||||
|
||||
@ -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": "Прокси",
|
||||
|
||||
@ -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": "代理",
|
||||
|
||||
@ -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)
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user