mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2026-02-10 11:40:28 +08:00
feat: add rule statistics and disable toggle
This commit is contained in:
parent
1c17cfb683
commit
d2f700a0ef
@ -85,6 +85,11 @@ export const mihomoRules = async (): Promise<IMihomoRulesInfo> => {
|
|||||||
return await instance.get('/rules')
|
return await instance.get('/rules')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const mihomoRulesDisable = async (rules: Record<string, boolean>): Promise<void> => {
|
||||||
|
const instance = await getAxios()
|
||||||
|
return await instance.patch('/rules/disable', rules)
|
||||||
|
}
|
||||||
|
|
||||||
export const mihomoProxies = async (): Promise<IMihomoProxies> => {
|
export const mihomoProxies = async (): Promise<IMihomoProxies> => {
|
||||||
const instance = await getAxios()
|
const instance = await getAxios()
|
||||||
const proxies = (await instance.get('/proxies')) as IMihomoProxies
|
const proxies = (await instance.get('/proxies')) as IMihomoProxies
|
||||||
|
|||||||
@ -24,7 +24,8 @@ import {
|
|||||||
mihomoVersion,
|
mihomoVersion,
|
||||||
patchMihomoConfig,
|
patchMihomoConfig,
|
||||||
mihomoSmartGroupWeights,
|
mihomoSmartGroupWeights,
|
||||||
mihomoSmartFlushCache
|
mihomoSmartFlushCache,
|
||||||
|
mihomoRulesDisable
|
||||||
} from '../core/mihomoApi'
|
} from '../core/mihomoApi'
|
||||||
import { checkAutoRun, disableAutoRun, enableAutoRun } from '../sys/autoRun'
|
import { checkAutoRun, disableAutoRun, enableAutoRun } from '../sys/autoRun'
|
||||||
import {
|
import {
|
||||||
@ -206,6 +207,7 @@ const asyncHandlers: Record<string, AsyncFn> = {
|
|||||||
mihomoCloseConnection,
|
mihomoCloseConnection,
|
||||||
mihomoCloseAllConnections,
|
mihomoCloseAllConnections,
|
||||||
mihomoRules,
|
mihomoRules,
|
||||||
|
mihomoRulesDisable,
|
||||||
mihomoProxies,
|
mihomoProxies,
|
||||||
mihomoGroups,
|
mihomoGroups,
|
||||||
mihomoProxyProviders,
|
mihomoProxyProviders,
|
||||||
|
|||||||
@ -7,6 +7,7 @@ const validInvokeChannels = [
|
|||||||
'mihomoCloseConnection',
|
'mihomoCloseConnection',
|
||||||
'mihomoCloseAllConnections',
|
'mihomoCloseAllConnections',
|
||||||
'mihomoRules',
|
'mihomoRules',
|
||||||
|
'mihomoRulesDisable',
|
||||||
'mihomoProxies',
|
'mihomoProxies',
|
||||||
'mihomoGroups',
|
'mihomoGroups',
|
||||||
'mihomoProxyProviders',
|
'mihomoProxyProviders',
|
||||||
|
|||||||
@ -1,18 +1,95 @@
|
|||||||
import { Card, CardBody } from '@heroui/react'
|
import { Card, CardBody, Chip, Switch } from '@heroui/react'
|
||||||
import React from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import { mihomoRulesDisable } from '@renderer/utils/ipc'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface RuleItemProps extends IMihomoRulesDetail {
|
||||||
|
index: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const RuleItem: React.FC<RuleItemProps> = (props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { type, payload, proxy, index: listIndex, extra } = props
|
||||||
|
const ruleIndex = props.index ?? listIndex
|
||||||
|
|
||||||
|
const { disabled, hitCount, hitAt, missCount, missAt } = extra
|
||||||
|
|
||||||
|
const [isEnabled, setIsEnabled] = useState(!disabled)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsEnabled(!disabled)
|
||||||
|
}, [disabled])
|
||||||
|
|
||||||
|
const handleToggle = async (v: boolean): Promise<void> => {
|
||||||
|
setIsEnabled(v)
|
||||||
|
try {
|
||||||
|
await mihomoRulesDisable({ [ruleIndex]: !v })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to toggle rule:', error)
|
||||||
|
setIsEnabled(!v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalCount = hitCount + missCount
|
||||||
|
const hitRate = totalCount > 0 ? (hitCount / totalCount) * 100 : 0
|
||||||
|
|
||||||
|
const formatRelativeTime = (timestamp: string): string => {
|
||||||
|
const now = Date.now()
|
||||||
|
const time = new Date(timestamp).getTime()
|
||||||
|
const diff = Math.floor((now - time) / 1000)
|
||||||
|
if (diff < 60) return t('rules.hitAt.seconds')
|
||||||
|
if (diff < 3600) return t('rules.hitAt.minutes', { count: Math.floor(diff / 60) })
|
||||||
|
if (diff < 86400) return t('rules.hitAt.hours', { count: Math.floor(diff / 3600) })
|
||||||
|
return t('rules.hitAt.days', { count: Math.floor(diff / 86400) })
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasStats = totalCount > 0
|
||||||
|
|
||||||
const RuleItem: React.FC<IMihomoRulesDetail & { index: number }> = (props) => {
|
|
||||||
const { type, payload, proxy, index } = props
|
|
||||||
return (
|
return (
|
||||||
<div className={`w-full px-2 pb-2 ${index === 0 ? 'pt-2' : ''}`}>
|
<div className={`w-full px-2 pb-2 ${listIndex === 0 ? 'pt-2' : ''}`}>
|
||||||
<Card>
|
<Card className={!isEnabled ? 'opacity-50' : ''}>
|
||||||
<CardBody className="w-full">
|
<CardBody className="py-3 px-4">
|
||||||
<div title={payload} className="text-ellipsis whitespace-nowrap overflow-hidden">
|
<div className="flex justify-between items-center gap-4">
|
||||||
{payload}
|
{/* 左侧:规则信息 */}
|
||||||
</div>
|
<div className="flex-1 min-w-0 flex items-center gap-3">
|
||||||
<div className="flex justify-start text-foreground-500">
|
{/* 规则内容 */}
|
||||||
<div>{type}</div>
|
<div className="flex-1 min-w-0">
|
||||||
<div className="ml-2">{proxy}</div>
|
<div title={payload} className="text-sm font-medium truncate mb-1.5">
|
||||||
|
{payload}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Chip size="sm" variant="flat" color="default" className="text-xs">
|
||||||
|
{type}
|
||||||
|
</Chip>
|
||||||
|
<Chip size="sm" variant="flat" color="default" className="text-xs">
|
||||||
|
{proxy}
|
||||||
|
</Chip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 统计信息 */}
|
||||||
|
{hasStats && (
|
||||||
|
<div className="flex items-center gap-3 text-xs shrink-0">
|
||||||
|
<span className="text-foreground-500 whitespace-nowrap">
|
||||||
|
{formatRelativeTime(hitAt || missAt)}
|
||||||
|
</span>
|
||||||
|
<span className="text-foreground-600 font-medium whitespace-nowrap">
|
||||||
|
{hitCount}/{totalCount}
|
||||||
|
</span>
|
||||||
|
<Chip size="sm" variant="flat" color="primary" className="text-xs">
|
||||||
|
{hitRate.toFixed(1)}%
|
||||||
|
</Chip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧开关 */}
|
||||||
|
<Switch
|
||||||
|
size="sm"
|
||||||
|
isSelected={isEnabled}
|
||||||
|
onValueChange={handleToggle}
|
||||||
|
aria-label="Toggle rule"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -529,6 +529,10 @@
|
|||||||
"outbound.modes.direct": "Direct",
|
"outbound.modes.direct": "Direct",
|
||||||
"rules.title": "Rules",
|
"rules.title": "Rules",
|
||||||
"rules.filter": "Filter Rules",
|
"rules.filter": "Filter Rules",
|
||||||
|
"rules.hitAt.seconds": "Hit moments ago",
|
||||||
|
"rules.hitAt.minutes": "Hit {{count}} min ago",
|
||||||
|
"rules.hitAt.hours": "Hit {{count}} hrs ago",
|
||||||
|
"rules.hitAt.days": "Hit {{count}} days ago",
|
||||||
"override.title": "Override",
|
"override.title": "Override",
|
||||||
"override.input.placeholder": "Enter override URL",
|
"override.input.placeholder": "Enter override URL",
|
||||||
"override.import": "Import",
|
"override.import": "Import",
|
||||||
|
|||||||
@ -498,6 +498,10 @@
|
|||||||
"outbound.modes.direct": "مستقیم",
|
"outbound.modes.direct": "مستقیم",
|
||||||
"rules.title": "قوانین",
|
"rules.title": "قوانین",
|
||||||
"rules.filter": "فیلتر قوانین",
|
"rules.filter": "فیلتر قوانین",
|
||||||
|
"rules.hitAt.seconds": "چند لحظه پیش",
|
||||||
|
"rules.hitAt.minutes": "{{count}} دقیقه پیش",
|
||||||
|
"rules.hitAt.hours": "{{count}} ساعت پیش",
|
||||||
|
"rules.hitAt.days": "{{count}} روز پیش",
|
||||||
"override.title": "جایگزینی",
|
"override.title": "جایگزینی",
|
||||||
"override.input.placeholder": "وارد کردن URL جایگزین",
|
"override.input.placeholder": "وارد کردن URL جایگزین",
|
||||||
"override.import": "وارد کردن",
|
"override.import": "وارد کردن",
|
||||||
|
|||||||
@ -500,6 +500,10 @@
|
|||||||
"outbound.modes.direct": "Прямой",
|
"outbound.modes.direct": "Прямой",
|
||||||
"rules.title": "Правила",
|
"rules.title": "Правила",
|
||||||
"rules.filter": "Фильтр правил",
|
"rules.filter": "Фильтр правил",
|
||||||
|
"rules.hitAt.seconds": "Совпадение несколько секунд назад",
|
||||||
|
"rules.hitAt.minutes": "Совпадение {{count}} мин назад",
|
||||||
|
"rules.hitAt.hours": "Совпадение {{count}} ч назад",
|
||||||
|
"rules.hitAt.days": "Совпадение {{count}} дн назад",
|
||||||
"override.title": "Переопределение",
|
"override.title": "Переопределение",
|
||||||
"override.input.placeholder": "Введите URL переопределения",
|
"override.input.placeholder": "Введите URL переопределения",
|
||||||
"override.import": "Импорт",
|
"override.import": "Импорт",
|
||||||
|
|||||||
@ -529,6 +529,10 @@
|
|||||||
"outbound.modes.direct": "直连",
|
"outbound.modes.direct": "直连",
|
||||||
"rules.title": "分流规则",
|
"rules.title": "分流规则",
|
||||||
"rules.filter": "筛选过滤",
|
"rules.filter": "筛选过滤",
|
||||||
|
"rules.hitAt.seconds": "最近命中于 几秒前",
|
||||||
|
"rules.hitAt.minutes": "最近命中于 {{count}} 分钟前",
|
||||||
|
"rules.hitAt.hours": "最近命中于 {{count}} 小时前",
|
||||||
|
"rules.hitAt.days": "最近命中于 {{count}} 天前",
|
||||||
"override.title": "覆写",
|
"override.title": "覆写",
|
||||||
"override.input.placeholder": "输入覆写 URL",
|
"override.input.placeholder": "输入覆写 URL",
|
||||||
"override.import": "导入",
|
"override.import": "导入",
|
||||||
|
|||||||
@ -529,6 +529,10 @@
|
|||||||
"outbound.modes.direct": "直連",
|
"outbound.modes.direct": "直連",
|
||||||
"rules.title": "分流規則",
|
"rules.title": "分流規則",
|
||||||
"rules.filter": "篩選過濾",
|
"rules.filter": "篩選過濾",
|
||||||
|
"rules.hitAt.seconds": "最近命中於 幾秒前",
|
||||||
|
"rules.hitAt.minutes": "最近命中於 {{count}} 分鐘前",
|
||||||
|
"rules.hitAt.hours": "最近命中於 {{count}} 小時前",
|
||||||
|
"rules.hitAt.days": "最近命中於 {{count}} 天前",
|
||||||
"override.title": "覆寫",
|
"override.title": "覆寫",
|
||||||
"override.input.placeholder": "輸入覆寫 URL",
|
"override.input.placeholder": "輸入覆寫 URL",
|
||||||
"override.import": "匯入",
|
"override.import": "匯入",
|
||||||
|
|||||||
@ -43,11 +43,12 @@ const Rules: React.FC = () => {
|
|||||||
data={filteredRules}
|
data={filteredRules}
|
||||||
itemContent={(i, rule) => (
|
itemContent={(i, rule) => (
|
||||||
<RuleItem
|
<RuleItem
|
||||||
index={i}
|
index={rule.index ?? i}
|
||||||
type={rule.type}
|
type={rule.type}
|
||||||
payload={rule.payload}
|
payload={rule.payload}
|
||||||
proxy={rule.proxy}
|
proxy={rule.proxy}
|
||||||
size={rule.size}
|
size={rule.size}
|
||||||
|
extra={rule.extra}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -19,6 +19,7 @@ interface IpcApi {
|
|||||||
mihomoCloseConnection: (id: string) => Promise<void>
|
mihomoCloseConnection: (id: string) => Promise<void>
|
||||||
mihomoCloseAllConnections: () => Promise<void>
|
mihomoCloseAllConnections: () => Promise<void>
|
||||||
mihomoRules: () => Promise<IMihomoRulesInfo>
|
mihomoRules: () => Promise<IMihomoRulesInfo>
|
||||||
|
mihomoRulesDisable: (rules: Record<string, boolean>) => Promise<void>
|
||||||
mihomoProxies: () => Promise<IMihomoProxies>
|
mihomoProxies: () => Promise<IMihomoProxies>
|
||||||
mihomoGroups: () => Promise<IMihomoMixedGroup[]>
|
mihomoGroups: () => Promise<IMihomoMixedGroup[]>
|
||||||
mihomoProxyProviders: () => Promise<IMihomoProxyProviders>
|
mihomoProxyProviders: () => Promise<IMihomoProxyProviders>
|
||||||
@ -173,6 +174,7 @@ export const {
|
|||||||
mihomoCloseConnection,
|
mihomoCloseConnection,
|
||||||
mihomoCloseAllConnections,
|
mihomoCloseAllConnections,
|
||||||
mihomoRules,
|
mihomoRules,
|
||||||
|
mihomoRulesDisable,
|
||||||
mihomoProxies,
|
mihomoProxies,
|
||||||
mihomoGroups,
|
mihomoGroups,
|
||||||
mihomoProxyProviders,
|
mihomoProxyProviders,
|
||||||
|
|||||||
8
src/shared/types.d.ts
vendored
8
src/shared/types.d.ts
vendored
@ -72,6 +72,14 @@ interface IMihomoRulesDetail {
|
|||||||
payload: string
|
payload: string
|
||||||
proxy: string
|
proxy: string
|
||||||
size: number
|
size: number
|
||||||
|
index: number
|
||||||
|
extra: {
|
||||||
|
disabled: boolean
|
||||||
|
hitCount: number
|
||||||
|
hitAt: string
|
||||||
|
missCount: number
|
||||||
|
missAt: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMihomoConnectionsInfo {
|
interface IMihomoConnectionsInfo {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user