mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 05:00:30 +08:00
Add provider file view/edit
This commit is contained in:
parent
7b1fc24be4
commit
e2653170c0
@ -26,9 +26,11 @@
|
|||||||
"@electron-toolkit/preload": "^3.0.1",
|
"@electron-toolkit/preload": "^3.0.1",
|
||||||
"@electron-toolkit/utils": "^3.0.0",
|
"@electron-toolkit/utils": "^3.0.0",
|
||||||
"@mihomo-party/sysproxy": "^2.0.4",
|
"@mihomo-party/sysproxy": "^2.0.4",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"chokidar": "^4.0.1",
|
"chokidar": "^4.0.1",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"express": "^5.0.1",
|
"express": "^5.0.1",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
|
|||||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@ -17,6 +17,9 @@ importers:
|
|||||||
'@mihomo-party/sysproxy':
|
'@mihomo-party/sysproxy':
|
||||||
specifier: ^2.0.4
|
specifier: ^2.0.4
|
||||||
version: 2.0.4
|
version: 2.0.4
|
||||||
|
'@types/crypto-js':
|
||||||
|
specifier: ^4.2.2
|
||||||
|
version: 4.2.2
|
||||||
adm-zip:
|
adm-zip:
|
||||||
specifier: ^0.5.16
|
specifier: ^0.5.16
|
||||||
version: 0.5.16
|
version: 0.5.16
|
||||||
@ -26,6 +29,9 @@ importers:
|
|||||||
chokidar:
|
chokidar:
|
||||||
specifier: ^4.0.1
|
specifier: ^4.0.1
|
||||||
version: 4.0.1
|
version: 4.0.1
|
||||||
|
crypto-js:
|
||||||
|
specifier: ^4.2.0
|
||||||
|
version: 4.2.0
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.13
|
specifier: ^1.11.13
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
@ -2013,6 +2019,9 @@ packages:
|
|||||||
'@types/connect@3.4.38':
|
'@types/connect@3.4.38':
|
||||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||||
|
|
||||||
|
'@types/crypto-js@4.2.2':
|
||||||
|
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
|
||||||
|
|
||||||
'@types/d3-array@3.2.1':
|
'@types/d3-array@3.2.1':
|
||||||
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
||||||
|
|
||||||
@ -2666,6 +2675,9 @@ packages:
|
|||||||
crypt@0.0.2:
|
crypt@0.0.2:
|
||||||
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
|
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
|
||||||
|
|
||||||
|
crypto-js@4.2.0:
|
||||||
|
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||||
|
|
||||||
cssesc@3.0.0:
|
cssesc@3.0.0:
|
||||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -7904,6 +7916,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.9.0
|
'@types/node': 22.9.0
|
||||||
|
|
||||||
|
'@types/crypto-js@4.2.2': {}
|
||||||
|
|
||||||
'@types/d3-array@3.2.1': {}
|
'@types/d3-array@3.2.1': {}
|
||||||
|
|
||||||
'@types/d3-color@3.1.3': {}
|
'@types/d3-color@3.1.3': {}
|
||||||
@ -8694,6 +8708,8 @@ snapshots:
|
|||||||
|
|
||||||
crypt@0.0.2: {}
|
crypt@0.0.2: {}
|
||||||
|
|
||||||
|
crypto-js@4.2.0: {}
|
||||||
|
|
||||||
cssesc@3.0.0: {}
|
cssesc@3.0.0: {}
|
||||||
|
|
||||||
csstype@3.1.3: {}
|
csstype@3.1.3: {}
|
||||||
|
|||||||
@ -5,6 +5,8 @@ export {
|
|||||||
getCurrentProfileItem,
|
getCurrentProfileItem,
|
||||||
getProfileItem,
|
getProfileItem,
|
||||||
getProfileConfig,
|
getProfileConfig,
|
||||||
|
getFileStr,
|
||||||
|
setFileStr,
|
||||||
setProfileConfig,
|
setProfileConfig,
|
||||||
addProfileItem,
|
addProfileItem,
|
||||||
removeProfileItem,
|
removeProfileItem,
|
||||||
|
|||||||
@ -140,10 +140,10 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
|
|||||||
res = await axios.get(item.url, {
|
res = await axios.get(item.url, {
|
||||||
proxy: newItem.useProxy
|
proxy: newItem.useProxy
|
||||||
? {
|
? {
|
||||||
protocol: 'http',
|
protocol: 'http',
|
||||||
host: '127.0.0.1',
|
host: '127.0.0.1',
|
||||||
port: mixedPort
|
port: mixedPort
|
||||||
}
|
}
|
||||||
: false,
|
: false,
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': userAgent || 'clash.meta'
|
'User-Agent': userAgent || 'clash.meta'
|
||||||
@ -220,3 +220,23 @@ function parseSubinfo(str: string): ISubscriptionUserInfo {
|
|||||||
})
|
})
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isAbsolutePath(path: string): boolean {
|
||||||
|
return path.startsWith('/') || /^[a-zA-Z]:\\/.test(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFileStr(path: string): Promise<string> {
|
||||||
|
if (isAbsolutePath(path)) {
|
||||||
|
return await readFile(path, 'utf-8')
|
||||||
|
} else {
|
||||||
|
return await readFile(mihomoProfileWorkDir(path), 'utf-8')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setFileStr(path: string, content: string): Promise<void> {
|
||||||
|
if (isAbsolutePath(path)) {
|
||||||
|
await writeFile(path, content, 'utf-8')
|
||||||
|
} else {
|
||||||
|
await writeFile(mihomoProfileWorkDir(path), content, 'utf-8')
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -114,6 +114,11 @@ export const mihomoUpdateProxyProviders = async (name: string): Promise<void> =>
|
|||||||
return await instance.put(`/providers/proxies/${encodeURIComponent(name)}`)
|
return await instance.put(`/providers/proxies/${encodeURIComponent(name)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const mihomoRunProxyProviders = async (): Promise<IMihomoProxyProviders> => {
|
||||||
|
const runtime = await getRuntimeConfig()
|
||||||
|
return runtime?.['proxy-providers']
|
||||||
|
}
|
||||||
|
|
||||||
export const mihomoRuleProviders = async (): Promise<IMihomoRuleProviders> => {
|
export const mihomoRuleProviders = async (): Promise<IMihomoRuleProviders> => {
|
||||||
const instance = await getAxios()
|
const instance = await getAxios()
|
||||||
return await instance.get('/providers/rules')
|
return await instance.get('/providers/rules')
|
||||||
@ -124,6 +129,11 @@ export const mihomoUpdateRuleProviders = async (name: string): Promise<void> =>
|
|||||||
return await instance.put(`/providers/rules/${encodeURIComponent(name)}`)
|
return await instance.put(`/providers/rules/${encodeURIComponent(name)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const mihomoRunRuleProviders = async (): Promise<IMihomoRuleProviders> => {
|
||||||
|
const runtime = await getRuntimeConfig()
|
||||||
|
return runtime?.['rule-providers']
|
||||||
|
}
|
||||||
|
|
||||||
export const mihomoChangeProxy = async (group: string, proxy: string): Promise<IMihomoProxy> => {
|
export const mihomoChangeProxy = async (group: string, proxy: string): Promise<IMihomoProxy> => {
|
||||||
const instance = await getAxios()
|
const instance = await getAxios()
|
||||||
return await instance.put(`/proxies/${encodeURIComponent(group)}`, { name: proxy })
|
return await instance.put(`/proxies/${encodeURIComponent(group)}`, { name: proxy })
|
||||||
@ -194,9 +204,9 @@ const mihomoTraffic = async (): Promise<void> => {
|
|||||||
if (process.platform !== 'linux') {
|
if (process.platform !== 'linux') {
|
||||||
tray?.setToolTip(
|
tray?.setToolTip(
|
||||||
'↑' +
|
'↑' +
|
||||||
`${calcTraffic(json.up)}/s`.padStart(9) +
|
`${calcTraffic(json.up)}/s`.padStart(9) +
|
||||||
'\n↓' +
|
'\n↓' +
|
||||||
`${calcTraffic(json.down)}/s`.padStart(9)
|
`${calcTraffic(json.down)}/s`.padStart(9)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
floatingWindow?.webContents.send('mihomoTraffic', json)
|
floatingWindow?.webContents.send('mihomoTraffic', json)
|
||||||
|
|||||||
@ -8,7 +8,9 @@ import {
|
|||||||
mihomoProxies,
|
mihomoProxies,
|
||||||
mihomoProxyDelay,
|
mihomoProxyDelay,
|
||||||
mihomoProxyProviders,
|
mihomoProxyProviders,
|
||||||
|
mihomoRunProxyProviders,
|
||||||
mihomoRuleProviders,
|
mihomoRuleProviders,
|
||||||
|
mihomoRunRuleProviders,
|
||||||
mihomoRules,
|
mihomoRules,
|
||||||
mihomoUnfixedProxy,
|
mihomoUnfixedProxy,
|
||||||
mihomoUpdateProxyProviders,
|
mihomoUpdateProxyProviders,
|
||||||
@ -31,6 +33,8 @@ import {
|
|||||||
removeProfileItem,
|
removeProfileItem,
|
||||||
changeCurrentProfile,
|
changeCurrentProfile,
|
||||||
getProfileStr,
|
getProfileStr,
|
||||||
|
getFileStr,
|
||||||
|
setFileStr,
|
||||||
setProfileStr,
|
setProfileStr,
|
||||||
updateProfileItem,
|
updateProfileItem,
|
||||||
setProfileConfig,
|
setProfileConfig,
|
||||||
@ -115,10 +119,12 @@ export function registerIpcMainHandlers(): void {
|
|||||||
ipcMain.handle('mihomoProxies', ipcErrorWrapper(mihomoProxies))
|
ipcMain.handle('mihomoProxies', ipcErrorWrapper(mihomoProxies))
|
||||||
ipcMain.handle('mihomoGroups', ipcErrorWrapper(mihomoGroups))
|
ipcMain.handle('mihomoGroups', ipcErrorWrapper(mihomoGroups))
|
||||||
ipcMain.handle('mihomoProxyProviders', ipcErrorWrapper(mihomoProxyProviders))
|
ipcMain.handle('mihomoProxyProviders', ipcErrorWrapper(mihomoProxyProviders))
|
||||||
|
ipcMain.handle('mihomoRunProxyProviders', ipcErrorWrapper(mihomoRunProxyProviders))
|
||||||
ipcMain.handle('mihomoUpdateProxyProviders', (_e, name) =>
|
ipcMain.handle('mihomoUpdateProxyProviders', (_e, name) =>
|
||||||
ipcErrorWrapper(mihomoUpdateProxyProviders)(name)
|
ipcErrorWrapper(mihomoUpdateProxyProviders)(name)
|
||||||
)
|
)
|
||||||
ipcMain.handle('mihomoRuleProviders', ipcErrorWrapper(mihomoRuleProviders))
|
ipcMain.handle('mihomoRuleProviders', ipcErrorWrapper(mihomoRuleProviders))
|
||||||
|
ipcMain.handle('mihomoRunRuleProviders', ipcErrorWrapper(mihomoRunRuleProviders))
|
||||||
ipcMain.handle('mihomoUpdateRuleProviders', (_e, name) =>
|
ipcMain.handle('mihomoUpdateRuleProviders', (_e, name) =>
|
||||||
ipcErrorWrapper(mihomoUpdateRuleProviders)(name)
|
ipcErrorWrapper(mihomoUpdateRuleProviders)(name)
|
||||||
)
|
)
|
||||||
@ -151,6 +157,8 @@ export function registerIpcMainHandlers(): void {
|
|||||||
ipcMain.handle('getCurrentProfileItem', ipcErrorWrapper(getCurrentProfileItem))
|
ipcMain.handle('getCurrentProfileItem', ipcErrorWrapper(getCurrentProfileItem))
|
||||||
ipcMain.handle('getProfileItem', (_e, id) => ipcErrorWrapper(getProfileItem)(id))
|
ipcMain.handle('getProfileItem', (_e, id) => ipcErrorWrapper(getProfileItem)(id))
|
||||||
ipcMain.handle('getProfileStr', (_e, id) => ipcErrorWrapper(getProfileStr)(id))
|
ipcMain.handle('getProfileStr', (_e, id) => ipcErrorWrapper(getProfileStr)(id))
|
||||||
|
ipcMain.handle('getFileStr', (_e, path) => ipcErrorWrapper(getFileStr)(path))
|
||||||
|
ipcMain.handle('setFileStr', (_e, path, str) => ipcErrorWrapper(setFileStr)(path, str))
|
||||||
ipcMain.handle('setProfileStr', (_e, id, str) => ipcErrorWrapper(setProfileStr)(id, str))
|
ipcMain.handle('setProfileStr', (_e, id, str) => ipcErrorWrapper(setProfileStr)(id, str))
|
||||||
ipcMain.handle('updateProfileItem', (_e, item) => ipcErrorWrapper(updateProfileItem)(item))
|
ipcMain.handle('updateProfileItem', (_e, item) => ipcErrorWrapper(updateProfileItem)(item))
|
||||||
ipcMain.handle('changeCurrentProfile', (_e, id) => ipcErrorWrapper(changeCurrentProfile)(id))
|
ipcMain.handle('changeCurrentProfile', (_e, id) => ipcErrorWrapper(changeCurrentProfile)(id))
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import pac from 'types-pac/pac.d.ts?raw'
|
|||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import { nanoid } from 'nanoid'
|
import { nanoid } from 'nanoid'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
type Language = 'yaml' | 'javascript' | 'css'
|
type Language = 'yaml' | 'javascript' | 'css' | 'json' | 'text'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: string
|
value: string
|
||||||
@ -125,9 +125,9 @@ export const BaseEditor: React.FC<Props> = (props) => {
|
|||||||
options={{
|
options={{
|
||||||
tabSize: ['yaml', 'javascript', 'json'].includes(language) ? 2 : 4, // 根据语言类型设置缩进大小
|
tabSize: ['yaml', 'javascript', 'json'].includes(language) ? 2 : 4, // 根据语言类型设置缩进大小
|
||||||
minimap: {
|
minimap: {
|
||||||
enabled: document.documentElement.clientWidth >= 1500 // 超过一定宽度显示minimap滚动条
|
enabled: document.documentElement.clientWidth >= 1500 // 超过一定宽度显示 minimap 滚动条
|
||||||
},
|
},
|
||||||
mouseWheelZoom: true, // 按住Ctrl滚轮调节缩放比例
|
mouseWheelZoom: true, // 按住 Ctrl 滚轮调节缩放比例
|
||||||
readOnly: readOnly, // 只读模式
|
readOnly: readOnly, // 只读模式
|
||||||
renderValidationDecorations: 'on', // 只读模式下显示校验信息
|
renderValidationDecorations: 'on', // 只读模式下显示校验信息
|
||||||
quickSuggestions: {
|
quickSuggestions: {
|
||||||
|
|||||||
@ -1,14 +1,42 @@
|
|||||||
import { mihomoProxyProviders, mihomoUpdateProxyProviders } from '@renderer/utils/ipc'
|
import { mihomoProxyProviders, mihomoUpdateProxyProviders, mihomoRunProxyProviders } from '@renderer/utils/ipc'
|
||||||
import { Fragment, useMemo, useState } from 'react'
|
import { Fragment, useEffect, useMemo, useState } from 'react'
|
||||||
|
import Viewer from './viewer'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import SettingCard from '../base/base-setting-card'
|
import SettingCard from '../base/base-setting-card'
|
||||||
import SettingItem from '../base/base-setting-item'
|
import SettingItem from '../base/base-setting-item'
|
||||||
import { Button, Chip } from '@nextui-org/react'
|
import { Button, Chip } from '@nextui-org/react'
|
||||||
import { IoMdRefresh } from 'react-icons/io'
|
import { IoMdRefresh, IoMdEye } from 'react-icons/io'
|
||||||
|
import { CgLoadbarDoc } from 'react-icons/cg'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { calcTraffic } from '@renderer/utils/calc'
|
import { calcTraffic } from '@renderer/utils/calc'
|
||||||
|
import { getHash } from '@renderer/utils/hash'
|
||||||
|
|
||||||
const ProxyProvider: React.FC = () => {
|
const ProxyProvider: React.FC = () => {
|
||||||
|
const [ShowProvider, setShowProvider] = useState(false)
|
||||||
|
const [ShowPath, setShowPath] = useState('')
|
||||||
|
const [ShowType, setShowType] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchProviderPath = async (name: string) => {
|
||||||
|
try {
|
||||||
|
const providers = await mihomoRunProxyProviders()
|
||||||
|
const provider = providers[name]
|
||||||
|
if (provider?.path) {
|
||||||
|
setShowPath(provider.path)
|
||||||
|
} else if (provider?.url) {
|
||||||
|
setShowPath(`proxies/` + getHash(provider.url))
|
||||||
|
}
|
||||||
|
setShowProvider(true)
|
||||||
|
} catch (error) {
|
||||||
|
setShowPath('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ShowPath != '') {
|
||||||
|
fetchProviderPath(ShowPath)
|
||||||
|
}
|
||||||
|
}, [ShowProvider, ShowPath])
|
||||||
|
|
||||||
const { data, mutate } = useSWR('mihomoProxyProviders', mihomoProxyProviders)
|
const { data, mutate } = useSWR('mihomoProxyProviders', mihomoProxyProviders)
|
||||||
const providers = useMemo(() => {
|
const providers = useMemo(() => {
|
||||||
if (!data) return []
|
if (!data) return []
|
||||||
@ -45,6 +73,7 @@ const ProxyProvider: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingCard>
|
<SettingCard>
|
||||||
|
{ShowProvider && <Viewer onClose={() => { setShowProvider(false); setShowPath(''); setShowType('')}} path={ShowPath} type={ShowType} />}
|
||||||
<SettingItem title="代理集合" divider>
|
<SettingItem title="代理集合" divider>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -58,56 +87,69 @@ const ProxyProvider: React.FC = () => {
|
|||||||
更新全部
|
更新全部
|
||||||
</Button>
|
</Button>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
{providers.map((provider, index) => {
|
{providers.map((provider, index) => (
|
||||||
return (
|
<Fragment key={provider.name}>
|
||||||
<Fragment key={provider.name}>
|
<SettingItem
|
||||||
|
title={provider.name}
|
||||||
|
actions={
|
||||||
|
<Chip className="ml-2" size="sm">
|
||||||
|
{provider.proxies?.length || 0}
|
||||||
|
</Chip>
|
||||||
|
}
|
||||||
|
divider={!provider.subscriptionInfo && index !== providers.length - 1}
|
||||||
|
>
|
||||||
|
<div className="flex h-[32px] leading-[32px] text-foreground-500">
|
||||||
|
<div>{dayjs(provider.updatedAt).fromNow()}</div>
|
||||||
|
<Button
|
||||||
|
isIconOnly
|
||||||
|
className="ml-2"
|
||||||
|
size="sm"
|
||||||
|
onPress={() => {
|
||||||
|
onUpdate(provider.name, index)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IoMdRefresh className={`text-lg ${updating[index] ? 'animate-spin' : ''}`} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
isIconOnly
|
||||||
|
className="ml-2"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<IoMdEye className="text-lg" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
isIconOnly
|
||||||
|
className="ml-2"
|
||||||
|
size="sm"
|
||||||
|
onPress={() => {
|
||||||
|
setShowType(provider.vehicleType)
|
||||||
|
setShowPath(provider.name)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CgLoadbarDoc className="text-lg" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</SettingItem>
|
||||||
|
{provider.subscriptionInfo && (
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title={provider.name}
|
divider={index !== providers.length - 1}
|
||||||
actions={
|
title={
|
||||||
<Chip className="ml-2" size="sm">
|
<div className="text-foreground-500">
|
||||||
{provider.proxies?.length || 0}
|
{`${calcTraffic(
|
||||||
</Chip>
|
provider.subscriptionInfo.Upload + provider.subscriptionInfo.Download
|
||||||
}
|
)} / ${calcTraffic(provider.subscriptionInfo.Total)}`}
|
||||||
divider={!provider.subscriptionInfo && index !== providers.length - 1}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
<div className="flex h-[32px] leading-[32px] text-foreground-500">
|
|
||||||
<div>{dayjs(provider.updatedAt).fromNow()}</div>
|
|
||||||
<Button
|
|
||||||
isIconOnly
|
|
||||||
className="ml-2"
|
|
||||||
size="sm"
|
|
||||||
onPress={() => {
|
|
||||||
onUpdate(provider.name, index)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IoMdRefresh className={`text-lg ${updating[index] ? 'animate-spin' : ''}`} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
<div className="h-[32px] leading-[32px] text-foreground-500">
|
||||||
|
{provider.subscriptionInfo.Expire
|
||||||
|
? dayjs.unix(provider.subscriptionInfo.Expire).format('YYYY-MM-DD')
|
||||||
|
: '长期有效'}
|
||||||
|
</div>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
{provider.subscriptionInfo && (
|
)}
|
||||||
<SettingItem
|
</Fragment>
|
||||||
divider={index !== providers.length - 1}
|
))}
|
||||||
title={
|
|
||||||
<div className="text-foreground-500">{`${calcTraffic(
|
|
||||||
provider.subscriptionInfo.Upload + provider.subscriptionInfo.Download
|
|
||||||
)}
|
|
||||||
/${calcTraffic(provider.subscriptionInfo.Total)}`}</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{provider.subscriptionInfo && (
|
|
||||||
<div className="h-[32px] leading-[32px] text-foreground-500">
|
|
||||||
{provider.subscriptionInfo.Expire
|
|
||||||
? dayjs.unix(provider.subscriptionInfo.Expire).format('YYYY-MM-DD')
|
|
||||||
: '长期有效'}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</SettingItem>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</SettingCard>
|
</SettingCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,21 @@
|
|||||||
import { mihomoRuleProviders, mihomoUpdateRuleProviders } from '@renderer/utils/ipc'
|
import { mihomoRuleProviders, mihomoUpdateRuleProviders, mihomoRunRuleProviders } from '@renderer/utils/ipc'
|
||||||
import { Fragment, useMemo, useState } from 'react'
|
import { getHash } from '@renderer/utils/hash'
|
||||||
|
import Viewer from './viewer'
|
||||||
|
import { Fragment, useEffect, useMemo, useState } from 'react'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import SettingCard from '../base/base-setting-card'
|
import SettingCard from '../base/base-setting-card'
|
||||||
import SettingItem from '../base/base-setting-item'
|
import SettingItem from '../base/base-setting-item'
|
||||||
import { Button, Chip } from '@nextui-org/react'
|
import { Button, Chip } from '@nextui-org/react'
|
||||||
import { IoMdRefresh } from 'react-icons/io'
|
import { IoMdRefresh } from 'react-icons/io'
|
||||||
|
import { CgLoadbarDoc } from 'react-icons/cg'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
const RuleProvider: React.FC = () => {
|
const RuleProvider: React.FC = () => {
|
||||||
|
const [ShowProvider, setShowProvider] = useState(false)
|
||||||
|
const [ShowPath, setShowPath] = useState('')
|
||||||
|
const [ShowType, setShowType] = useState('')
|
||||||
|
const [ShowFormat, setShowFormat] = useState('')
|
||||||
|
|
||||||
const { data, mutate } = useSWR('mihomoRuleProviders', mihomoRuleProviders)
|
const { data, mutate } = useSWR('mihomoRuleProviders', mihomoRuleProviders)
|
||||||
const providers = useMemo(() => {
|
const providers = useMemo(() => {
|
||||||
if (!data) return []
|
if (!data) return []
|
||||||
@ -16,6 +24,26 @@ const RuleProvider: React.FC = () => {
|
|||||||
}, [data])
|
}, [data])
|
||||||
const [updating, setUpdating] = useState(Array(providers.length).fill(false))
|
const [updating, setUpdating] = useState(Array(providers.length).fill(false))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchProviderPath = async (name: string) => {
|
||||||
|
try {
|
||||||
|
const providers = await mihomoRunRuleProviders()
|
||||||
|
const provider = providers[name]
|
||||||
|
if (provider?.path) {
|
||||||
|
setShowPath(provider.path)
|
||||||
|
} else if (provider?.url) {
|
||||||
|
setShowPath(`rules/` + getHash(provider.url))
|
||||||
|
}
|
||||||
|
setShowProvider(true)
|
||||||
|
} catch (error) {
|
||||||
|
setShowPath('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ShowPath != '') {
|
||||||
|
fetchProviderPath(ShowPath)
|
||||||
|
}
|
||||||
|
}, [ShowProvider, ShowPath])
|
||||||
|
|
||||||
const onUpdate = async (name: string, index: number): Promise<void> => {
|
const onUpdate = async (name: string, index: number): Promise<void> => {
|
||||||
setUpdating((prev) => {
|
setUpdating((prev) => {
|
||||||
prev[index] = true
|
prev[index] = true
|
||||||
@ -40,6 +68,12 @@ const RuleProvider: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingCard>
|
<SettingCard>
|
||||||
|
{ShowProvider && <Viewer
|
||||||
|
path={ShowPath}
|
||||||
|
type={ShowType}
|
||||||
|
format={ShowFormat}
|
||||||
|
onClose={() => { setShowProvider(false); setShowPath(''); setShowType('') }}
|
||||||
|
/>}
|
||||||
<SettingItem title="规则集合" divider>
|
<SettingItem title="规则集合" divider>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -53,44 +87,54 @@ const RuleProvider: React.FC = () => {
|
|||||||
更新全部
|
更新全部
|
||||||
</Button>
|
</Button>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
{providers.map((provider, index) => {
|
{providers.map((provider, index) => (
|
||||||
return (
|
<Fragment key={provider.name}>
|
||||||
<Fragment key={provider.name}>
|
<SettingItem
|
||||||
<SettingItem
|
title={provider.name}
|
||||||
title={provider.name}
|
actions={
|
||||||
actions={
|
<Chip className="ml-2" size="sm">
|
||||||
<Chip className="ml-2" size="sm">
|
{provider.ruleCount}
|
||||||
{provider.ruleCount}
|
</Chip>
|
||||||
</Chip>
|
}
|
||||||
}
|
>
|
||||||
>
|
<div className="flex h-[32px] leading-[32px] text-foreground-500">
|
||||||
{
|
<div>{dayjs(provider.updatedAt).fromNow()}</div>
|
||||||
<div className="flex h-[32px] leading-[32px] text-foreground-500">
|
<Button
|
||||||
<div>{dayjs(provider.updatedAt).fromNow()}</div>
|
isIconOnly
|
||||||
<Button
|
className="ml-2"
|
||||||
isIconOnly
|
size="sm"
|
||||||
className="ml-2"
|
onPress={() => {
|
||||||
size="sm"
|
onUpdate(provider.name, index)
|
||||||
onPress={() => {
|
}}
|
||||||
onUpdate(provider.name, index)
|
>
|
||||||
}}
|
<IoMdRefresh className={`text-lg ${updating[index] ? 'animate-spin' : ''}`} />
|
||||||
>
|
</Button>
|
||||||
<IoMdRefresh className={`text-lg ${updating[index] ? 'animate-spin' : ''}`} />
|
{provider.format !== "MrsRule" && (
|
||||||
</Button>
|
<Button
|
||||||
</div>
|
isIconOnly
|
||||||
}
|
className="ml-2"
|
||||||
</SettingItem>
|
size="sm"
|
||||||
<SettingItem
|
onPress={() => {
|
||||||
title={<div className="text-foreground-500">{provider.format}</div>}
|
setShowType(provider.vehicleType)
|
||||||
divider={index !== providers.length - 1}
|
setShowFormat(provider.format)
|
||||||
>
|
setShowPath(provider.name)
|
||||||
<div className="h-[32px] leading-[32px] text-foreground-500">
|
}}
|
||||||
{provider.vehicleType}::{provider.behavior}
|
>
|
||||||
</div>
|
<CgLoadbarDoc className={`text-lg`} />
|
||||||
</SettingItem>
|
</Button>
|
||||||
</Fragment>
|
)}
|
||||||
)
|
</div>
|
||||||
})}
|
</SettingItem>
|
||||||
|
<SettingItem
|
||||||
|
title={<div className="text-foreground-500">{provider.format}</div>}
|
||||||
|
divider={index !== providers.length - 1}
|
||||||
|
>
|
||||||
|
<div className="h-[32px] leading-[32px] text-foreground-500">
|
||||||
|
{provider.vehicleType}::{provider.behavior}
|
||||||
|
</div>
|
||||||
|
</SettingItem>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
</SettingCard>
|
</SettingCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/renderer/src/components/resources/viewer.tsx
Normal file
62
src/renderer/src/components/resources/viewer.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { BaseEditor } from '../base/base-editor'
|
||||||
|
import { getFileStr, setFileStr } from '@renderer/utils/ipc'
|
||||||
|
type Language = 'yaml' | 'javascript' | 'css' | 'json' | 'text'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onClose: () => void
|
||||||
|
path: string
|
||||||
|
type: string
|
||||||
|
format?: string
|
||||||
|
}
|
||||||
|
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 getContent = async (): Promise<void> => {
|
||||||
|
setCurrData(await getFileStr(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getContent()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
backdrop="blur"
|
||||||
|
classNames={{ backdrop: 'top-[48px]' }}
|
||||||
|
size="5xl"
|
||||||
|
hideCloseButton
|
||||||
|
isOpen={true}
|
||||||
|
onOpenChange={onClose}
|
||||||
|
scrollBehavior="inside"
|
||||||
|
>
|
||||||
|
<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)}/>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter className="pt-0">
|
||||||
|
<Button size="sm" variant="light" onPress={onClose}>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
{type == 'File' &&
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
color="primary"
|
||||||
|
onPress={async () => {
|
||||||
|
await setFileStr(path, currData)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>}
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Viewer
|
||||||
31
src/renderer/src/utils/hash.ts
Normal file
31
src/renderer/src/utils/hash.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { MD5 } from 'crypto-js';
|
||||||
|
|
||||||
|
export class HashType {
|
||||||
|
private hashValue: string;
|
||||||
|
|
||||||
|
constructor(hash: string) {
|
||||||
|
this.hashValue = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
static makeHash(data: string): HashType {
|
||||||
|
const hash = MD5(data).toString();
|
||||||
|
return new HashType(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
equal(hash: HashType): boolean {
|
||||||
|
return this.hashValue === hash.hashValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return this.hashValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid(): boolean {
|
||||||
|
return this.hashValue.length === 32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHash(name: string): string {
|
||||||
|
const hash = HashType.makeHash(name);
|
||||||
|
return hash.toString();
|
||||||
|
}
|
||||||
@ -43,6 +43,10 @@ export async function mihomoUpdateProxyProviders(name: string): Promise<void> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function mihomoRunProxyProviders(): Promise<IMihomoRuleProviders> {
|
||||||
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoRunProxyProviders'))
|
||||||
|
}
|
||||||
|
|
||||||
export async function mihomoRuleProviders(): Promise<IMihomoRuleProviders> {
|
export async function mihomoRuleProviders(): Promise<IMihomoRuleProviders> {
|
||||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoRuleProviders'))
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoRuleProviders'))
|
||||||
}
|
}
|
||||||
@ -53,6 +57,10 @@ export async function mihomoUpdateRuleProviders(name: string): Promise<void> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function mihomoRunRuleProviders(): Promise<IMihomoRuleProviders> {
|
||||||
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoRunRuleProviders'))
|
||||||
|
}
|
||||||
|
|
||||||
export async function mihomoChangeProxy(group: string, proxy: string): Promise<IMihomoProxy> {
|
export async function mihomoChangeProxy(group: string, proxy: string): Promise<IMihomoProxy> {
|
||||||
return ipcErrorWrapper(
|
return ipcErrorWrapper(
|
||||||
await window.electron.ipcRenderer.invoke('mihomoChangeProxy', group, proxy)
|
await window.electron.ipcRenderer.invoke('mihomoChangeProxy', group, proxy)
|
||||||
@ -151,6 +159,14 @@ export async function getProfileStr(id: string): Promise<string> {
|
|||||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getProfileStr', id))
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getProfileStr', id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getFileStr(id: string): Promise<string> {
|
||||||
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getFileStr', id))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setFileStr(id: string, str: string): Promise<void> {
|
||||||
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setFileStr', id, str))
|
||||||
|
}
|
||||||
|
|
||||||
export async function setProfileStr(id: string, str: string): Promise<void> {
|
export async function setProfileStr(id: string, str: string): Promise<void> {
|
||||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setProfileStr', id, str))
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setProfileStr', id, str))
|
||||||
}
|
}
|
||||||
|
|||||||
4
src/shared/types.d.ts
vendored
4
src/shared/types.d.ts
vendored
@ -179,6 +179,8 @@ interface IMihomoRuleProvider {
|
|||||||
type: string
|
type: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
vehicleType: string
|
vehicleType: string
|
||||||
|
url: string
|
||||||
|
path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMihomoProxyProviders {
|
interface IMihomoProxyProviders {
|
||||||
@ -201,6 +203,8 @@ interface IMihomoProxyProvider {
|
|||||||
testUrl?: string
|
testUrl?: string
|
||||||
updatedAt?: string
|
updatedAt?: string
|
||||||
vehicleType: string
|
vehicleType: string
|
||||||
|
url: string
|
||||||
|
path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISysProxyConfig {
|
interface ISysProxyConfig {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user