diff --git a/src/components/proxy/proxy-group.tsx b/src/components/proxy/proxy-group.tsx index 2638660..addca60 100644 --- a/src/components/proxy/proxy-group.tsx +++ b/src/components/proxy/proxy-group.tsx @@ -1,5 +1,6 @@ import { useRef, useState } from "react"; -import { Virtuoso, VirtuosoHandle } from "react-virtuoso"; +import { useSWRConfig } from "swr"; +import { Virtuoso } from "react-virtuoso"; import { Box, Collapse, @@ -19,6 +20,7 @@ import { import { ApiType } from "../../services/types"; import { updateProxy } from "../../services/api"; import { getProfiles, patchProfile } from "../../services/cmds"; +import delayManager from "../../services/delay"; import ProxyItem from "./proxy-item"; interface Props { @@ -26,13 +28,15 @@ interface Props { } const ProxyGroup = ({ group }: Props) => { + const { mutate } = useSWRConfig(); + const listRef = useRef(); const [open, setOpen] = useState(false); const [now, setNow] = useState(group.now); const proxies = group.all ?? []; - const onUpdate = async (name: string) => { + const onSelect = async (name: string) => { // can not call update if (group.type !== "Selector") { // Todo @@ -80,6 +84,21 @@ const ProxyGroup = ({ group }: Props) => { } }; + const onCheckAll = async () => { + let names = proxies.map((p) => p.name); + + while (names.length) { + const list = names.slice(0, 10); + names = names.slice(10); + + await Promise.all( + list.map((n) => delayManager.checkDelay(n, group.name)) + ); + + mutate("getProxies"); + } + }; + return ( <> setOpen(!open)} dense> @@ -104,7 +123,7 @@ const ProxyGroup = ({ group }: Props) => { - + @@ -116,10 +135,11 @@ const ProxyGroup = ({ group }: Props) => { totalCount={proxies.length} itemContent={(index) => ( )} /> @@ -132,10 +152,11 @@ const ProxyGroup = ({ group }: Props) => { {proxies.map((proxy) => ( ))} diff --git a/src/components/proxy/proxy-item.tsx b/src/components/proxy/proxy-item.tsx index b8bb337..88d90cc 100644 --- a/src/components/proxy/proxy-item.tsx +++ b/src/components/proxy/proxy-item.tsx @@ -1,24 +1,51 @@ +import { useEffect, useState } from "react"; import { CheckCircleOutlineRounded } from "@mui/icons-material"; import { alpha, + Box, ListItem, ListItemButton, ListItemIcon, ListItemText, + styled, SxProps, Theme, } from "@mui/material"; import { ApiType } from "../../services/types"; +import delayManager from "../../services/delay"; interface Props { + groupName: string; proxy: ApiType.ProxyItem; selected: boolean; sx?: SxProps; onClick?: (name: string) => void; } +const Widget = styled(Box)(() => ({ + padding: "4px 6px", + fontSize: 14, +})); + const ProxyItem = (props: Props) => { - const { proxy, selected, sx, onClick } = props; + const { groupName, proxy, selected, sx, onClick } = props; + const [delay, setDelay] = useState(-1); + + useEffect(() => { + if (proxy) { + setDelay(delayManager.getDelay(proxy.name, groupName)); + } + }, [proxy]); + + const onDelay = (e: any) => { + e.preventDefault(); + e.stopPropagation(); + + delayManager + .checkDelay(proxy.name, groupName) + .then((result) => setDelay(result)) + .catch(() => setDelay(1e6)); + }; return ( @@ -27,9 +54,7 @@ const ProxyItem = (props: Props) => { selected={selected} onClick={() => onClick?.(proxy.name)} sx={[ - { - borderRadius: 1, - }, + { borderRadius: 1 }, ({ palette: { mode, primary } }) => { const bgcolor = mode === "light" @@ -37,7 +62,16 @@ const ProxyItem = (props: Props) => { : alpha(primary.main, 0.35); const color = mode === "light" ? primary.main : primary.light; + const showDelay = delay > 0; + const showIcon = !showDelay && selected; + return { + ".the-check": { display: "none" }, + ".the-delay": { display: showDelay ? "block" : "none" }, + ".the-icon": { display: showIcon ? "block" : "none" }, + "&:hover .the-check": { display: !showDelay ? "block" : "none" }, + "&:hover .the-delay": { display: showDelay ? "block" : "none" }, + "&:hover .the-icon": { display: "none" }, "&.Mui-selected": { bgcolor }, "&.Mui-selected .MuiListItemText-secondary": { color }, }; @@ -45,10 +79,32 @@ const ProxyItem = (props: Props) => { ]} > + - {selected && } + + Check + + + 500 + ? "error.main" + : delay < 100 + ? "success.main" + : "text.secondary" + } + > + {delay > 1e5 ? "Error" : delay > 3000 ? "Timeout" : `${delay}ms`} + + + diff --git a/src/pages/proxies.tsx b/src/pages/proxies.tsx index adb1892..c95b447 100644 --- a/src/pages/proxies.tsx +++ b/src/pages/proxies.tsx @@ -114,6 +114,7 @@ const ProxyPage = () => { totalCount={filterProxies.length} itemContent={(index) => ( ; } +/// Get Proxy delay +export async function getProxyDelay( + name: string, + url?: string +): Promise<{ delay: number }> { + const params = { + timeout: 3000, + url: url || "http://www.gstatic.com/generate_204", + }; + + const instance = await getAxios(); + return instance.get(`/proxies/${encodeURIComponent(name)}/delay`, { params }); +} + /// Update the Proxy Choose export async function updateProxy(group: string, proxy: string) { const instance = await getAxios(); - return instance.put(`/proxies/${group}`, { name: proxy }); + return instance.put(`/proxies/${encodeURIComponent(group)}`, { name: proxy }); } /// Get the Proxy infomation diff --git a/src/services/delay.ts b/src/services/delay.ts new file mode 100644 index 0000000..8b2b8f0 --- /dev/null +++ b/src/services/delay.ts @@ -0,0 +1,37 @@ +import { getProxyDelay } from "./api"; + +const hashKey = (name: string, group: string) => `${group ?? ""}::${name}`; + +class DelayManager { + private cache = new Map(); + + setDelay(name: string, group: string, delay: number) { + this.cache.set(hashKey(name, group), [Date.now(), delay]); + } + + getDelay(name: string, group: string) { + if (!name) return -1; + + const result = this.cache.get(hashKey(name, group)); + if (result && Date.now() - result[0] <= 18e5) { + return result[1]; + } + return -1; + } + + async checkDelay(name: string, group: string) { + let delay = -1; + + try { + const result = await getProxyDelay(name); + delay = result.delay; + } catch { + delay = 1e6; // error + } + + this.setDelay(name, group, delay); + return delay; + } +} + +export default new DelayManager();