diff options
| author | Olivi <[email protected]> | 2025-12-13 11:40:54 +0000 |
|---|---|---|
| committer | Olivi <[email protected]> | 2025-12-13 11:40:54 +0000 |
| commit | 4ac43e692cea4682179612e7fecb4c159e96c039 (patch) | |
| tree | 504322c2fbc1893ff8cab233075d1d629dd54962 /src/components | |
| parent | 3959e45747aabe9ec3dea011f66851d8c219a5fb (diff) | |
feat: add healthcheck functionality for specified proxy
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/proxies/Proxy.tsx | 44 | ||||
| -rw-r--r-- | src/components/proxies/ProxyLatency.module.scss | 48 | ||||
| -rw-r--r-- | src/components/proxies/ProxyLatency.tsx | 51 |
3 files changed, 125 insertions, 18 deletions
diff --git a/src/components/proxies/Proxy.tsx b/src/components/proxies/Proxy.tsx index 833d94b..1ba966e 100644 --- a/src/components/proxies/Proxy.tsx +++ b/src/components/proxies/Proxy.tsx @@ -3,10 +3,11 @@ import cx from 'clsx'; import * as React from 'react'; import { keyCodes } from '~/misc/keycode'; -import { getLatencyTestUrl } from '~/store/app'; -import { ProxyItem } from '~/store/types'; +import { getClashAPIConfig, getLatencyTestUrl } from '~/store/app'; +import { DispatchFn, ProxyItem } from '~/store/types'; +import { ClashAPIConfig } from '~/types'; -import { getDelay, getProxies } from '../../store/proxies'; +import { getDelay, getProxies, healthcheckProxy } from '../../store/proxies'; import { connect } from '../StateProvider'; import s0 from './Proxy.module.scss'; import { ProxyLatency } from './ProxyLatency'; @@ -65,12 +66,12 @@ type ProxyProps = { name: string; now?: boolean; proxy: ProxyItem; - latency: any; + latency: { number?: number; error?: string; testing?: boolean }; httpsLatencyTest: boolean; isSelectable?: boolean; - udp: boolean; - tfo: boolean; onClick?: (proxyName: string) => unknown; + apiConfig: ClashAPIConfig; + dispatch: DispatchFn; }; function ProxySmallImpl({ @@ -86,7 +87,7 @@ function ProxySmallImpl({ const latencyNumber = latency?.number ?? delay; const color = useMemo( () => getProxyDotBackgroundColor({ number: latencyNumber }, httpsLatencyTest), - [latencyNumber] + [latencyNumber, httpsLatencyTest] ); const title = useMemo(() => { @@ -161,13 +162,22 @@ function ProxyImpl({ httpsLatencyTest, isSelectable, onClick, + apiConfig, + dispatch, }: ProxyProps) { const delay = proxy.history[proxy.history.length - 1]?.delay; - const latencyNumber = latency?.number ?? delay; + const latencyNumber = + typeof latency?.number === 'number' + ? latency.number + : typeof delay === 'number' + ? delay + : undefined; + const hasLatencyNumber = typeof latencyNumber === 'number' && latencyNumber > 0; const color = useMemo( - () => getLabelColor({ number: latencyNumber }, httpsLatencyTest), - [latencyNumber] + () => getLabelColor({ number: hasLatencyNumber ? latencyNumber : undefined }, httpsLatencyTest), + [hasLatencyNumber, latencyNumber, httpsLatencyTest] ); + const isTestingLatency = Boolean(latency?.testing); const doSelect = React.useCallback(() => { isSelectable && onClick && onClick(name); @@ -210,7 +220,10 @@ function ProxyImpl({ }); }, [isSelectable, now, latency]); - // const latencyNumber = latency?.number ?? proxy.history[proxy.history.length - 1]?.delay; + const runLatencyTest = React.useCallback(() => { + if (isTestingLatency) return; + dispatch(healthcheckProxy(apiConfig, name)); + }, [apiConfig, dispatch, isTestingLatency, name]); return ( <div @@ -241,7 +254,13 @@ function ProxyImpl({ {formatTfo(proxy.tfo)} </div> - {latencyNumber ? <ProxyLatency number={latencyNumber} color={color} /> : null} + <ProxyLatency + number={hasLatencyNumber ? latencyNumber : undefined} + color={color} + isTesting={isTestingLatency} + error={latency?.error} + onClick={runLatencyTest} + /> </div> </div> ); @@ -256,6 +275,7 @@ const mapState = (s: any, { name }) => { proxy: proxy, latency: delay[name], httpsLatencyTest: latencyTestUrl.startsWith('https://'), + apiConfig: getClashAPIConfig(s), }; }; diff --git a/src/components/proxies/ProxyLatency.module.scss b/src/components/proxies/ProxyLatency.module.scss index 37502a8..f1731f6 100644 --- a/src/components/proxies/ProxyLatency.module.scss +++ b/src/components/proxies/ProxyLatency.module.scss @@ -1,10 +1,54 @@ @import '~/styles/utils/custom-media'; .proxyLatency { - border-radius: 20px; - color: #eee; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 50px; + padding: 4px 10px; + gap: 4px; + border-radius: 9999px; + border: 1px solid var(--color-proxy-border); + background: var(--bg-near-transparent); + color: var(--color-text); font-size: 0.75em; + transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, + color 0.15s ease, transform 0.15s ease; + user-select: none; + outline: none; @media (--breakpoint-not-small) { + padding: 5px 12px; font-size: 0.8em; } } + +.clickable { + cursor: pointer; +} + +.clickable:hover, +.clickable:focus-visible { + background: var(--color-bg-proxy); + border-color: var(--card-hover-border-lightness); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18); +} + +.placeholder { + color: var(--color-text-secondary); +} + +.testing { + animation: proxyLatencyPulse 1s ease-in-out infinite; +} + +@keyframes proxyLatencyPulse { + 0% { + opacity: 0.8; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.8; + } +} diff --git a/src/components/proxies/ProxyLatency.tsx b/src/components/proxies/ProxyLatency.tsx index 48e55af..3f91eac 100644 --- a/src/components/proxies/ProxyLatency.tsx +++ b/src/components/proxies/ProxyLatency.tsx @@ -1,16 +1,59 @@ +import cx from 'clsx'; import * as React from 'react'; import s0 from './ProxyLatency.module.scss'; type ProxyLatencyProps = { - number: number; + number?: number; color: string; + isTesting?: boolean; + error?: string; + onClick?: () => void; }; -export function ProxyLatency({ number, color }: ProxyLatencyProps) { +export function ProxyLatency({ number, color, isTesting, error, onClick }: ProxyLatencyProps) { + const hasNumber = typeof number === 'number'; + const label = isTesting ? 'Testing...' : hasNumber ? `${number} ms` : error || '--'; + + const className = cx(s0.proxyLatency, { + [s0.clickable]: Boolean(onClick), + [s0.placeholder]: !hasNumber || Boolean(error), + [s0.testing]: isTesting, + }); + + const handleClick = React.useCallback( + (e: React.MouseEvent) => { + if (!onClick || isTesting) return; + e.preventDefault(); + e.stopPropagation(); + onClick(); + }, + [isTesting, onClick] + ); + + const handleKeyDown = React.useCallback( + (e: React.KeyboardEvent) => { + if (!onClick || isTesting) return; + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + e.stopPropagation(); + onClick(); + } + }, + [isTesting, onClick] + ); + return ( - <span className={s0.proxyLatency} style={{ color }}> - <span>{number} ms</span> + <span + className={className} + style={{ color: hasNumber ? color : undefined }} + role={onClick ? 'button' : undefined} + tabIndex={onClick ? 0 : undefined} + onClick={handleClick} + onKeyDown={handleKeyDown} + title={label} + > + <span>{label}</span> </span> ); } |
