mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/mihomo-party-org/clash-party.git
synced 2026-09-20 08:03:39 +08:00
feat: replace restartCore with hot reload for config switches and overrides
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { readFile, writeFile } from 'fs/promises'
|
||||
import { existsSync } from 'fs'
|
||||
import { ipcMain } from 'electron'
|
||||
import { controledMihomoConfigPath } from '../utils/dirs'
|
||||
import { parse, stringify } from '../utils/yaml'
|
||||
import { generateProfile } from '../core/factory'
|
||||
@@ -67,8 +66,6 @@ export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>):
|
||||
) {
|
||||
// 恢复 DNS 状态并清除保存的状态
|
||||
await patchAppConfig({ controlDns: controlDnsBeforePause, controlDnsBeforePause: undefined })
|
||||
// 通过事件通知重启核心,避免循环依赖
|
||||
ipcMain.emit('restartCore')
|
||||
}
|
||||
|
||||
// 过滤端口字段中的 NaN 值,防止写入无效配置
|
||||
@@ -102,6 +99,17 @@ export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>):
|
||||
|
||||
await generateProfile()
|
||||
await writeFile(controledMihomoConfigPath(), stringify(controledMihomoConfig), 'utf-8')
|
||||
|
||||
// 优先对运行中内核进行热更新,避免无意义重启
|
||||
try {
|
||||
const { patchMihomoConfig } = await import('../core/mihomoApi')
|
||||
await patchMihomoConfig(patch)
|
||||
} catch (error) {
|
||||
controledMihomoLogger.warn(
|
||||
'Hot patch /configs failed, changes will apply on next restart',
|
||||
error
|
||||
)
|
||||
}
|
||||
})
|
||||
await controledMihomoWriteQueue
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as chromeRequest from '../utils/chromeRequest'
|
||||
import { parse, stringify } from '../utils/yaml'
|
||||
import { defaultProfile } from '../utils/template'
|
||||
import { subStorePort } from '../resolve/server'
|
||||
import { mihomoUpgradeConfig } from '../core/mihomoApi'
|
||||
import { mihomoUpgradeConfig, mihomoHotReloadConfig } from '../core/mihomoApi'
|
||||
import { restartCore } from '../core/manager'
|
||||
import { addProfileUpdater, removeProfileUpdater } from '../core/profileUpdater'
|
||||
import { mihomoProfileWorkDir, mihomoWorkDir, profileConfigPath, profilePath } from '../utils/dirs'
|
||||
@@ -77,7 +77,12 @@ export async function changeCurrentProfile(id: string): Promise<void> {
|
||||
config.current = id
|
||||
return config
|
||||
})
|
||||
await restartCore()
|
||||
const { useHotReloadProfile = false } = await getAppConfig()
|
||||
if (useHotReloadProfile) {
|
||||
await mihomoHotReloadConfig()
|
||||
} else {
|
||||
await restartCore()
|
||||
}
|
||||
} catch (e) {
|
||||
// 回滚配置
|
||||
await updateProfileConfig((config) => {
|
||||
|
||||
@@ -224,6 +224,19 @@ export const mihomoUpgradeConfig = async (): Promise<void> => {
|
||||
}
|
||||
}
|
||||
|
||||
export const mihomoHotReloadConfig = async (): Promise<void> => {
|
||||
mihomoApiLogger.info('mihomoHotReloadConfig called')
|
||||
const { generateProfile } = await import('./factory')
|
||||
const current = await generateProfile()
|
||||
const { diffWorkDir = false } = await getAppConfig()
|
||||
const { mihomoWorkConfigPath } = await import('../utils/dirs')
|
||||
const configPath = diffWorkDir ? mihomoWorkConfigPath(current) : mihomoWorkConfigPath('work')
|
||||
mihomoApiLogger.info(`hot reload config path: ${configPath}`)
|
||||
const instance = await getAxios()
|
||||
await instance.put('/configs?force=true', { path: configPath })
|
||||
mihomoApiLogger.info('hot reload config completed')
|
||||
}
|
||||
|
||||
// Smart 内核 API
|
||||
export const mihomoSmartGroupWeights = async (
|
||||
groupName: string
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
patchControledMihomoConfig
|
||||
} from '../config'
|
||||
import { triggerSysProxy } from '../sys/sysproxy'
|
||||
import { patchMihomoConfig } from '../core/mihomoApi'
|
||||
import { quitWithoutCore, restartCore } from '../core/manager'
|
||||
import { quitWithoutCore } from '../core/manager'
|
||||
import i18next from '../../shared/i18n'
|
||||
import { floatingWindow, triggerFloatingWindow } from './floatingWindow'
|
||||
import { copyEnv, updateTrayIcon } from './tray'
|
||||
@@ -70,7 +69,6 @@ export async function registerShortcut(
|
||||
} else {
|
||||
await patchControledMihomoConfig({ tun: { enable: !enable } })
|
||||
}
|
||||
await restartCore()
|
||||
new Notification({
|
||||
title: i18next.t(
|
||||
!enable ? 'common.notification.tunEnabled' : 'common.notification.tunDisabled'
|
||||
@@ -89,7 +87,6 @@ export async function registerShortcut(
|
||||
case 'ruleModeShortcut': {
|
||||
return globalShortcut.register(newShortcut, async () => {
|
||||
await patchControledMihomoConfig({ mode: 'rule' })
|
||||
await patchMihomoConfig({ mode: 'rule' })
|
||||
new Notification({
|
||||
title: i18next.t('common.notification.ruleMode')
|
||||
}).show()
|
||||
@@ -101,7 +98,6 @@ export async function registerShortcut(
|
||||
case 'globalModeShortcut': {
|
||||
return globalShortcut.register(newShortcut, async () => {
|
||||
await patchControledMihomoConfig({ mode: 'global' })
|
||||
await patchMihomoConfig({ mode: 'global' })
|
||||
new Notification({
|
||||
title: i18next.t('common.notification.globalMode')
|
||||
}).show()
|
||||
@@ -113,7 +109,6 @@ export async function registerShortcut(
|
||||
case 'directModeShortcut': {
|
||||
return globalShortcut.register(newShortcut, async () => {
|
||||
await patchControledMihomoConfig({ mode: 'direct' })
|
||||
await patchMihomoConfig({ mode: 'direct' })
|
||||
new Notification({
|
||||
title: i18next.t('common.notification.directMode')
|
||||
}).show()
|
||||
|
||||
@@ -30,7 +30,6 @@ import { dataDir, logDir, mihomoCoreDir, mihomoWorkDir } from '../utils/dirs'
|
||||
import { triggerSysProxy } from '../sys/sysproxy'
|
||||
import {
|
||||
quitWithoutCore,
|
||||
restartCore,
|
||||
checkMihomoCorePermissions,
|
||||
requestTunPermissions,
|
||||
restartAsAdmin
|
||||
@@ -264,7 +263,6 @@ export const buildContextMenu = async (): Promise<Menu> => {
|
||||
}
|
||||
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||
floatingWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||
await restartCore()
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
mihomoUpgradeGeo,
|
||||
mihomoUpgradeUI,
|
||||
mihomoUpgradeConfig,
|
||||
mihomoHotReloadConfig,
|
||||
mihomoVersion,
|
||||
patchMihomoConfig,
|
||||
mihomoSmartGroupWeights,
|
||||
@@ -284,6 +285,7 @@ const asyncHandlers: Record<string, AsyncFn> = {
|
||||
readTextFile,
|
||||
// Core
|
||||
restartCore,
|
||||
mihomoHotReloadConfig,
|
||||
startMonitor,
|
||||
quitWithoutCore,
|
||||
// System
|
||||
|
||||
@@ -59,7 +59,8 @@ export const defaultConfig: IAppConfig = {
|
||||
enableRedirPort: false,
|
||||
showTproxyPort: 0,
|
||||
enableTproxyPort: false,
|
||||
testProfileOnStart: true
|
||||
testProfileOnStart: true,
|
||||
useHotReloadProfile: false
|
||||
}
|
||||
|
||||
export const defaultControledMihomoConfig: Partial<IMihomoConfig> = {
|
||||
|
||||
@@ -13,9 +13,9 @@ const SettingItem: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="select-text h-[32px] w-full flex justify-between">
|
||||
<div className="select-text h-8 w-full flex justify-between">
|
||||
<div className="h-full flex items-center">
|
||||
<h4 className="h-full text-md leading-[32px] whitespace-nowrap">{title}</h4>
|
||||
<h4 className="h-full text-md leading-8 whitespace-nowrap">{title}</h4>
|
||||
<div>{actions}</div>
|
||||
</div>
|
||||
{children}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'
|
||||
import { toast } from '@renderer/components/base/toast'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { getOverride, restartCore, setOverride } from '@renderer/utils/ipc'
|
||||
import { getOverride, mihomoHotReloadConfig, setOverride } from '@renderer/utils/ipc'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BaseEditor } from '../base/base-editor'
|
||||
|
||||
@@ -58,7 +58,7 @@ const EditFileModal: React.FC<Props> = (props) => {
|
||||
onPress={async () => {
|
||||
try {
|
||||
await setOverride(id, language === 'javascript' ? 'js' : 'yaml', currData)
|
||||
await restartCore()
|
||||
await mihomoHotReloadConfig()
|
||||
onClose()
|
||||
} catch (e) {
|
||||
toast.error(String(e))
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
Switch
|
||||
} from '@heroui/react'
|
||||
import React, { useState } from 'react'
|
||||
import { restartCore } from '@renderer/utils/ipc'
|
||||
import { mihomoHotReloadConfig } from '@renderer/utils/ipc'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import SettingItem from '../base/base-setting-item'
|
||||
|
||||
@@ -25,7 +25,7 @@ const EditInfoModal: React.FC<Props> = (props) => {
|
||||
|
||||
const onSave = async (): Promise<void> => {
|
||||
await updateOverrideItem(values)
|
||||
await restartCore()
|
||||
await mihomoHotReloadConfig()
|
||||
onClose()
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import dayjs from '@renderer/utils/dayjs'
|
||||
import React, { Key, useMemo, useState } from 'react'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { openFile, restartCore } from '@renderer/utils/ipc'
|
||||
import { openFile, mihomoHotReloadConfig } from '@renderer/utils/ipc'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ExecLogModal from './exec-log-modal'
|
||||
import EditInfoModal from './edit-info-modal'
|
||||
@@ -189,7 +189,7 @@ const OverrideItem: React.FC<Props> = (props) => {
|
||||
setUpdating(true)
|
||||
try {
|
||||
await addOverrideItem(info)
|
||||
await restartCore()
|
||||
await mihomoHotReloadConfig()
|
||||
} catch (e) {
|
||||
toast.error(String(e))
|
||||
} finally {
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { toast } from '@renderer/components/base/toast'
|
||||
import React, { useState } from 'react'
|
||||
import { useOverrideConfig } from '@renderer/hooks/use-override-config'
|
||||
import { restartCore, addProfileUpdater } from '@renderer/utils/ipc'
|
||||
import { mihomoHotReloadConfig, addProfileUpdater } from '@renderer/utils/ipc'
|
||||
import { MdDeleteForever } from 'react-icons/md'
|
||||
import { FaPlus } from 'react-icons/fa6'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -49,7 +49,7 @@ const EditInfoModal: React.FC<Props> = (props) => {
|
||||
}
|
||||
await updateProfileItem(updatedItem)
|
||||
await addProfileUpdater(updatedItem)
|
||||
await restartCore()
|
||||
await mihomoHotReloadConfig()
|
||||
onClose()
|
||||
} catch (e) {
|
||||
toast.error(String(e))
|
||||
|
||||
@@ -17,6 +17,7 @@ const MihomoConfig: React.FC = () => {
|
||||
const { appConfig, patchAppConfig } = useAppConfig()
|
||||
const {
|
||||
diffWorkDir = false,
|
||||
useHotReloadProfile = false,
|
||||
delayTestConcurrency,
|
||||
delayTestTimeout,
|
||||
githubToken = '',
|
||||
@@ -57,7 +58,7 @@ const MihomoConfig: React.FC = () => {
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
size="sm"
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
type="number"
|
||||
value={(subscriptionTimeout / 1000)?.toString()}
|
||||
onValueChange={async (v: string) => {
|
||||
@@ -148,7 +149,7 @@ const MihomoConfig: React.FC = () => {
|
||||
<SettingItem title={t('mihomo.proxyColumns.title')} divider>
|
||||
<Select
|
||||
classNames={{ trigger: 'data-[hover=true]:bg-default-200' }}
|
||||
className="w-[150px]"
|
||||
className="w-37.5"
|
||||
size="sm"
|
||||
selectedKeys={new Set([proxyCols])}
|
||||
aria-label={t('mihomo.proxyColumns.title')}
|
||||
@@ -168,7 +169,7 @@ const MihomoConfig: React.FC = () => {
|
||||
<SettingItem title={t('mihomo.cpuPriority.title')} divider>
|
||||
<Select
|
||||
classNames={{ trigger: 'data-[hover=true]:bg-default-200' }}
|
||||
className="w-[150px]"
|
||||
className="w-37.5"
|
||||
size="sm"
|
||||
selectedKeys={new Set([mihomoCpuPriority])}
|
||||
disallowEmptySelection={true}
|
||||
@@ -221,6 +222,26 @@ const MihomoConfig: React.FC = () => {
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
title={t('mihomo.hotReloadProfile.title')}
|
||||
actions={
|
||||
<Tooltip content={t('mihomo.hotReloadProfile.tooltip')}>
|
||||
<Button isIconOnly size="sm" variant="light">
|
||||
<IoIosHelpCircle className="text-lg" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
divider
|
||||
>
|
||||
<Switch
|
||||
size="sm"
|
||||
isSelected={useHotReloadProfile}
|
||||
onValueChange={(v) => {
|
||||
patchAppConfig({ useHotReloadProfile: v })
|
||||
}}
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title={t('mihomo.autoCloseConnection')} divider>
|
||||
<Switch
|
||||
size="sm"
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
|
||||
import { toast } from '@renderer/components/base/toast'
|
||||
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||
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 { mihomoHotReloadConfig } from '@renderer/utils/ipc'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
@@ -26,7 +25,6 @@ const DNSCard: React.FC<Props> = (props) => {
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const match = location.pathname.includes('/dns')
|
||||
const { patchControledMihomoConfig } = useControledMihomoConfig()
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
@@ -41,8 +39,7 @@ const DNSCard: React.FC<Props> = (props) => {
|
||||
const onChange = async (controlDns: boolean): Promise<void> => {
|
||||
try {
|
||||
await patchAppConfig({ controlDns })
|
||||
await patchControledMihomoConfig({})
|
||||
await restartCore()
|
||||
await mihomoHotReloadConfig()
|
||||
} catch (e) {
|
||||
toast.error(String(e))
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ import { toast } from '@renderer/components/base/toast'
|
||||
import BorderSwitch from '@renderer/components/base/border-swtich'
|
||||
import { RiScan2Fill } from 'react-icons/ri'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { restartCore } from '@renderer/utils/ipc'
|
||||
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||
import { mihomoHotReloadConfig } from '@renderer/utils/ipc'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
@@ -26,7 +25,6 @@ const SniffCard: React.FC<Props> = (props) => {
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const match = location.pathname.includes('/sniffer')
|
||||
const { patchControledMihomoConfig } = useControledMihomoConfig()
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
@@ -41,8 +39,7 @@ const SniffCard: React.FC<Props> = (props) => {
|
||||
const onChange = async (controlSniff: boolean): Promise<void> => {
|
||||
try {
|
||||
await patchAppConfig({ controlSniff })
|
||||
await patchControledMihomoConfig({})
|
||||
await restartCore()
|
||||
await mihomoHotReloadConfig()
|
||||
} catch (e) {
|
||||
toast.error(String(e))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-c
|
||||
import BorderSwitch from '@renderer/components/base/border-swtich'
|
||||
import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { restartCore, updateTrayIconImmediate } from '@renderer/utils/ipc'
|
||||
import { updateTrayIconImmediate } from '@renderer/utils/ipc'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import React from 'react'
|
||||
@@ -96,7 +96,6 @@ const TunSwitcher: React.FC<Props> = (props) => {
|
||||
} else {
|
||||
await patchControledMihomoConfig({ tun: { enable } })
|
||||
}
|
||||
await restartCore()
|
||||
window.electron.ipcRenderer.send('updateFloatingWindow')
|
||||
window.electron.ipcRenderer.send('updateTrayMenu')
|
||||
}
|
||||
|
||||
@@ -161,6 +161,8 @@
|
||||
"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.hotReloadProfile.title": "Hot Reload Config on Profile Switch",
|
||||
"mihomo.hotReloadProfile.tooltip": "When enabled, switching profiles uses the API to hot reload config without restarting the core. When disabled, the core restarts as usual.",
|
||||
"mihomo.controlSniff": "Control Domain Sniffing",
|
||||
"mihomo.autoCloseConnection": "Auto Close Connection",
|
||||
"mihomo.testProfileOnStart": "Test Profile on Start",
|
||||
|
||||
@@ -141,6 +141,8 @@
|
||||
"mihomo.cpuPriority.low": "پایین",
|
||||
"mihomo.workDir.title": "استفاده از پوشه کاری مجزا برای اشتراکهای مختلف",
|
||||
"mihomo.workDir.tooltip": "برای جلوگیری از تداخل گروههای پراکسی با نام یکسان در اشتراکهای مختلف",
|
||||
"mihomo.hotReloadProfile.title": "بارگذاری گرم پیکربندی در تغییر پروفایل",
|
||||
"mihomo.hotReloadProfile.tooltip": "در صورت فعال بودن، تغییر پروفایل بدون راهاندازی مجدد هسته انجام میشود",
|
||||
"mihomo.controlSniff": "کنترل تشخیص دامنه",
|
||||
"mihomo.autoCloseConnection": "بستن خودکار اتصال",
|
||||
"mihomo.testProfileOnStart": "بررسی پروفایل در شروع",
|
||||
|
||||
@@ -143,6 +143,8 @@
|
||||
"mihomo.cpuPriority.low": "Низкий",
|
||||
"mihomo.workDir.title": "Отдельные рабочие каталоги для разных подписок",
|
||||
"mihomo.workDir.tooltip": "Включите для избежания конфликтов при наличии групп прокси с одинаковыми именами в разных подписках",
|
||||
"mihomo.hotReloadProfile.title": "Горячая перезагрузка конфигурации при смене профиля",
|
||||
"mihomo.hotReloadProfile.tooltip": "При включении смена профиля выполняется через API без перезапуска ядра; при отключении используется перезапуск ядра",
|
||||
"mihomo.controlSniff": "Управление сниффингом доменов",
|
||||
"mihomo.autoCloseConnection": "Автозакрытие соединений",
|
||||
"mihomo.testProfileOnStart": "Проверять профиль при запуске",
|
||||
|
||||
@@ -164,6 +164,8 @@
|
||||
"mihomo.cpuPriority.low": "低",
|
||||
"mihomo.workDir.title": "不同订阅使用独立工作目录",
|
||||
"mihomo.workDir.tooltip": "启用后可避免不同订阅中存在相同名称的代理组时发生冲突",
|
||||
"mihomo.hotReloadProfile.title": "切换订阅时热重载配置",
|
||||
"mihomo.hotReloadProfile.tooltip": "启用后切换订阅时通过 API 热重载配置而无需重启内核,禁用时仍使用重启方式切换",
|
||||
"mihomo.controlSniff": "控制域名嗅探",
|
||||
"mihomo.autoCloseConnection": "自动关闭连接",
|
||||
"mihomo.testProfileOnStart": "启动时检查配置文件",
|
||||
|
||||
@@ -164,6 +164,8 @@
|
||||
"mihomo.cpuPriority.low": "低",
|
||||
"mihomo.workDir.title": "不同訂閱使用獨立工作目錄",
|
||||
"mihomo.workDir.tooltip": "啟用後可避免不同訂閱中存在相同名稱的代理組時發生衝突",
|
||||
"mihomo.hotReloadProfile.title": "切換訂閱時熱重載配置",
|
||||
"mihomo.hotReloadProfile.tooltip": "啟用後切換訂閱時透過 API 熱重載配置而無需重啟內核,禁用時仍使用重啟方式切換",
|
||||
"mihomo.controlSniff": "控制域名嗅探",
|
||||
"mihomo.autoCloseConnection": "自動關閉連接",
|
||||
"mihomo.testProfileOnStart": "啟動時檢查配置檔案",
|
||||
|
||||
@@ -6,7 +6,7 @@ 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 { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
import { restartCore, patchMihomoConfig } from '@renderer/utils/ipc'
|
||||
import { mihomoHotReloadConfig } from '@renderer/utils/ipc'
|
||||
import React, { Key, ReactNode, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
@@ -144,8 +144,7 @@ const DNS: React.FC = () => {
|
||||
setChanged(false)
|
||||
await patchControledMihomoConfig(patch)
|
||||
if (controlDns) {
|
||||
await patchMihomoConfig(patch)
|
||||
await restartCore()
|
||||
await mihomoHotReloadConfig()
|
||||
}
|
||||
} catch (e) {
|
||||
showErrorSync(e, t('common.error.dnsConfigSaveFailed'))
|
||||
@@ -427,7 +426,7 @@ const DNS: React.FC = () => {
|
||||
<SettingItem title={t('dns.fallbackFilter.geoipCode')} divider>
|
||||
<Input
|
||||
size="sm"
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
value={typeof values.fallbackGeoipCode === 'string' ? values.fallbackGeoipCode : ''}
|
||||
placeholder="CN"
|
||||
onValueChange={(v) => {
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
import PubSub from 'pubsub-js'
|
||||
import {
|
||||
mihomoUpgrade,
|
||||
mihomoHotReloadConfig,
|
||||
restartCore,
|
||||
startSubStoreBackendServer,
|
||||
triggerSysProxy,
|
||||
@@ -319,7 +320,6 @@ const Mihomo: React.FC = () => {
|
||||
|
||||
const onChangeNeedRestart = async (patch: Partial<IMihomoConfig>): Promise<void> => {
|
||||
await patchControledMihomoConfig(patch)
|
||||
await restartCore()
|
||||
}
|
||||
|
||||
const handleConfigChangeWithRestart = async (key: string, value: unknown) => {
|
||||
@@ -472,7 +472,7 @@ const Mihomo: React.FC = () => {
|
||||
color="primary"
|
||||
onValueChange={async (v) => {
|
||||
await patchAppConfig({ enableSmartOverride: v })
|
||||
await restartCore()
|
||||
await mihomoHotReloadConfig()
|
||||
}}
|
||||
/>
|
||||
</SettingItem>
|
||||
@@ -535,7 +535,7 @@ const Mihomo: React.FC = () => {
|
||||
? 'data-[hover=true]:bg-blue-100 dark:data-[hover=true]:bg-blue-900/50'
|
||||
: 'data-[hover=true]:bg-default-200'
|
||||
}}
|
||||
className="w-[150px]"
|
||||
className="w-37.5"
|
||||
size="sm"
|
||||
aria-label={t('mihomo.selectCoreVersion')}
|
||||
selectedKeys={new Set([core])}
|
||||
@@ -587,7 +587,7 @@ const Mihomo: React.FC = () => {
|
||||
isSelected={smartCoreUseLightGBM}
|
||||
onValueChange={async (v) => {
|
||||
await patchAppConfig({ smartCoreUseLightGBM: v })
|
||||
await restartCore()
|
||||
await mihomoHotReloadConfig()
|
||||
}}
|
||||
/>
|
||||
</SettingItem>
|
||||
@@ -613,7 +613,7 @@ const Mihomo: React.FC = () => {
|
||||
isSelected={smartCoreCollectData}
|
||||
onValueChange={async (v) => {
|
||||
await patchAppConfig({ smartCoreCollectData: v })
|
||||
await restartCore()
|
||||
await mihomoHotReloadConfig()
|
||||
}}
|
||||
/>
|
||||
</SettingItem>
|
||||
@@ -636,7 +636,7 @@ const Mihomo: React.FC = () => {
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
size="sm"
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
type="number"
|
||||
value={smartCollectorSize.toString()}
|
||||
onValueChange={async (v: string) => {
|
||||
@@ -650,7 +650,7 @@ const Mihomo: React.FC = () => {
|
||||
if (isNaN(num)) num = 100
|
||||
if (num < 1) num = 1
|
||||
await patchAppConfig({ smartCollectorSize: num })
|
||||
await restartCore()
|
||||
await mihomoHotReloadConfig()
|
||||
}}
|
||||
/>
|
||||
<span className="text-default-500">MB</span>
|
||||
@@ -662,7 +662,7 @@ const Mihomo: React.FC = () => {
|
||||
classNames={{
|
||||
trigger: 'data-[hover=true]:bg-blue-100 dark:data-[hover=true]:bg-blue-900/50'
|
||||
}}
|
||||
className="w-[150px]"
|
||||
className="w-37.5"
|
||||
size="sm"
|
||||
aria-label={t('mihomo.smartCoreStrategy')}
|
||||
selectedKeys={new Set([smartCoreStrategy])}
|
||||
@@ -670,7 +670,7 @@ const Mihomo: React.FC = () => {
|
||||
onSelectionChange={async (v) => {
|
||||
const strategy = v.currentKey as 'sticky-sessions' | 'round-robin'
|
||||
await patchAppConfig({ smartCoreStrategy: strategy })
|
||||
await restartCore()
|
||||
await mihomoHotReloadConfig()
|
||||
}}
|
||||
>
|
||||
<SelectItem key="sticky-sessions">
|
||||
@@ -710,7 +710,7 @@ const Mihomo: React.FC = () => {
|
||||
<Input
|
||||
size="sm"
|
||||
type="number"
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
value={(showMixedPort ?? mixedPort ?? '').toString()}
|
||||
max={65535}
|
||||
min={0}
|
||||
@@ -771,7 +771,7 @@ const Mihomo: React.FC = () => {
|
||||
<Input
|
||||
size="sm"
|
||||
type="number"
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
value={(showSocksPort ?? socksPort ?? '').toString()}
|
||||
max={65535}
|
||||
min={0}
|
||||
@@ -832,7 +832,7 @@ const Mihomo: React.FC = () => {
|
||||
<Input
|
||||
size="sm"
|
||||
type="number"
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
value={(showHttpPort ?? httpPort ?? '').toString()}
|
||||
max={65535}
|
||||
min={0}
|
||||
@@ -894,7 +894,7 @@ const Mihomo: React.FC = () => {
|
||||
<Input
|
||||
size="sm"
|
||||
type="number"
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
value={(showRedirPort ?? redirPort ?? '').toString()}
|
||||
max={65535}
|
||||
min={0}
|
||||
@@ -957,7 +957,7 @@ const Mihomo: React.FC = () => {
|
||||
<Input
|
||||
size="sm"
|
||||
type="number"
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
value={(showTproxyPort ?? tproxyPort ?? '').toString()}
|
||||
max={65535}
|
||||
min={0}
|
||||
@@ -1029,7 +1029,7 @@ const Mihomo: React.FC = () => {
|
||||
>
|
||||
<Input
|
||||
size="sm"
|
||||
className={`w-[200px] ${externalControllerError ? 'border-red-500 ring-1 ring-red-500 rounded-lg' : ''}`}
|
||||
className={`w-50 ${externalControllerError ? 'border-red-500 ring-1 ring-red-500 rounded-lg' : ''}`}
|
||||
value={externalControllerInput}
|
||||
onValueChange={(v) => {
|
||||
setExternalControllerInput(v)
|
||||
@@ -1081,7 +1081,7 @@ const Mihomo: React.FC = () => {
|
||||
<Input
|
||||
size="sm"
|
||||
type={isSecretVisible ? 'text' : 'password'}
|
||||
className="w-[200px]"
|
||||
className="w-50"
|
||||
value={secretInput}
|
||||
onValueChange={(v) => {
|
||||
setSecretInput(v)
|
||||
@@ -1422,7 +1422,7 @@ const Mihomo: React.FC = () => {
|
||||
<Input
|
||||
size="sm"
|
||||
type="number"
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
value={maxLogDays.toString()}
|
||||
onValueChange={(v) => {
|
||||
const num = parseInt(v)
|
||||
@@ -1436,7 +1436,7 @@ const Mihomo: React.FC = () => {
|
||||
<Input
|
||||
size="sm"
|
||||
type="number"
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
value={maxLogFileSize.toString()}
|
||||
onValueChange={(v) => {
|
||||
const num = parseInt(v)
|
||||
@@ -1455,7 +1455,7 @@ const Mihomo: React.FC = () => {
|
||||
<SettingItem title={t('mihomo.logLevel')} divider>
|
||||
<Select
|
||||
classNames={{ trigger: 'data-[hover=true]:bg-default-200' }}
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
size="sm"
|
||||
aria-label={t('mihomo.selectLogLevel')}
|
||||
selectedKeys={new Set([logLevel])}
|
||||
@@ -1474,7 +1474,7 @@ const Mihomo: React.FC = () => {
|
||||
<SettingItem title={t('mihomo.findProcess')}>
|
||||
<Select
|
||||
classNames={{ trigger: 'data-[hover=true]:bg-default-200' }}
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
size="sm"
|
||||
aria-label={t('mihomo.selectFindProcessMode')}
|
||||
selectedKeys={new Set([findProcessMode])}
|
||||
@@ -1506,7 +1506,7 @@ const Mihomo: React.FC = () => {
|
||||
<ModalBody className="flex flex-col h-full">
|
||||
<div className="flex flex-col h-full">
|
||||
{/* 添加/编辑面板表单 */}
|
||||
<div className="flex flex-col gap-2 p-3 bg-default-100 rounded-lg flex-shrink-0">
|
||||
<div className="flex flex-col gap-2 p-3 bg-default-100 rounded-lg shrink-0">
|
||||
<Input
|
||||
label={t('settings.webui.panelName')}
|
||||
placeholder={t('settings.webui.panelNamePlaceholder')}
|
||||
@@ -1563,12 +1563,12 @@ const Mihomo: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* 面板列表 */}
|
||||
<div className="flex flex-col gap-2 mt-2 overflow-y-auto flex-grow">
|
||||
<div className="flex flex-col gap-2 mt-2 overflow-y-auto grow">
|
||||
<h3 className="text-lg font-semibold">{t('settings.webui.panels')}</h3>
|
||||
{allPanels.map((panel) => (
|
||||
<div
|
||||
key={panel.id}
|
||||
className="flex items-start justify-between p-3 bg-default-50 rounded-lg flex-shrink-0"
|
||||
className="flex items-start justify-between p-3 bg-default-50 rounded-lg shrink-0"
|
||||
>
|
||||
<div className="flex-1 mr-2">
|
||||
<p className="font-medium">{panel.name}</p>
|
||||
|
||||
@@ -5,7 +5,7 @@ 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 { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
import { restartCore, patchMihomoConfig } from '@renderer/utils/ipc'
|
||||
import { mihomoHotReloadConfig } from '@renderer/utils/ipc'
|
||||
import React, { ReactNode, useState } from 'react'
|
||||
import { MdDeleteForever } from 'react-icons/md'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -67,8 +67,7 @@ const Sniffer: React.FC = () => {
|
||||
await patchControledMihomoConfig(patch)
|
||||
|
||||
if (controlSniff) {
|
||||
await patchMihomoConfig(patch)
|
||||
await restartCore()
|
||||
await mihomoHotReloadConfig()
|
||||
}
|
||||
} catch (e) {
|
||||
showErrorSync(e, t('common.error.snifferConfigSaveFailed'))
|
||||
|
||||
@@ -64,7 +64,6 @@ const Tun: React.FC = () => {
|
||||
|
||||
const onSave = async (patch: Partial<IMihomoConfig>): Promise<void> => {
|
||||
await patchControledMihomoConfig(patch)
|
||||
await restartCore()
|
||||
setChanged(false)
|
||||
}
|
||||
|
||||
@@ -169,7 +168,7 @@ const Tun: React.FC = () => {
|
||||
<SettingItem title={t('tun.device.title')} divider>
|
||||
<Input
|
||||
size="sm"
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
value={values.device}
|
||||
placeholder={platform === 'darwin' ? 'utun1500' : 'Mihomo'}
|
||||
onValueChange={(v) => {
|
||||
@@ -220,7 +219,7 @@ const Tun: React.FC = () => {
|
||||
<Input
|
||||
size="sm"
|
||||
type="number"
|
||||
className="w-[100px]"
|
||||
className="w-25"
|
||||
value={values.mtu.toString()}
|
||||
onValueChange={(v) => {
|
||||
const num = parseInt(v)
|
||||
|
||||
@@ -83,6 +83,7 @@ interface IpcApi {
|
||||
openFile: (type: 'profile' | 'override', id: string, ext?: 'yaml' | 'js') => Promise<void>
|
||||
// Core
|
||||
restartCore: () => Promise<void>
|
||||
mihomoHotReloadConfig: () => Promise<void>
|
||||
startMonitor: () => Promise<void>
|
||||
quitWithoutCore: () => Promise<void>
|
||||
// System
|
||||
@@ -240,6 +241,7 @@ export const {
|
||||
openFile,
|
||||
// Core
|
||||
restartCore,
|
||||
mihomoHotReloadConfig,
|
||||
startMonitor,
|
||||
quitWithoutCore,
|
||||
// System
|
||||
@@ -361,7 +363,7 @@ export async function getAppName(appPath: string): Promise<string> {
|
||||
return invoke<string>('getAppName', appPath)
|
||||
}
|
||||
|
||||
// getIconDataURL: 获取应用图标的Base64数据
|
||||
// getIconDataURL: 获取应用图标的 Base64 数据
|
||||
export async function getIconDataURL(appPath: string): Promise<string> {
|
||||
return invoke<string>('getIconDataURL', appPath)
|
||||
}
|
||||
|
||||
1
src/shared/types.d.ts
vendored
1
src/shared/types.d.ts
vendored
@@ -358,6 +358,7 @@ interface IAppConfig {
|
||||
showTproxyPort?: number
|
||||
enableTproxyPort?: boolean
|
||||
testProfileOnStart?: boolean
|
||||
useHotReloadProfile?: boolean
|
||||
}
|
||||
|
||||
interface IMihomoTunConfig {
|
||||
|
||||
Reference in New Issue
Block a user