feat(proxy): merge proxies and proxyProviders into one page

This commit is contained in:
kunish 2023-09-06 20:06:26 +08:00
parent 714158742d
commit 93114b22a2
No known key found for this signature in database
GPG Key ID: 647A12B4F782C430
9 changed files with 230 additions and 233 deletions

View File

@ -6,7 +6,6 @@ import { ROUTES } from '~/constants'
import { import {
curTheme, curTheme,
endpoint, endpoint,
renderProxiesInSamePage,
selectedEndpoint, selectedEndpoint,
useAutoSwitchTheme, useAutoSwitchTheme,
useProxies, useProxies,
@ -18,7 +17,6 @@ const Overview = lazy(() => import('~/pages/Overview'))
const Connections = lazy(() => import('~/pages/Connections')) const Connections = lazy(() => import('~/pages/Connections'))
const Logs = lazy(() => import('~/pages/Logs')) const Logs = lazy(() => import('~/pages/Logs'))
const Proxies = lazy(() => import('~/pages/Proxies')) const Proxies = lazy(() => import('~/pages/Proxies'))
const ProxyProvider = lazy(() => import('~/pages/ProxyProvider'))
const Rules = lazy(() => import('~/pages/Rules')) const Rules = lazy(() => import('~/pages/Rules'))
const Config = lazy(() => import('~/pages/Config')) const Config = lazy(() => import('~/pages/Config'))
@ -54,9 +52,6 @@ export const App = () => {
<Show when={selectedEndpoint()}> <Show when={selectedEndpoint()}>
<Route path={ROUTES.Overview} component={Overview} /> <Route path={ROUTES.Overview} component={Overview} />
<Route path={ROUTES.Proxies} component={Proxies} /> <Route path={ROUTES.Proxies} component={Proxies} />
<Show when={!renderProxiesInSamePage()}>
<Route path={ROUTES.ProxyProvider} component={ProxyProvider} />
</Show>
<Route path={ROUTES.Rules} component={Rules} /> <Route path={ROUTES.Rules} component={Rules} />
<Route path={ROUTES.Conns} component={Connections} /> <Route path={ROUTES.Conns} component={Connections} />
<Route path={ROUTES.Log} component={Logs} /> <Route path={ROUTES.Log} component={Logs} />

View File

@ -3,7 +3,6 @@ import { A, useLocation } from '@solidjs/router'
import { import {
IconFileStack, IconFileStack,
IconGlobe, IconGlobe,
IconGlobeFilled,
IconHome, IconHome,
IconMenu, IconMenu,
IconNetwork, IconNetwork,
@ -11,10 +10,10 @@ import {
IconRuler, IconRuler,
IconSettings, IconSettings,
} from '@tabler/icons-solidjs' } from '@tabler/icons-solidjs'
import { For, ParentComponent, Show, createMemo, createSignal } from 'solid-js' import { For, ParentComponent, Show, createSignal } from 'solid-js'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { ROUTES, themes } from '~/constants' import { ROUTES, themes } from '~/constants'
import { renderProxiesInSamePage, setCurTheme, useProxies } from '~/signals' import { setCurTheme } from '~/signals'
const Nav: ParentComponent<{ href: string; tooltip: string }> = ({ const Nav: ParentComponent<{ href: string; tooltip: string }> = ({
href, href,
@ -74,51 +73,38 @@ const LogoText = () => (
export const Header = () => { export const Header = () => {
const [t] = useI18n() const [t] = useI18n()
const { proxyProviders } = useProxies() const navs = () => [
const navs = createMemo(() => { {
const list = [ href: ROUTES.Overview,
{ name: t('overview'),
href: ROUTES.Overview, icon: <IconHome />,
name: t('overview'), },
icon: <IconHome />, {
}, href: ROUTES.Proxies,
{ name: t('proxies'),
href: ROUTES.Proxies, icon: <IconGlobe />,
name: t('proxies'), },
icon: <IconGlobe />, {
}, href: ROUTES.Rules,
{ name: t('rules'),
href: ROUTES.Rules, icon: <IconRuler />,
name: t('rules'), },
icon: <IconRuler />, {
}, href: ROUTES.Conns,
{ name: t('connections'),
href: ROUTES.Conns, icon: <IconNetwork />,
name: t('connections'), },
icon: <IconNetwork />, {
}, href: ROUTES.Log,
{ name: t('logs'),
href: ROUTES.Log, icon: <IconFileStack />,
name: t('logs'), },
icon: <IconFileStack />, {
}, href: ROUTES.Config,
{ name: t('config'),
href: ROUTES.Config, icon: <IconSettings />,
name: t('config'), },
icon: <IconSettings />, ]
},
]
if (proxyProviders().length > 0 && !renderProxiesInSamePage()) {
list.splice(2, 0, {
href: ROUTES.ProxyProvider,
name: t('proxyProviders'),
icon: <IconGlobeFilled />,
})
}
return list
})
const location = useLocation() const location = useLocation()

View File

@ -71,4 +71,5 @@ export default {
switchLanguage: 'Switch Language', switchLanguage: 'Switch Language',
latencyTestTimeoutDuration: 'Latency Test Timeout Duration', latencyTestTimeoutDuration: 'Latency Test Timeout Duration',
closedConnections: 'Closed Connections', closedConnections: 'Closed Connections',
all: 'All',
} }

View File

@ -70,4 +70,5 @@ export default {
switchLanguage: '切换语言', switchLanguage: '切换语言',
latencyTestTimeoutDuration: '测速超时时间', latencyTestTimeoutDuration: '测速超时时间',
closedConnections: '已关闭连接', closedConnections: '已关闭连接',
all: '全部',
} }

View File

@ -30,7 +30,6 @@ import {
proxiesOrderingType, proxiesOrderingType,
proxiesPreviewType, proxiesPreviewType,
renderInTwoColumns, renderInTwoColumns,
renderProxiesInSamePage,
renderRulesAndProviderInTwoColumns, renderRulesAndProviderInTwoColumns,
setAutoCloseConns, setAutoCloseConns,
setAutoSwitchTheme, setAutoSwitchTheme,
@ -40,7 +39,6 @@ import {
setProxiesOrderingType, setProxiesOrderingType,
setProxiesPreviewType, setProxiesPreviewType,
setRenderInTwoColumns, setRenderInTwoColumns,
setRenderProxiesInSamePage,
setRenderRulesAndProviderInTwoColumns, setRenderRulesAndProviderInTwoColumns,
setSelectedEndpoint, setSelectedEndpoint,
setTableSize, setTableSize,
@ -326,11 +324,6 @@ const ConfigForXd = () => {
value: renderRulesAndProviderInTwoColumns, value: renderRulesAndProviderInTwoColumns,
onChange: setRenderRulesAndProviderInTwoColumns, onChange: setRenderRulesAndProviderInTwoColumns,
}, },
{
label: 'renderProxiesInSamePage',
value: renderProxiesInSamePage,
onChange: setRenderProxiesInSamePage,
},
{ {
label: 'autoSwitchTheme', label: 'autoSwitchTheme',
value: autoSwitchTheme, value: autoSwitchTheme,

View File

@ -113,7 +113,7 @@ export default () => {
) )
setClosedConnectionsWithSpeed((prev) => setClosedConnectionsWithSpeed((prev) =>
[...prev, ...closedConnections].slice(-100), [...prev, ...closedConnections].slice(-1000),
) )
return connections.slice(-100) return connections.slice(-100)
@ -157,7 +157,7 @@ export default () => {
}, },
) )
const columns: ColumnDef<ConnectionWithSpeed>[] = [ const columns = createMemo<ColumnDef<ConnectionWithSpeed>[]>(() => [
{ {
header: () => t('close'), header: () => t('close'),
enableGrouping: false, enableGrouping: false,
@ -265,7 +265,7 @@ export default () => {
row.metadata.destinationIP || row.metadata.destinationIP ||
row.metadata.host, row.metadata.host,
}, },
] ])
const [grouping, setGrouping] = createSignal<GroupingState>([]) const [grouping, setGrouping] = createSignal<GroupingState>([])
const [sorting, setSorting] = createSignal<SortingState>([ const [sorting, setSorting] = createSignal<SortingState>([
@ -297,7 +297,7 @@ export default () => {
}, },
sortDescFirst: true, sortDescFirst: true,
enableHiding: true, enableHiding: true,
columns, columns: columns(),
onGlobalFilterChange: setSearch, onGlobalFilterChange: setSearch,
onGroupingChange: setGrouping, onGroupingChange: setGrouping,
onSortingChange: setSorting, onSortingChange: setSorting,
@ -327,10 +327,14 @@ export default () => {
<For each={tabs()}> <For each={tabs()}>
{(tab) => ( {(tab) => (
<button <button
class={twMerge(activeTab() === tab.type && 'tab-active', 'tab')} class={twMerge(
activeTab() === tab.type && 'tab-active',
'tab gap-2',
)}
onClick={() => setActiveTab(tab.type)} onClick={() => setActiveTab(tab.type)}
> >
{tab.name} ({tab.count}) <span>{tab.name}</span>
<div class="badge badge-sm">{tab.count}</div>
</button> </button>
)} )}
</For> </For>

View File

@ -1,6 +1,6 @@
import { useI18n } from '@solid-primitives/i18n' import { useI18n } from '@solid-primitives/i18n'
import { IconBrandSpeedtest } from '@tabler/icons-solidjs' import { IconBrandSpeedtest, IconReload } from '@tabler/icons-solidjs'
import { Show } from 'solid-js' import { For, Show, createSignal } from 'solid-js'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { import {
Button, Button,
@ -8,15 +8,21 @@ import {
ForTwoColumns, ForTwoColumns,
ProxyCardGroups, ProxyCardGroups,
ProxyNodePreview, ProxyNodePreview,
SubscriptionInfo,
} from '~/components' } from '~/components'
import { sortProxiesByOrderingType, useStringBooleanMap } from '~/helpers'
import { import {
proxiesOrderingType, formatTimeFromNow,
renderProxiesInSamePage, sortProxiesByOrderingType,
useProxies, useStringBooleanMap,
} from '~/signals' } from '~/helpers'
import { proxiesOrderingType, useProxies } from '~/signals'
import type { Proxy } from '~/types' import type { Proxy } from '~/types'
import ProxyProvider from './ProxyProvider'
enum ActiveTab {
all = 'all',
proxyProviders = 'proxyProviders',
proxies = 'proxies',
}
export default () => { export default () => {
const [t] = useI18n() const [t] = useI18n()
@ -25,27 +31,184 @@ export default () => {
setProxyGroupByProxyName, setProxyGroupByProxyName,
latencyTestByProxyGroupName, latencyTestByProxyGroupName,
latencyMap, latencyMap,
proxyProviders,
updateProviderByProviderName,
updateAllProvider,
healthCheckByProviderName,
} = useProxies() } = useProxies()
const { map: collapsedMap, set: setCollapsedMap } = useStringBooleanMap() const { map: collapsedMap, set: setCollapsedMap } = useStringBooleanMap()
const { map: speedTestingMap, setWithCallback: setSpeedTestingMap } = const { map: latencyTestingMap, setWithCallback: setLatencyTestingMap } =
useStringBooleanMap() useStringBooleanMap()
const onProxyNodeClick = async (proxy: Proxy, proxyName: string) => { const onProxyNodeClick = async (proxy: Proxy, proxyName: string) => {
void setProxyGroupByProxyName(proxy, proxyName) void setProxyGroupByProxyName(proxy, proxyName)
} }
const onSpeedTestClick = async (e: MouseEvent, name: string) => { const onLatencyTestClick = async (e: MouseEvent, name: string) => {
e.stopPropagation() e.stopPropagation()
setSpeedTestingMap(name, () => latencyTestByProxyGroupName(name)) void setLatencyTestingMap(name, () => latencyTestByProxyGroupName(name))
} }
const { map: healthCheckingMap, setWithCallback: setHealthCheckingMap } =
useStringBooleanMap()
const { map: updatingMap, setWithCallback: setUpdatingMap } =
useStringBooleanMap()
const [isAllProviderUpdating, setIsAllProviderUpdating] = createSignal(false)
const onHealthCheckClick = (e: MouseEvent, name: string) => {
e.stopPropagation()
void setHealthCheckingMap(name, () => healthCheckByProviderName(name))
}
const onUpdateProviderClick = (e: MouseEvent, name: string) => {
e.stopPropagation()
void setUpdatingMap(name, () => updateProviderByProviderName(name))
}
const onUpdateAllProviderClick = async (e: MouseEvent) => {
e.stopPropagation()
setIsAllProviderUpdating(true)
try {
await updateAllProvider()
} catch {}
setIsAllProviderUpdating(false)
}
const [activeTab, setActiveTab] = createSignal(ActiveTab.all)
const tabs = () => [
{
type: ActiveTab.all,
name: t('all'),
count: proxyProviders().length + proxies().length,
},
{
type: ActiveTab.proxyProviders,
name: t('proxyProviders'),
count: proxyProviders().length,
},
{
type: ActiveTab.proxies,
name: t('proxies'),
count: proxies().length,
},
]
return ( return (
<> <div class="flex flex-col gap-2">
<div class="flex flex-col gap-2"> <div class="flex items-center justify-between">
<h1 class="flex h-8 items-center pb-2 text-lg font-semibold"> <div class="tabs tabs-boxed gap-2">
{t('proxies')} <For each={tabs()}>
</h1> {(tab) => (
<button
class={twMerge(
activeTab() === tab.type && 'tab-active',
'tab gap-2',
)}
onClick={() => setActiveTab(tab.type)}
>
<span>{tab.name}</span>
<div class="badge badge-sm">{tab.count}</div>
</button>
)}
</For>
</div>
<Button
class="btn btn-circle btn-sm mr-2"
onClick={(e) => onUpdateAllProviderClick(e)}
>
<IconReload
class={twMerge(
isAllProviderUpdating() && 'animate-spin text-success',
)}
/>
</Button>
</div>
<Show
when={
activeTab() === ActiveTab.all ||
activeTab() === ActiveTab.proxyProviders
}
>
<ForTwoColumns
subChild={proxyProviders().map((proxyProvider) => {
const sortedProxyNames = sortProxiesByOrderingType(
proxyProvider.proxies.map((i) => i.name) ?? [],
latencyMap(),
proxiesOrderingType(),
)
const title = (
<>
<div class="mr-8 flex items-center justify-between">
<span>{proxyProvider.name}</span>
<div>
<Button
class="btn btn-circle btn-sm mr-2"
onClick={(e) =>
onUpdateProviderClick(e, proxyProvider.name)
}
>
<IconReload
class={twMerge(
updatingMap()[proxyProvider.name] &&
'animate-spin text-success',
)}
/>
</Button>
<Button
class="btn btn-circle btn-sm"
onClick={(e) => onHealthCheckClick(e, proxyProvider.name)}
>
<IconBrandSpeedtest
class={twMerge(
healthCheckingMap()[proxyProvider.name] &&
'animate-pulse text-success',
)}
/>
</Button>
</div>
</div>
<SubscriptionInfo
subscriptionInfo={proxyProvider.subscriptionInfo}
/>
<div class="text-sm text-slate-500">
{proxyProvider.vehicleType} :: {t('updated')}{' '}
{formatTimeFromNow(proxyProvider.updatedAt)}
</div>
<Show when={!collapsedMap()[proxyProvider.name]}>
<ProxyNodePreview proxyNameList={sortedProxyNames} />
</Show>
</>
)
const content = <ProxyCardGroups proxyNames={sortedProxyNames} />
return (
<Collapse
isOpen={collapsedMap()[proxyProvider.name]}
title={title}
content={content}
onCollapse={(val) => setCollapsedMap(proxyProvider.name, val)}
/>
)
})}
/>
</Show>
<Show when={activeTab() === ActiveTab.all}>
<div class="divider" />
</Show>
<Show
when={
activeTab() === ActiveTab.all || activeTab() === ActiveTab.proxies
}
>
<ForTwoColumns <ForTwoColumns
subChild={proxies().map((proxy) => { subChild={proxies().map((proxy) => {
const sortedProxyNames = sortProxiesByOrderingType( const sortedProxyNames = sortProxiesByOrderingType(
@ -60,19 +223,21 @@ export default () => {
<span>{proxy.name}</span> <span>{proxy.name}</span>
<Button <Button
class="btn-circle btn-sm" class="btn-circle btn-sm"
onClick={(e) => onSpeedTestClick(e, proxy.name)} onClick={(e) => onLatencyTestClick(e, proxy.name)}
> >
<IconBrandSpeedtest <IconBrandSpeedtest
class={twMerge( class={twMerge(
speedTestingMap()[proxy.name] && latencyTestingMap()[proxy.name] &&
'animate-pulse text-success', 'animate-pulse text-success',
)} )}
/> />
</Button> </Button>
</div> </div>
<div class="text-sm text-slate-500"> <div class="text-sm text-slate-500">
{proxy.type} {proxy.now?.length > 0 && ` :: ${proxy.now}`} {proxy.type} {proxy.now?.length > 0 && ` :: ${proxy.now}`}
</div> </div>
<Show when={!collapsedMap()[proxy.name]}> <Show when={!collapsedMap()[proxy.name]}>
<ProxyNodePreview <ProxyNodePreview
proxyNameList={sortedProxyNames} proxyNameList={sortedProxyNames}
@ -102,11 +267,7 @@ export default () => {
) )
})} })}
/> />
</div>
<Show when={renderProxiesInSamePage()}>
<div class="divider"></div>
<ProxyProvider />
</Show> </Show>
</> </div>
) )
} }

View File

@ -1,139 +0,0 @@
import { useI18n } from '@solid-primitives/i18n'
import { IconBrandSpeedtest, IconReload } from '@tabler/icons-solidjs'
import { Show, createSignal } from 'solid-js'
import { twMerge } from 'tailwind-merge'
import {
Button,
Collapse,
ForTwoColumns,
ProxyCardGroups,
ProxyNodePreview,
SubscriptionInfo,
} from '~/components'
import {
formatTimeFromNow,
sortProxiesByOrderingType,
useStringBooleanMap,
} from '~/helpers'
import { proxiesOrderingType, useProxies } from '~/signals'
export default () => {
const [t] = useI18n()
const {
proxyProviders,
updateProviderByProviderName,
updateAllProvider,
healthCheckByProviderName,
latencyMap,
} = useProxies()
const { map: collapsedMap, set: setCollapsedMap } = useStringBooleanMap()
const { map: healthCheckingMap, setWithCallback: setHealthCheckingMap } =
useStringBooleanMap()
const { map: updateingMap, setWithCallback: setUpdateingMap } =
useStringBooleanMap()
const [allProviderIsUpdating, setAllProviderIsUpdating] = createSignal(false)
const onHealthCheckClick = (e: MouseEvent, name: string) => {
e.stopPropagation()
setHealthCheckingMap(name, () => healthCheckByProviderName(name))
}
const onUpdateProviderClick = (e: MouseEvent, name: string) => {
e.stopPropagation()
setUpdateingMap(name, () => updateProviderByProviderName(name))
}
const onUpdateAllProviderClick = async (e: MouseEvent) => {
e.stopPropagation()
setAllProviderIsUpdating(true)
try {
await updateAllProvider()
} catch {}
setAllProviderIsUpdating(false)
}
return (
<div class="flex flex-col gap-2">
<h1 class="flex h-8 items-center pb-2 text-lg font-semibold">
{t('proxyProviders')}
<Button
class="btn-circle btn-ghost btn-sm ml-2"
onClick={(e) => onUpdateAllProviderClick(e)}
>
<IconReload
class={twMerge(
allProviderIsUpdating() && 'animate-spin text-success',
)}
/>
</Button>
</h1>
<ForTwoColumns
subChild={proxyProviders().map((proxyProvider) => {
const sortedProxyNames = sortProxiesByOrderingType(
proxyProvider.proxies.map((i) => i.name) ?? [],
latencyMap(),
proxiesOrderingType(),
)
const title = (
<>
<div class="mr-8 flex items-center justify-between">
<span>{proxyProvider.name}</span>
<div>
<Button
class="btn btn-circle btn-sm mr-2"
onClick={(e) =>
onUpdateProviderClick(e, proxyProvider.name)
}
>
<IconReload
class={twMerge(
updateingMap()[proxyProvider.name] &&
'animate-spin text-success',
)}
/>
</Button>
<Button
class="btn btn-circle btn-sm"
onClick={(e) => onHealthCheckClick(e, proxyProvider.name)}
>
<IconBrandSpeedtest
class={twMerge(
healthCheckingMap()[proxyProvider.name] &&
'animate-pulse text-success',
)}
/>
</Button>
</div>
</div>
<SubscriptionInfo
subscriptionInfo={proxyProvider.subscriptionInfo}
/>
<div class="text-sm text-slate-500">
{proxyProvider.vehicleType} :: {t('updated')}{' '}
{formatTimeFromNow(proxyProvider.updatedAt)}
</div>
<Show when={!collapsedMap()[proxyProvider.name]}>
<ProxyNodePreview proxyNameList={sortedProxyNames} />
</Show>
</>
)
const content = <ProxyCardGroups proxyNames={sortedProxyNames} />
return (
<Collapse
isOpen={collapsedMap()[proxyProvider.name]}
title={title}
content={content}
onCollapse={(val) => setCollapsedMap(proxyProvider.name, val)}
/>
)
})}
/>
</div>
)
}

View File

@ -45,11 +45,6 @@ export const [renderInTwoColumns, setRenderInTwoColumns] = makePersisted(
createSignal(true), createSignal(true),
{ name: 'renderInTwoColumn', storage: localStorage }, { name: 'renderInTwoColumn', storage: localStorage },
) )
export const [renderProxiesInSamePage, setRenderProxiesInSamePage] =
makePersisted(createSignal(false), {
name: 'renderProxiesInSamePage',
storage: localStorage,
})
export const [ export const [
renderRulesAndProviderInTwoColumns, renderRulesAndProviderInTwoColumns,
setRenderRulesAndProviderInTwoColumns, setRenderRulesAndProviderInTwoColumns,