mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 05:00:30 +08:00
Compare commits
No commits in common. "v1.8.1" and "main" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,4 +8,3 @@ out
|
||||
*.log*
|
||||
.idea
|
||||
*.ttf
|
||||
party.md
|
||||
@ -8,7 +8,5 @@
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -51,14 +51,6 @@ else
|
||||
log "Warning: mihomo-alpha binary not found at $APP_PATH/Contents/Resources/sidecar/mihomo-alpha"
|
||||
fi
|
||||
|
||||
if [ -f "$APP_PATH/Contents/Resources/sidecar/mihomo-smart" ]; then
|
||||
chown root:admin "$APP_PATH/Contents/Resources/sidecar/mihomo-smart"
|
||||
chmod +s "$APP_PATH/Contents/Resources/sidecar/mihomo-smart"
|
||||
log "Set permissions for mihomo-smart"
|
||||
else
|
||||
log "Warning: mihomo-smart binary not found at $APP_PATH/Contents/Resources/sidecar/mihomo-smart"
|
||||
fi
|
||||
|
||||
# 复制 helper 工具
|
||||
log "Installing helper tool..."
|
||||
if [ -f "$APP_PATH/Contents/Resources/files/party.mihomo.helper" ]; then
|
||||
|
||||
79
changelog.md
79
changelog.md
@ -1,21 +1,72 @@
|
||||
## 1.8.1
|
||||
|
||||
## 1.7.7
|
||||
### 新功能 (Feat)
|
||||
- 重构 DNS 控制模块,改为“覆写”逻辑,当开关打开后,使用DNS 设置中的配置覆盖订阅原始配置,关闭开关恢复订阅原始配置
|
||||
|
||||
### 性能提升(Perf)
|
||||
- 更新依赖,提升页面响应性
|
||||
- 优化订阅切换逻辑,大幅提升切换速度和稳定性
|
||||
- Mihomo 内核升级 v1.19.12
|
||||
- 新增 Webdav 最大备数设置和清理逻辑
|
||||
|
||||
### 修复 (Fix)
|
||||
- “使用自动 Smart 规则覆写”没有覆盖兜底的 MATCH 规则
|
||||
- 移除默认的 Smart "policy-priority" 规则
|
||||
-
|
||||
- 修复 MacOS 下无法启动的问题(重置工作目录权限)
|
||||
- 尝试修复不同版本 MacOS 下安装软件时候的报错(Input/output error)
|
||||
- 部分遗漏的多国语言翻译
|
||||
|
||||
## 1.8.0
|
||||
## 1.7.6
|
||||
|
||||
**此版本修复了 1.7.5 中的几个严重 bug,推荐所有人更新**
|
||||
|
||||
### 修复 (Fix)
|
||||
- 修复了内核1.19.8更新后gist同步失效的问题(#780)
|
||||
- 部分遗漏的多国语言翻译
|
||||
- MacOS 下启动Error: EACCES: permission denied
|
||||
- MacOS 系统代理 bypass 不生效
|
||||
- MacOS 系统代理开启时 500 报错
|
||||
|
||||
## 1.7.5
|
||||
|
||||
### 新功能 (Feat)
|
||||
**重大更新:本次更新增加了 Smart Core,可以根据用户使用习惯和节点质量自动选择符合您的最优节点。并内置了“一键开启”,适合不想折腾自定义规则的用户
|
||||
“一键开启”内置 Smart规则的功能在“内核设置”下的“使用自动 Smart 规则覆写”,原理:当开关开启后,自动载入覆写脚本,新增 Smart Group,并替换当前配置文件下的默认出站规则为"Smart Group",您的所有代理流量都将从此分组下的节点流出。如果使用“全局模式”请选择名称为"Smart Group"的节点,以使用该功能。**
|
||||
- 增加组延迟测试时的动画
|
||||
- 订阅卡片可右键点击
|
||||
-
|
||||
|
||||
注意:本功能还在测试中,如遇到问题请发 issue 反馈
|
||||
### 修复 (Fix)
|
||||
- 1.7.4引入的内核启动错误
|
||||
- 无法手动设置内核权限
|
||||
- 完善 系统代理socket 重建和检测机制
|
||||
|
||||
## 1.7.4
|
||||
|
||||
### 新功能 (Feat)
|
||||
- Mihomo 内核升级 v1.19.10
|
||||
- 改进 socket创建机制,防止 MacOS 下系统代理开启无法找到 socket 文件的问题
|
||||
- mihomo-party-helper增加更多日志,以方便调试
|
||||
- 改进 MacOS 下签名和公正流程
|
||||
- 增加 MacOS 下 plist 权限设置
|
||||
- 改进安装流程
|
||||
-
|
||||
|
||||
### 修复 (Fix)
|
||||
- 修复mihomo-party-helper本地提权漏洞
|
||||
- 修复 MacOS 下安装失败的问题
|
||||
- 移除节点页面的滚动位置记忆,解决页面溢出的问题
|
||||
- DNS hosts 设置在 useHosts 不为 true 时也会被错误应用的问题(#742)
|
||||
- 当用户在 Profile 设置中修改了更新间隔并保存后,新的间隔时间不会立即生效(#671)
|
||||
- 禁止选择器组件选择空值
|
||||
- 修复proxy-provider
|
||||
|
||||
## 1.7.3
|
||||
**注意:如安装后为英文,请在设置中反复选择几次不同语言以写入配置文件**
|
||||
|
||||
### 新功能 (Feat)
|
||||
- Mihomo 内核升级 v1.19.5
|
||||
- MacOS 下添加 Dock 图标动态展现方式 (#594)
|
||||
- 更改默认 UA 并添加版本
|
||||
- 添加固定间隔的配置文件更新按钮 (#670)
|
||||
- 重构Linux上的手动授权内核方式
|
||||
- 将sub-store迁移到工作目录下(#552)
|
||||
- 重置软件增加警告提示
|
||||
|
||||
### 修复 (Fix)
|
||||
- 修复代理节点页面因为重复刷新导致的溢出问题
|
||||
- 修复由于 Mihomo 核心错误导致启动时窗口丢失 (#601)
|
||||
- 修复macOS下的sub-store更新问题 (#552)
|
||||
- 修复多语言翻译
|
||||
- 修复 defaultBypass 几乎总是 Windows 默认绕过设置 (#602)
|
||||
- 修复重置防火墙时发生的错误,因为没有指定防火墙规则 (#650)
|
||||
|
||||
@ -39,14 +39,12 @@ mac:
|
||||
target:
|
||||
- pkg
|
||||
entitlementsInherit: build/entitlements.mac.plist
|
||||
hardenedRuntime: true
|
||||
gatekeeperAssess: false
|
||||
extendInfo:
|
||||
- NSCameraUsageDescription: Application requests access to the device's camera.
|
||||
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||
notarize: false
|
||||
notarize: true
|
||||
artifactName: ${name}-macos-${version}-${arch}.${ext}
|
||||
pkg:
|
||||
allowAnywhere: false
|
||||
@ -56,9 +54,8 @@ pkg:
|
||||
file: build/background.png
|
||||
linux:
|
||||
desktop:
|
||||
entry:
|
||||
Name: Mihomo Party
|
||||
MimeType: 'x-scheme-handler/clash;x-scheme-handler/mihomo'
|
||||
Name: Mihomo Party
|
||||
MimeType: 'x-scheme-handler/clash;x-scheme-handler/mihomo'
|
||||
target:
|
||||
- deb
|
||||
- rpm
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { resolve } from 'path'
|
||||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
// https://github.com/vdesjs/vite-plugin-monaco-editor/issues/21#issuecomment-1827562674
|
||||
import monacoEditorPluginModule from 'vite-plugin-monaco-editor'
|
||||
const isObjectWithDefaultFunction = (
|
||||
@ -38,7 +37,6 @@ export default defineConfig({
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
tailwindcss(),
|
||||
monacoEditorPlugin({
|
||||
languageWorkers: ['editorWorkerService', 'typescript', 'css'],
|
||||
customDistPath: (_, out) => `${out}/monacoeditorwork`,
|
||||
|
||||
109
package.json
109
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mihomo-party",
|
||||
"version": "1.8.1",
|
||||
"version": "1.7.7",
|
||||
"description": "Mihomo Party",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "mihomo-party-org",
|
||||
@ -23,76 +23,75 @@
|
||||
"build:linux": "electron-vite build && electron-builder --publish never --linux"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron-toolkit/preload": "^3.0.2",
|
||||
"@electron-toolkit/utils": "^4.0.0",
|
||||
"@heroui/react": "^2.8.2",
|
||||
"@mihomo-party/sysproxy": "^2.0.8",
|
||||
"@mihomo-party/sysproxy-darwin-arm64": "^2.0.8",
|
||||
"@electron-toolkit/preload": "^3.0.1",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@heroui/react": "^2.6.14",
|
||||
"@mihomo-party/sysproxy": "^2.0.7",
|
||||
"@mihomo-party/sysproxy-darwin-arm64": "^2.0.7",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.11.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"chokidar": "^4.0.3",
|
||||
"axios": "^1.7.7",
|
||||
"chokidar": "^4.0.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"express": "^5.1.0",
|
||||
"i18next": "^25.3.2",
|
||||
"express": "^5.0.1",
|
||||
"i18next": "^24.2.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-i18next": "^15.6.1",
|
||||
"webdav": "^5.8.0",
|
||||
"ws": "^8.18.3",
|
||||
"yaml": "^2.8.0"
|
||||
"react-i18next": "^15.4.0",
|
||||
"webdav": "^5.7.1",
|
||||
"ws": "^8.18.0",
|
||||
"yaml": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
||||
"@electron-toolkit/eslint-config-ts": "^3.1.0",
|
||||
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
|
||||
"@electron-toolkit/eslint-config-ts": "^2.0.0",
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/node": "^24.1.0",
|
||||
"@types/adm-zip": "^0.5.6",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^22.13.1",
|
||||
"@types/pubsub-js": "^1.8.6",
|
||||
"@types/react": "^19.1.9",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"cron-validator": "^1.4.0",
|
||||
"driver.js": "^1.3.6",
|
||||
"electron": "^37.2.5",
|
||||
"electron-builder": "26.0.12",
|
||||
"electron-vite": "^4.0.0",
|
||||
"@types/react": "^19.0.4",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"@types/ws": "^8.5.13",
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"cron-validator": "^1.3.1",
|
||||
"driver.js": "^1.3.5",
|
||||
"electron": "^34.0.2",
|
||||
"electron-builder": "25.1.8",
|
||||
"electron-vite": "^2.3.0",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"eslint": "9.32.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"form-data": "^4.0.4",
|
||||
"framer-motion": "12.23.12",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"form-data": "^4.0.1",
|
||||
"framer-motion": "12.0.11",
|
||||
"lodash": "^4.17.21",
|
||||
"meta-json-schema": "^1.19.12",
|
||||
"monaco-yaml": "^5.4.0",
|
||||
"nanoid": "^5.1.5",
|
||||
"next-themes": "^0.4.6",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"meta-json-schema": "^1.18.9",
|
||||
"monaco-yaml": "^5.2.3",
|
||||
"nanoid": "^5.0.8",
|
||||
"next-themes": "^0.4.3",
|
||||
"postcss": "^8.4.47",
|
||||
"prettier": "^3.3.3",
|
||||
"pubsub-js": "^1.9.5",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-monaco-editor": "^0.59.0",
|
||||
"react-router-dom": "^7.7.1",
|
||||
"react-virtuoso": "^4.13.0",
|
||||
"swr": "^2.3.4",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-error-boundary": "^5.0.0",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-monaco-editor": "^0.58.0",
|
||||
"react-router-dom": "^7.1.5",
|
||||
"react-virtuoso": "^4.12.0",
|
||||
"recharts": "^2.13.3",
|
||||
"swr": "^2.2.5",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tar": "^7.4.3",
|
||||
"tsx": "^4.20.3",
|
||||
"tsx": "^4.19.2",
|
||||
"types-pac": "^1.0.3",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.0.6",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^6.0.7",
|
||||
"vite-plugin-monaco-editor": "^1.1.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c"
|
||||
|
||||
9268
pnpm-lock.yaml
generated
9268
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
@ -45,36 +45,6 @@ async function getLatestAlphaVersion() {
|
||||
}
|
||||
}
|
||||
|
||||
/* ======= mihomo smart ======= */
|
||||
const MIHOMO_SMART_VERSION_URL =
|
||||
'https://github.com/vernesong/mihomo/releases/download/Prerelease-Alpha/version.txt'
|
||||
const MIHOMO_SMART_URL_PREFIX = `https://github.com/vernesong/mihomo/releases/download/Prerelease-Alpha`
|
||||
let MIHOMO_SMART_VERSION
|
||||
|
||||
const MIHOMO_SMART_MAP = {
|
||||
'win32-x64': 'mihomo-windows-amd64-v2-go120',
|
||||
'win32-ia32': 'mihomo-windows-386-go120',
|
||||
'win32-arm64': 'mihomo-windows-arm64',
|
||||
'darwin-x64': 'mihomo-darwin-amd64-v2-go120',
|
||||
'darwin-arm64': 'mihomo-darwin-arm64',
|
||||
'linux-x64': 'mihomo-linux-amd64-v2-go120',
|
||||
'linux-arm64': 'mihomo-linux-arm64'
|
||||
}
|
||||
|
||||
async function getLatestSmartVersion() {
|
||||
try {
|
||||
const response = await fetch(MIHOMO_SMART_VERSION_URL, {
|
||||
method: 'GET'
|
||||
})
|
||||
let v = await response.text()
|
||||
MIHOMO_SMART_VERSION = v.trim() // Trim to remove extra whitespaces
|
||||
console.log(`Latest smart version: ${MIHOMO_SMART_VERSION}`)
|
||||
} catch (error) {
|
||||
console.error('Error fetching latest smart version:', error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
/* ======= mihomo release ======= */
|
||||
const MIHOMO_VERSION_URL =
|
||||
'https://github.com/MetaCubeX/mihomo/releases/latest/download/version.txt'
|
||||
@ -117,10 +87,6 @@ if (!MIHOMO_ALPHA_MAP[`${platform}-${arch}`]) {
|
||||
throw new Error(`unsupported platform "${platform}-${arch}"`)
|
||||
}
|
||||
|
||||
if (!MIHOMO_SMART_MAP[`${platform}-${arch}`]) {
|
||||
throw new Error(`unsupported platform "${platform}-${arch}"`)
|
||||
}
|
||||
|
||||
/**
|
||||
* core info
|
||||
*/
|
||||
@ -157,23 +123,6 @@ function mihomo() {
|
||||
downloadURL
|
||||
}
|
||||
}
|
||||
|
||||
function mihomoSmart() {
|
||||
const name = MIHOMO_SMART_MAP[`${platform}-${arch}`]
|
||||
const isWin = platform === 'win32'
|
||||
const urlExt = isWin ? 'zip' : 'gz'
|
||||
const downloadURL = `${MIHOMO_SMART_URL_PREFIX}/${name}-${MIHOMO_SMART_VERSION}.${urlExt}`
|
||||
const exeFile = `${name}${isWin ? '.exe' : ''}`
|
||||
const zipFile = `${name}-${MIHOMO_SMART_VERSION}.${urlExt}`
|
||||
|
||||
return {
|
||||
name: 'mihomo-smart',
|
||||
targetFile: `mihomo-smart${isWin ? '.exe' : ''}`,
|
||||
exeFile,
|
||||
zipFile,
|
||||
downloadURL
|
||||
}
|
||||
}
|
||||
/**
|
||||
* download sidecar and rename
|
||||
*/
|
||||
@ -411,11 +360,6 @@ const tasks = [
|
||||
func: () => getLatestReleaseVersion().then(() => resolveSidecar(mihomo())),
|
||||
retry: 5
|
||||
},
|
||||
{
|
||||
name: 'mihomo-smart',
|
||||
func: () => getLatestSmartVersion().then(() => resolveSidecar(mihomoSmart())),
|
||||
retry: 5
|
||||
},
|
||||
{ name: 'mmdb', func: resolveMmdb, retry: 5 },
|
||||
{ name: 'metadb', func: resolveMetadb, retry: 5 },
|
||||
{ name: 'geosite', func: resolveGeosite, retry: 5 },
|
||||
@ -488,14 +432,7 @@ async function runTask() {
|
||||
break
|
||||
} catch (err) {
|
||||
console.error(`[ERROR]: task::${task.name} try ${i} ==`, err.message)
|
||||
if (i === task.retry - 1) {
|
||||
if (task.optional) {
|
||||
console.log(`[WARN]: Optional task::${task.name} failed, skipping...`)
|
||||
break
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
if (i === task.retry - 1) throw err
|
||||
}
|
||||
}
|
||||
return runTask()
|
||||
|
||||
@ -20,22 +20,15 @@ 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()
|
||||
|
||||
// DNS 配置覆写逻辑
|
||||
if (controlDns) {
|
||||
if (!controledMihomoConfig.dns || controledMihomoConfig.dns?.ipv6 === undefined) {
|
||||
controledMihomoConfig.dns = { ...defaultControledMihomoConfig.dns }
|
||||
}
|
||||
const originalEnable = controledMihomoConfig.dns?.enable
|
||||
controledMihomoConfig.dns = deepMerge(controledMihomoConfig.dns || {}, defaultControledMihomoConfig.dns || {})
|
||||
if (originalEnable !== undefined) {
|
||||
controledMihomoConfig.dns.enable = originalEnable
|
||||
}
|
||||
} else {
|
||||
if (!controlDns) {
|
||||
delete controledMihomoConfig.dns
|
||||
delete controledMihomoConfig.hosts
|
||||
} else {
|
||||
// 从不接管状态恢复
|
||||
if (controledMihomoConfig.dns?.ipv6 === undefined) {
|
||||
controledMihomoConfig.dns = defaultControledMihomoConfig.dns
|
||||
}
|
||||
}
|
||||
|
||||
if (!controlSniff) {
|
||||
delete controledMihomoConfig.sniffer
|
||||
} else {
|
||||
@ -44,7 +37,6 @@ export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>):
|
||||
controledMihomoConfig.sniffer = defaultControledMihomoConfig.sniffer
|
||||
}
|
||||
}
|
||||
|
||||
if (patch.hosts) {
|
||||
controledMihomoConfig.hosts = patch.hosts
|
||||
}
|
||||
@ -52,9 +44,7 @@ export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>):
|
||||
controledMihomoConfig.dns = controledMihomoConfig.dns || {}
|
||||
controledMihomoConfig.dns['nameserver-policy'] = patch.dns['nameserver-policy']
|
||||
}
|
||||
|
||||
controledMihomoConfig = deepMerge(controledMihomoConfig, patch)
|
||||
|
||||
if (!useNameserverPolicy) {
|
||||
delete controledMihomoConfig?.dns?.['nameserver-policy']
|
||||
}
|
||||
|
||||
@ -27,9 +27,3 @@ export {
|
||||
setOverride,
|
||||
updateOverrideItem
|
||||
} from './override'
|
||||
export {
|
||||
createSmartOverride,
|
||||
removeSmartOverride,
|
||||
manageSmartOverride,
|
||||
isSmartOverrideExists
|
||||
} from './smartOverride'
|
||||
|
||||
@ -37,21 +37,15 @@ export async function getProfileItem(id: string | undefined): Promise<IProfileIt
|
||||
export async function changeCurrentProfile(id: string): Promise<void> {
|
||||
const config = await getProfileConfig()
|
||||
const current = config.current
|
||||
|
||||
if (current === id) {
|
||||
return
|
||||
}
|
||||
|
||||
config.current = id
|
||||
await setProfileConfig(config)
|
||||
|
||||
try {
|
||||
await restartCore()
|
||||
} catch (e) {
|
||||
// 如果重启失败,恢复原来的配置
|
||||
config.current = current
|
||||
await setProfileConfig(config)
|
||||
throw e
|
||||
} finally {
|
||||
await setProfileConfig(config)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,283 +0,0 @@
|
||||
import { getAppConfig } from './app'
|
||||
import { addOverrideItem, removeOverrideItem, getOverrideItem } from './override'
|
||||
|
||||
const SMART_OVERRIDE_ID = 'smart-core-override'
|
||||
|
||||
/**
|
||||
* Smart 内核的覆写配置模板
|
||||
*/
|
||||
function generateSmartOverrideTemplate(useLightGBM: boolean, collectData: boolean, strategy: string): string {
|
||||
return `
|
||||
// 配置会在启用 Smart 内核时自动应用
|
||||
|
||||
function main(config) {
|
||||
try {
|
||||
// 确保配置对象存在
|
||||
if (!config || typeof config !== 'object') {
|
||||
console.log('[Smart Override] Invalid config object')
|
||||
return config
|
||||
}
|
||||
|
||||
// 确保代理组配置存在
|
||||
if (!config['proxy-groups']) {
|
||||
config['proxy-groups'] = []
|
||||
}
|
||||
|
||||
// 确保代理组是数组
|
||||
if (!Array.isArray(config['proxy-groups'])) {
|
||||
console.log('[Smart Override] proxy-groups is not an array, converting...')
|
||||
config['proxy-groups'] = []
|
||||
}
|
||||
|
||||
// 查找现有的 Smart 代理组并更新
|
||||
let smartGroupExists = false
|
||||
for (let i = 0; i < config['proxy-groups'].length; i++) {
|
||||
const group = config['proxy-groups'][i]
|
||||
if (group && group.type === 'smart') {
|
||||
smartGroupExists = true
|
||||
console.log('[Smart Override] Found existing smart group:', group.name)
|
||||
|
||||
if (!group['policy-priority']) {
|
||||
group['policy-priority'] = '' // policy-priority: <1 means lower priority, >1 means higher priority, the default is 1, pattern support regex and string
|
||||
}
|
||||
group.uselightgbm = ${useLightGBM}
|
||||
group.collectdata = ${collectData}
|
||||
group.strategy = '${strategy}'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有 Smart 组且有可用代理,创建示例组
|
||||
if (!smartGroupExists && config.proxies && Array.isArray(config.proxies) && config.proxies.length > 0) {
|
||||
console.log('[Smart Override] Creating new smart group with', config.proxies.length, 'proxies')
|
||||
|
||||
// 获取所有代理的名称
|
||||
const proxyNames = config.proxies
|
||||
.filter(proxy => proxy && typeof proxy === 'object' && proxy.name)
|
||||
.map(proxy => proxy.name)
|
||||
|
||||
if (proxyNames.length > 0) {
|
||||
const smartGroup = {
|
||||
name: 'Smart Group',
|
||||
type: 'smart',
|
||||
'policy-priority': '', // policy-priority: <1 means lower priority, >1 means higher priority, the default is 1, pattern support regex and string
|
||||
uselightgbm: ${useLightGBM},
|
||||
collectdata: ${collectData},
|
||||
strategy: '${strategy}',
|
||||
proxies: proxyNames
|
||||
}
|
||||
config['proxy-groups'].unshift(smartGroup)
|
||||
console.log('[Smart Override] Created smart group at first position with proxies:', proxyNames)
|
||||
} else {
|
||||
console.log('[Smart Override] No valid proxies found, skipping smart group creation')
|
||||
}
|
||||
} else if (!smartGroupExists) {
|
||||
console.log('[Smart Override] No proxies available, skipping smart group creation')
|
||||
}
|
||||
|
||||
// 处理规则替换
|
||||
if (config.rules && Array.isArray(config.rules)) {
|
||||
console.log('[Smart Override] Processing rules, original count:', config.rules.length)
|
||||
|
||||
// 收集所有代理组名称
|
||||
const proxyGroupNames = new Set()
|
||||
if (config['proxy-groups'] && Array.isArray(config['proxy-groups'])) {
|
||||
config['proxy-groups'].forEach(group => {
|
||||
if (group && group.name) {
|
||||
proxyGroupNames.add(group.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 添加常见的内置目标
|
||||
const builtinTargets = new Set([
|
||||
'DIRECT',
|
||||
'REJECT',
|
||||
'REJECT-DROP',
|
||||
'PASS',
|
||||
'COMPATIBLE'
|
||||
])
|
||||
|
||||
// 添加常见的规则参数,不应该替换
|
||||
const ruleParams = new Set(['no-resolve', 'force-remote-dns', 'prefer-ipv6'])
|
||||
|
||||
console.log('[Smart Override] Found', proxyGroupNames.size, 'proxy groups:', Array.from(proxyGroupNames))
|
||||
|
||||
let replacedCount = 0
|
||||
config.rules = config.rules.map(rule => {
|
||||
if (typeof rule === 'string') {
|
||||
// 检查是否是复杂规则格式(包含括号的嵌套规则)
|
||||
if (rule.includes('((') || rule.includes('))')) {
|
||||
console.log('[Smart Override] Skipping complex nested rule:', rule)
|
||||
return rule
|
||||
}
|
||||
|
||||
// 处理字符串格式的规则
|
||||
const parts = rule.split(',').map(part => part.trim())
|
||||
if (parts.length >= 2) {
|
||||
// 找到代理组名称的位置
|
||||
let targetIndex = -1
|
||||
let targetValue = ''
|
||||
|
||||
// 处理 MATCH 规则
|
||||
if (parts[0] === 'MATCH' && parts.length === 2) {
|
||||
targetIndex = 1
|
||||
targetValue = parts[1]
|
||||
} else if (parts.length >= 3) {
|
||||
// 处理其他规则
|
||||
for (let i = 2; i < parts.length; i++) {
|
||||
const part = parts[i]
|
||||
if (!ruleParams.has(part)) {
|
||||
targetIndex = i
|
||||
targetValue = part
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetIndex !== -1 && targetValue) {
|
||||
// 检查是否应该替换
|
||||
const shouldReplace = !builtinTargets.has(targetValue) &&
|
||||
(proxyGroupNames.has(targetValue) ||
|
||||
!ruleParams.has(targetValue))
|
||||
|
||||
if (shouldReplace) {
|
||||
parts[targetIndex] = 'Smart Group'
|
||||
replacedCount++
|
||||
console.log('[Smart Override] Replaced rule target:', targetValue, '→ Smart Group')
|
||||
return parts.join(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (typeof rule === 'object' && rule !== null) {
|
||||
// 处理对象格式
|
||||
let targetField = ''
|
||||
let targetValue = ''
|
||||
|
||||
if (rule.target) {
|
||||
targetField = 'target'
|
||||
targetValue = rule.target
|
||||
} else if (rule.proxy) {
|
||||
targetField = 'proxy'
|
||||
targetValue = rule.proxy
|
||||
}
|
||||
|
||||
if (targetField && targetValue) {
|
||||
const shouldReplace = !builtinTargets.has(targetValue) &&
|
||||
(proxyGroupNames.has(targetValue) ||
|
||||
!ruleParams.has(targetValue))
|
||||
|
||||
if (shouldReplace) {
|
||||
rule[targetField] = 'Smart Group'
|
||||
replacedCount++
|
||||
console.log('[Smart Override] Replaced rule target:', targetValue, '→ Smart Group')
|
||||
}
|
||||
}
|
||||
}
|
||||
return rule
|
||||
})
|
||||
|
||||
console.log('[Smart Override] Rules processed, replaced', replacedCount, 'non-DIRECT rules with Smart Group')
|
||||
} else {
|
||||
console.log('[Smart Override] No rules found or rules is not an array')
|
||||
}
|
||||
|
||||
console.log('[Smart Override] Configuration processed successfully')
|
||||
return config
|
||||
} catch (error) {
|
||||
console.error('[Smart Override] Error processing config:', error)
|
||||
// 发生错误时返回原始配置,避免破坏整个配置
|
||||
return config
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或更新 Smart 内核覆写配置
|
||||
*/
|
||||
export async function createSmartOverride(): Promise<void> {
|
||||
try {
|
||||
// 获取应用配置
|
||||
const {
|
||||
smartCoreUseLightGBM = false,
|
||||
smartCoreCollectData = false,
|
||||
smartCoreStrategy = 'sticky-sessions'
|
||||
} = await getAppConfig()
|
||||
|
||||
// 生成覆写模板
|
||||
const template = generateSmartOverrideTemplate(
|
||||
smartCoreUseLightGBM,
|
||||
smartCoreCollectData,
|
||||
smartCoreStrategy
|
||||
)
|
||||
|
||||
// 检查是否已存在 Smart 覆写配置
|
||||
const existingOverride = await getOverrideItem(SMART_OVERRIDE_ID)
|
||||
|
||||
if (existingOverride) {
|
||||
// 如果已存在,更新配置
|
||||
await addOverrideItem({
|
||||
id: SMART_OVERRIDE_ID,
|
||||
name: 'Smart Core Override',
|
||||
type: 'local',
|
||||
ext: 'js',
|
||||
global: true,
|
||||
file: template
|
||||
})
|
||||
} else {
|
||||
// 如果不存在,创建新的覆写配置
|
||||
await addOverrideItem({
|
||||
id: SMART_OVERRIDE_ID,
|
||||
name: 'Smart Core Override',
|
||||
type: 'local',
|
||||
ext: 'js',
|
||||
global: true,
|
||||
file: template
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to create Smart override:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Smart 内核覆写配置
|
||||
*/
|
||||
export async function removeSmartOverride(): Promise<void> {
|
||||
try {
|
||||
const existingOverride = await getOverrideItem(SMART_OVERRIDE_ID)
|
||||
if (existingOverride) {
|
||||
await removeOverrideItem(SMART_OVERRIDE_ID)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to remove Smart override:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据应用配置管理 Smart 覆写
|
||||
*/
|
||||
export async function manageSmartOverride(): Promise<void> {
|
||||
const { enableSmartCore = true, enableSmartOverride = true, core } = await getAppConfig()
|
||||
|
||||
if (enableSmartCore && enableSmartOverride && core === 'mihomo-smart') {
|
||||
await createSmartOverride()
|
||||
} else {
|
||||
await removeSmartOverride()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 Smart 覆写是否存在
|
||||
*/
|
||||
export async function isSmartOverrideExists(): Promise<boolean> {
|
||||
try {
|
||||
const override = await getOverrideItem(SMART_OVERRIDE_ID)
|
||||
return !!override
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -15,8 +15,7 @@ import {
|
||||
getControledMihomoConfig,
|
||||
getProfileConfig,
|
||||
patchAppConfig,
|
||||
patchControledMihomoConfig,
|
||||
manageSmartOverride
|
||||
patchControledMihomoConfig
|
||||
} from '../config'
|
||||
import { app, dialog, ipcMain, net } from 'electron'
|
||||
import {
|
||||
@ -84,10 +83,6 @@ export async function startCore(detached = false): Promise<Promise<void>[]> {
|
||||
const { current } = await getProfileConfig()
|
||||
const { tun } = await getControledMihomoConfig()
|
||||
const corePath = mihomoCorePath(core)
|
||||
|
||||
// 管理 Smart 内核覆写配置
|
||||
await manageSmartOverride()
|
||||
|
||||
await generateProfile()
|
||||
await checkProfile()
|
||||
await stopCore()
|
||||
@ -212,12 +207,7 @@ export async function restartCore(): Promise<void> {
|
||||
try {
|
||||
await startCore()
|
||||
} catch (e) {
|
||||
// 记录错误到日志而不是显示阻塞对话框
|
||||
await writeFile(logPath(), `[Manager]: restart core failed, ${e}\n`, {
|
||||
flag: 'a'
|
||||
})
|
||||
// 重新抛出错误,让调用者处理
|
||||
throw e
|
||||
dialog.showErrorBox(i18next.t('mihomo.error.coreStartFailed'), `${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,32 +249,15 @@ async function checkProfile(): Promise<void> {
|
||||
mihomoTestDir()
|
||||
], { env })
|
||||
} catch (error) {
|
||||
console.error('Profile check failed:', error)
|
||||
|
||||
if (error instanceof Error && 'stdout' in error) {
|
||||
const { stdout, stderr } = error as { stdout: string; stderr?: string }
|
||||
console.log('Profile check stdout:', stdout)
|
||||
console.log('Profile check stderr:', stderr)
|
||||
|
||||
const { stdout } = error as { stdout: string }
|
||||
const errorLines = stdout
|
||||
.split('\n')
|
||||
.filter((line) => line.includes('level=error') || line.includes('error'))
|
||||
.map((line) => {
|
||||
if (line.includes('level=error')) {
|
||||
return line.split('level=error')[1]?.trim() || line
|
||||
}
|
||||
return line.trim()
|
||||
})
|
||||
.filter(line => line.length > 0)
|
||||
|
||||
if (errorLines.length === 0) {
|
||||
const allLines = stdout.split('\n').filter(line => line.trim().length > 0)
|
||||
throw new Error(`${i18next.t('mihomo.error.profileCheckFailed')}:\n${allLines.join('\n')}`)
|
||||
} else {
|
||||
throw new Error(`${i18next.t('mihomo.error.profileCheckFailed')}:\n${errorLines.join('\n')}`)
|
||||
}
|
||||
.filter((line) => line.includes('level=error'))
|
||||
.map((line) => line.split('level=error')[1])
|
||||
throw new Error(`${i18next.t('mihomo.error.profileCheckFailed')}:\n${errorLines.join('\n')}`)
|
||||
} else {
|
||||
throw new Error(`${i18next.t('mihomo.error.profileCheckFailed')}: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,21 +168,6 @@ export const mihomoUpgrade = async (): Promise<void> => {
|
||||
return await instance.post('/upgrade')
|
||||
}
|
||||
|
||||
// Smart 内核 API
|
||||
export const mihomoSmartGroupWeights = async (groupName: string): Promise<Record<string, number>> => {
|
||||
const instance = await getAxios()
|
||||
return await instance.get(`/group/${encodeURIComponent(groupName)}/weights`)
|
||||
}
|
||||
|
||||
export const mihomoSmartFlushCache = async (configName?: string): Promise<void> => {
|
||||
const instance = await getAxios()
|
||||
if (configName) {
|
||||
return await instance.post(`/cache/smart/flush/${encodeURIComponent(configName)}`)
|
||||
} else {
|
||||
return await instance.post('/cache/smart/flush')
|
||||
}
|
||||
}
|
||||
|
||||
export const startMihomoTraffic = async (): Promise<void> => {
|
||||
await mihomoTraffic()
|
||||
}
|
||||
|
||||
@ -69,10 +69,6 @@ export function mihomoCoreDir(): string {
|
||||
|
||||
export function mihomoCorePath(core: string): string {
|
||||
const isWin = process.platform === 'win32'
|
||||
// 处理 Smart 内核
|
||||
if (core === 'mihomo-smart') {
|
||||
return path.join(mihomoCoreDir(), `mihomo-smart${isWin ? '.exe' : ''}`)
|
||||
}
|
||||
return path.join(mihomoCoreDir(), `${core}${isWin ? '.exe' : ''}`)
|
||||
}
|
||||
|
||||
|
||||
@ -16,9 +16,7 @@ import {
|
||||
mihomoUpgrade,
|
||||
mihomoUpgradeGeo,
|
||||
mihomoVersion,
|
||||
patchMihomoConfig,
|
||||
mihomoSmartGroupWeights,
|
||||
mihomoSmartFlushCache
|
||||
patchMihomoConfig
|
||||
} from '../core/mihomoApi'
|
||||
import { checkAutoRun, disableAutoRun, enableAutoRun } from '../sys/autoRun'
|
||||
import {
|
||||
@ -143,13 +141,6 @@ export function registerIpcMainHandlers(): void {
|
||||
ipcErrorWrapper(mihomoGroupDelay)(group, url)
|
||||
)
|
||||
ipcMain.handle('patchMihomoConfig', (_e, patch) => ipcErrorWrapper(patchMihomoConfig)(patch))
|
||||
// Smart 内核 API
|
||||
ipcMain.handle('mihomoSmartGroupWeights', (_e, groupName) =>
|
||||
ipcErrorWrapper(mihomoSmartGroupWeights)(groupName)
|
||||
)
|
||||
ipcMain.handle('mihomoSmartFlushCache', (_e, configName) =>
|
||||
ipcErrorWrapper(mihomoSmartFlushCache)(configName)
|
||||
)
|
||||
ipcMain.handle('checkAutoRun', ipcErrorWrapper(checkAutoRun))
|
||||
ipcMain.handle('enableAutoRun', ipcErrorWrapper(enableAutoRun))
|
||||
ipcMain.handle('disableAutoRun', ipcErrorWrapper(disableAutoRun))
|
||||
@ -260,18 +251,6 @@ export function registerIpcMainHandlers(): void {
|
||||
ipcMain.handle('alert', (_e, msg) => {
|
||||
dialog.showErrorBox('Mihomo Party', msg)
|
||||
})
|
||||
ipcMain.handle('showDetailedError', (_e, title, message) => {
|
||||
dialog.showErrorBox(title, message)
|
||||
})
|
||||
ipcMain.handle('getSmartOverrideContent', async () => {
|
||||
const { getOverrideItem } = await import('../config')
|
||||
try {
|
||||
const override = await getOverrideItem('smart-core-override')
|
||||
return override?.file || null
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
})
|
||||
ipcMain.handle('resetAppConfig', resetAppConfig)
|
||||
ipcMain.handle('relaunchApp', () => {
|
||||
app.relaunch()
|
||||
|
||||
@ -1,10 +1,5 @@
|
||||
export const defaultConfig: IAppConfig = {
|
||||
core: 'mihomo',
|
||||
enableSmartCore: true,
|
||||
enableSmartOverride: true,
|
||||
smartCoreUseLightGBM: false,
|
||||
smartCoreCollectData: false,
|
||||
smartCoreStrategy: 'sticky-sessions',
|
||||
silentStart: false,
|
||||
appTheme: 'system',
|
||||
useWindowFrame: false,
|
||||
@ -79,10 +74,8 @@ export const defaultControledMihomoConfig: Partial<IMihomoConfig> = {
|
||||
'fake-ip-filter': ['*', '+.lan', '+.local', 'time.*.com', 'ntp.*.com', '+.market.xiaomi.com'],
|
||||
'use-hosts': false,
|
||||
'use-system-hosts': false,
|
||||
'respect-rules': false,
|
||||
'default-nameserver': ['tls://223.5.5.5'],
|
||||
nameserver: ['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query'],
|
||||
'proxy-server-nameserver': ['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query'],
|
||||
nameserver: ['https://120.53.53.53/dns-query', 'https://223.5.5.5/dns-query'],
|
||||
'proxy-server-nameserver': ['https://120.53.53.53/dns-query', 'https://223.5.5.5/dns-query'],
|
||||
'direct-nameserver': []
|
||||
},
|
||||
sniffer: {
|
||||
|
||||
@ -373,13 +373,13 @@ const App: React.FC = () => {
|
||||
setSiderWidthValue(e.clientX)
|
||||
}
|
||||
}}
|
||||
className={`w-full h-screen flex ${resizing ? 'cursor-ew-resize' : ''}`}
|
||||
className={`w-full h-[100vh] flex ${resizing ? 'cursor-ew-resize' : ''}`}
|
||||
>
|
||||
{siderWidthValue === narrowWidth ? (
|
||||
<div style={{ width: `${narrowWidth}px` }} className="side h-full">
|
||||
<div className="app-drag flex justify-center items-center z-40 bg-transparent h-[49px]">
|
||||
{platform !== 'darwin' && (
|
||||
<MihomoIcon className="h-[32px] leading-[32px] text-lg mx-px" />
|
||||
<MihomoIcon className="h-[32px] leading-[32px] text-lg mx-[1px]" />
|
||||
)}
|
||||
<UpdaterButton iconOnly={true} />
|
||||
</div>
|
||||
@ -417,7 +417,7 @@ const App: React.FC = () => {
|
||||
className={`flex justify-between p-2 ${!useWindowFrame && platform === 'darwin' ? 'ml-[60px]' : ''}`}
|
||||
>
|
||||
<div className="flex ml-1">
|
||||
<MihomoIcon className="h-[32px] leading-[32px] text-lg mx-px" />
|
||||
<MihomoIcon className="h-[32px] leading-[32px] text-lg mx-[1px]" />
|
||||
<h3 className="text-lg font-bold leading-[32px]">ihomo Party</h3>
|
||||
</div>
|
||||
<UpdaterButton />
|
||||
|
||||
@ -59,9 +59,9 @@ const FloatingApp: React.FC = () => {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="app-drag h-screen w-screen overflow-hidden">
|
||||
<div className="floating-bg border border-divider flex rounded-full bg-content1 h-[calc(100%-2px)] w-[calc(100%-2px)]">
|
||||
<div className="flex justify-center items-center h-full aspect-square">
|
||||
<div className="app-drag h-[100vh] w-[100vw] overflow-hidden">
|
||||
<div className="floating-bg border-1 border-divider flex rounded-full bg-content1 h-[calc(100%-2px)] w-[calc(100%-2px)]">
|
||||
<div className="flex justify-center items-center h-[100%] aspect-square">
|
||||
<div
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault()
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
@import 'tailwindcss';
|
||||
@plugin './hero.ts';
|
||||
|
||||
@source '../**/*.{js,ts,jsx,tsx}';
|
||||
@source '../../../../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}';
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.floating-text {
|
||||
font-family:
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
import { heroui } from '@heroui/react'
|
||||
export default heroui()
|
||||
@ -1,12 +1,6 @@
|
||||
@import 'tailwindcss';
|
||||
@plugin './hero.ts';
|
||||
|
||||
@source '../**/*.{js,ts,jsx,tsx}';
|
||||
@source '../../../../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}';
|
||||
|
||||
@theme {
|
||||
--default-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
font-family: 'Noto Color Emoji';
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
.border-switch {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.border-switch input[type='checkbox'] {
|
||||
width: 100%;
|
||||
input[type='checkbox'] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ const ConnectionItem: React.FC<Props> = (props) => {
|
||||
<Chip
|
||||
color={`${info.isActive ? 'primary' : 'danger'}`}
|
||||
size="sm"
|
||||
radius="xs"
|
||||
radius="sm"
|
||||
variant="dot"
|
||||
>
|
||||
{info.metadata.type}({info.metadata.network.toUpperCase()})
|
||||
@ -65,16 +65,16 @@ const ConnectionItem: React.FC<Props> = (props) => {
|
||||
<Chip
|
||||
className="flag-emoji text-ellipsis whitespace-nowrap overflow-hidden"
|
||||
size="sm"
|
||||
radius="xs"
|
||||
radius="sm"
|
||||
variant="bordered"
|
||||
>
|
||||
{info.chains[0]}
|
||||
</Chip>
|
||||
<Chip size="sm" radius="xs" variant="bordered">
|
||||
<Chip size="sm" radius="sm" variant="bordered">
|
||||
↑ {calcTraffic(info.upload)} ↓ {calcTraffic(info.download)}
|
||||
</Chip>
|
||||
{info.uploadSpeed !== 0 || info.downloadSpeed !== 0 ? (
|
||||
<Chip color="primary" size="sm" radius="xs" variant="bordered">
|
||||
<Chip color="primary" size="sm" radius="sm" variant="bordered">
|
||||
↑ {calcTraffic(info.uploadSpeed || 0)}/s ↓ {calcTraffic(info.downloadSpeed || 0)}
|
||||
/s
|
||||
</Chip>
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
import { calcPercent, calcTraffic } from '@renderer/utils/calc'
|
||||
import { IoMdMore, IoMdRefresh } from 'react-icons/io'
|
||||
import dayjs from '@renderer/utils/dayjs'
|
||||
import React, { Key, useMemo, useState } from 'react'
|
||||
import React, { Key, useEffect, useMemo, useState } from 'react'
|
||||
import EditFileModal from './edit-file-modal'
|
||||
import EditInfoModal from './edit-info-modal'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
@ -72,8 +72,7 @@ const ProfileItem: React.FC<Props> = (props) => {
|
||||
id: info.id
|
||||
})
|
||||
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
|
||||
const [isActuallyDragging, setIsActuallyDragging] = useState(false)
|
||||
const [clickStartPos, setClickStartPos] = useState<{ x: number; y: number } | null>(null)
|
||||
const [disableSelect, setDisableSelect] = useState(false)
|
||||
|
||||
const menuItems: MenuItem[] = useMemo(() => {
|
||||
const list = [
|
||||
@ -151,35 +150,19 @@ const ProfileItem: React.FC<Props> = (props) => {
|
||||
setDropdownOpen(true)
|
||||
}
|
||||
|
||||
// 智能区分点击和拖拽的事件处理
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
setClickStartPos({ x: e.clientX, y: e.clientY })
|
||||
setIsActuallyDragging(false)
|
||||
}
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent) => {
|
||||
if (clickStartPos) {
|
||||
const dx = e.clientX - clickStartPos.x
|
||||
const dy = e.clientY - clickStartPos.y
|
||||
// 移动距离超过 5px 认为是拖拽
|
||||
if (dx * dx + dy * dy > 25) {
|
||||
setIsActuallyDragging(true)
|
||||
}
|
||||
useEffect(() => {
|
||||
if (isDragging) {
|
||||
setTimeout(() => {
|
||||
setDisableSelect(true)
|
||||
}, 200)
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
setDisableSelect(false)
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
}, [isDragging])
|
||||
|
||||
const handleMouseUp = () => {
|
||||
// 如果没有拖拽,则处理为点击事件
|
||||
if (!isActuallyDragging && !isDragging && clickStartPos) {
|
||||
setSelecting(true)
|
||||
onPress().finally(() => {
|
||||
setSelecting(false)
|
||||
})
|
||||
}
|
||||
|
||||
setClickStartPos(null)
|
||||
setTimeout(() => setIsActuallyDragging(false), 100)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -203,19 +186,18 @@ const ProfileItem: React.FC<Props> = (props) => {
|
||||
<Card
|
||||
as="div"
|
||||
fullWidth
|
||||
isPressable={false}
|
||||
isPressable
|
||||
onPress={() => {
|
||||
if (disableSelect) return
|
||||
setSelecting(true)
|
||||
onPress().finally(() => {
|
||||
setSelecting(false)
|
||||
})
|
||||
}}
|
||||
onContextMenu={handleContextMenu}
|
||||
className={`${isCurrent ? 'bg-primary' : ''} ${selecting ? 'blur-sm' : ''} cursor-pointer`}
|
||||
className={`${isCurrent ? 'bg-primary' : ''} ${selecting ? 'blur-sm' : ''}`}
|
||||
>
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className="w-full h-full"
|
||||
onMouseDownCapture={handleMouseDown}
|
||||
onMouseMoveCapture={handleMouseMove}
|
||||
onMouseUpCapture={handleMouseUp}
|
||||
>
|
||||
<div ref={setNodeRef} {...attributes} {...listeners} className="w-full h-full">
|
||||
<CardBody className="pb-1">
|
||||
<div className="flex justify-between h-[32px]">
|
||||
<h3
|
||||
|
||||
@ -60,7 +60,7 @@ const ProxyItem: React.FC<Props> = (props) => {
|
||||
onPress={() => onSelect(group.name, proxy.name)}
|
||||
isPressable
|
||||
fullWidth
|
||||
shadow="xs"
|
||||
shadow="sm"
|
||||
className={`${
|
||||
fixed
|
||||
? 'bg-secondary/30 border-r-2 border-r-secondary border-l-2 border-l-secondary'
|
||||
@ -68,7 +68,7 @@ const ProxyItem: React.FC<Props> = (props) => {
|
||||
? 'bg-primary/30 border-r-2 border-r-primary border-l-2 border-l-primary'
|
||||
: 'bg-content2'
|
||||
}`}
|
||||
radius="xs"
|
||||
radius="sm"
|
||||
>
|
||||
<CardBody className="p-1">
|
||||
{proxyDisplayMode === 'full' ? (
|
||||
|
||||
@ -2,29 +2,16 @@ import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
|
||||
import { FaCircleArrowDown, FaCircleArrowUp } from 'react-icons/fa6'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { calcTraffic } from '@renderer/utils/calc'
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { IoLink } from 'react-icons/io5'
|
||||
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
import { platform } from '@renderer/utils/init'
|
||||
import { Line } from 'react-chartjs-2'
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Filler,
|
||||
ChartOptions,
|
||||
ScriptableContext
|
||||
} from 'chart.js'
|
||||
import { Area, AreaChart, ResponsiveContainer } from 'recharts'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
// 注册 Chart.js 组件
|
||||
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Filler)
|
||||
|
||||
let currentUpload: number | undefined = undefined
|
||||
let currentDownload: number | undefined = undefined
|
||||
let hasShowTraffic = false
|
||||
@ -34,9 +21,10 @@ interface Props {
|
||||
iconOnly?: boolean
|
||||
}
|
||||
const ConnCard: React.FC<Props> = (props) => {
|
||||
const { theme = 'system', systemTheme = 'dark' } = useTheme()
|
||||
const { iconOnly } = props
|
||||
const { appConfig } = useAppConfig()
|
||||
const { showTraffic = false, connectionCardStatus = 'col-span-2' } = appConfig || {}
|
||||
const { showTraffic = false, connectionCardStatus = 'col-span-2', customTheme } = appConfig || {}
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const match = location.pathname.includes('/connections')
|
||||
@ -55,69 +43,25 @@ const ConnCard: React.FC<Props> = (props) => {
|
||||
id: 'connection'
|
||||
})
|
||||
const [series, setSeries] = useState(Array(10).fill(0))
|
||||
const [chartColor, setChartColor] = useState('rgba(255,255,255)')
|
||||
|
||||
// Chart.js 配置
|
||||
const chartData = useMemo(() => {
|
||||
return {
|
||||
labels: Array(10).fill(''),
|
||||
datasets: [
|
||||
{
|
||||
data: series,
|
||||
fill: true,
|
||||
backgroundColor: (context: ScriptableContext<'line'>) => {
|
||||
const chart = context.chart
|
||||
const { ctx, chartArea } = chart
|
||||
if (!chartArea) {
|
||||
return 'transparent'
|
||||
}
|
||||
useEffect(() => {
|
||||
setChartColor(
|
||||
match
|
||||
? `hsla(${getComputedStyle(document.documentElement).getPropertyValue('--heroui-primary-foreground')})`
|
||||
: `hsla(${getComputedStyle(document.documentElement).getPropertyValue('--heroui-foreground')})`
|
||||
)
|
||||
}, [theme, systemTheme, match])
|
||||
|
||||
const gradient = ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom)
|
||||
|
||||
// 颜色处理
|
||||
const isMatch = location.pathname.includes('/connections')
|
||||
const baseColor = isMatch ? '6, 182, 212' : '161, 161, 170' // primary vs foreground 的近似 RGB 值
|
||||
|
||||
gradient.addColorStop(0, `rgba(${baseColor}, 0.8)`)
|
||||
gradient.addColorStop(1, `rgba(${baseColor}, 0)`)
|
||||
return gradient
|
||||
},
|
||||
borderColor: 'transparent',
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 0,
|
||||
tension: 0.4
|
||||
}
|
||||
]
|
||||
}
|
||||
}, [series, location.pathname])
|
||||
|
||||
const chartOptions: ChartOptions<'line'> = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: false
|
||||
},
|
||||
y: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
borderWidth: 0
|
||||
}
|
||||
},
|
||||
interaction: {
|
||||
intersect: false
|
||||
},
|
||||
animation: {
|
||||
duration: 0
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setChartColor(
|
||||
match
|
||||
? `hsla(${getComputedStyle(document.documentElement).getPropertyValue('--heroui-primary-foreground')})`
|
||||
: `hsla(${getComputedStyle(document.documentElement).getPropertyValue('--heroui-foreground')})`
|
||||
)
|
||||
}, 200)
|
||||
}, [customTheme])
|
||||
|
||||
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
|
||||
useEffect(() => {
|
||||
@ -224,9 +168,30 @@ const ConnCard: React.FC<Props> = (props) => {
|
||||
</h3>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<div className="w-full h-full absolute top-0 left-0 pointer-events-none overflow-hidden rounded-[14px]">
|
||||
<Line data={chartData} options={chartOptions} />
|
||||
</div>
|
||||
<ResponsiveContainer
|
||||
height="100%"
|
||||
width="100%"
|
||||
className="w-full h-full absolute top-0 left-0 pointer-events-none overflow-hidden rounded-[14px]"
|
||||
>
|
||||
<AreaChart
|
||||
data={series.map((v) => ({ traffic: v }))}
|
||||
margin={{ top: 50, right: 0, left: 0, bottom: 0 }}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="gradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor={chartColor} stopOpacity={0.8} />
|
||||
<stop offset="100%" stopColor={chartColor} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Area
|
||||
isAnimationActive={false}
|
||||
type="monotone"
|
||||
dataKey="traffic"
|
||||
stroke="none"
|
||||
fill="url(#gradient)"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</>
|
||||
) : (
|
||||
<Card
|
||||
|
||||
@ -3,7 +3,7 @@ import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-c
|
||||
import BorderSwitch from '@renderer/components/base/border-swtich'
|
||||
import { LuServer } from 'react-icons/lu'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { restartCore } from '@renderer/utils/ipc'
|
||||
import { patchMihomoConfig } from '@renderer/utils/ipc'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
@ -15,14 +15,15 @@ interface Props {
|
||||
}
|
||||
const DNSCard: React.FC<Props> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
const { appConfig, patchAppConfig } = useAppConfig()
|
||||
const { appConfig } = useAppConfig()
|
||||
const { iconOnly } = props
|
||||
const { dnsCardStatus = 'col-span-1', controlDns = true } = appConfig || {}
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const match = location.pathname.includes('/dns')
|
||||
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
|
||||
const { tun } = controledMihomoConfig || {}
|
||||
const { dns, tun } = controledMihomoConfig || {}
|
||||
const { enable = true } = dns || {}
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
@ -34,33 +35,20 @@ const DNSCard: React.FC<Props> = (props) => {
|
||||
id: 'dns'
|
||||
})
|
||||
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
|
||||
const onChange = async (controlDnsEnabled: boolean): Promise<void> => {
|
||||
try {
|
||||
await patchAppConfig({ controlDns: controlDnsEnabled })
|
||||
await patchControledMihomoConfig({})
|
||||
await restartCore()
|
||||
} catch (e) {
|
||||
alert(e)
|
||||
}
|
||||
const onChange = async (enable: boolean): Promise<void> => {
|
||||
await patchControledMihomoConfig({ dns: { enable } })
|
||||
await patchMihomoConfig({ dns: { enable } })
|
||||
}
|
||||
|
||||
if (iconOnly) {
|
||||
return (
|
||||
<div className={`${dnsCardStatus} flex justify-center`}>
|
||||
<Tooltip
|
||||
content={
|
||||
controlDns
|
||||
? `${t('sider.cards.dns')} (${t('sider.cards.dns.overrideMode')})`
|
||||
: `${t('sider.cards.dns')} (${t('sider.cards.dns.originalConfig')})`
|
||||
}
|
||||
placement="right"
|
||||
>
|
||||
<div className={`${dnsCardStatus} ${!controlDns ? 'hidden' : ''} flex justify-center`}>
|
||||
<Tooltip content={t('sider.cards.dns')} placement="right">
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
color={match ? 'primary' : 'default'}
|
||||
variant={match ? 'solid' : 'light'}
|
||||
className={!controlDns ? 'opacity-60' : ''}
|
||||
onPress={() => {
|
||||
navigate('/dns')
|
||||
}}
|
||||
@ -80,14 +68,14 @@ const DNSCard: React.FC<Props> = (props) => {
|
||||
transition,
|
||||
zIndex: isDragging ? 'calc(infinity)' : undefined
|
||||
}}
|
||||
className={`${dnsCardStatus} dns-card`}
|
||||
className={`${dnsCardStatus} ${!controlDns ? 'hidden' : ''} dns-card`}
|
||||
>
|
||||
<Card
|
||||
fullWidth
|
||||
ref={setNodeRef}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className={`${match ? 'bg-primary' : 'hover:bg-primary/30'} ${isDragging ? 'scale-[0.97] tap-highlight-transparent' : ''} ${!controlDns ? 'opacity-60' : ''}`}
|
||||
className={`${match ? 'bg-primary' : 'hover:bg-primary/30'} ${isDragging ? 'scale-[0.97] tap-highlight-transparent' : ''}`}
|
||||
>
|
||||
<CardBody className="pb-1 pt-0 px-0">
|
||||
<div className="flex justify-between">
|
||||
@ -102,8 +90,8 @@ const DNSCard: React.FC<Props> = (props) => {
|
||||
/>
|
||||
</Button>
|
||||
<BorderSwitch
|
||||
isShowBorder={match && controlDns}
|
||||
isSelected={controlDns}
|
||||
isShowBorder={match && enable}
|
||||
isSelected={enable}
|
||||
isDisabled={tun?.enable}
|
||||
onValueChange={onChange}
|
||||
/>
|
||||
|
||||
@ -42,14 +42,13 @@ const SniffCard: React.FC<Props> = (props) => {
|
||||
|
||||
if (iconOnly) {
|
||||
return (
|
||||
<div className={`${sniffCardStatus} flex justify-center`}>
|
||||
<div className={`${sniffCardStatus} ${!controlSniff ? 'hidden' : ''} flex justify-center`}>
|
||||
<Tooltip content={t('sider.cards.sniff')} placement="right">
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
color={match ? 'primary' : 'default'}
|
||||
variant={match ? 'solid' : 'light'}
|
||||
className={!enable ? 'opacity-60' : ''}
|
||||
onPress={() => {
|
||||
navigate('/sniffer')
|
||||
}}
|
||||
@ -69,14 +68,14 @@ const SniffCard: React.FC<Props> = (props) => {
|
||||
transition,
|
||||
zIndex: isDragging ? 'calc(infinity)' : undefined
|
||||
}}
|
||||
className={`${sniffCardStatus} sniff-card`}
|
||||
className={`${sniffCardStatus} ${!controlSniff ? 'hidden' : ''} sniff-card`}
|
||||
>
|
||||
<Card
|
||||
fullWidth
|
||||
ref={setNodeRef}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className={`${match ? 'bg-primary' : 'hover:bg-primary/30'} ${isDragging ? 'scale-[0.97] tap-highlight-transparent' : ''} ${!enable ? 'opacity-60' : ''}`}
|
||||
className={`${match ? 'bg-primary' : 'hover:bg-primary/30'} ${isDragging ? 'scale-[0.97] tap-highlight-transparent' : ''}`}
|
||||
>
|
||||
<CardBody className="pb-1 pt-0 px-0">
|
||||
<div className="flex justify-between">
|
||||
|
||||
@ -58,7 +58,6 @@ const SysproxySwitcher: React.FC<Props> = (props) => {
|
||||
isIconOnly
|
||||
color={match ? 'primary' : 'default'}
|
||||
variant={match ? 'solid' : 'light'}
|
||||
className={!enable ? 'opacity-60' : ''}
|
||||
onPress={() => {
|
||||
navigate('/sysproxy')
|
||||
}}
|
||||
@ -85,7 +84,7 @@ const SysproxySwitcher: React.FC<Props> = (props) => {
|
||||
ref={setNodeRef}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className={`${match ? 'bg-primary' : 'hover:bg-primary/30'} ${isDragging ? 'scale-[0.97] tap-highlight-transparent' : ''} ${!enable ? 'opacity-60' : ''}`}
|
||||
className={`${match ? 'bg-primary' : 'hover:bg-primary/30'} ${isDragging ? 'scale-[0.97] tap-highlight-transparent' : ''}`}
|
||||
>
|
||||
<CardBody className="pb-1 pt-0 px-0">
|
||||
<div className="flex justify-between">
|
||||
|
||||
@ -56,7 +56,6 @@ const TunSwitcher: React.FC<Props> = (props) => {
|
||||
isIconOnly
|
||||
color={match ? 'primary' : 'default'}
|
||||
variant={match ? 'solid' : 'light'}
|
||||
className={!enable ? 'opacity-60' : ''}
|
||||
onPress={() => {
|
||||
navigate('/tun')
|
||||
}}
|
||||
@ -83,7 +82,7 @@ const TunSwitcher: React.FC<Props> = (props) => {
|
||||
ref={setNodeRef}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className={`${match ? 'bg-primary' : 'hover:bg-primary/30'} ${isDragging ? 'scale-[0.97] tap-highlight-transparent' : ''} ${!enable ? 'opacity-60' : ''}`}
|
||||
className={`${match ? 'bg-primary' : 'hover:bg-primary/30'} ${isDragging ? 'scale-[0.97] tap-highlight-transparent' : ''}`}
|
||||
>
|
||||
<CardBody className="pb-1 pt-0 px-0">
|
||||
<div className="flex justify-between">
|
||||
|
||||
@ -71,34 +71,13 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
|
||||
}
|
||||
|
||||
const changeCurrentProfile = async (id: string): Promise<void> => {
|
||||
if (profileConfig?.current === id) {
|
||||
return
|
||||
}
|
||||
|
||||
// 乐观更新:立即更新 UI 状态,提供即时反馈
|
||||
if (profileConfig) {
|
||||
const optimisticUpdate = { ...profileConfig, current: id }
|
||||
mutateProfileConfig(optimisticUpdate, false)
|
||||
}
|
||||
|
||||
try {
|
||||
// 异步执行后台切换,不阻塞 UI
|
||||
change(id).then(() => {
|
||||
window.electron.ipcRenderer.send('updateTrayMenu')
|
||||
mutateProfileConfig()
|
||||
}).catch((e) => {
|
||||
const errorMsg = e?.message || String(e)
|
||||
// 处理 IPC 超时错误
|
||||
if (errorMsg.includes('reply was never sent')) {
|
||||
setTimeout(() => mutateProfileConfig(), 1000)
|
||||
} else {
|
||||
alert(`切换 Profile 失败: ${errorMsg}`)
|
||||
mutateProfileConfig()
|
||||
}
|
||||
})
|
||||
await change(id)
|
||||
} catch (e) {
|
||||
alert(`切换 Profile 失败: ${e}`)
|
||||
alert(e)
|
||||
} finally {
|
||||
mutateProfileConfig()
|
||||
window.electron.ipcRenderer.send('updateTrayMenu')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -98,7 +98,7 @@
|
||||
"mihomo.cpuPriority.low": "Low",
|
||||
"mihomo.workDir.title": "Separate Work Directory for Different Subscriptions",
|
||||
"mihomo.workDir.tooltip": "Enable to avoid conflicts when different subscriptions have proxy groups with the same name",
|
||||
"mihomo.controlDns": "Override DNS Settings",
|
||||
"mihomo.controlDns": "Control DNS Settings",
|
||||
"mihomo.controlSniff": "Control Domain Sniffing",
|
||||
"mihomo.autoCloseConnection": "Auto Close Connection",
|
||||
"mihomo.pauseSSID.title": "Direct Connection for Specific WiFi SSIDs",
|
||||
@ -114,15 +114,6 @@
|
||||
"mihomo.selectCoreVersion": "Select Core Version",
|
||||
"mihomo.stableVersion": "Stable",
|
||||
"mihomo.alphaVersion": "Alpha",
|
||||
"mihomo.smartVersion": "Smart",
|
||||
"mihomo.enableSmartCore": "Enable Smart Core",
|
||||
"mihomo.enableSmartOverride": "Use Auto Smart Rule Override",
|
||||
"mihomo.smartOverrideTooltip": "Use Party's built-in smart override script to extract all nodes from subscriptions and replace default rules. Perfect for users who don't want to tinker, one-click functionality; if using global mode, please select the node named 'Smart Group'",
|
||||
"mihomo.smartCoreUseLightGBM": "Use LightGBM",
|
||||
"mihomo.smartCoreCollectData": "Collect Data",
|
||||
"mihomo.smartCoreStrategy": "Strategy Mode",
|
||||
"mihomo.smartCoreStrategyStickySession": "Sticky Sessions",
|
||||
"mihomo.smartCoreStrategyRoundRobin": "Round Robin",
|
||||
"mihomo.mixedPort": "Mixed Port",
|
||||
"mihomo.confirm": "Confirm",
|
||||
"mihomo.socksPort": "Socks Port",
|
||||
@ -216,9 +207,7 @@
|
||||
"sider.cards.override": "Override",
|
||||
"sider.cards.connections": "Connections",
|
||||
"sider.cards.core": "Core Settings",
|
||||
"sider.cards.dns": "DNS OVR",
|
||||
"sider.cards.dns.originalConfig": "Using Original Config",
|
||||
"sider.cards.dns.overrideMode": "Override Mode",
|
||||
"sider.cards.dns": "DNS",
|
||||
"sider.cards.sniff": "Sniffing",
|
||||
"sider.cards.logs": "Logs",
|
||||
"sider.cards.substore": "Sub-Store",
|
||||
@ -318,7 +307,6 @@
|
||||
"tun.notifications.firewallResetSuccess": "Firewall Reset Successful",
|
||||
"tun.error.tunPermissionDenied": "TUN interface start failed, please try to manually grant core permissions",
|
||||
"dns.title": "DNS Settings",
|
||||
"dns.enable.title": "Enable DNS",
|
||||
"dns.enhancedMode.title": "Domain Mapping Mode",
|
||||
"dns.enhancedMode.fakeIp": "Fake IP",
|
||||
"dns.enhancedMode.redirHost": "Real IP",
|
||||
@ -516,7 +504,7 @@
|
||||
"guide.override.title": "Override",
|
||||
"guide.override.description": "Mihomo Party provides powerful override functionality to customize your imported subscription configurations, such as adding rules and customizing proxy groups. You can directly import override files written by others or write your own. <b>Remember to enable the override file on the subscription you want to override</b>. For override file syntax, please refer to the <a href=\"https://mihomo.party/docs/guide/override\" target=\"_blank\">official documentation</a>.",
|
||||
"guide.dns.title": "DNS",
|
||||
"guide.dns.description": "The software overrides subscription DNS settings with application DNS settings by default. If you need to use the DNS settings from your subscription configuration, you can disable 'Override DNS Settings' in the application settings. The same applies to domain sniffing.",
|
||||
"guide.dns.description": "The software takes control of the core's DNS settings by default. If you need to use the DNS settings from your subscription configuration, you can disable 'Control DNS Settings' in the application settings. The same applies to domain sniffing.",
|
||||
"guide.end.title": "Tutorial Complete",
|
||||
"guide.end.description": "Now that you understand the basic usage of the software, import your subscription and start using it. Enjoy!\nYou can also join our official <a href=\"https://t.me/mihomo_party_group\" target=\"_blank\">Telegram group</a> for the latest news."
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@
|
||||
"mihomo.cpuPriority.low": "低",
|
||||
"mihomo.workDir.title": "不同订阅使用独立工作目录",
|
||||
"mihomo.workDir.tooltip": "启用后可避免不同订阅中存在相同名称的代理组时发生冲突",
|
||||
"mihomo.controlDns": "覆写 DNS 设置",
|
||||
"mihomo.controlDns": "控制 DNS 设置",
|
||||
"mihomo.controlSniff": "控制域名嗅探",
|
||||
"mihomo.autoCloseConnection": "自动关闭连接",
|
||||
"mihomo.pauseSSID.title": "指定 WiFi SSID 直连",
|
||||
@ -114,15 +114,6 @@
|
||||
"mihomo.selectCoreVersion": "选择内核版本",
|
||||
"mihomo.stableVersion": "稳定版",
|
||||
"mihomo.alphaVersion": "预览版",
|
||||
"mihomo.smartVersion": "Smart 版",
|
||||
"mihomo.enableSmartCore": "启用 Smart 内核",
|
||||
"mihomo.enableSmartOverride": "使用自动 Smart 规则覆写",
|
||||
"mihomo.smartOverrideTooltip": "使用 Party 自带的智能覆写脚本,提取订阅中的所有节点,并替换默认规则,适合不想折腾的用户,功能一键生效;如果使用全局模式,请选择名称为“Smart Group 的节点",
|
||||
"mihomo.smartCoreUseLightGBM": "使用 LightGBM",
|
||||
"mihomo.smartCoreCollectData": "收集数据",
|
||||
"mihomo.smartCoreStrategy": "策略模式",
|
||||
"mihomo.smartCoreStrategyStickySession": "粘性会话",
|
||||
"mihomo.smartCoreStrategyRoundRobin": "轮询",
|
||||
"mihomo.mixedPort": "混合端口",
|
||||
"mihomo.confirm": "确认",
|
||||
"mihomo.socksPort": "Socks 端口",
|
||||
@ -216,9 +207,7 @@
|
||||
"sider.cards.override": "覆写",
|
||||
"sider.cards.connections": "连接",
|
||||
"sider.cards.core": "内核设置",
|
||||
"sider.cards.dns": "DNS覆写",
|
||||
"sider.cards.dns.originalConfig": "使用原始配置",
|
||||
"sider.cards.dns.overrideMode": "覆写模式",
|
||||
"sider.cards.dns": "DNS",
|
||||
"sider.cards.sniff": "域名嗅探",
|
||||
"sider.cards.logs": "日志",
|
||||
"sider.cards.substore": "Sub-Store",
|
||||
@ -318,7 +307,6 @@
|
||||
"tun.notifications.firewallResetSuccess": "防火墙重设成功",
|
||||
"tun.error.tunPermissionDenied": "虚拟网卡启动失败,请尝试手动授予内核权限",
|
||||
"dns.title": "DNS 设置",
|
||||
"dns.enable.title": "启用 DNS",
|
||||
"dns.enhancedMode.title": "域名映射模式",
|
||||
"dns.enhancedMode.fakeIp": "虚假 IP",
|
||||
"dns.enhancedMode.redirHost": "真实 IP",
|
||||
@ -516,7 +504,7 @@
|
||||
"guide.override.title": "覆写",
|
||||
"guide.override.description": "Mihomo Party 提供强大的覆写功能,可以对您导入的订阅配置进行个性化修改,如添加规则、自定义代理组等,您可以直接导入别人写好的覆写文件,也可以自己动手编写,<b>编辑好覆写文件一定要记得在需要覆写的订阅上启用</b>,覆写文件的语法请参考 <a href=\"https://mihomo.party/docs/guide/override\" target=\"_blank\">官方文档</a>",
|
||||
"guide.dns.title": "DNS",
|
||||
"guide.dns.description": "软件默认使用应用的 DNS 设置覆写订阅配置,如果您需要使用订阅配置中的 DNS 设置,可以到应用设置中关闭\"覆写 DNS 设置\",域名嗅探同理",
|
||||
"guide.dns.description": "软件默认接管了内核的 DNS 设置,如果您需要使用订阅配置中的 DNS 设置,可以到应用设置中关闭\"接管 DNS 设置\",域名嗅探同理",
|
||||
"guide.end.title": "教程结束",
|
||||
"guide.end.description": "现在您已经了解了软件的基本用法,导入您的订阅开始使用吧,祝您使用愉快!\n您还可以加入我们的官方 <a href=\"https://t.me/mihomo_party_group\" target=\"_blank\">Telegram 群组</a> 获取最新资讯"
|
||||
}
|
||||
|
||||
@ -273,7 +273,7 @@ const Connections: React.FC = () => {
|
||||
</div>
|
||||
<Divider />
|
||||
</div>
|
||||
<div className="h-[calc(100vh-100px)] mt-px">
|
||||
<div className="h-[calc(100vh-100px)] mt-[1px]">
|
||||
<Virtuoso
|
||||
data={filteredConnections}
|
||||
itemContent={(i, connection) => (
|
||||
|
||||
@ -16,7 +16,6 @@ const DNS: React.FC = () => {
|
||||
const { nameserverPolicy, useNameserverPolicy } = appConfig || {}
|
||||
const { dns, hosts } = controledMihomoConfig || {}
|
||||
const {
|
||||
enable = true,
|
||||
ipv6 = false,
|
||||
'fake-ip-range': fakeIPRange = '198.18.0.1/16',
|
||||
'fake-ip-filter': fakeIPFilter = [
|
||||
@ -41,7 +40,6 @@ const DNS: React.FC = () => {
|
||||
} = dns || {}
|
||||
const [changed, setChanged] = useState(false)
|
||||
const [values, originSetValues] = useState({
|
||||
enable,
|
||||
ipv6,
|
||||
useHosts,
|
||||
enhancedMode,
|
||||
@ -145,7 +143,6 @@ const DNS: React.FC = () => {
|
||||
color="primary"
|
||||
onPress={() => {
|
||||
const dnsConfig = {
|
||||
enable: values.enable,
|
||||
ipv6: values.ipv6,
|
||||
'fake-ip-range': values.fakeIPRange,
|
||||
'fake-ip-filter': values.fakeIPFilter,
|
||||
@ -180,15 +177,6 @@ const DNS: React.FC = () => {
|
||||
}
|
||||
>
|
||||
<SettingCard>
|
||||
<SettingItem title={t('dns.enable.title')} divider>
|
||||
<Switch
|
||||
size="sm"
|
||||
isSelected={values.enable}
|
||||
onValueChange={(v) => {
|
||||
setValues({ ...values, enable: v })
|
||||
}}
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem title={t('dns.enhancedMode.title')} divider>
|
||||
<Tabs
|
||||
size="sm"
|
||||
@ -276,7 +264,7 @@ const DNS: React.FC = () => {
|
||||
{[...values.nameserverPolicy, { domain: '', value: '' }].map(
|
||||
({ domain, value }, index) => (
|
||||
<div key={index} className="flex mb-2">
|
||||
<div className="flex-4">
|
||||
<div className="flex-[4]">
|
||||
<Input
|
||||
size="sm"
|
||||
fullWidth
|
||||
@ -293,7 +281,7 @@ const DNS: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
<span className="mx-2">:</span>
|
||||
<div className="flex-6 flex">
|
||||
<div className="flex-[6] flex">
|
||||
<Input
|
||||
size="sm"
|
||||
fullWidth
|
||||
@ -344,7 +332,7 @@ const DNS: React.FC = () => {
|
||||
<h3 className="mb-2">{t('dns.customHosts.list')}</h3>
|
||||
{[...values.hosts, { domain: '', value: '' }].map(({ domain, value }, index) => (
|
||||
<div key={index} className="flex mb-2">
|
||||
<div className="flex-4">
|
||||
<div className="flex-[4]">
|
||||
<Input
|
||||
size="sm"
|
||||
fullWidth
|
||||
@ -361,7 +349,7 @@ const DNS: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
<span className="mx-2">:</span>
|
||||
<div className="flex-6 flex">
|
||||
<div className="flex-[6] flex">
|
||||
<Input
|
||||
size="sm"
|
||||
fullWidth
|
||||
|
||||
@ -109,7 +109,7 @@ const Logs: React.FC = () => {
|
||||
</div>
|
||||
<Divider />
|
||||
</div>
|
||||
<div className="h-[calc(100vh-100px)] mt-px">
|
||||
<div className="h-[calc(100vh-100px)] mt-[1px]">
|
||||
<Virtuoso
|
||||
ref={virtuosoRef}
|
||||
data={filteredLogs}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Button, Divider, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react'
|
||||
import { Button, Divider, Input, Select, SelectItem, Switch } from '@heroui/react'
|
||||
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'
|
||||
@ -6,14 +6,13 @@ import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||
import { platform } from '@renderer/utils/init'
|
||||
import { FaNetworkWired } from 'react-icons/fa'
|
||||
import { IoMdCloudDownload, IoMdInformationCircleOutline } from 'react-icons/io'
|
||||
import { IoMdCloudDownload } from 'react-icons/io'
|
||||
import PubSub from 'pubsub-js'
|
||||
import {
|
||||
mihomoUpgrade,
|
||||
restartCore,
|
||||
startSubStoreBackendServer,
|
||||
triggerSysProxy,
|
||||
showDetailedError
|
||||
triggerSysProxy
|
||||
} from '@renderer/utils/ipc'
|
||||
import React, { useState } from 'react'
|
||||
import InterfaceModal from '@renderer/components/mihomo/interface-modal'
|
||||
@ -22,8 +21,7 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
const CoreMap = {
|
||||
mihomo: 'mihomo.stableVersion',
|
||||
'mihomo-alpha': 'mihomo.alphaVersion',
|
||||
'mihomo-smart': 'mihomo.smartVersion'
|
||||
'mihomo-alpha': 'mihomo.alphaVersion'
|
||||
}
|
||||
|
||||
const Mihomo: React.FC = () => {
|
||||
@ -31,11 +29,6 @@ const Mihomo: React.FC = () => {
|
||||
const { appConfig, patchAppConfig } = useAppConfig()
|
||||
const {
|
||||
core = 'mihomo',
|
||||
enableSmartCore = true,
|
||||
enableSmartOverride = true,
|
||||
smartCoreUseLightGBM = false,
|
||||
smartCoreCollectData = false,
|
||||
smartCoreStrategy = 'sticky-sessions',
|
||||
maxLogDays = 7,
|
||||
sysProxy,
|
||||
disableLoopbackDetector,
|
||||
@ -89,14 +82,7 @@ const Mihomo: React.FC = () => {
|
||||
await patchAppConfig({ [key]: value })
|
||||
await restartCore()
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : String(e)
|
||||
console.error('Core restart failed:', errorMessage)
|
||||
|
||||
if (errorMessage.includes('配置检查失败') || errorMessage.includes('Profile Check Failed')) {
|
||||
await showDetailedError(t('mihomo.error.profileCheckFailed'), errorMessage)
|
||||
} else {
|
||||
alert(errorMessage)
|
||||
}
|
||||
alert(e)
|
||||
} finally {
|
||||
PubSub.publish('mihomo-core-changed')
|
||||
}
|
||||
@ -106,184 +92,59 @@ const Mihomo: React.FC = () => {
|
||||
<>
|
||||
{lanOpen && <InterfaceModal onClose={() => setLanOpen(false)} />}
|
||||
<BasePage title={t('mihomo.title')}>
|
||||
{/* Smart 内核设置 */}
|
||||
<SettingCard>
|
||||
<div className={`rounded-md border p-2 transition-all duration-200 ${
|
||||
enableSmartCore
|
||||
? 'border-blue-300 bg-blue-50/30 dark:border-blue-700 dark:bg-blue-950/20'
|
||||
: 'border-gray-300 bg-gray-50/30 dark:border-gray-600 dark:bg-gray-800/20'
|
||||
}`}>
|
||||
<SettingItem
|
||||
title={t('mihomo.enableSmartCore')}
|
||||
divider
|
||||
>
|
||||
<Switch
|
||||
<SettingItem
|
||||
title={t('mihomo.coreVersion')}
|
||||
actions={
|
||||
<Button
|
||||
size="sm"
|
||||
isSelected={enableSmartCore}
|
||||
color={enableSmartCore ? 'primary' : 'default'}
|
||||
onValueChange={async (v) => {
|
||||
await patchAppConfig({ enableSmartCore: v })
|
||||
if (v && core !== 'mihomo-smart') {
|
||||
await handleConfigChangeWithRestart('core', 'mihomo-smart')
|
||||
} else if (!v && core === 'mihomo-smart') {
|
||||
await handleConfigChangeWithRestart('core', 'mihomo')
|
||||
isIconOnly
|
||||
title={t('mihomo.upgradeCore')}
|
||||
variant="light"
|
||||
isLoading={upgrading}
|
||||
onPress={async () => {
|
||||
try {
|
||||
setUpgrading(true)
|
||||
await mihomoUpgrade()
|
||||
setTimeout(() => {
|
||||
PubSub.publish('mihomo-core-changed')
|
||||
}, 2000)
|
||||
if (platform !== 'win32') {
|
||||
new Notification(t('mihomo.coreAuthLost'), {
|
||||
body: t('mihomo.coreUpgradeSuccess')
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
if (typeof e === 'string' && e.includes('already using latest version')) {
|
||||
new Notification(t('mihomo.alreadyLatestVersion'))
|
||||
} else {
|
||||
alert(e)
|
||||
}
|
||||
} finally {
|
||||
setUpgrading(false)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
{/* Smart 覆写开关 */}
|
||||
{enableSmartCore && (
|
||||
<SettingItem
|
||||
title={
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{t('mihomo.enableSmartOverride')}</span>
|
||||
<Tooltip
|
||||
content={t('mihomo.smartOverrideTooltip')}
|
||||
placement="top"
|
||||
className="max-w-xs"
|
||||
>
|
||||
<IoMdInformationCircleOutline className="text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 cursor-help" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
divider={core === 'mihomo-smart'}
|
||||
>
|
||||
<Switch
|
||||
size="sm"
|
||||
isSelected={enableSmartOverride}
|
||||
color="primary"
|
||||
onValueChange={async (v) => {
|
||||
await patchAppConfig({ enableSmartOverride: v })
|
||||
await restartCore()
|
||||
}}
|
||||
/>
|
||||
</SettingItem>
|
||||
)}
|
||||
|
||||
<SettingItem
|
||||
title={t('mihomo.coreVersion')}
|
||||
actions={
|
||||
<Button
|
||||
size="sm"
|
||||
isIconOnly
|
||||
title={t('mihomo.upgradeCore')}
|
||||
variant="light"
|
||||
isLoading={upgrading}
|
||||
onPress={async () => {
|
||||
try {
|
||||
setUpgrading(true)
|
||||
await mihomoUpgrade()
|
||||
setTimeout(() => {
|
||||
PubSub.publish('mihomo-core-changed')
|
||||
}, 2000)
|
||||
if (platform !== 'win32') {
|
||||
new Notification(t('mihomo.coreAuthLost'), {
|
||||
body: t('mihomo.coreUpgradeSuccess')
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
if (typeof e === 'string' && e.includes('already using latest version')) {
|
||||
new Notification(t('mihomo.alreadyLatestVersion'))
|
||||
} else {
|
||||
alert(e)
|
||||
}
|
||||
} finally {
|
||||
setUpgrading(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IoMdCloudDownload className="text-lg" />
|
||||
</Button>
|
||||
}
|
||||
divider={enableSmartCore && core === 'mihomo-smart'}
|
||||
<IoMdCloudDownload className="text-lg" />
|
||||
</Button>
|
||||
}
|
||||
divider
|
||||
>
|
||||
<Select
|
||||
classNames={{ trigger: 'data-[hover=true]:bg-default-200' }}
|
||||
className="w-[100px]"
|
||||
size="sm"
|
||||
aria-label={t('mihomo.selectCoreVersion')}
|
||||
selectedKeys={new Set([core])}
|
||||
disallowEmptySelection={true}
|
||||
onSelectionChange={async (v) => {
|
||||
handleConfigChangeWithRestart('core', v.currentKey as 'mihomo' | 'mihomo-alpha')
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
classNames={{
|
||||
trigger: enableSmartCore
|
||||
? 'data-[hover=true]:bg-blue-100 dark:data-[hover=true]:bg-blue-900/50'
|
||||
: 'data-[hover=true]:bg-default-200'
|
||||
}}
|
||||
className="w-[100px]"
|
||||
size="sm"
|
||||
aria-label={t('mihomo.selectCoreVersion')}
|
||||
selectedKeys={new Set([core])}
|
||||
disallowEmptySelection={true}
|
||||
onSelectionChange={async (v) => {
|
||||
handleConfigChangeWithRestart('core', v.currentKey as 'mihomo' | 'mihomo-alpha' | 'mihomo-smart')
|
||||
}}
|
||||
>
|
||||
{enableSmartCore ? (
|
||||
<SelectItem key="mihomo-smart">{t(CoreMap['mihomo-smart'])}</SelectItem>
|
||||
) : (
|
||||
<>
|
||||
<SelectItem key="mihomo">{t(CoreMap['mihomo'])}</SelectItem>
|
||||
<SelectItem key="mihomo-alpha">{t(CoreMap['mihomo-alpha'])}</SelectItem>
|
||||
</>
|
||||
)}
|
||||
</Select>
|
||||
</SettingItem>
|
||||
|
||||
{/* Smart 内核配置项 */}
|
||||
{enableSmartCore && core === 'mihomo-smart' && (
|
||||
<>
|
||||
<SettingItem
|
||||
title={t('mihomo.smartCoreUseLightGBM')}
|
||||
divider
|
||||
>
|
||||
<Switch
|
||||
size="sm"
|
||||
color="primary"
|
||||
isSelected={smartCoreUseLightGBM}
|
||||
onValueChange={async (v) => {
|
||||
await patchAppConfig({ smartCoreUseLightGBM: v })
|
||||
await restartCore()
|
||||
}}
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
title={t('mihomo.smartCoreCollectData')}
|
||||
divider
|
||||
>
|
||||
<Switch
|
||||
size="sm"
|
||||
color="primary"
|
||||
isSelected={smartCoreCollectData}
|
||||
onValueChange={async (v) => {
|
||||
await patchAppConfig({ smartCoreCollectData: v })
|
||||
await restartCore()
|
||||
}}
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
title={t('mihomo.smartCoreStrategy')}
|
||||
>
|
||||
<Select
|
||||
classNames={{ trigger: 'data-[hover=true]:bg-blue-100 dark:data-[hover=true]:bg-blue-900/50' }}
|
||||
className="w-[150px]"
|
||||
size="sm"
|
||||
aria-label={t('mihomo.smartCoreStrategy')}
|
||||
selectedKeys={new Set([smartCoreStrategy])}
|
||||
disallowEmptySelection={true}
|
||||
onSelectionChange={async (v) => {
|
||||
const strategy = v.currentKey as 'sticky-sessions' | 'round-robin'
|
||||
await patchAppConfig({ smartCoreStrategy: strategy })
|
||||
await restartCore()
|
||||
}}
|
||||
>
|
||||
<SelectItem key="sticky-sessions">{t('mihomo.smartCoreStrategyStickySession')}</SelectItem>
|
||||
<SelectItem key="round-robin">{t('mihomo.smartCoreStrategyRoundRobin')}</SelectItem>
|
||||
</Select>
|
||||
</SettingItem>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</SettingCard>
|
||||
|
||||
{/* 常规内核设置 */}
|
||||
<SettingCard>
|
||||
<SelectItem key="mihomo">{t(CoreMap['mihomo'])}</SelectItem>
|
||||
<SelectItem key="mihomo-alpha">{t(CoreMap['mihomo-alpha'])}</SelectItem>
|
||||
</Select>
|
||||
</SettingItem>
|
||||
<SettingItem title={t('mihomo.mixedPort')} divider>
|
||||
<div className="flex">
|
||||
{mixedPortInput !== mixedPort && (
|
||||
@ -642,7 +503,7 @@ const Mihomo: React.FC = () => {
|
||||
const [user, pass] = auth.split(':')
|
||||
return (
|
||||
<div key={index} className="flex mb-2">
|
||||
<div className="flex-4">
|
||||
<div className="flex-[4]">
|
||||
<Input
|
||||
size="sm"
|
||||
fullWidth
|
||||
@ -662,7 +523,7 @@ const Mihomo: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
<span className="mx-2">:</span>
|
||||
<div className="flex-6 flex">
|
||||
<div className="flex-[6] flex">
|
||||
<Input
|
||||
size="sm"
|
||||
fullWidth
|
||||
|
||||
@ -85,7 +85,7 @@ const Profiles: React.FC = () => {
|
||||
<div>
|
||||
{sub.tag?.map((tag) => {
|
||||
return (
|
||||
<Chip key={tag} size="sm" className="ml-1" radius="xs">
|
||||
<Chip key={tag} size="sm" className="ml-1" radius="sm">
|
||||
{tag}
|
||||
</Chip>
|
||||
)
|
||||
@ -108,7 +108,7 @@ const Profiles: React.FC = () => {
|
||||
<div>
|
||||
{sub.tag?.map((tag) => {
|
||||
return (
|
||||
<Chip key={tag} size="sm" className="ml-1" radius="xs">
|
||||
<Chip key={tag} size="sm" className="ml-1" radius="sm">
|
||||
{tag}
|
||||
</Chip>
|
||||
)
|
||||
|
||||
@ -324,7 +324,7 @@ const Proxies: React.FC = () => {
|
||||
<Avatar
|
||||
className="bg-transparent mr-2"
|
||||
size="sm"
|
||||
radius="xs"
|
||||
radius="sm"
|
||||
src={
|
||||
groups[index].icon.startsWith('<svg')
|
||||
? `data:image/svg+xml;utf8,${groups[index].icon}`
|
||||
|
||||
@ -38,7 +38,7 @@ const Rules: React.FC = () => {
|
||||
</div>
|
||||
<Divider />
|
||||
</div>
|
||||
<div className="h-[calc(100vh-100px)] mt-px">
|
||||
<div className="h-[calc(100vh-100px)] mt-[1px]">
|
||||
<Virtuoso
|
||||
data={filteredRules}
|
||||
itemContent={(i, rule) => (
|
||||
|
||||
@ -79,22 +79,6 @@ export async function mihomoGroupDelay(group: string, url?: string): Promise<IMi
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoGroupDelay', group, url))
|
||||
}
|
||||
|
||||
export async function mihomoSmartGroupWeights(groupName: string): Promise<Record<string, number>> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoSmartGroupWeights', groupName))
|
||||
}
|
||||
|
||||
export async function mihomoSmartFlushCache(configName?: string): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoSmartFlushCache', configName))
|
||||
}
|
||||
|
||||
export async function showDetailedError(title: string, message: string): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showDetailedError', title, message))
|
||||
}
|
||||
|
||||
export async function getSmartOverrideContent(): Promise<string | null> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getSmartOverrideContent'))
|
||||
}
|
||||
|
||||
export async function patchMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('patchMihomoConfig', patch))
|
||||
}
|
||||
|
||||
7
src/shared/types.d.ts
vendored
7
src/shared/types.d.ts
vendored
@ -216,12 +216,7 @@ interface ISysProxyConfig {
|
||||
}
|
||||
|
||||
interface IAppConfig {
|
||||
core: 'mihomo' | 'mihomo-alpha' | 'mihomo-smart'
|
||||
enableSmartCore: boolean
|
||||
enableSmartOverride: boolean
|
||||
smartCoreUseLightGBM: boolean
|
||||
smartCoreCollectData: boolean
|
||||
smartCoreStrategy: 'sticky-sessions' | 'round-robin'
|
||||
core: 'mihomo' | 'mihomo-alpha'
|
||||
disableLoopbackDetector: boolean
|
||||
disableEmbedCA: boolean
|
||||
disableSystemCA: boolean
|
||||
|
||||
14
tailwind.config.js
Normal file
14
tailwind.config.js
Normal file
@ -0,0 +1,14 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
const { heroui } = require('@heroui/react')
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
'./src/renderer/src/**/*.{js,ts,jsx,tsx}',
|
||||
'./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}'
|
||||
],
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
darkMode: 'class',
|
||||
plugins: [heroui()]
|
||||
}
|
||||
@ -3,7 +3,6 @@
|
||||
"include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*", "src/shared/**/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"types": ["electron-vite/node"],
|
||||
"moduleResolution": "bundler"
|
||||
"types": ["electron-vite/node"]
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user