feat(proxies): auto close connections when proxy selected

This commit is contained in:
Zephyruso 2023-09-08 11:22:16 +08:00
parent 59a7273665
commit 15d20cde4a
9 changed files with 144 additions and 80 deletions

View File

@ -9,12 +9,8 @@ export const formatTimeFromNow = (time: number | string) => {
export const formatProxyType = (type = '') => { export const formatProxyType = (type = '') => {
const t = type.toLowerCase() const t = type.toLowerCase()
if (t === 'shadowsocks') { if (t.includes('shadowsocks')) {
return 'ss' return type.replace('shadowsocks', 'ss') // for both ss and ssr
}
if (t === 'shadowsocksr') {
return 'ssr'
} }
if (t === 'hysteria') { if (t === 'hysteria') {

View File

@ -42,7 +42,7 @@ export default {
off: 'Off', off: 'Off',
proxiesPreviewType: 'Proxies preview type', proxiesPreviewType: 'Proxies preview type',
urlForLatencyTest: 'URL for latency test', urlForLatencyTest: 'URL for latency test',
autoCloseConns: 'Automatically close all connections', autoCloseConns: 'Automatically close connections when proxy is selected',
useTwemoji: 'Use Twemoji Mozilla Font', useTwemoji: 'Use Twemoji Mozilla Font',
autoSwitchTheme: 'Automatically switch theme', autoSwitchTheme: 'Automatically switch theme',
favDayTheme: 'Favorite light theme', favDayTheme: 'Favorite light theme',

View File

@ -42,7 +42,7 @@ export default {
off: '关闭', off: '关闭',
proxiesPreviewType: '节点组预览样式', proxiesPreviewType: '节点组预览样式',
urlForLatencyTest: '测速链接', urlForLatencyTest: '测速链接',
autoCloseConns: '切换代理时自动断开全部连接', autoCloseConns: '切换代理时自动断开连接',
useTwemoji: '使用 Twemoji Mozilla 字体', useTwemoji: '使用 Twemoji Mozilla 字体',
autoSwitchTheme: '自动切换主题', autoSwitchTheme: '自动切换主题',
favDayTheme: '浅色主题偏好', favDayTheme: '浅色主题偏好',

View File

@ -25,8 +25,7 @@ import {
} from '@tanstack/solid-table' } from '@tanstack/solid-table'
import byteSize from 'byte-size' import byteSize from 'byte-size'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { differenceWith } from 'lodash' import { For, createMemo, createSignal } from 'solid-js'
import { For, createEffect, createMemo, createSignal } from 'solid-js'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { Button, ConnectionsTableOrderingModal } from '~/components' import { Button, ConnectionsTableOrderingModal } from '~/components'
import { import {
@ -38,8 +37,8 @@ import { formatTimeFromNow } from '~/helpers'
import { import {
tableSize, tableSize,
tableSizeClassName, tableSizeClassName,
useConnections,
useRequest, useRequest,
useWsRequest,
} from '~/signals' } from '~/signals'
import type { Connection } from '~/types' import type { Connection } from '~/types'
@ -62,63 +61,12 @@ export default () => {
const [search, setSearch] = createSignal('') const [search, setSearch] = createSignal('')
const [activeTab, setActiveTab] = createSignal(ActiveTab.activeConnections) const [activeTab, setActiveTab] = createSignal(ActiveTab.activeConnections)
const {
const connections = useWsRequest<{ connections: Connection[] }>('connections') activeConnectionsWithSpeed,
closedConnectionsWithSpeed,
const [closedConnectionsWithSpeed, setClosedConnectionsWithSpeed] = paused,
createSignal<ConnectionWithSpeed[]>([], { equals: () => paused() }) setPaused,
} = useConnections()
const [activeConnectionsWithSpeed, setActiveConnectionsWithSpeed] =
createSignal<ConnectionWithSpeed[]>([], { equals: () => paused() })
const [paused, setPaused] = createSignal(false)
const updateConnectionsWithSpeed =
(connections: Connection[]) => (prevConnections: ConnectionWithSpeed[]) => {
const prevMap = new Map<string, Connection>()
prevConnections.forEach((prev) => prevMap.set(prev.id, prev))
const connectionWithSpeed = connections.map((connection) => {
const prevConn = prevMap.get(connection.id)
if (!prevConn) {
return { ...connection, downloadSpeed: 0, uploadSpeed: 0 }
}
return {
...connection,
downloadSpeed:
connection.download - (prevConn.download ?? connection.download),
uploadSpeed:
connection.upload - (prevConn.upload ?? connection.upload),
}
})
const closedConnections = differenceWith(
prevConnections,
connectionWithSpeed,
(a, b) => a.id === b.id,
)
setClosedConnectionsWithSpeed((prev) =>
[...prev, ...closedConnections].slice(-1000),
)
return connectionWithSpeed.slice(-200)
}
createEffect(() => {
const connection = connections()?.connections
if (!connection) {
return
}
const updater = updateConnectionsWithSpeed(connection)
setActiveConnectionsWithSpeed(updater)
})
const onCloseConnection = (id: string) => request.delete(`connections/${id}`) const onCloseConnection = (id: string) => request.delete(`connections/${id}`)
const [columnVisibility, setColumnVisibility] = makePersisted( const [columnVisibility, setColumnVisibility] = makePersisted(

View File

@ -13,8 +13,7 @@ import {
createSignal, createSignal,
} from 'solid-js' } from 'solid-js'
import { CHART_MAX_XAXIS, DEFAULT_CHART_OPTIONS } from '~/constants' import { CHART_MAX_XAXIS, DEFAULT_CHART_OPTIONS } from '~/constants'
import { useWsRequest } from '~/signals' import { connections, useWsRequest } from '~/signals'
import type { Connection } from '~/types'
const TrafficWidget: ParentComponent<{ label: JSX.Element }> = (props) => ( const TrafficWidget: ParentComponent<{ label: JSX.Element }> = (props) => (
<div class="stat flex-1 place-items-center"> <div class="stat flex-1 place-items-center">
@ -84,12 +83,6 @@ export default () => {
{ name: t('memory'), data: memories() }, { name: t('memory'), data: memories() },
]) ])
const connection = useWsRequest<{
downloadTotal: number
uploadTotal: number
connections: Connection[]
}>('connections')
return ( return (
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="stats stats-vertical w-full grid-cols-2 bg-primary shadow lg:stats-horizontal lg:flex"> <div class="stats stats-vertical w-full grid-cols-2 bg-primary shadow lg:stats-horizontal lg:flex">
@ -102,15 +95,15 @@ export default () => {
</TrafficWidget> </TrafficWidget>
<TrafficWidget label={t('uploadTotal')}> <TrafficWidget label={t('uploadTotal')}>
{byteSize(connection()?.uploadTotal || 0).toString()} {byteSize(connections()?.uploadTotal || 0).toString()}
</TrafficWidget> </TrafficWidget>
<TrafficWidget label={t('downloadTotal')}> <TrafficWidget label={t('downloadTotal')}>
{byteSize(connection()?.downloadTotal || 0).toString()} {byteSize(connections()?.downloadTotal || 0).toString()}
</TrafficWidget> </TrafficWidget>
<TrafficWidget label={t('activeConnections')}> <TrafficWidget label={t('activeConnections')}>
{connection()?.connections.length || 0} {connections()?.connections.length || 0}
</TrafficWidget> </TrafficWidget>
<TrafficWidget label={t('memoryUsage')}> <TrafficWidget label={t('memoryUsage')}>

113
src/signals/connections.ts Normal file
View File

@ -0,0 +1,113 @@
import { differenceWith, unionWith } from 'lodash'
import {
createEffect,
createMemo,
createResource,
createSignal,
untrack,
} from 'solid-js'
import { Connection, ConnectionWithSpeed } from '~/types'
import { selectedEndpoint, useWsRequest } from './request'
type WsMsg = {
connections: Connection[]
uploadTotal: number
downloadTotal: number
} | null
// we make allconnections global so we can keep track of connections when user in proxy page
// when user selects proxy and close some connections they can back and check connections
// they closed
const [allConnectionsWithSpeed, setAllConnectionsWithSpeed] = createSignal<
ConnectionWithSpeed[]
>([])
const [latestConnectionWsMessage] = createResource(async () => {
await new Promise<void>((resolve) => {
createEffect(() => {
if (selectedEndpoint()) {
resolve()
}
})
})
return useWsRequest<WsMsg>('connections')
})
export const connections = createMemo(() => latestConnectionWsMessage()?.())
export const useConnections = () => {
const [closedConnectionsWithSpeed, setClosedConnectionsWithSpeed] =
createSignal<ConnectionWithSpeed[]>([])
const [activeConnectionsWithSpeed, setActiveConnectionsWithSpeed] =
createSignal<ConnectionWithSpeed[]>([])
const [paused, setPaused] = createSignal(false)
const updateConnectionsWithSpeed = (connections: Connection[]) => {
const prevActiveConnections = activeConnectionsWithSpeed()
const prevMap = new Map<string, Connection>()
prevActiveConnections.forEach((prev) => prevMap.set(prev.id, prev))
const activeConnnections: ConnectionWithSpeed[] = connections.map(
(connection) => {
const prevConn = prevMap.get(connection.id)
if (!prevConn) {
return { ...connection, downloadSpeed: 0, uploadSpeed: 0 }
}
return {
...connection,
downloadSpeed:
connection.download - (prevConn.download ?? connection.download),
uploadSpeed:
connection.upload - (prevConn.upload ?? connection.upload),
}
},
)
const allConnections = unionWith(
allConnectionsWithSpeed(),
activeConnnections,
(a, b) => a.id === b.id,
)
const closedConnections = differenceWith(
allConnections,
activeConnnections,
(a, b) => a.id === b.id,
)
return {
activeConns: activeConnnections.slice(-200),
closedConns: closedConnections.slice(-200),
allConns: allConnections.slice(-400),
}
}
createEffect(() => {
const connection = connections()?.connections
if (!connection) {
return
}
untrack(() => {
const { activeConns, closedConns, allConns } =
updateConnectionsWithSpeed(connection)
if (!paused()) {
setActiveConnectionsWithSpeed(activeConns)
setClosedConnectionsWithSpeed(closedConns)
}
setAllConnectionsWithSpeed(allConns)
})
})
return {
closedConnectionsWithSpeed,
activeConnectionsWithSpeed,
paused,
setPaused,
}
}

View File

@ -1,4 +1,5 @@
export * from './config' export * from './config'
export * from './connections'
export * from './proxies' export * from './proxies'
export * from './request' export * from './request'
export * from './rules' export * from './rules'

View File

@ -6,6 +6,7 @@ import {
useRequest, useRequest,
} from '~/signals' } from '~/signals'
import type { Proxy, ProxyNode, ProxyProvider } from '~/types' import type { Proxy, ProxyNode, ProxyProvider } from '~/types'
import { useConnections } from './connections'
type ProxyInfo = { type ProxyInfo = {
name: string name: string
@ -46,6 +47,7 @@ const setProxiesInfo = (proxies: (Proxy | ProxyNode)[]) => {
export const useProxies = () => { export const useProxies = () => {
const request = useRequest() const request = useRequest()
const { activeConnectionsWithSpeed } = useConnections()
const updateProxies = async () => { const updateProxies = async () => {
const [{ providers }, { proxies }] = await Promise.all([ const [{ providers }, { proxies }] = await Promise.all([
@ -80,7 +82,13 @@ export const useProxies = () => {
const proxyGroupList = proxies().slice() const proxyGroupList = proxies().slice()
const proxyGroup = proxyGroupList.find((i) => i.name === proxy.name)! const proxyGroup = proxyGroupList.find((i) => i.name === proxy.name)!
if (autoCloseConns()) request.delete('connections') if (autoCloseConns()) {
activeConnectionsWithSpeed().forEach(({ id, chains }) => {
if (chains.includes(proxy.name)) {
request.delete(`connections/${id}`)
}
})
}
await request.put(`proxies/${proxy.name}`, { await request.put(`proxies/${proxy.name}`, {
body: JSON.stringify({ body: JSON.stringify({

View File

@ -100,6 +100,11 @@ export type Connection = {
} }
} }
export type ConnectionWithSpeed = Connection & {
downloadSpeed: number
uploadSpeed: number
}
export type Log = { export type Log = {
type: string type: string
payload: string payload: string