mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-26 20:50: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/utils": "^3.0.0",
|
||||
"@mihomo-party/sysproxy": "^2.0.4",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.7.7",
|
||||
"chokidar": "^4.0.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"express": "^5.0.1",
|
||||
"iconv-lite": "^0.6.3",
|
||||
|
||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@ -17,6 +17,9 @@ importers:
|
||||
'@mihomo-party/sysproxy':
|
||||
specifier: ^2.0.4
|
||||
version: 2.0.4
|
||||
'@types/crypto-js':
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2
|
||||
adm-zip:
|
||||
specifier: ^0.5.16
|
||||
version: 0.5.16
|
||||
@ -26,6 +29,9 @@ importers:
|
||||
chokidar:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
crypto-js:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
@ -2013,6 +2019,9 @@ packages:
|
||||
'@types/connect@3.4.38':
|
||||
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':
|
||||
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
||||
|
||||
@ -2666,6 +2675,9 @@ packages:
|
||||
crypt@0.0.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||
engines: {node: '>=4'}
|
||||
@ -7904,6 +7916,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 22.9.0
|
||||
|
||||
'@types/crypto-js@4.2.2': {}
|
||||
|
||||
'@types/d3-array@3.2.1': {}
|
||||
|
||||
'@types/d3-color@3.1.3': {}
|
||||
@ -8694,6 +8708,8 @@ snapshots:
|
||||
|
||||
crypt@0.0.2: {}
|
||||
|
||||
crypto-js@4.2.0: {}
|
||||
|
||||
cssesc@3.0.0: {}
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
@ -5,6 +5,8 @@ export {
|
||||
getCurrentProfileItem,
|
||||
getProfileItem,
|
||||
getProfileConfig,
|
||||
getFileStr,
|
||||
setFileStr,
|
||||
setProfileConfig,
|
||||
addProfileItem,
|
||||
removeProfileItem,
|
||||
|
||||
@ -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'
|
||||
@ -220,3 +220,23 @@ function parseSubinfo(str: string): ISubscriptionUserInfo {
|
||||
})
|
||||
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)}`)
|
||||
}
|
||||
|
||||
export const mihomoRunProxyProviders = async (): Promise<IMihomoProxyProviders> => {
|
||||
const runtime = await getRuntimeConfig()
|
||||
return runtime?.['proxy-providers']
|
||||
}
|
||||
|
||||
export const mihomoRuleProviders = async (): Promise<IMihomoRuleProviders> => {
|
||||
const instance = await getAxios()
|
||||
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)}`)
|
||||
}
|
||||
|
||||
export const mihomoRunRuleProviders = async (): Promise<IMihomoRuleProviders> => {
|
||||
const runtime = await getRuntimeConfig()
|
||||
return runtime?.['rule-providers']
|
||||
}
|
||||
|
||||
export const mihomoChangeProxy = async (group: string, proxy: string): Promise<IMihomoProxy> => {
|
||||
const instance = await getAxios()
|
||||
return await instance.put(`/proxies/${encodeURIComponent(group)}`, { name: proxy })
|
||||
@ -194,9 +204,9 @@ const mihomoTraffic = async (): Promise<void> => {
|
||||
if (process.platform !== 'linux') {
|
||||
tray?.setToolTip(
|
||||
'↑' +
|
||||
`${calcTraffic(json.up)}/s`.padStart(9) +
|
||||
'\n↓' +
|
||||
`${calcTraffic(json.down)}/s`.padStart(9)
|
||||
`${calcTraffic(json.up)}/s`.padStart(9) +
|
||||
'\n↓' +
|
||||
`${calcTraffic(json.down)}/s`.padStart(9)
|
||||
)
|
||||
}
|
||||
floatingWindow?.webContents.send('mihomoTraffic', json)
|
||||
|
||||
@ -8,7 +8,9 @@ import {
|
||||
mihomoProxies,
|
||||
mihomoProxyDelay,
|
||||
mihomoProxyProviders,
|
||||
mihomoRunProxyProviders,
|
||||
mihomoRuleProviders,
|
||||
mihomoRunRuleProviders,
|
||||
mihomoRules,
|
||||
mihomoUnfixedProxy,
|
||||
mihomoUpdateProxyProviders,
|
||||
@ -31,6 +33,8 @@ import {
|
||||
removeProfileItem,
|
||||
changeCurrentProfile,
|
||||
getProfileStr,
|
||||
getFileStr,
|
||||
setFileStr,
|
||||
setProfileStr,
|
||||
updateProfileItem,
|
||||
setProfileConfig,
|
||||
@ -115,10 +119,12 @@ export function registerIpcMainHandlers(): void {
|
||||
ipcMain.handle('mihomoProxies', ipcErrorWrapper(mihomoProxies))
|
||||
ipcMain.handle('mihomoGroups', ipcErrorWrapper(mihomoGroups))
|
||||
ipcMain.handle('mihomoProxyProviders', ipcErrorWrapper(mihomoProxyProviders))
|
||||
ipcMain.handle('mihomoRunProxyProviders', ipcErrorWrapper(mihomoRunProxyProviders))
|
||||
ipcMain.handle('mihomoUpdateProxyProviders', (_e, name) =>
|
||||
ipcErrorWrapper(mihomoUpdateProxyProviders)(name)
|
||||
)
|
||||
ipcMain.handle('mihomoRuleProviders', ipcErrorWrapper(mihomoRuleProviders))
|
||||
ipcMain.handle('mihomoRunRuleProviders', ipcErrorWrapper(mihomoRunRuleProviders))
|
||||
ipcMain.handle('mihomoUpdateRuleProviders', (_e, name) =>
|
||||
ipcErrorWrapper(mihomoUpdateRuleProviders)(name)
|
||||
)
|
||||
@ -151,6 +157,8 @@ export function registerIpcMainHandlers(): void {
|
||||
ipcMain.handle('getCurrentProfileItem', ipcErrorWrapper(getCurrentProfileItem))
|
||||
ipcMain.handle('getProfileItem', (_e, id) => ipcErrorWrapper(getProfileItem)(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('updateProfileItem', (_e, item) => ipcErrorWrapper(updateProfileItem)(item))
|
||||
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 { nanoid } from 'nanoid'
|
||||
import React from 'react'
|
||||
type Language = 'yaml' | 'javascript' | 'css'
|
||||
type Language = 'yaml' | 'javascript' | 'css' | 'json' | 'text'
|
||||
|
||||
interface Props {
|
||||
value: string
|
||||
@ -125,9 +125,9 @@ export const BaseEditor: React.FC<Props> = (props) => {
|
||||
options={{
|
||||
tabSize: ['yaml', 'javascript', 'json'].includes(language) ? 2 : 4, // 根据语言类型设置缩进大小
|
||||
minimap: {
|
||||
enabled: document.documentElement.clientWidth >= 1500 // 超过一定宽度显示minimap滚动条
|
||||
enabled: document.documentElement.clientWidth >= 1500 // 超过一定宽度显示 minimap 滚动条
|
||||
},
|
||||
mouseWheelZoom: true, // 按住Ctrl滚轮调节缩放比例
|
||||
mouseWheelZoom: true, // 按住 Ctrl 滚轮调节缩放比例
|
||||
readOnly: readOnly, // 只读模式
|
||||
renderValidationDecorations: 'on', // 只读模式下显示校验信息
|
||||
quickSuggestions: {
|
||||
|
||||
@ -1,14 +1,42 @@
|
||||
import { mihomoProxyProviders, mihomoUpdateProxyProviders } from '@renderer/utils/ipc'
|
||||
import { Fragment, useMemo, useState } from 'react'
|
||||
import { mihomoProxyProviders, mihomoUpdateProxyProviders, mihomoRunProxyProviders } from '@renderer/utils/ipc'
|
||||
import { Fragment, useEffect, useMemo, useState } from 'react'
|
||||
import Viewer from './viewer'
|
||||
import useSWR from 'swr'
|
||||
import SettingCard from '../base/base-setting-card'
|
||||
import SettingItem from '../base/base-setting-item'
|
||||
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 { calcTraffic } from '@renderer/utils/calc'
|
||||
import { getHash } from '@renderer/utils/hash'
|
||||
|
||||
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 providers = useMemo(() => {
|
||||
if (!data) return []
|
||||
@ -45,6 +73,7 @@ const ProxyProvider: React.FC = () => {
|
||||
|
||||
return (
|
||||
<SettingCard>
|
||||
{ShowProvider && <Viewer onClose={() => { setShowProvider(false); setShowPath(''); setShowType('')}} path={ShowPath} type={ShowType} />}
|
||||
<SettingItem title="代理集合" divider>
|
||||
<Button
|
||||
size="sm"
|
||||
@ -58,56 +87,69 @@ const ProxyProvider: React.FC = () => {
|
||||
更新全部
|
||||
</Button>
|
||||
</SettingItem>
|
||||
{providers.map((provider, index) => {
|
||||
return (
|
||||
<Fragment key={provider.name}>
|
||||
{providers.map((provider, index) => (
|
||||
<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
|
||||
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>
|
||||
divider={index !== providers.length - 1}
|
||||
title={
|
||||
<div className="text-foreground-500">
|
||||
{`${calcTraffic(
|
||||
provider.subscriptionInfo.Upload + provider.subscriptionInfo.Download
|
||||
)} / ${calcTraffic(provider.subscriptionInfo.Total)}`}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="h-[32px] leading-[32px] text-foreground-500">
|
||||
{provider.subscriptionInfo.Expire
|
||||
? dayjs.unix(provider.subscriptionInfo.Expire).format('YYYY-MM-DD')
|
||||
: '长期有效'}
|
||||
</div>
|
||||
</SettingItem>
|
||||
{provider.subscriptionInfo && (
|
||||
<SettingItem
|
||||
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>
|
||||
)
|
||||
})}
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</SettingCard>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,13 +1,21 @@
|
||||
import { mihomoRuleProviders, mihomoUpdateRuleProviders } from '@renderer/utils/ipc'
|
||||
import { Fragment, useMemo, useState } from 'react'
|
||||
import { mihomoRuleProviders, mihomoUpdateRuleProviders, mihomoRunRuleProviders } from '@renderer/utils/ipc'
|
||||
import { getHash } from '@renderer/utils/hash'
|
||||
import Viewer from './viewer'
|
||||
import { Fragment, useEffect, useMemo, useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import SettingCard from '../base/base-setting-card'
|
||||
import SettingItem from '../base/base-setting-item'
|
||||
import { Button, Chip } from '@nextui-org/react'
|
||||
import { IoMdRefresh } from 'react-icons/io'
|
||||
import { CgLoadbarDoc } from 'react-icons/cg'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
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 providers = useMemo(() => {
|
||||
if (!data) return []
|
||||
@ -16,6 +24,26 @@ const RuleProvider: React.FC = () => {
|
||||
}, [data])
|
||||
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> => {
|
||||
setUpdating((prev) => {
|
||||
prev[index] = true
|
||||
@ -40,6 +68,12 @@ const RuleProvider: React.FC = () => {
|
||||
|
||||
return (
|
||||
<SettingCard>
|
||||
{ShowProvider && <Viewer
|
||||
path={ShowPath}
|
||||
type={ShowType}
|
||||
format={ShowFormat}
|
||||
onClose={() => { setShowProvider(false); setShowPath(''); setShowType('') }}
|
||||
/>}
|
||||
<SettingItem title="规则集合" divider>
|
||||
<Button
|
||||
size="sm"
|
||||
@ -53,44 +87,54 @@ const RuleProvider: React.FC = () => {
|
||||
更新全部
|
||||
</Button>
|
||||
</SettingItem>
|
||||
{providers.map((provider, index) => {
|
||||
return (
|
||||
<Fragment key={provider.name}>
|
||||
<SettingItem
|
||||
title={provider.name}
|
||||
actions={
|
||||
<Chip className="ml-2" size="sm">
|
||||
{provider.ruleCount}
|
||||
</Chip>
|
||||
}
|
||||
>
|
||||
{
|
||||
<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>
|
||||
}
|
||||
</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>
|
||||
)
|
||||
})}
|
||||
{providers.map((provider, index) => (
|
||||
<Fragment key={provider.name}>
|
||||
<SettingItem
|
||||
title={provider.name}
|
||||
actions={
|
||||
<Chip className="ml-2" size="sm">
|
||||
{provider.ruleCount}
|
||||
</Chip>
|
||||
}
|
||||
>
|
||||
<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>
|
||||
{provider.format !== "MrsRule" && (
|
||||
<Button
|
||||
isIconOnly
|
||||
className="ml-2"
|
||||
size="sm"
|
||||
onPress={() => {
|
||||
setShowType(provider.vehicleType)
|
||||
setShowFormat(provider.format)
|
||||
setShowPath(provider.name)
|
||||
}}
|
||||
>
|
||||
<CgLoadbarDoc className={`text-lg`} />
|
||||
</Button>
|
||||
)}
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
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> {
|
||||
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> {
|
||||
return ipcErrorWrapper(
|
||||
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))
|
||||
}
|
||||
|
||||
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> {
|
||||
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
|
||||
updatedAt: string
|
||||
vehicleType: string
|
||||
url: string
|
||||
path: string
|
||||
}
|
||||
|
||||
interface IMihomoProxyProviders {
|
||||
@ -201,6 +203,8 @@ interface IMihomoProxyProvider {
|
||||
testUrl?: string
|
||||
updatedAt?: string
|
||||
vehicleType: string
|
||||
url: string
|
||||
path: string
|
||||
}
|
||||
|
||||
interface ISysProxyConfig {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user