proxies page

This commit is contained in:
pompurin404 2024-08-03 12:20:47 +08:00
parent deeaa5933d
commit ba9c9ba84d
No known key found for this signature in database
10 changed files with 216 additions and 2 deletions

View File

@ -30,6 +30,7 @@
"react-icons": "^5.2.1", "react-icons": "^5.2.1",
"react-monaco-editor": "^0.55.0", "react-monaco-editor": "^0.55.0",
"react-router-dom": "^6.25.1", "react-router-dom": "^6.25.1",
"react-virtuoso": "^4.9.0",
"swr": "^2.2.5", "swr": "^2.2.5",
"ws": "^8.18.0", "ws": "^8.18.0",
"yaml": "^2.5.0" "yaml": "^2.5.0"

View File

@ -41,6 +41,9 @@ importers:
react-router-dom: react-router-dom:
specifier: ^6.25.1 specifier: ^6.25.1
version: 6.25.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 6.25.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-virtuoso:
specifier: ^4.9.0
version: 4.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
swr: swr:
specifier: ^2.2.5 specifier: ^2.2.5
version: 2.2.5(react@18.3.1) version: 2.2.5(react@18.3.1)
@ -3669,6 +3672,13 @@ packages:
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-virtuoso@4.9.0:
resolution: {integrity: sha512-MiiSGKqvYPfAK3FUe852n2L3M5IXMKP0pUgYQ/UTk90A/l2UNQOvaEUvAZp+0ytL0kOCNk8i8/J8FMKvIq7kqg==}
engines: {node: '>=10'}
peerDependencies:
react: '>=16 || >=17 || >= 18'
react-dom: '>=16 || >=17 || >= 18'
react@18.3.1: react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -9000,6 +9010,11 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- '@types/react' - '@types/react'
react-virtuoso@4.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react@18.3.1: react@18.3.1:
dependencies: dependencies:
loose-envify: 1.4.0 loose-envify: 1.4.0

View File

@ -49,6 +49,16 @@ export const mihomoRules = async (): Promise<IMihomoRulesInfo> => {
return instance.get('/rules') as Promise<IMihomoRulesInfo> return instance.get('/rules') as Promise<IMihomoRulesInfo>
} }
export const mihomoProxies = async (): Promise<IMihomoProxies> => {
const instance = await getAxios()
return instance.get('/proxies') as Promise<IMihomoProxies>
}
export const mihomoChangeProxy = async (group: string, proxy: string): Promise<IMihomoProxy> => {
const instance = await getAxios()
return instance.put(`/proxies/${encodeURIComponent(group)}`, { name: proxy })
}
export const startMihomoTraffic = (): void => { export const startMihomoTraffic = (): void => {
mihomoTraffic() mihomoTraffic()
} }

View File

@ -1,7 +1,9 @@
import { ipcMain } from 'electron' import { ipcMain } from 'electron'
import { import {
mihomoChangeProxy,
mihomoConfig, mihomoConfig,
mihomoConnections, mihomoConnections,
mihomoProxies,
mihomoRules, mihomoRules,
mihomoVersion, mihomoVersion,
patchMihomoConfig, patchMihomoConfig,
@ -29,6 +31,8 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('mihomoConfig', mihomoConfig) ipcMain.handle('mihomoConfig', mihomoConfig)
ipcMain.handle('mihomoConnections', mihomoConnections) ipcMain.handle('mihomoConnections', mihomoConnections)
ipcMain.handle('mihomoRules', mihomoRules) ipcMain.handle('mihomoRules', mihomoRules)
ipcMain.handle('mihomoProxies', () => mihomoProxies())
ipcMain.handle('mihomoChangeProxy', (_e, group, proxy) => mihomoChangeProxy(group, proxy))
ipcMain.handle('startMihomoLogs', startMihomoLogs) ipcMain.handle('startMihomoLogs', startMihomoLogs)
ipcMain.handle('stopMihomoLogs', () => stopMihomoLogs()) ipcMain.handle('stopMihomoLogs', () => stopMihomoLogs())
ipcMain.handle('patchMihomoConfig', async (_e, patch) => await patchMihomoConfig(patch)) ipcMain.handle('patchMihomoConfig', async (_e, patch) => await patchMihomoConfig(patch))

View File

@ -6,7 +6,7 @@
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta <meta
http-equiv="Content-Security-Policy" http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src *"
/> />
</head> </head>

View File

@ -0,0 +1,33 @@
import { Card, CardBody, Divider } from '@nextui-org/react'
import React from 'react'
interface Props {
proxy: IMihomoProxy | IMihomoGroup
onSelect: (proxy: string) => void
selected: boolean
}
const ProxyItem: React.FC<Props> = (props) => {
const { proxy, selected, onSelect } = props
return (
<>
<Divider />
<Card
onPress={() => onSelect(proxy.name)}
isPressable
fullWidth
className={`my-1 ${selected ? 'bg-primary' : ''}`}
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>
</CardBody>
</Card>
</>
)
}
export default ProxyItem

View File

@ -0,0 +1,30 @@
import React from 'react'
import { Virtuoso } from 'react-virtuoso'
import ProxyItem from './proxy-item'
interface Props {
onChangeProxy: (proxy: string) => void
proxies: (IMihomoProxy | IMihomoGroup)[]
now: string
}
const ProxyList: React.FC<Props> = (props) => {
const { onChangeProxy, proxies, now } = props
return (
<Virtuoso
style={{ height: `min(calc(100vh - 200px), ${proxies.length * 44}px)` }}
totalCount={proxies.length}
increaseViewportBy={100}
itemContent={(index) => (
<ProxyItem
onSelect={onChangeProxy}
proxy={proxies[index]}
selected={proxies[index].name === now}
/>
)}
/>
)
}
export default ProxyList

View File

@ -1,7 +1,80 @@
import { Accordion, AccordionItem, Avatar } from '@nextui-org/react'
import BasePage from '@renderer/components/base/base-page' import BasePage from '@renderer/components/base/base-page'
import ProxyList from '@renderer/components/proxies/proxy-list'
import { mihomoChangeProxy, mihomoProxies } from '@renderer/utils/ipc'
import { useEffect, useMemo } from 'react'
import useSWR from 'swr'
const Proxies: React.FC = () => { const Proxies: React.FC = () => {
return <BasePage title="代理组"></BasePage> const { data: proxies, mutate } = useSWR('mihomoProxies', mihomoProxies)
const groups = useMemo(() => {
const groups: IMihomoGroup[] = []
if (proxies) {
const globalGroup = proxies.proxies['GLOBAL'] as IMihomoGroup
for (const global of globalGroup.all) {
if (isGroup(proxies.proxies[global])) {
groups.push(proxies.proxies[global] as IMihomoGroup)
}
}
Object.keys(proxies.proxies).forEach((key) => {
if (isGroup(proxies.proxies[key])) {
if (!groups.find((group) => group.name === key)) {
groups.push(proxies.proxies[key] as IMihomoGroup)
}
}
})
}
return groups
}, [proxies])
const groupProxies = useMemo(() => {
const groupProxies: Record<string, (IMihomoProxy | IMihomoGroup)[]> = {}
if (proxies) {
for (const group of groups) {
groupProxies[group.name] = group.all.map((name) => proxies.proxies[name])
}
}
return groupProxies
}, [proxies])
const onChangeProxy = (group: string, proxy: string): void => {
mihomoChangeProxy(group, proxy).then(() => {
mutate()
})
}
useEffect(() => {}, [])
return (
<BasePage title="代理组">
<Accordion variant="splitted" className="p-2">
{groups.map((group) => {
return (
<AccordionItem
key={group.name}
title={group.name}
classNames={{ content: 'p-0' }}
startContent={
group.icon.length > 0 ? (
<Avatar className="bg-transparent" size="sm" radius="sm" src={group.icon} />
) : null
}
>
<ProxyList
onChangeProxy={(proxy) => onChangeProxy(group.name, proxy)}
proxies={groupProxies[group.name]}
now={group.now}
/>
</AccordionItem>
)
})}
</Accordion>
</BasePage>
)
}
function isGroup(proxy: IMihomoProxy | IMihomoGroup): proxy is IMihomoGroup {
return 'all' in proxy
} }
export default Proxies export default Proxies

View File

@ -13,6 +13,15 @@ export async function mihomoConnections(): Promise<IMihomoConnectionsInfo> {
export async function mihomoRules(): Promise<IMihomoRulesInfo> { export async function mihomoRules(): Promise<IMihomoRulesInfo> {
return await window.electron.ipcRenderer.invoke('mihomoRules') return await window.electron.ipcRenderer.invoke('mihomoRules')
} }
export async function mihomoProxies(): Promise<IMihomoProxies> {
return await window.electron.ipcRenderer.invoke('mihomoProxies')
}
export async function mihomoChangeProxy(group: string, proxy: string): Promise<IMihomoProxy> {
return await window.electron.ipcRenderer.invoke('mihomoChangeProxy', group, proxy)
}
export async function startMihomoLogs(): Promise<void> { export async function startMihomoLogs(): Promise<void> {
return await window.electron.ipcRenderer.invoke('startMihomoLogs') return await window.electron.ipcRenderer.invoke('startMihomoLogs')
} }

39
src/shared/types.d.ts vendored
View File

@ -1,6 +1,9 @@
type OutboundMode = 'rule' | 'global' | 'direct' type OutboundMode = 'rule' | 'global' | 'direct'
type LogLevel = 'info' | 'debug' | 'warning' | 'error' | 'silent' type LogLevel = 'info' | 'debug' | 'warning' | 'error' | 'silent'
type SysProxyMode = 'auto' | 'manual' type SysProxyMode = 'auto' | 'manual'
type MihomoGroupType = 'Selector'
type MihomoProxyType = 'Shadowsocks'
interface IMihomoVersion { interface IMihomoVersion {
version: string version: string
meta: boolean meta: boolean
@ -69,6 +72,42 @@ interface IMihomoConnectionDetail {
rulePayload: string rulePayload: string
} }
interface IMihomoHistory {
time: string
delay: number
}
interface IMihomoProxy {
alive: boolean
extra: Record<string, { alive: boolean; history: IMihomoHistory[] }>
history: IMihomoHistory[]
id: string
name: string
tfo: boolean
type: MihomoProxyType
udp: boolean
xudp: boolean
}
interface IMihomoGroup {
alive: boolean
all: string[]
extra: Record<string, { alive: boolean; history: IMihomoHistory[] }>
hidden: boolean
history: IMihomoHistory[]
icon: string
name: string
now: string
tfo: boolean
type: MihomoGroupType
udp: boolean
xudp: boolean
}
interface IMihomoProxies {
proxies: Record<string, IMihomoProxy | IMihomoGroup>
}
interface ISysProxyConfig { interface ISysProxyConfig {
enable: boolean enable: boolean
host?: string host?: string