mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-26 20:50:30 +08:00
support reset application
This commit is contained in:
parent
ec3efe89c7
commit
22c35b606e
@ -140,10 +140,10 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
|
||||
res = await axios.get(item.url, {
|
||||
proxy: newItem.useProxy
|
||||
? {
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: mixedPort
|
||||
}
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: mixedPort
|
||||
}
|
||||
: false,
|
||||
headers: {
|
||||
'User-Agent': userAgent || 'clash.meta'
|
||||
@ -222,7 +222,7 @@ function parseSubinfo(str: string): ISubscriptionUserInfo {
|
||||
}
|
||||
|
||||
function isAbsolutePath(path: string): boolean {
|
||||
return path.startsWith('/') || /^[a-zA-Z]:\\/.test(path);
|
||||
return path.startsWith('/') || /^[a-zA-Z]:\\/.test(path)
|
||||
}
|
||||
|
||||
export async function getFileStr(path: string): Promise<string> {
|
||||
@ -239,4 +239,4 @@ export async function setFileStr(path: string, content: string): Promise<void> {
|
||||
} else {
|
||||
await writeFile(mihomoProfileWorkDir(path), content, 'utf-8')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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={
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
<Button title='复制规则' isIconOnly size='sm' variant='light'>
|
||||
<BiCopy className='text-lg' />
|
||||
<Button title="复制规则" isIconOnly size="sm" variant="light">
|
||||
<BiCopy className="text-lg" />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu
|
||||
onAction={key =>
|
||||
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 }) => (
|
||||
<DropdownItem key={key}>{text}</DropdownItem>
|
||||
))}
|
||||
{menuItems
|
||||
.filter((item) => item !== null)
|
||||
.map(({ key, text }) => (
|
||||
<DropdownItem key={key}>{text}</DropdownItem>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
@ -94,170 +129,174 @@ const ConnectionDetailModal: React.FC<Props> = (props) => {
|
||||
const { connection, onClose } = props
|
||||
return (
|
||||
<Modal
|
||||
backdrop='blur'
|
||||
backdrop="blur"
|
||||
classNames={{ backdrop: 'top-[48px]' }}
|
||||
size='xl'
|
||||
size="xl"
|
||||
hideCloseButton
|
||||
isOpen={true}
|
||||
onOpenChange={onClose}
|
||||
scrollBehavior='inside'
|
||||
scrollBehavior="inside"
|
||||
>
|
||||
<ModalContent className='flag-emoji break-all'>
|
||||
<ModalHeader className='flex app-drag'>连接详情</ModalHeader>
|
||||
<ModalContent className="flag-emoji break-all">
|
||||
<ModalHeader className="flex app-drag">连接详情</ModalHeader>
|
||||
<ModalBody>
|
||||
<SettingItem title='连接建立时间'>{dayjs(connection.start).fromNow()}</SettingItem>
|
||||
<SettingItem title='规则'>
|
||||
<SettingItem title="连接建立时间">{dayjs(connection.start).fromNow()}</SettingItem>
|
||||
<SettingItem title="规则">
|
||||
{connection.rule}
|
||||
{connection.rulePayload ? `(${connection.rulePayload})` : ''}
|
||||
</SettingItem>
|
||||
<SettingItem title='代理链'>{[...connection.chains].reverse().join('>>')}</SettingItem>
|
||||
<SettingItem title='上传速度'>{calcTraffic(connection.uploadSpeed || 0)}/s</SettingItem>
|
||||
<SettingItem title='下载速度'>{calcTraffic(connection.downloadSpeed || 0)}/s</SettingItem>
|
||||
<SettingItem title='上传量'>{calcTraffic(connection.upload)}</SettingItem>
|
||||
<SettingItem title='下载量'>{calcTraffic(connection.download)}</SettingItem>
|
||||
<SettingItem title="代理链">{[...connection.chains].reverse().join('>>')}</SettingItem>
|
||||
<SettingItem title="上传速度">{calcTraffic(connection.uploadSpeed || 0)}/s</SettingItem>
|
||||
<SettingItem title="下载速度">{calcTraffic(connection.downloadSpeed || 0)}/s</SettingItem>
|
||||
<SettingItem title="上传量">{calcTraffic(connection.upload)}</SettingItem>
|
||||
<SettingItem title="下载量">{calcTraffic(connection.download)}</SettingItem>
|
||||
<CopyableSettingItem
|
||||
title='连接类型'
|
||||
title="连接类型"
|
||||
value={[connection.metadata.type, connection.metadata.network]}
|
||||
displayName={`${connection.metadata.type}(${connection.metadata.network})`}
|
||||
prefix={['IN-TYPE', 'NETWORK']}
|
||||
/>
|
||||
{connection.metadata.host && (
|
||||
<CopyableSettingItem
|
||||
title='主机'
|
||||
title="主机"
|
||||
value={connection.metadata.host}
|
||||
prefix={['DOMAIN', 'DOMAIN-SUFFIX']}
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.sniffHost && (
|
||||
<CopyableSettingItem
|
||||
title='嗅探主机'
|
||||
title="嗅探主机"
|
||||
value={connection.metadata.sniffHost}
|
||||
prefix={['DOMAIN', 'DOMAIN-SUFFIX']}
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.process && (
|
||||
<CopyableSettingItem
|
||||
title='进程名'
|
||||
value={[connection.metadata.process, connection.metadata.uid ? connection.metadata.uid.toString() : '']}
|
||||
title="进程名"
|
||||
value={[
|
||||
connection.metadata.process,
|
||||
connection.metadata.uid ? connection.metadata.uid.toString() : ''
|
||||
]}
|
||||
displayName={`${connection.metadata.process}${connection.metadata.uid ? `(${connection.metadata.uid})` : ''}`}
|
||||
prefix={['PROCESS-NAME', 'UID']}
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.processPath && (
|
||||
<CopyableSettingItem
|
||||
title='进程路径'
|
||||
title="进程路径"
|
||||
value={connection.metadata.processPath}
|
||||
prefix={['PROCESS-PATH']}
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.sourceIP && (
|
||||
<CopyableSettingItem
|
||||
title='来源IP'
|
||||
title="来源IP"
|
||||
value={connection.metadata.sourceIP}
|
||||
prefix={['SRC-IP-CIDR']}
|
||||
suffix='/32'
|
||||
suffix="/32"
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.sourceGeoIP && connection.metadata.sourceGeoIP.length > 0 && (
|
||||
<CopyableSettingItem
|
||||
title='来源GeoIP'
|
||||
title="来源GeoIP"
|
||||
value={connection.metadata.sourceGeoIP}
|
||||
prefix={['SRC-GEOIP']}
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.sourceIPASN && (
|
||||
<CopyableSettingItem
|
||||
title='来源ASN'
|
||||
title="来源ASN"
|
||||
value={connection.metadata.sourceIPASN}
|
||||
prefix={['SRC-IP-ASN']}
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.destinationIP && (
|
||||
<CopyableSettingItem
|
||||
title='目标IP'
|
||||
title="目标IP"
|
||||
value={connection.metadata.destinationIP}
|
||||
prefix={['IP-CIDR']}
|
||||
suffix='/32'
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.destinationGeoIP && connection.metadata.destinationGeoIP.length > 0 && (
|
||||
<CopyableSettingItem
|
||||
title='目标GeoIP'
|
||||
value={connection.metadata.destinationGeoIP}
|
||||
prefix={['GEOIP']}
|
||||
suffix="/32"
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.destinationGeoIP &&
|
||||
connection.metadata.destinationGeoIP.length > 0 && (
|
||||
<CopyableSettingItem
|
||||
title="目标GeoIP"
|
||||
value={connection.metadata.destinationGeoIP}
|
||||
prefix={['GEOIP']}
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.destinationIPASN && (
|
||||
<CopyableSettingItem
|
||||
title='目标ASN'
|
||||
title="目标ASN"
|
||||
value={connection.metadata.destinationIPASN}
|
||||
prefix={['IP-ASN']}
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.sourcePort && (
|
||||
<CopyableSettingItem
|
||||
title='来源端口'
|
||||
title="来源端口"
|
||||
value={connection.metadata.sourcePort}
|
||||
prefix={['SRC-PORT']}
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.destinationPort && (
|
||||
<CopyableSettingItem
|
||||
title='目标端口'
|
||||
title="目标端口"
|
||||
value={connection.metadata.destinationPort}
|
||||
prefix={['DST-PORT']}
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.inboundIP && (
|
||||
<CopyableSettingItem
|
||||
title='入站IP'
|
||||
title="入站IP"
|
||||
value={connection.metadata.inboundIP}
|
||||
prefix={['SRC-IP-CIDR']}
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.inboundPort && (
|
||||
<CopyableSettingItem
|
||||
title='入站端口'
|
||||
title="入站端口"
|
||||
value={connection.metadata.inboundPort}
|
||||
prefix={['SRC-PORT']}
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.inboundName && (
|
||||
<CopyableSettingItem
|
||||
title='入站名称'
|
||||
title="入站名称"
|
||||
value={connection.metadata.inboundName}
|
||||
prefix={['IN-NAME']}
|
||||
/>
|
||||
)}
|
||||
{connection.metadata.inboundUser && (
|
||||
<CopyableSettingItem
|
||||
title='入站用户'
|
||||
title="入站用户"
|
||||
value={connection.metadata.inboundUser}
|
||||
prefix={['IN-USER']}
|
||||
/>
|
||||
)}
|
||||
|
||||
<CopyableSettingItem
|
||||
title='DSCP'
|
||||
title="DSCP"
|
||||
value={connection.metadata.dscp.toString()}
|
||||
prefix={['DSCP']}
|
||||
/>
|
||||
|
||||
{connection.metadata.remoteDestination && (
|
||||
<SettingItem title='远程目标'>{connection.metadata.remoteDestination}</SettingItem>
|
||||
<SettingItem title="远程目标">{connection.metadata.remoteDestination}</SettingItem>
|
||||
)}
|
||||
{connection.metadata.dnsMode && (
|
||||
<SettingItem title='DNS模式'>{connection.metadata.dnsMode}</SettingItem>
|
||||
<SettingItem title="DNS模式">{connection.metadata.dnsMode}</SettingItem>
|
||||
)}
|
||||
{connection.metadata.specialProxy && (
|
||||
<SettingItem title='特殊代理'>{connection.metadata.specialProxy}</SettingItem>
|
||||
<SettingItem title="特殊代理">{connection.metadata.specialProxy}</SettingItem>
|
||||
)}
|
||||
{connection.metadata.specialRules && (
|
||||
<SettingItem title='特殊规则'>{connection.metadata.specialRules}</SettingItem>
|
||||
<SettingItem title="特殊规则">{connection.metadata.specialRules}</SettingItem>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button size='sm' variant='light' onPress={onClose}>
|
||||
<Button size="sm" variant="light" onPress={onClose}>
|
||||
关闭
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
||||
@ -22,7 +22,7 @@ const ProxyProvider: React.FC = () => {
|
||||
const [ShowType, setShowType] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
const fetchProviderPath = async (name: string) => {
|
||||
const fetchProviderPath = async (name: string): Promise<void> => {
|
||||
try {
|
||||
const providers = await getRuntimeConfig()
|
||||
const provider = providers['proxy-providers'][name]
|
||||
|
||||
@ -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<void> => {
|
||||
try {
|
||||
const providers = await getRuntimeConfig()
|
||||
const provider = providers['rule-providers'][name]
|
||||
|
||||
@ -13,7 +13,7 @@ interface Props {
|
||||
const Viewer: React.FC<Props> = (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<void> => {
|
||||
setCurrData(await getFileStr(path))
|
||||
@ -36,13 +36,18 @@ const Viewer: React.FC<Props> = (props) => {
|
||||
<ModalContent className="h-full w-[calc(100%-100px)]">
|
||||
<ModalHeader className="flex pb-0 app-drag">Provider 内容</ModalHeader>
|
||||
<ModalBody className="h-full">
|
||||
<BaseEditor language={language} value={currData} readOnly={type != 'File'} onChange={(value) => setCurrData(value)}/>
|
||||
<BaseEditor
|
||||
language={language}
|
||||
value={currData}
|
||||
readOnly={type != 'File'}
|
||||
onChange={(value) => setCurrData(value)}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter className="pt-0">
|
||||
<Button size="sm" variant="light" onPress={onClose}>
|
||||
关闭
|
||||
</Button>
|
||||
{type == 'File' &&
|
||||
{type == 'File' && (
|
||||
<Button
|
||||
size="sm"
|
||||
color="primary"
|
||||
@ -52,7 +57,8 @@ const Viewer: React.FC<Props> = (props) => {
|
||||
}}
|
||||
>
|
||||
保存
|
||||
</Button>}
|
||||
</Button>
|
||||
)}
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
@ -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 = () => {
|
||||
检查更新
|
||||
</Button>
|
||||
</SettingItem>
|
||||
<SettingItem
|
||||
title="重置软件"
|
||||
actions={
|
||||
<Tooltip content="删除所有配置,将软件恢复初始状态">
|
||||
<Button isIconOnly size="sm" variant="light">
|
||||
<IoIosHelpCircle className="text-lg" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
divider
|
||||
>
|
||||
<Button size="sm" onPress={resetAppConfig}>
|
||||
重置软件
|
||||
</Button>
|
||||
</SettingItem>
|
||||
<SettingItem
|
||||
title="创建堆快照"
|
||||
actions={
|
||||
|
||||
@ -1,31 +1,31 @@
|
||||
import { MD5 } from 'crypto-js';
|
||||
import { MD5 } from 'crypto-js'
|
||||
|
||||
export class HashType {
|
||||
private hashValue: string;
|
||||
private hashValue: string
|
||||
|
||||
constructor(hash: string) {
|
||||
this.hashValue = hash;
|
||||
this.hashValue = hash
|
||||
}
|
||||
|
||||
static makeHash(data: string): HashType {
|
||||
const hash = MD5(data).toString();
|
||||
return new HashType(hash);
|
||||
const hash = MD5(data).toString()
|
||||
return new HashType(hash)
|
||||
}
|
||||
|
||||
equal(hash: HashType): boolean {
|
||||
return this.hashValue === hash.hashValue;
|
||||
return this.hashValue === hash.hashValue
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.hashValue;
|
||||
return this.hashValue
|
||||
}
|
||||
|
||||
isValid(): boolean {
|
||||
return this.hashValue.length === 32;
|
||||
return this.hashValue.length === 32
|
||||
}
|
||||
}
|
||||
|
||||
export function getHash(name: string): string {
|
||||
const hash = HashType.makeHash(name);
|
||||
return hash.toString();
|
||||
const hash = HashType.makeHash(name)
|
||||
return hash.toString()
|
||||
}
|
||||
|
||||
@ -383,6 +383,10 @@ export async function openDevTools(): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('openDevTools'))
|
||||
}
|
||||
|
||||
export async function resetAppConfig(): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('resetAppConfig'))
|
||||
}
|
||||
|
||||
export async function createHeapSnapshot(): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('createHeapSnapshot'))
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user