Compare commits

..
Author SHA1 Message Date
Tunglies c576122a4c perf(proxies): reduce delay-driven list and card churn 2026-11-11 05:59:37 +08:00
24 changed files with 302 additions and 123 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
"features": {
"ghcr.io/devcontainers/features/node:2": {
"version": "26.13.1",
"version": "26.13.0",
"pnpmVersion": "12.18.0"
},
"ghcr.io/devcontainers/features/rust:1": {
+5 -5
View File
@@ -51,7 +51,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -182,7 +182,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
cache: 'pnpm'
- name: Pnpm install and check
@@ -262,7 +262,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
cache: 'pnpm'
- name: Pnpm install and check
@@ -395,7 +395,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
cache: 'pnpm'
- name: Pnpm install and check
@@ -479,7 +479,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
- uses: pnpm/action-setup@v6.1.1
name: Install pnpm
+1 -1
View File
@@ -63,7 +63,7 @@ jobs:
- uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
+2 -2
View File
@@ -33,7 +33,7 @@ jobs:
- name: Install gh-aw extension
uses: github/gh-aw-actions/setup-cli@53d0749e19424a918acd82b4d140264ce48f2f14 # v1.2.2
with:
version: v1.2.2
version: v1.0.1
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
with:
@@ -49,7 +49,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
cache: 'pnpm'
- name: Install frontend dependencies
env:
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
- uses: pnpm/action-setup@v6
name: Install pnpm
+1 -1
View File
@@ -104,7 +104,7 @@ jobs:
if: github.event.inputs[matrix.input] == 'true'
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
cache: 'pnpm'
- name: Pnpm install and check
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
- uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
+1 -1
View File
@@ -61,7 +61,7 @@ jobs:
- uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
- uses: pnpm/action-setup@v6
if: github.event.action != 'closed'
+8 -8
View File
@@ -46,7 +46,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
- name: Validate source and publish the approved snapshot
id: source
env:
@@ -231,7 +231,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
cache: 'pnpm'
- name: Pnpm install and check
@@ -330,7 +330,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
cache: 'pnpm'
- name: Pnpm install and check
@@ -461,7 +461,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
cache: 'pnpm'
- name: Pnpm install and check
@@ -542,7 +542,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
- uses: pnpm/action-setup@v6
with:
run_install: false
@@ -565,7 +565,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
- uses: pnpm/action-setup@v6
name: Install pnpm
@@ -593,7 +593,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
- uses: pnpm/action-setup@v6
name: Install pnpm
@@ -661,7 +661,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
- uses: pnpm/action-setup@v6
name: Install pnpm
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
- name: Install pnpm
uses: pnpm/action-setup@v6
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
- uses: pnpm/action-setup@v6
name: Install pnpm
+2 -2
View File
@@ -15,7 +15,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
- uses: pnpm/action-setup@v6
name: Install pnpm
@@ -39,7 +39,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v7
with:
node-version: '26.13.1'
node-version: '26.13.0'
- uses: pnpm/action-setup@v6
name: Install pnpm
+1 -1
View File
@@ -1,3 +1,3 @@
nodejs 26.13.1
nodejs 26.13.0
pnpm 12.18.0
rust 1.99.1
+1 -3
View File
@@ -24,8 +24,6 @@
<details>
<summary><strong> 🐞 修复问题 </strong></summary>
- 修复应用退出时无条件重写 `clash.yaml`、`verge.yaml` 和 `profiles.yaml` 导致文件创建时间丢失的问题
- 修复检测更新后又发布新版时,应用内更新失败
- 修复订阅响应过大导致内存占用过高,限制单次下载为 4 MiB
- 修复全局模式默认停在 `DIRECT` 或 `REJECT` 导致流量未经代理,现自动切换到第一个策略组
@@ -42,7 +40,6 @@
<details>
<summary><strong> ✨ 新增功能 </strong></summary>
- 新增连接页面按网络协议(TCP/UDP)筛选连接
</details>
@@ -51,6 +48,7 @@
- 优化连接、规则和日志筛选:切换页面后保留过滤条件
- 优化全局模式代理列表:仅显示 `GLOBAL` 节点,选中其中的策略组时才显示该组
- 优化代理页面和首页节点卡片,减少延迟更新时的卡顿
**🍎 macOS**
+4 -8
View File
@@ -74,16 +74,12 @@ impl<T: Clone> Draft<T> {
result
}
/// Commits and clears the draft, reporting whether one was staged.
/// Commits and clears the draft.
#[inline]
pub fn apply(&self) -> bool {
pub fn apply(&self) {
let mut guard = self.inner.data.lock();
match guard.1.take() {
Some(d) => {
guard.0 = d;
true
}
None => false,
if let Some(d) = guard.1.take() {
guard.0 = d;
}
}
+6 -10
View File
@@ -280,29 +280,25 @@ impl Config {
}
/// Commits drafts during exit/restart/shutdown so user changes are not lost.
/// A config with nothing staged keeps its file, so its timestamps stay put.
pub async fn apply_all_and_save_file() {
logging!(debug, Type::Config, "save all draft data");
let save_clash_task = AsyncHandler::spawn(|| async {
let clash = Self::clash().await;
if clash.apply() {
logging_error!(Type::Config, clash.data_arc().save_config().await);
}
clash.apply();
logging_error!(Type::Config, clash.data_arc().save_config().await);
});
let save_verge_task = AsyncHandler::spawn(|| async {
let verge = Self::verge().await;
if verge.apply() {
logging_error!(Type::Config, verge.data_arc().save_file().await);
}
verge.apply();
logging_error!(Type::Config, verge.data_arc().save_file().await);
});
let save_profiles_task = AsyncHandler::spawn(|| async {
let _profile_write = super::profiles::PROFILE_WRITE_LOCK.lock().await;
let profiles = Self::profiles().await;
if profiles.apply() {
logging_error!(Type::Config, profiles.data_arc().save_file().await);
}
profiles.apply();
logging_error!(Type::Config, profiles.data_arc().save_file().await);
});
let _ = tokio::join!(save_clash_task, save_verge_task, save_profiles_task);
+16 -8
View File
@@ -605,12 +605,14 @@ export const CurrentProxyCard = () => {
[getProfileStorageKey],
)
const { select } = useProxySelection({
onError: (error) => {
const onSelectionError = useCallback(
(error: unknown) => {
console.error('代理切换失败', error)
refreshProxy()
},
})
[refreshProxy],
)
const { select } = useProxySelection({ onError: onSelectionError })
const mode = clashConfig?.mode?.toLowerCase() || 'rule'
const isGlobalMode = mode === 'global'
@@ -673,7 +675,6 @@ export const CurrentProxyCard = () => {
if (proxyView && lastGroupName !== selectedGroupName) {
setLastGroupName(selectedGroupName)
}
const delays = useGroupDelays(selectedGroupName || null)
const selectedGroup = useMemo<ProxyGroupView | null>(() => {
if (!proxyView || isDirectMode) return null
@@ -767,10 +768,17 @@ export const CurrentProxyCard = () => {
const currentProxy = currentMember ? memberDetails(currentMember) : undefined
const selectedProxyName = currentMember?.ref.name ?? ''
const delays = useGroupDelays(
selectedGroupName,
currentMember,
openSelect === 'proxy',
)
const currentDelay =
currentMember && selectedGroupName
? delayManager.getDelayFix(currentMember, selectedGroupName)
: -1
typeof delays === 'number'
? delays
: currentMember
? delays.of(currentMember)
: -1
const signalInfo =
currentProxy && selectedGroupName
@@ -892,7 +900,7 @@ export const CurrentProxyCard = () => {
const proxyOptions = useMemo(
() =>
isDirectMode || openSelect !== 'proxy'
isDirectMode || openSelect !== 'proxy' || typeof delays === 'number'
? []
: sortProxyOptions(
unsortedProxyOptions,
+5 -3
View File
@@ -469,12 +469,14 @@ function NormalProxyGroups(props: { mode: string }) {
}
}, [handleScroll])
const { select } = useProxySelection({
onError: (error) => {
const onSelectionError = useCallback(
(error: unknown) => {
console.error('代理切换失败', error)
onProxies()
},
})
[onProxies],
)
const { select } = useProxySelection({ onError: onSelectionError })
const handleChangeProxy = useCallback(
(group: ProxyGroupView, member: ResolvedProxyMember) => {
+3
View File
@@ -29,6 +29,9 @@ export function filterSort(
const regex1 = /delay([=<>])(\d+|timeout|error)/i
const regex2 = /type=(.*)/i
export const isDelayFilter = (filterText: string) =>
regex1.test(filterText.trim())
function filterProxies(
proxies: ResolvedMemberOccurrence[],
groupName: string,
@@ -0,0 +1,119 @@
import { expect, it, vi } from 'vitest'
import type { DelaySnapshot } from '@/services/delay'
import { resolveMember, type ProxyViewV1 } from '@/types/proxy-view'
import { filterSort } from './use-filter-sort'
import { useRenderList as readRenderList } from './use-render-list'
const hooks = vi.hoisted(() => {
const slots: { value: unknown; deps?: unknown[] }[] = []
let cursor = 0
return {
reset: () => {
cursor = 0
},
memo: (factory: () => unknown, deps: unknown[]) => {
const index = cursor++
const old = slots[index]
if (!old || deps.some((dep, i) => !Object.is(dep, old.deps?.[i]))) {
slots[index] = { value: factory(), deps }
}
return slots[index].value
},
ref: (current: unknown) => {
const index = cursor++
slots[index] ??= { value: { current } }
return slots[index].value
},
}
})
const state = vi.hoisted(() => ({
view: null as ProxyViewV1 | null,
delays: new Map<string, DelaySnapshot>(),
}))
vi.mock('react', () => ({
useMemo: hooks.memo,
useRef: hooks.ref,
useEffect: () => {},
}))
vi.mock('@/hooks/use-profiles', () => ({}))
vi.mock('@/hooks/use-clash', () => ({
useRuntimeConfig: () => ({ data: null }),
}))
vi.mock('@/hooks/use-verge', () => ({
useVerge: () => ({ verge: { proxy_layout_column: 1 } }),
}))
vi.mock('@/providers/app-data-context', () => ({
useProxiesData: () => ({ proxyView: state.view }),
useAppRefreshers: () => ({ refreshProxy: () => {} }),
}))
vi.mock('@/hooks/use-group-delays', () => ({
useGroupsDelays: (names: string[]) =>
new Map(names.map((name) => [name, state.delays.get(name)])),
}))
vi.mock('./use-window-width', () => ({
useWindowWidth: () => ({ width: 800 }),
}))
vi.mock('./use-head-state', async (original) => {
const actual = await original<typeof import('./use-head-state')>()
const heads = {
plain: { ...actual.DEFAULT_STATE, open: true },
sorted: { ...actual.DEFAULT_STATE, open: true, sortType: 1 },
}
return { ...actual, useHeadStateNew: () => [heads, () => {}] }
})
vi.mock('./use-filter-sort', async (original) => {
const actual = await original<typeof import('./use-filter-sort')>()
return { ...actual, filterSort: vi.fn(actual.filterSort) }
})
vi.mock('@/types/proxy-view', async (original) => {
const actual = await original<typeof import('@/types/proxy-view')>()
return { ...actual, resolveMember: vi.fn(actual.resolveMember) }
})
it('skips delay-independent projections and reuses members for delay sorting', () => {
const group = (name: string) => ({
name,
type: 'Selector',
history: [],
udp: false,
xudp: false,
tfo: false,
mptcp: false,
smux: false,
members: Array.from({ length: 500 }, (_, i) => ({
kind: 'unresolved' as const,
name: `${i}`,
reason: 'missing' as const,
})),
})
state.view = {
schemaVersion: 1,
orderSource: 'runtime',
providerState: 'ready',
global: null,
direct: null,
groups: [group('plain'), group('sorted')],
records: {},
standalone: [],
providers: [],
}
const render = () => {
hooks.reset()
return readRenderList('rule')
}
state.delays.set('plain', { of: () => 42 })
state.delays.set('sorted', { of: () => 42 })
render()
vi.mocked(filterSort).mockClear()
vi.mocked(resolveMember).mockClear()
state.delays.set('plain', { of: () => 43 })
render()
expect(filterSort).toHaveBeenCalledTimes(0)
state.delays.set('sorted', { of: () => 43 })
render()
expect(filterSort).toHaveBeenCalledTimes(1)
expect(resolveMember).toHaveBeenCalledTimes(0)
})
+58 -17
View File
@@ -17,7 +17,7 @@ import {
} from '@/types/proxy-view'
import { debugLog } from '@/utils/debug'
import { filterSort } from './use-filter-sort'
import { filterSort, isDelayFilter } from './use-filter-sort'
import {
DEFAULT_STATE,
useHeadStateNew,
@@ -60,14 +60,14 @@ export const hasRenderableItems = (
): boolean => renderList.some((item) => item.type !== 0 || !item.group.hidden)
type GroupCache = {
now: string | undefined
members: ProxyGroupView['members']
group: ProxyGroupView
headState: HeadState
col: number
latencyTimeout: number | undefined
/// This group's own delays. Compared by identity so that a test settling in one group
/// does not throw away every other group's sorted order.
delays: DelaySnapshot | undefined
occurrences: ResolvedMemberOccurrence[] | undefined
items: IRenderItem[]
}
@@ -222,19 +222,51 @@ export const useRenderList = (
return current?.kind === 'group' ? [global, current.group] : [global]
}, [mode, proxyView])
// Every group this list draws, so a test settling in any of them re-sorts that group.
const repeatedNameGroups = useMemo(() => {
const repeated = new Set<string>()
if (isChainMode) return repeated
for (const group of renderGroups) {
const names = new Set<string>()
if (
group.members.some(({ name }) => {
if (names.has(name)) return true
names.add(name)
return false
})
) {
repeated.add(group.name)
}
}
return repeated
}, [isChainMode, renderGroups])
// Rows own delay updates unless repeated names share the current item listener.
const renderedGroupNames = useMemo(() => {
if (!proxyView) return []
if (isChainMode)
return selectedGroup ? [selectedGroup] : [CHAIN_DELAY_GROUP]
return renderGroups.map(({ name }) => name)
}, [isChainMode, proxyView, renderGroups, selectedGroup])
if (!proxyView || isChainMode) return []
return renderGroups
.filter((group) => {
const state = headStates[group.name] || DEFAULT_STATE
return (
(state.open || group === proxyView.global) &&
(state.sortType === 1 ||
isDelayFilter(state.filterText) ||
repeatedNameGroups.has(group.name))
)
})
.map(({ name }) => name)
}, [headStates, isChainMode, proxyView, renderGroups, repeatedNameGroups])
const groupDelays = useGroupsDelays(renderedGroupNames)
const groupCacheRef = useRef<Map<string, GroupCache>>(new Map())
const groupCacheRef = useRef<Map<string, GroupCache> | null>(null)
const cachedViewRef = useRef(proxyView)
const prevListRef = useRef<IRenderItem[]>([])
const renderList = useMemo<IRenderItem[]>(() => {
const cache = (groupCacheRef.current ??= new Map())
if (cachedViewRef.current !== proxyView) {
cache.clear()
cachedViewRef.current = proxyView
}
if (!proxyView) return []
if (isChainMode) {
@@ -269,16 +301,13 @@ export const useRenderList = (
}))
}
const cache = groupCacheRef.current
const retList = renderGroups.flatMap((group) => {
const isFlat = group === proxyView.global
const headState = headStates[group.name] || DEFAULT_STATE
const cached = cache.get(group.name)
if (
cached &&
cached.now === group.now &&
cached.members === group.members &&
cached.group === group &&
cached.headState === headState &&
cached.col === col &&
cached.latencyTimeout === latencyTimeout &&
@@ -300,9 +329,18 @@ export const useRenderList = (
},
]
// Duplicate names share one item listener until the delay-store fix lands.
const refreshRepeated =
repeatedNameGroups.has(group.name) &&
cached?.delays !== groupDelays.get(group.name)
let resolved =
cached?.group === group && !refreshRepeated
? cached.occurrences
: undefined
if (headState.open || isFlat) {
resolved ??= resolveOccurrences(proxyView, group)
const occurrences = filterSort(
resolveOccurrences(proxyView, group),
resolved,
group.name,
headState.filterText,
headState.sortType,
@@ -337,15 +375,17 @@ export const useRenderList = (
})),
)
}
} else {
resolved = undefined
}
cache.set(group.name, {
now: group.now!,
members: group.members,
group,
headState,
col,
latencyTimeout: latencyTimeout!,
delays: groupDelays.get(group.name),
occurrences: resolved,
items: ret,
})
return ret
@@ -371,6 +411,7 @@ export const useRenderList = (
mode,
proxyView,
renderGroups,
repeatedNameGroups,
selectedGroup,
])
+45
View File
@@ -0,0 +1,45 @@
import { useSyncExternalStore } from 'react'
import { expect, it, vi } from 'vitest'
import delayManager from '@/services/delay'
import type { ResolvedProxyMember } from '@/types/proxy-view'
import { useGroupDelays } from './use-group-delays'
vi.mock('react', () => ({
useCallback: (callback: unknown) => callback,
useSyncExternalStore: vi.fn((_subscribe, read) => read()),
}))
it('keeps the closed-menu snapshot unchanged when another member settles', () => {
const member: ResolvedProxyMember = {
kind: 'node',
ref: { kind: 'node', name: 'current', recordId: 'current' },
node: {
recordId: 'current',
name: 'current',
type: 'Shadowsocks',
source: { kind: 'core', proxyName: 'current' },
history: [],
udp: false,
xudp: false,
tfo: false,
mptcp: false,
smux: false,
},
}
vi.spyOn(delayManager, 'getDelayFix').mockReturnValue(42)
const snapshot = vi.spyOn(delayManager, 'groupDelays')
snapshot.mockReturnValue({ of: () => 42 })
useGroupDelays('group', member, false)
const [, read] = vi.mocked(useSyncExternalStore).mock.calls.at(-1)!
let previous = read()
let changes = 0
for (let i = 0; i < 120; i++) {
snapshot.mockReturnValue({ of: () => 42 })
const next = read()
if (!Object.is(next, previous)) changes++
previous = next
}
expect(changes).toBe(0)
})
+14 -6
View File
@@ -1,19 +1,27 @@
import { useCallback, useSyncExternalStore } from 'react'
import delayManager, { type DelaySnapshot } from '@/services/delay'
import type { ResolvedProxyMember } from '@/types/proxy-view'
const NO_DELAYS: DelaySnapshot = { of: () => -1 }
/** Exposes the external delay store to React, updating sort order only after a test settles. */
export const useGroupDelays = (group: string | null): DelaySnapshot => {
/** A closed menu observes only its current member, not every settled group delay. */
export const useGroupDelays = (
group: string | null,
member: ResolvedProxyMember | undefined,
menuOpen: boolean,
): DelaySnapshot | number => {
const subscribe = useCallback(
(onSettle: () => void) =>
group ? delayManager.addGroupListener(group, onSettle) : () => {},
[group],
)
const read = useCallback(
() => (group ? delayManager.groupDelays(group) : NO_DELAYS),
[group],
() =>
group && menuOpen
? delayManager.groupDelays(group)
: group && member
? delayManager.getDelayFix(member, group)
: -1,
[group, member, menuOpen],
)
return useSyncExternalStore(subscribe, read, read)
+5 -42
View File
@@ -71,10 +71,6 @@ const ORDER_OPTIONS = [
type OrderKey = (typeof ORDER_OPTIONS)[number]['id']
type NetworkFilter = 'all' | 'tcp' | 'udp'
let savedNetworkFilter: NetworkFilter = 'all'
const orderFunctionMap = ORDER_OPTIONS.reduce<Record<OrderKey, OrderFunc>>(
(acc, option) => {
acc[option.id] = option.fn
@@ -90,9 +86,6 @@ const ConnectionsPage = () => {
const { searchState, match, onSearch } = usePageSearch('connections')
const hasSearch = searchState.text.length > 0
const [curOrderOpt, setCurOrderOpt] = useState<OrderKey>('default')
const [networkFilter, setNetworkFilter] =
useState<NetworkFilter>(savedNetworkFilter)
const hasFilter = networkFilter !== 'all'
const [connectionsType, setConnectionsType] = useState<'active' | 'closed'>(
'active',
)
@@ -119,30 +112,18 @@ const ConnectionsPage = () => {
const filterConn = useMemo(() => {
const orderFunc = orderFunctionMap[curOrderOpt]
if (isTableLayout && !hasSearch && !hasFilter) return selectedConnections
if (!hasSearch && !hasFilter) return orderFunc([...selectedConnections])
if (isTableLayout && !hasSearch) return selectedConnections
if (!hasSearch) return orderFunc([...selectedConnections])
const matchConns = selectedConnections.filter((conn) => {
const { host, destinationIP, process, network } = conn.metadata
if (networkFilter !== 'all' && network !== networkFilter) return false
const { host, destinationIP, process } = conn.metadata
return (
!hasSearch ||
match(host || '') ||
match(destinationIP || '') ||
match(process || '')
match(host || '') || match(destinationIP || '') || match(process || '')
)
})
return orderFunc ? orderFunc(matchConns) : matchConns
}, [
selectedConnections,
isTableLayout,
hasSearch,
hasFilter,
networkFilter,
match,
curOrderOpt,
])
}, [selectedConnections, isTableLayout, hasSearch, match, curOrderOpt])
const displayRows = useConnectionRowViews(
isTableLayout ? EMPTY_CONNECTIONS : filterConn,
@@ -274,24 +255,6 @@ const ConnectionsPage = () => {
))}
</BaseStyledSelect>
)}
<BaseStyledSelect
style={{ width: 80 }}
value={networkFilter}
onChange={(e) => {
savedNetworkFilter = e.target.value as NetworkFilter
setNetworkFilter(savedNetworkFilter)
}}
>
<MenuItem value="all">
<span style={{ fontSize: 14 }}>All</span>
</MenuItem>
<MenuItem value="tcp">
<span style={{ fontSize: 14 }}>TCP</span>
</MenuItem>
<MenuItem value="udp">
<span style={{ fontSize: 14 }}>UDP</span>
</MenuItem>
</BaseStyledSelect>
<Box
sx={{
flex: 1,