proxy delay

This commit is contained in:
pompurin404 2024-08-03 16:37:22 +08:00
parent ba9c9ba84d
commit 0fc43b339d
No known key found for this signature in database
12 changed files with 244 additions and 23 deletions

View File

@ -27,6 +27,7 @@
"electron-updater": "^6.2.1",
"framer-motion": "^11.3.19",
"next-themes": "^0.3.0",
"pubsub-js": "^1.9.4",
"react-icons": "^5.2.1",
"react-monaco-editor": "^0.55.0",
"react-router-dom": "^6.25.1",
@ -40,6 +41,7 @@
"@electron-toolkit/eslint-config-ts": "^2.0.0",
"@electron-toolkit/tsconfig": "^1.0.1",
"@types/node": "^22.0.0",
"@types/pubsub-js": "^1.8.6",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/ws": "^8.5.12",

View File

@ -32,6 +32,9 @@ importers:
next-themes:
specifier: ^0.3.0
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
pubsub-js:
specifier: ^1.9.4
version: 1.9.4
react-icons:
specifier: ^5.2.1
version: 5.2.1(react@18.3.1)
@ -66,6 +69,9 @@ importers:
'@types/node':
specifier: ^22.0.0
version: 22.0.0
'@types/pubsub-js':
specifier: ^1.8.6
version: 1.8.6
'@types/react':
specifier: ^18.3.3
version: 18.3.3
@ -1795,6 +1801,9 @@ packages:
'@types/prop-types@15.7.12':
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
'@types/pubsub-js@1.8.6':
resolution: {integrity: sha512-Kwug5cwV0paUDm/NfwDx1sp9xI0bGIvmWJjJWCU8NngkCCMt3EIC7oPDvb6fV7BR8kPpFyyBu4D11bda/2MdPA==}
'@types/react-dom@18.3.0':
resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
@ -3585,6 +3594,9 @@ packages:
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
pubsub-js@1.9.4:
resolution: {integrity: sha512-hJYpaDvPH4w8ZX/0Fdf9ma1AwRgU353GfbaVfPjfJQf1KxZ2iHaHl3fAUw1qlJIR5dr4F3RzjGaWohYUEyoh7A==}
pump@3.0.0:
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
@ -6848,6 +6860,8 @@ snapshots:
'@types/prop-types@15.7.12': {}
'@types/pubsub-js@1.8.6': {}
'@types/react-dom@18.3.0':
dependencies:
'@types/react': 18.3.3
@ -8929,6 +8943,8 @@ snapshots:
proxy-from-env@1.1.0: {}
pubsub-js@1.9.4: {}
pump@3.0.0:
dependencies:
end-of-stream: 1.4.4

View File

@ -1,5 +1,5 @@
import axios, { AxiosInstance } from 'axios'
import { getControledMihomoConfig } from '../config'
import { getAppConfig, getControledMihomoConfig } from '../config'
import WebSocket from 'ws'
import { window } from '..'
@ -59,6 +59,19 @@ export const mihomoChangeProxy = async (group: string, proxy: string): Promise<I
return instance.put(`/proxies/${encodeURIComponent(group)}`, { name: proxy })
}
export const mihomoProxyDelay = async (proxy: string, url?: string): Promise<IMihomoDelay> => {
const appConfig = getAppConfig()
const { delayTestUrl, delayTestTimeout } = appConfig
const instance = await getAxios()
return instance.get(`/proxies/${encodeURIComponent(proxy)}/delay`, {
params: {
url: url || delayTestUrl || 'https://www.gstatic.com/generate_204',
timeout: delayTestTimeout || 5000
},
timeout: delayTestTimeout || 5000
})
}
export const startMihomoTraffic = (): void => {
mihomoTraffic()
}

View File

@ -4,6 +4,7 @@ import {
mihomoConfig,
mihomoConnections,
mihomoProxies,
mihomoProxyDelay,
mihomoRules,
mihomoVersion,
patchMihomoConfig,
@ -33,6 +34,7 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('mihomoRules', mihomoRules)
ipcMain.handle('mihomoProxies', () => mihomoProxies())
ipcMain.handle('mihomoChangeProxy', (_e, group, proxy) => mihomoChangeProxy(group, proxy))
ipcMain.handle('mihomoProxyDelay', (_e, proxy, url) => mihomoProxyDelay(proxy, url))
ipcMain.handle('startMihomoLogs', startMihomoLogs)
ipcMain.handle('stopMihomoLogs', () => stopMihomoLogs())
ipcMain.handle('patchMihomoConfig', async (_e, patch) => await patchMihomoConfig(patch))

View File

@ -1,14 +1,65 @@
import { Card, CardBody, Divider } from '@nextui-org/react'
import React from 'react'
import { Button, Card, CardBody, Divider } from '@nextui-org/react'
import { useAppConfig } from '@renderer/hooks/use-config'
import PubSub from 'pubsub-js'
import React, { useEffect, useState } from 'react'
interface Props {
onProxyDelay: (proxy: string) => Promise<IMihomoDelay>
proxyDisplayMode: 'simple' | 'full'
proxy: IMihomoProxy | IMihomoGroup
group: string
onSelect: (proxy: string) => void
selected: boolean
}
const ProxyItem: React.FC<Props> = (props) => {
const { proxy, selected, onSelect } = props
const { proxyDisplayMode, group, proxy, selected, onSelect, onProxyDelay } = props
const { appConfig } = useAppConfig()
const { delayTestTimeout = 5000 } = appConfig || {}
const [delay, setDelay] = useState(() => {
if (proxy.history.length > 0) {
return proxy.history[0].delay
}
return 0
})
const [loading, setLoading] = useState(false)
function delayColor(delay: number): 'primary' | 'success' | 'warning' | 'danger' {
if (delay < 0) return 'danger'
if (delay === 0) return 'primary'
if (delay < 500) return 'success'
if (delay < delayTestTimeout) return 'warning'
return 'danger'
}
function delayText(delay: number): string {
if (delay < 0) return 'Error'
if (delay === 0) return 'Delay'
if (delay < delayTestTimeout) return delay.toString()
return 'Timeout'
}
const onDelay = (): void => {
setLoading(true)
onProxyDelay(proxy.name).then(
(delay) => {
setDelay(delay.delay || delayTestTimeout + 1)
setLoading(false)
},
() => {
setDelay(-1)
setLoading(false)
}
)
}
useEffect(() => {
const token = PubSub.subscribe(`${group}-delay`, onDelay)
return (): void => {
PubSub.unsubscribe(token)
}
}, [])
return (
<>
<Divider />
@ -16,13 +67,26 @@ const ProxyItem: React.FC<Props> = (props) => {
onPress={() => onSelect(proxy.name)}
isPressable
fullWidth
className={`my-1 ${selected ? 'bg-primary' : ''}`}
className={`my-1 ${selected ? 'bg-primary/30' : ''}`}
radius="sm"
>
<CardBody className="p-1">
<div className="flex justify-between items-center">
<div>{proxy.name}</div>
<div className="mx-2 text-sm">{proxy.history.length > 0 && proxy.history[0].delay}</div>
<div>
<div className="inline">{proxy.name}</div>
{proxyDisplayMode === 'full' && (
<div className="inline ml-2 text-default-500">{proxy.type}</div>
)}
</div>
<Button
isLoading={loading}
color={delayColor(delay)}
onPress={onDelay}
variant="light"
className="h-full min-w-[50px] p-0 mx-2 text-sm hover:bg-content"
>
{delayText(delay)}
</Button>
</div>
</CardBody>
</Card>

View File

@ -3,13 +3,16 @@ import { Virtuoso } from 'react-virtuoso'
import ProxyItem from './proxy-item'
interface Props {
onProxyDelay: (proxy: string) => Promise<IMihomoDelay>
onChangeProxy: (proxy: string) => void
proxyDisplayMode: 'simple' | 'full'
proxies: (IMihomoProxy | IMihomoGroup)[]
group: string
now: string
}
const ProxyList: React.FC<Props> = (props) => {
const { onChangeProxy, proxies, now } = props
const { proxyDisplayMode, onProxyDelay, onChangeProxy, proxies, group, now } = props
return (
<Virtuoso
@ -18,8 +21,11 @@ const ProxyList: React.FC<Props> = (props) => {
increaseViewportBy={100}
itemContent={(index) => (
<ProxyItem
onProxyDelay={onProxyDelay}
onSelect={onChangeProxy}
proxy={proxies[index]}
group={group}
proxyDisplayMode={proxyDisplayMode}
selected={proxies[index].name === now}
/>
)}

View File

@ -1,11 +1,14 @@
import { Button, Card, CardBody, CardFooter } from '@nextui-org/react'
import { SiSpeedtest } from 'react-icons/si'
import { Button, Card, CardBody, CardFooter, Chip } from '@nextui-org/react'
import { mihomoProxies } from '@renderer/utils/ipc'
import { SiNginxproxymanager } from 'react-icons/si'
import { useLocation, useNavigate } from 'react-router-dom'
import useSWR from 'swr'
const ProxyCard: React.FC = () => {
const navigate = useNavigate()
const location = useLocation()
const match = location.pathname.includes('/proxies')
const { data: proxies = { proxies: {} } } = useSWR('mihomoProxies', mihomoProxies)
return (
<Card
fullWidth
@ -13,16 +16,38 @@ const ProxyCard: React.FC = () => {
isPressable
onPress={() => navigate('/proxies')}
>
<CardBody>
<div className="flex justify-between h-[32px]">
<h3 className="select-none text-md font-bold leading-[32px]"></h3>
<Button isIconOnly size="sm" variant="light" color="default">
<SiSpeedtest color="default" className="text-[20px]" />
<CardBody className="pb-1 pt-0 px-0">
<div className="flex justify-between">
<Button
isIconOnly
className="bg-transparent pointer-events-none"
variant="flat"
color="default"
>
<SiNginxproxymanager color="default" className="text-[20px]" />
</Button>
<Chip
classNames={
match
? {
base: 'border-foreground',
content: 'text-foreground'
}
: {
base: 'border-primary',
content: 'text-primary'
}
}
size="sm"
variant="bordered"
className="mr-3 mt-2"
>
{Object.keys(proxies.proxies).length ?? 0}
</Chip>
</div>
</CardBody>
<CardFooter className="pt-1">
<small></small>
<h3 className="select-none text-md font-bold"></h3>
</CardFooter>
</Card>
)

View File

@ -1,12 +1,18 @@
import { Accordion, AccordionItem, Avatar } from '@nextui-org/react'
import { Accordion, AccordionItem, Avatar, Button } from '@nextui-org/react'
import BasePage from '@renderer/components/base/base-page'
import ProxyList from '@renderer/components/proxies/proxy-list'
import { mihomoChangeProxy, mihomoProxies } from '@renderer/utils/ipc'
import { useAppConfig } from '@renderer/hooks/use-config'
import { MdOutlineSpeed } from 'react-icons/md'
import { mihomoChangeProxy, mihomoProxies, mihomoProxyDelay } from '@renderer/utils/ipc'
import { CgDetailsLess, CgDetailsMore } from 'react-icons/cg'
import { useEffect, useMemo } from 'react'
import PubSub from 'pubsub-js'
import useSWR from 'swr'
const Proxies: React.FC = () => {
const { data: proxies, mutate } = useSWR('mihomoProxies', mihomoProxies)
const { appConfig, patchAppConfig } = useAppConfig()
const { proxyDisplayMode = 'simple' } = appConfig || {}
const groups = useMemo(() => {
const groups: IMihomoGroup[] = []
@ -44,16 +50,60 @@ const Proxies: React.FC = () => {
})
}
const onProxyDelay = async (proxy: string, url?: string): Promise<IMihomoDelay> => {
return await mihomoProxyDelay(proxy, url)
}
useEffect(() => {}, [])
return (
<BasePage title="代理组">
<BasePage
title="代理组"
header={
<Button
size="sm"
isIconOnly
onPress={() => {
patchAppConfig({ proxyDisplayMode: proxyDisplayMode === 'simple' ? 'full' : 'simple' })
}}
>
{proxyDisplayMode === 'simple' ? (
<CgDetailsMore size={20} />
) : (
<CgDetailsLess size={20} />
)}
</Button>
}
>
<Accordion variant="splitted" className="p-2">
{groups.map((group) => {
return (
<AccordionItem
key={group.name}
title={group.name}
classNames={{ content: 'p-0' }}
title={
<div className="flex justify-between">
<div>{group.name}</div>
<Button
variant="light"
size="sm"
isIconOnly
onPress={() => {
PubSub.publish(`${group.name}-delay`)
}}
>
<MdOutlineSpeed className="text-lg text-default-500" />
</Button>
</div>
}
subtitle={
proxyDisplayMode === 'full' && (
<div>
{group.type}
&nbsp;
{group.now}
</div>
)
}
classNames={{ title: 'select-none', base: 'px-2', content: 'pt-2', trigger: 'py-2' }}
startContent={
group.icon.length > 0 ? (
<Avatar className="bg-transparent" size="sm" radius="sm" src={group.icon} />
@ -61,8 +111,11 @@ const Proxies: React.FC = () => {
}
>
<ProxyList
onProxyDelay={(proxy) => onProxyDelay(proxy, group.testUrl)}
onChangeProxy={(proxy) => onChangeProxy(group.name, proxy)}
proxyDisplayMode={proxyDisplayMode}
proxies={groupProxies[group.name]}
group={group.name}
now={group.now}
/>
</AccordionItem>

View File

@ -1,4 +1,4 @@
import { Button, Switch } from '@nextui-org/react'
import { Button, Input, Switch } from '@nextui-org/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'
@ -15,7 +15,7 @@ const Settings: React.FC = () => {
})
const { appConfig, patchAppConfig } = useAppConfig()
const { silentStart = false } = appConfig || {}
const { silentStart = false, delayTestUrl, delayTestTimeout } = appConfig || {}
return (
<BasePage
@ -57,6 +57,32 @@ const Settings: React.FC = () => {
/>
</SettingItem>
</SettingCard>
<SettingCard>
<SettingItem title="延迟测试地址" divider>
<Input
size="sm"
className="w-[60%]"
spellCheck={false}
value={delayTestUrl}
placeholder="默认https://www.gstatic.com/generate_204"
onValueChange={(v) => {
patchAppConfig({ delayTestUrl: v })
}}
></Input>
</SettingItem>
<SettingItem title="延迟测试超时时间">
<Input
type="number"
size="sm"
className="w-[60%]"
value={delayTestTimeout?.toString()}
placeholder="默认5000"
onValueChange={(v) => {
patchAppConfig({ delayTestTimeout: parseInt(v) })
}}
></Input>
</SettingItem>
</SettingCard>
</BasePage>
)
}

View File

@ -57,6 +57,7 @@ const Sysproxy: React.FC = () => {
size="sm"
className="w-[50%]"
value={values.host}
spellCheck={false}
placeholder="默认127.0.0.1若无特殊需求请勿修改"
onValueChange={(v) => {
setValues({ ...values, host: v })

View File

@ -22,6 +22,10 @@ export async function mihomoChangeProxy(group: string, proxy: string): Promise<I
return await window.electron.ipcRenderer.invoke('mihomoChangeProxy', group, proxy)
}
export async function mihomoProxyDelay(proxy: string, url?: string): Promise<IMihomoDelay> {
return await window.electron.ipcRenderer.invoke('mihomoProxyDelay', proxy, url)
}
export async function startMihomoLogs(): Promise<void> {
return await window.electron.ipcRenderer.invoke('startMihomoLogs')
}

View File

@ -77,6 +77,11 @@ interface IMihomoHistory {
delay: number
}
interface IMihomoDelay {
delay?: number
message?: string
}
interface IMihomoProxy {
alive: boolean
extra: Record<string, { alive: boolean; history: IMihomoHistory[] }>
@ -93,6 +98,7 @@ interface IMihomoGroup {
alive: boolean
all: string[]
extra: Record<string, { alive: boolean; history: IMihomoHistory[] }>
testUrl?: string
hidden: boolean
history: IMihomoHistory[]
icon: string
@ -118,9 +124,12 @@ interface ISysProxyConfig {
interface IAppConfig {
core: 'mihomo' | 'mihomo-alpha'
proxyDisplayMode: 'simple' | 'full'
silentStart: boolean
sysProxy: ISysProxyConfig
userAgent?: string
delayTestUrl?: string
delayTestTimeout?: number
}
interface IMihomoTunConfig {