feat(proxies): sortProxiesByOrderingType

This commit is contained in:
kunish 2023-09-03 05:35:08 +08:00
parent c9ec575437
commit 635b8ba966
No known key found for this signature in database
GPG Key ID: 647A12B4F782C430
18 changed files with 137 additions and 77 deletions

View File

@ -3,6 +3,7 @@
"class", "class",
"className", "className",
"ngClass", "ngClass",
"classList",
".*ClassName*" ".*ClassName*"
] ]
} }

View File

@ -37,6 +37,7 @@
"daisyui": "^3.6.4", "daisyui": "^3.6.4",
"dayjs": "^1.11.9", "dayjs": "^1.11.9",
"husky": "^8.0.3", "husky": "^8.0.3",
"immer": "^10.0.2",
"is-ip": "^5.0.1", "is-ip": "^5.0.1",
"ky": "^1.0.0", "ky": "^1.0.0",
"lint-staged": "^14.0.1", "lint-staged": "^14.0.1",

View File

@ -83,6 +83,9 @@ dependencies:
husky: husky:
specifier: ^8.0.3 specifier: ^8.0.3
version: 8.0.3 version: 8.0.3
immer:
specifier: ^10.0.2
version: 10.0.2
is-ip: is-ip:
specifier: ^5.0.1 specifier: ^5.0.1
version: 5.0.1 version: 5.0.1
@ -4438,6 +4441,13 @@ packages:
engines: { node: '>= 4' } engines: { node: '>= 4' }
dev: false dev: false
/immer@10.0.2:
resolution:
{
integrity: sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==,
}
dev: false
/import-fresh@3.3.0: /import-fresh@3.3.0:
resolution: resolution:
{ {

View File

@ -26,11 +26,11 @@ export const App = () => {
createEffect(() => { createEffect(() => {
if (selectedEndpoint() && endpoint()) { if (selectedEndpoint() && endpoint()) {
useProxies().updateProxy() useProxies().updateProxies()
} }
}) })
onMount(async () => { onMount(() => {
if (!selectedEndpoint()) { if (!selectedEndpoint()) {
navigate('/setup') navigate('/setup')
} }

View File

@ -1,20 +1,22 @@
import { useI18n } from '@solid-primitives/i18n'
import { Show, createEffect, createMemo, createSignal } from 'solid-js' import { Show, createEffect, createMemo, createSignal } from 'solid-js'
import { LATENCY_QUALITY_MAP_HTTP } from '~/constants' import { LATENCY_QUALITY_MAP_HTTP } from '~/constants'
import { latencyQualityMap, useProxies } from '~/signals' import { latencyQualityMap, useProxies } from '~/signals'
export const Latency = (props: { name?: string }) => { export const Latency = (props: { name?: string }) => {
const { delayMap } = useProxies() const [t] = useI18n()
const { latencyMap } = useProxies()
const [textClassName, setTextClassName] = createSignal('') const [textClassName, setTextClassName] = createSignal('')
const delay = createMemo(() => { const latency = createMemo(() => {
return delayMap()[props.name!] return latencyMap()[props.name!]
}) })
createEffect(() => { createEffect(() => {
setTextClassName('text-success') setTextClassName('text-success')
if (delay() > latencyQualityMap().HIGH) { if (latency() > latencyQualityMap().HIGH) {
setTextClassName('text-error') setTextClassName('text-error')
} else if (delay() > latencyQualityMap().MEDIUM) { } else if (latency() > latencyQualityMap().MEDIUM) {
setTextClassName('text-warning') setTextClassName('text-warning')
} }
}) })
@ -23,11 +25,14 @@ export const Latency = (props: { name?: string }) => {
<> <>
<Show <Show
when={ when={
typeof delay() === 'number' && typeof latency() === 'number' &&
delay() !== LATENCY_QUALITY_MAP_HTTP.NOT_CONNECTED latency() !== LATENCY_QUALITY_MAP_HTTP.NOT_CONNECTED
} }
> >
<span class={textClassName()}>{delay()}ms</span> <span class={`whitespace-nowrap ${textClassName()}`}>
{latency()}
{t('ms')}
</span>
</Show> </Show>
</> </>
) )

View File

@ -1,6 +1,6 @@
import InfiniteScroll from 'solid-infinite-scroll' import InfiniteScroll from 'solid-infinite-scroll'
import { createMemo, createSignal } from 'solid-js' import { createMemo, createSignal } from 'solid-js'
import ProxyNodeCard from './ProxyNodeCard' import { ProxyNodeCard } from '~/components'
export const ProxyCardGroups = (props: { export const ProxyCardGroups = (props: {
proxies: string[] proxies: string[]

View File

@ -1,9 +1,10 @@
import { createMemo } from 'solid-js' import { createMemo } from 'solid-js'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { Latency } from '~/components' import { Latency } from '~/components'
import { formatProxyType } from '~/helpers'
import { useProxies } from '~/signals' import { useProxies } from '~/signals'
export default (props: { export const ProxyNodeCard = (props: {
proxyName: string proxyName: string
isSelected?: boolean isSelected?: boolean
onClick?: () => void onClick?: () => void
@ -12,16 +13,6 @@ export default (props: {
const { proxyNodeMap } = useProxies() const { proxyNodeMap } = useProxies()
const proxyNode = createMemo(() => proxyNodeMap()[proxyName]) const proxyNode = createMemo(() => proxyNodeMap()[proxyName])
const formatProxyType = (type = '') => {
const t = type.toLowerCase()
if (t.includes('shadowsocks')) {
return t.replace('shadowsocks', 'ss')
}
return t
}
return ( return (
<div <div
class={twMerge( class={twMerge(

View File

@ -6,9 +6,9 @@ export const ProxyPreviewBar = (props: {
proxyNameList: string[] proxyNameList: string[]
now?: string now?: string
}) => { }) => {
const { delayMap } = useProxies() const { latencyMap } = useProxies()
const delayList = createMemo(() => const delayList = createMemo(() =>
props.proxyNameList.map((i) => delayMap()[i]), props.proxyNameList.map((i) => latencyMap()[i]),
) )
const all = createMemo(() => delayList().length) const all = createMemo(() => delayList().length)
const good = createMemo( const good = createMemo(

View File

@ -1,27 +1,22 @@
import { For } from 'solid-js' import { For } from 'solid-js'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { import { latencyQualityMap, useProxies } from '~/signals'
LATENCY_QUALITY_MAP_HTTP,
LATENCY_QUALITY_MAP_HTTPS,
} from '~/constants'
import { isLatencyTestByHttps, useProxies } from '~/signals'
const DelayDots = (p: { delay: number | undefined; selected: boolean }) => { const DelayDots = (p: { delay: number | undefined; selected: boolean }) => {
const delayMap = isLatencyTestByHttps()
? LATENCY_QUALITY_MAP_HTTP
: LATENCY_QUALITY_MAP_HTTPS
let dotClassName = p.selected let dotClassName = p.selected
? 'bg-white border-4 border-success' ? 'bg-white border-4 border-success'
: 'bg-success' : 'bg-success'
if (typeof p.delay !== 'number' || p.delay === delayMap.NOT_CONNECTED) { if (
typeof p.delay !== 'number' ||
p.delay === latencyQualityMap().NOT_CONNECTED
) {
dotClassName = p.selected dotClassName = p.selected
? 'bg-white border-4 border-neutral' ? 'bg-white border-4 border-neutral'
: 'bg-neutral' : 'bg-neutral'
} else if (p.delay > delayMap.HIGH) { } else if (p.delay > latencyQualityMap().HIGH) {
dotClassName = p.selected ? 'bg-white border-4 border-error' : 'bg-error' dotClassName = p.selected ? 'bg-white border-4 border-error' : 'bg-error'
} else if (p.delay > delayMap.MEDIUM) { } else if (p.delay > latencyQualityMap().MEDIUM) {
dotClassName = p.selected dotClassName = p.selected
? 'bg-white border-4 border-warning' ? 'bg-white border-4 border-warning'
: 'bg-warning' : 'bg-warning'
@ -34,14 +29,14 @@ export const ProxyPreviewDots = (props: {
proxyNameList: string[] proxyNameList: string[]
now?: string now?: string
}) => { }) => {
const { delayMap } = useProxies() const { latencyMap } = useProxies()
return ( return (
<div class="flex w-full flex-wrap items-center"> <div class="flex w-full flex-wrap items-center">
<For <For
each={props.proxyNameList.map((name): [string, number] => [ each={props.proxyNameList.map((name): [string, number] => [
name, name,
delayMap()[name], latencyMap()[name],
])} ])}
> >
{([name, delay]) => { {([name, delay]) => {

View File

@ -41,13 +41,13 @@ export enum ROUTES {
} }
export enum LATENCY_QUALITY_MAP_HTTP { export enum LATENCY_QUALITY_MAP_HTTP {
NOT_CONNECTED = 0, NOT_CONNECTED = -1,
MEDIUM = 200, MEDIUM = 200,
HIGH = 500, HIGH = 500,
} }
export enum LATENCY_QUALITY_MAP_HTTPS { export enum LATENCY_QUALITY_MAP_HTTPS {
NOT_CONNECTED = 0, NOT_CONNECTED = -1,
MEDIUM = 800, MEDIUM = 800,
HIGH = 1500, HIGH = 1500,
} }
@ -59,7 +59,7 @@ export enum PROXIES_PREVIEW_TYPE {
Auto = 'auto', Auto = 'auto',
} }
export enum PROXIES_SORTING_TYPE { export enum PROXIES_ORDERING_TYPE {
NATURAL = 'orderNatural', NATURAL = 'orderNatural',
LATENCY_ASC = 'orderLatency_asc', LATENCY_ASC = 'orderLatency_asc',
LATENCY_DESC = 'orderLatency_desc', LATENCY_DESC = 'orderLatency_desc',

View File

@ -1,7 +1,5 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' import { PROXIES_ORDERING_TYPE } from '~/constants'
dayjs.extend(relativeTime)
export const formatTimeFromNow = (time: number | string) => { export const formatTimeFromNow = (time: number | string) => {
return dayjs(time).fromNow() return dayjs(time).fromNow()
@ -16,3 +14,45 @@ export const getBtnElFromClickEvent = (event: MouseEvent) => {
return el return el
} }
export const formatProxyType = (type = '') => {
const t = type.toLowerCase()
if (t.includes('shadowsocks')) {
return t.replace('shadowsocks', 'ss')
}
return t
}
export const sortProxiesByOrderingType = (
proxyNames: string[],
proxyLatencyMap: Record<string, number>,
orderingType: PROXIES_ORDERING_TYPE,
) => {
if (orderingType === PROXIES_ORDERING_TYPE.NATURAL) {
return proxyNames
}
return proxyNames.sort((a, b) => {
const prevLatency = proxyLatencyMap[a]
const nextLatency = proxyLatencyMap[b]
switch (orderingType) {
case PROXIES_ORDERING_TYPE.LATENCY_ASC:
if (prevLatency === -1) return 1
if (nextLatency === -1) return -1
return prevLatency - nextLatency
case PROXIES_ORDERING_TYPE.LATENCY_DESC:
if (prevLatency === -1) return 1
if (nextLatency === -1) return -1
return nextLatency - prevLatency
case PROXIES_ORDERING_TYPE.NAME_ASC:
return a.localeCompare(b)
case PROXIES_ORDERING_TYPE.NAME_DESC:
return b.localeCompare(a)
default:
return 0
}
})
}

View File

@ -54,4 +54,5 @@ export default {
orderLatency_desc: 'By latency from high to low', orderLatency_desc: 'By latency from high to low',
orderName_asc: 'By name alphabetically (A-Z)', orderName_asc: 'By name alphabetically (A-Z)',
orderName_desc: 'By name alphabetically (Z-A)', orderName_desc: 'By name alphabetically (Z-A)',
ms: 'ms',
} }

View File

@ -54,4 +54,5 @@ export default {
orderLatency_desc: '按延迟从高到低', orderLatency_desc: '按延迟从高到低',
orderName_asc: '按名称字母排序 (A-Z)', orderName_asc: '按名称字母排序 (A-Z)',
orderName_desc: '按名称字母排序 (Z-A)', orderName_desc: '按名称字母排序 (Z-A)',
ms: '毫秒',
} }

View File

@ -2,11 +2,13 @@
import '~/index.css' import '~/index.css'
import { Router, hashIntegration } from '@solidjs/router' import { Router, hashIntegration } from '@solidjs/router'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { render } from 'solid-js/web' import { render } from 'solid-js/web'
import { App } from '~/App' import { App } from '~/App'
import { I18nProvider } from '~/i18n' import { I18nProvider } from '~/i18n'
const root = document.getElementById('root') dayjs.extend(relativeTime)
render( render(
() => ( () => (
@ -16,5 +18,5 @@ render(
</Router> </Router>
</I18nProvider> </I18nProvider>
), ),
root!, document.getElementById('root')!,
) )

View File

@ -4,22 +4,26 @@ import { useI18n } from '@solid-primitives/i18n'
import { For, Show, createSignal, onMount } from 'solid-js' import { For, Show, createSignal, onMount } from 'solid-js'
import { z } from 'zod' import { z } from 'zod'
import { Button } from '~/components' import { Button } from '~/components'
import { PROXIES_PREVIEW_TYPE, PROXIES_SORTING_TYPE, themes } from '~/constants' import {
PROXIES_ORDERING_TYPE,
PROXIES_PREVIEW_TYPE,
themes,
} from '~/constants'
import { import {
applyThemeByMode, applyThemeByMode,
autoCloseConns, autoCloseConns,
autoSwitchTheme, autoSwitchTheme,
favDayTheme, favDayTheme,
favNightTheme, favNightTheme,
proxiesOrderingType,
proxiesPreviewType, proxiesPreviewType,
proxiesSortingType,
renderInTwoColumn, renderInTwoColumn,
setAutoCloseConns, setAutoCloseConns,
setAutoSwitchTheme, setAutoSwitchTheme,
setFavDayTheme, setFavDayTheme,
setFavNightTheme, setFavNightTheme,
setProxiesOrderingType,
setProxiesPreviewType, setProxiesPreviewType,
setProxiesSortingType,
setRenderInTwoColumn, setRenderInTwoColumn,
setUrlForDelayTest, setUrlForDelayTest,
urlForDelayTest, urlForDelayTest,
@ -289,7 +293,7 @@ const ConfigForXd = () => {
<div class="pb-4">{t('proxiesSorting')}</div> <div class="pb-4">{t('proxiesSorting')}</div>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<For each={Object.values(PROXIES_SORTING_TYPE)}> <For each={Object.values(PROXIES_ORDERING_TYPE)}>
{(value) => ( {(value) => (
<label class="flex items-center gap-2"> <label class="flex items-center gap-2">
<span>{t(value)}</span> <span>{t(value)}</span>
@ -298,8 +302,8 @@ const ConfigForXd = () => {
class="radio" class="radio"
aria-label={value} aria-label={value}
type="radio" type="radio"
checked={value === proxiesSortingType()} checked={value === proxiesOrderingType()}
onChange={() => setProxiesSortingType(value)} onChange={() => setProxiesOrderingType(value)}
/> />
</label> </label>
)} )}

View File

@ -8,14 +8,18 @@ import {
ProxyCardGroups, ProxyCardGroups,
ProxyNodePreview, ProxyNodePreview,
} from '~/components' } from '~/components'
import { getBtnElFromClickEvent } from '~/helpers' import { getBtnElFromClickEvent, sortProxiesByOrderingType } from '~/helpers'
import { useProxies } from '~/signals' import { proxiesOrderingType, useProxies } from '~/signals'
import type { Proxy } from '~/types' import type { Proxy } from '~/types'
export default () => { export default () => {
const [t] = useI18n() const [t] = useI18n()
const { proxies, setProxyGroupByProxyName, delayTestByProxyGroupName } = const {
useProxies() proxies,
setProxyGroupByProxyName,
delayTestByProxyGroupName,
latencyMap,
} = useProxies()
const [collapsedMap, setCollapsedMap] = createSignal<Record<string, boolean>>( const [collapsedMap, setCollapsedMap] = createSignal<Record<string, boolean>>(
{}, {},
@ -66,7 +70,11 @@ export default () => {
const content = ( const content = (
<ProxyCardGroups <ProxyCardGroups
proxies={proxy.all!} proxies={sortProxiesByOrderingType(
proxy.all ?? [],
latencyMap(),
proxiesOrderingType(),
)}
now={proxy.now} now={proxy.now}
onClick={(name) => { onClick={(name) => {
onProxyNodeClick(proxy, name) onProxyNodeClick(proxy, name)

View File

@ -3,8 +3,8 @@ import { createSignal } from 'solid-js'
import { import {
LATENCY_QUALITY_MAP_HTTP, LATENCY_QUALITY_MAP_HTTP,
LATENCY_QUALITY_MAP_HTTPS, LATENCY_QUALITY_MAP_HTTPS,
PROXIES_ORDERING_TYPE,
PROXIES_PREVIEW_TYPE, PROXIES_PREVIEW_TYPE,
PROXIES_SORTING_TYPE,
} from '~/constants' } from '~/constants'
import { setCurTheme } from '~/signals' import { setCurTheme } from '~/signals'
@ -12,9 +12,9 @@ export const [proxiesPreviewType, setProxiesPreviewType] = makePersisted(
createSignal(PROXIES_PREVIEW_TYPE.Auto), createSignal(PROXIES_PREVIEW_TYPE.Auto),
{ name: 'proxiesPreviewType', storage: localStorage }, { name: 'proxiesPreviewType', storage: localStorage },
) )
export const [proxiesSortingType, setProxiesSortingType] = makePersisted( export const [proxiesOrderingType, setProxiesOrderingType] = makePersisted(
createSignal(PROXIES_SORTING_TYPE.NATURAL), createSignal(PROXIES_ORDERING_TYPE.NATURAL),
{ name: 'proxiesSortingType', storage: localStorage }, { name: 'proxiesOrderingType', storage: localStorage },
) )
export const [urlForDelayTest, setUrlForDelayTest] = makePersisted( export const [urlForDelayTest, setUrlForDelayTest] = makePersisted(
createSignal('https://www.gstatic.com/generate_204'), createSignal('https://www.gstatic.com/generate_204'),

View File

@ -11,7 +11,7 @@ type ProxyInfo = {
const [proxies, setProxies] = createSignal<Proxy[]>([]) const [proxies, setProxies] = createSignal<Proxy[]>([])
const [proxyProviders, setProxyProviders] = createSignal<ProxyProvider[]>([]) const [proxyProviders, setProxyProviders] = createSignal<ProxyProvider[]>([])
const [delayMap, setDelayMap] = createSignal<Record<string, number>>({}) const [latencyMap, setLatencyMap] = createSignal<Record<string, number>>({})
const [proxyNodeMap, setProxyNodeMap] = createSignal<Record<string, ProxyInfo>>( const [proxyNodeMap, setProxyNodeMap] = createSignal<Record<string, ProxyInfo>>(
{}, {},
) )
@ -21,7 +21,7 @@ export const useProxies = () => {
const setProxyInfoByProixes = (proxies: Proxy[] | ProxyNode[]) => { const setProxyInfoByProixes = (proxies: Proxy[] | ProxyNode[]) => {
proxies.forEach((proxy) => { proxies.forEach((proxy) => {
const delay = proxy.history.at(-1)?.delay ?? 0 const delay = proxy.history.at(-1)?.delay ?? -1
setProxyNodeMap({ setProxyNodeMap({
...proxyNodeMap(), ...proxyNodeMap(),
@ -31,14 +31,14 @@ export const useProxies = () => {
name: proxy.name, name: proxy.name,
}, },
}) })
setDelayMap({ setLatencyMap({
...delayMap(), ...latencyMap(),
[proxy.name]: delay, [proxy.name]: delay,
}) })
}) })
} }
const updateProxy = async () => { const updateProxies = async () => {
const { providers } = await request const { providers } = await request
.get('providers/proxies') .get('providers/proxies')
.json<{ providers: Record<string, ProxyProvider> }>() .json<{ providers: Record<string, ProxyProvider> }>()
@ -58,14 +58,15 @@ export const useProxies = () => {
.get('proxies') .get('proxies')
.json<{ proxies: Record<string, Proxy> }>() .json<{ proxies: Record<string, Proxy> }>()
const sortIndex = [...(proxies['GLOBAL'].all ?? []), 'GLOBAL'] const sortIndex = [...(proxies['GLOBAL'].all ?? []), 'GLOBAL']
const proxiesArray = Object.values(proxies)
setProxyInfoByProixes(Object.values(proxies)) setProxyInfoByProixes(proxiesArray)
setProxies( setProxies(
Object.values(proxies) proxiesArray
.filter((proxy) => proxy.all?.length) .filter((proxy) => proxy.all?.length)
.sort( .sort(
(pre, next) => (prev, next) =>
sortIndex.indexOf(pre.name) - sortIndex.indexOf(next.name), sortIndex.indexOf(prev.name) - sortIndex.indexOf(next.name),
), ),
) )
} }
@ -97,8 +98,8 @@ export const useProxies = () => {
}) })
.json() .json()
setDelayMap({ setLatencyMap({
...delayMap(), ...latencyMap(),
...data, ...data,
}) })
} }
@ -107,7 +108,7 @@ export const useProxies = () => {
try { try {
await request.put(`providers/proxies/${proxyProviderName}`) await request.put(`providers/proxies/${proxyProviderName}`)
} catch {} } catch {}
await updateProxy() await updateProxies()
} }
const updateAllProvider = async () => { const updateAllProvider = async () => {
@ -116,23 +117,23 @@ export const useProxies = () => {
return request.put(`providers/proxies/${provider.name}`) return request.put(`providers/proxies/${provider.name}`)
}), }),
) )
await updateProxy() await updateProxies()
} }
const healthCheckByProviderName = async (providerName: string) => { const healthCheckByProviderName = async (providerName: string) => {
await request.get(`providers/proxies/${providerName}/healthcheck`, { await request.get(`providers/proxies/${providerName}/healthcheck`, {
timeout: 30 * 1000, // thie api is a little bit slow sometimes... timeout: 30 * 1000, // thie api is a little bit slow sometimes...
}) })
await updateProxy() await updateProxies()
} }
return { return {
proxies, proxies,
proxyProviders, proxyProviders,
delayTestByProxyGroupName, delayTestByProxyGroupName,
delayMap, latencyMap,
proxyNodeMap, proxyNodeMap,
updateProxy, updateProxies,
setProxyGroupByProxyName, setProxyGroupByProxyName,
updateProviderByProviderName, updateProviderByProviderName,
updateAllProvider, updateAllProvider,