diff --git a/src/main/config/profile.ts b/src/main/config/profile.ts index 735b4b7e..4c0e6004 100644 --- a/src/main/config/profile.ts +++ b/src/main/config/profile.ts @@ -140,10 +140,10 @@ export async function createProfile(item: Partial): Promise { @@ -239,4 +239,4 @@ export async function setFileStr(path: string, content: string): Promise { } else { await writeFile(mihomoProfileWorkDir(path), content, 'utf-8') } -} \ No newline at end of file +} diff --git a/src/main/sys/misc.ts b/src/main/sys/misc.ts index baa2a8e5..b2b30991 100644 --- a/src/main/sys/misc.ts +++ b/src/main/sys/misc.ts @@ -1,9 +1,10 @@ -import { exec, execFile, execSync } from 'child_process' -import { dialog, nativeTheme, shell } from 'electron' +import { exec, execFile, execSync, spawn } from 'child_process' +import { app, dialog, nativeTheme, shell } from 'electron' import { readFile } from 'fs/promises' import path from 'path' import { promisify } from 'util' import { + dataDir, exePath, mihomoCorePath, overridePath, @@ -112,3 +113,33 @@ export function createElevateTask(): void { `%SystemRoot%\\System32\\schtasks.exe /create /tn "mihomo-party-run" /xml "${taskFilePath}" /f` ) } + +export function resetAppConfig(): void { + if (process.platform === 'win32') { + spawn( + 'cmd', + [ + '/C', + `"timeout /t 2 /nobreak >nul && rmdir /s /q "${dataDir()}" && start "" "${exePath()}""` + ], + { + shell: true, + detached: true + } + ).unref() + } else { + const script = `while kill -0 ${process.pid} 2>/dev/null; do + sleep 0.1 +done + rm -rf '${dataDir()}' + ${process.argv.join(' ')} & disown +exit +` + spawn('sh', ['-c', `"${script}"`], { + shell: true, + detached: true, + stdio: 'ignore' + }) + } + app.quit() +} diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts index c8021628..304da090 100644 --- a/src/main/utils/ipc.ts +++ b/src/main/utils/ipc.ts @@ -61,6 +61,7 @@ import { openFile, openUWPTool, readTextFile, + resetAppConfig, setNativeTheme, setupFirewall } from '../sys/misc' @@ -246,6 +247,7 @@ export function registerIpcMainHandlers(): void { ipcMain.handle('alert', (_e, msg) => { dialog.showErrorBox('Mihomo Party', msg) }) + ipcMain.handle('resetAppConfig', resetAppConfig) ipcMain.handle('relaunchApp', () => { app.relaunch() app.quit() diff --git a/src/renderer/src/components/connections/connection-detail-modal.tsx b/src/renderer/src/components/connections/connection-detail-modal.tsx index 06887246..d618a28d 100644 --- a/src/renderer/src/components/connections/connection-detail-modal.tsx +++ b/src/renderer/src/components/connections/connection-detail-modal.tsx @@ -1,4 +1,15 @@ -import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button, Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from '@nextui-org/react' +import { + Modal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + Button, + Dropdown, + DropdownTrigger, + DropdownMenu, + DropdownItem +} from '@nextui-org/react' import React from 'react' import SettingItem from '../base/base-setting-item' import { calcTraffic } from '@renderer/utils/calc' @@ -17,50 +28,70 @@ const CopyableSettingItem: React.FC<{ prefix?: string[] suffix?: string }> = ({ title, value, displayName, prefix = [], suffix = '' }) => { - const getSubDomains = (domain: string) => + const getSubDomains = (domain: string): string[] => domain.split('.').length <= 2 ? [domain] - : domain.split('.').map((_, i, parts) => parts.slice(i).join('.')).slice(0, -1) + : domain + .split('.') + .map((_, i, parts) => parts.slice(i).join('.')) + .slice(0, -1) const menuItems = [ { key: 'raw', text: displayName || (Array.isArray(value) ? value.join(', ') : value) }, ...(Array.isArray(value) && value.length === prefix.length - ? prefix.map((p, i) => value[i] ? ({ - key: `${p},${p === 'IP-ASN' ? value[i].split(' ')[0] : value[i]}${suffix}`, - text: `${p},${p === 'IP-ASN' ? value[i].split(' ')[0] : value[i]}${suffix}` - }) : null).filter(Boolean) - : prefix.flatMap(p => - (Array.isArray(value) - ? value.map(v => p === 'DOMAIN-SUFFIX' - ? getSubDomains(v).map(subV => ({ - key: `${p},${subV}${suffix}`, - text: `${p},${subV}${suffix}` - })) - : p === 'IP-ASN' || p === 'SRC-IP-ASN' - ? [{ - key: `${p},${v.split(' ')[0]}${suffix}`, - text: `${p},${v.split(' ')[0]}${suffix}` - }] - : [{ - key: `${p},${v}${suffix}`, - text: `${p},${v}${suffix}` - }] - ).flat() - : p === 'DOMAIN-SUFFIX' - ? getSubDomains(value).map(v => ({ - key: `${p},${v}${suffix}`, - text: `${p},${v}${suffix}` - })) - : p === 'IP-ASN' || p === 'SRC-IP-ASN' - ? [{ - key: `${p},${value.split(' ')[0]}${suffix}`, - text: `${p},${value.split(' ')[0]}${suffix}` - }] - : [{ - key: `${p},${value}${suffix}`, - text: `${p},${value}${suffix}` - }] - ))) + ? prefix + .map((p, i) => + value[i] + ? { + key: `${p},${p === 'IP-ASN' ? value[i].split(' ')[0] : value[i]}${suffix}`, + text: `${p},${p === 'IP-ASN' ? value[i].split(' ')[0] : value[i]}${suffix}` + } + : null + ) + .filter(Boolean) + : prefix.flatMap((p) => + Array.isArray(value) + ? value + .map((v) => + p === 'DOMAIN-SUFFIX' + ? getSubDomains(v).map((subV) => ({ + key: `${p},${subV}${suffix}`, + text: `${p},${subV}${suffix}` + })) + : p === 'IP-ASN' || p === 'SRC-IP-ASN' + ? [ + { + key: `${p},${v.split(' ')[0]}${suffix}`, + text: `${p},${v.split(' ')[0]}${suffix}` + } + ] + : [ + { + key: `${p},${v}${suffix}`, + text: `${p},${v}${suffix}` + } + ] + ) + .flat() + : p === 'DOMAIN-SUFFIX' + ? getSubDomains(value).map((v) => ({ + key: `${p},${v}${suffix}`, + text: `${p},${v}${suffix}` + })) + : p === 'IP-ASN' || p === 'SRC-IP-ASN' + ? [ + { + key: `${p},${value.split(' ')[0]}${suffix}`, + text: `${p},${value.split(' ')[0]}${suffix}` + } + ] + : [ + { + key: `${p},${value}${suffix}`, + text: `${p},${value}${suffix}` + } + ] + )) ] return ( @@ -69,18 +100,22 @@ const CopyableSettingItem: React.FC<{ actions={ - - navigator.clipboard.writeText(key === 'raw' ? (Array.isArray(value) ? value.join(', ') : value) : key as string) + onAction={(key) => + navigator.clipboard.writeText( + key === 'raw' ? (Array.isArray(value) ? value.join(', ') : value) : (key as string) + ) } > - {menuItems.filter(item => item !== null).map(({ key, text }) => ( - {text} - ))} + {menuItems + .filter((item) => item !== null) + .map(({ key, text }) => ( + {text} + ))} } @@ -94,170 +129,174 @@ const ConnectionDetailModal: React.FC = (props) => { const { connection, onClose } = props return ( - - 连接详情 + + 连接详情 - {dayjs(connection.start).fromNow()} - + {dayjs(connection.start).fromNow()} + {connection.rule} {connection.rulePayload ? `(${connection.rulePayload})` : ''} - {[...connection.chains].reverse().join('>>')} - {calcTraffic(connection.uploadSpeed || 0)}/s - {calcTraffic(connection.downloadSpeed || 0)}/s - {calcTraffic(connection.upload)} - {calcTraffic(connection.download)} + {[...connection.chains].reverse().join('>>')} + {calcTraffic(connection.uploadSpeed || 0)}/s + {calcTraffic(connection.downloadSpeed || 0)}/s + {calcTraffic(connection.upload)} + {calcTraffic(connection.download)} {connection.metadata.host && ( )} {connection.metadata.sniffHost && ( )} {connection.metadata.process && ( )} {connection.metadata.processPath && ( )} {connection.metadata.sourceIP && ( )} {connection.metadata.sourceGeoIP && connection.metadata.sourceGeoIP.length > 0 && ( )} {connection.metadata.sourceIPASN && ( )} {connection.metadata.destinationIP && ( - )} - {connection.metadata.destinationGeoIP && connection.metadata.destinationGeoIP.length > 0 && ( - )} + {connection.metadata.destinationGeoIP && + connection.metadata.destinationGeoIP.length > 0 && ( + + )} {connection.metadata.destinationIPASN && ( )} {connection.metadata.sourcePort && ( )} {connection.metadata.destinationPort && ( )} {connection.metadata.inboundIP && ( )} {connection.metadata.inboundPort && ( )} {connection.metadata.inboundName && ( )} {connection.metadata.inboundUser && ( )} {connection.metadata.remoteDestination && ( - {connection.metadata.remoteDestination} + {connection.metadata.remoteDestination} )} {connection.metadata.dnsMode && ( - {connection.metadata.dnsMode} + {connection.metadata.dnsMode} )} {connection.metadata.specialProxy && ( - {connection.metadata.specialProxy} + {connection.metadata.specialProxy} )} {connection.metadata.specialRules && ( - {connection.metadata.specialRules} + {connection.metadata.specialRules} )} - diff --git a/src/renderer/src/components/resources/proxy-provider.tsx b/src/renderer/src/components/resources/proxy-provider.tsx index b66a2988..e7361a55 100644 --- a/src/renderer/src/components/resources/proxy-provider.tsx +++ b/src/renderer/src/components/resources/proxy-provider.tsx @@ -22,7 +22,7 @@ const ProxyProvider: React.FC = () => { const [ShowType, setShowType] = useState('') useEffect(() => { - const fetchProviderPath = async (name: string) => { + const fetchProviderPath = async (name: string): Promise => { try { const providers = await getRuntimeConfig() const provider = providers['proxy-providers'][name] diff --git a/src/renderer/src/components/resources/rule-provider.tsx b/src/renderer/src/components/resources/rule-provider.tsx index e10e43b5..00f974c3 100644 --- a/src/renderer/src/components/resources/rule-provider.tsx +++ b/src/renderer/src/components/resources/rule-provider.tsx @@ -30,7 +30,7 @@ const RuleProvider: React.FC = () => { const [updating, setUpdating] = useState(Array(providers.length).fill(false)) useEffect(() => { - const fetchProviderPath = async (name: string) => { + const fetchProviderPath = async (name: string): Promise => { try { const providers = await getRuntimeConfig() const provider = providers['rule-providers'][name] diff --git a/src/renderer/src/components/resources/viewer.tsx b/src/renderer/src/components/resources/viewer.tsx index dff7e0af..36735c80 100644 --- a/src/renderer/src/components/resources/viewer.tsx +++ b/src/renderer/src/components/resources/viewer.tsx @@ -13,7 +13,7 @@ interface Props { const Viewer: React.FC = (props) => { const { type, path, format, onClose } = props const [currData, setCurrData] = useState('') - const language: Language = (!format || format === 'YamlRule') ? 'yaml' : 'text' + const language: Language = !format || format === 'YamlRule' ? 'yaml' : 'text' const getContent = async (): Promise => { setCurrData(await getFileStr(path)) @@ -36,13 +36,18 @@ const Viewer: React.FC = (props) => { Provider 内容 - setCurrData(value)}/> + setCurrData(value)} + /> - {type == 'File' && + {type == 'File' && ( } + + )} diff --git a/src/renderer/src/components/settings/actions.tsx b/src/renderer/src/components/settings/actions.tsx index e446486b..1a80925b 100644 --- a/src/renderer/src/components/settings/actions.tsx +++ b/src/renderer/src/components/settings/actions.tsx @@ -1,7 +1,13 @@ import { Button, Tooltip } from '@nextui-org/react' import SettingCard from '../base/base-setting-card' import SettingItem from '../base/base-setting-item' -import { checkUpdate, createHeapSnapshot, quitApp, quitWithoutCore } from '@renderer/utils/ipc' +import { + checkUpdate, + createHeapSnapshot, + quitApp, + quitWithoutCore, + resetAppConfig +} from '@renderer/utils/ipc' import { useState } from 'react' import UpdaterModal from '../updater/updater-modal' import { version } from '@renderer/utils/init' @@ -54,6 +60,21 @@ const Actions: React.FC = () => { 检查更新 + + + + } + divider + > + + { return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('openDevTools')) } +export async function resetAppConfig(): Promise { + return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('resetAppConfig')) +} + export async function createHeapSnapshot(): Promise { return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('createHeapSnapshot')) }