From 71e69003757201207cfbd35fdb0ddea0faf172a3 Mon Sep 17 00:00:00 2001 From: GyDi <segydi@foxmail.com> Date: Sun, 4 Sep 2022 22:55:54 +0800 Subject: [PATCH] feat: compatible with proxy providers health check --- src/components/proxy/proxy-global.tsx | 21 +++++-- src/components/proxy/proxy-group.tsx | 22 +++++-- src/components/proxy/proxy-item.tsx | 52 ++++++++++------ src/services/api.ts | 87 ++++++++++++++++++--------- src/services/types.d.ts | 9 +++ 5 files changed, 136 insertions(+), 55 deletions(-) diff --git a/src/components/proxy/proxy-global.tsx b/src/components/proxy/proxy-global.tsx index 9053cd4..a6f5d46 100644 --- a/src/components/proxy/proxy-global.tsx +++ b/src/components/proxy/proxy-global.tsx @@ -2,7 +2,7 @@ import useSWR, { useSWRConfig } from "swr"; import { useEffect, useRef, useState } from "react"; import { useLockFn } from "ahooks"; import { Virtuoso } from "react-virtuoso"; -import { updateProxy } from "@/services/api"; +import { providerHealthCheck, updateProxy } from "@/services/api"; import { getProfiles, patchProfile } from "@/services/cmds"; import delayManager from "@/services/delay"; import useSortProxy from "./use-sort-proxy"; @@ -74,10 +74,23 @@ const ProxyGlobal = (props: Props) => { }; const onCheckAll = useLockFn(async () => { - const names = sortedProxies.map((p) => p.name); + const providers = new Set( + sortedProxies.map((p) => p.provider!).filter(Boolean) + ); + + if (providers.size) { + Promise.allSettled( + [...providers].map((p) => providerHealthCheck(p)) + ).then(() => mutate("getProxies")); + } - await delayManager.checkListDelay({ names, groupName, skipNum: 8 }, () => - mutate("getProxies") + await delayManager.checkListDelay( + { + names: sortedProxies.filter((p) => !p.provider).map((p) => p.name), + groupName, + skipNum: 16, + }, + () => mutate("getProxies") ); }); diff --git a/src/components/proxy/proxy-group.tsx b/src/components/proxy/proxy-group.tsx index 716b0ba..eb1b4c0 100644 --- a/src/components/proxy/proxy-group.tsx +++ b/src/components/proxy/proxy-group.tsx @@ -15,7 +15,7 @@ import { ExpandLessRounded, ExpandMoreRounded, } from "@mui/icons-material"; -import { updateProxy } from "@/services/api"; +import { providerHealthCheck, updateProxy } from "@/services/api"; import { getProfiles, patchProfile } from "@/services/cmds"; import delayManager from "@/services/delay"; import useSortProxy from "./use-sort-proxy"; @@ -94,11 +94,23 @@ const ProxyGroup = ({ group }: Props) => { }; const onCheckAll = useLockFn(async () => { - const names = sortedProxies.map((p) => p.name); - const groupName = group.name; + const providers = new Set( + sortedProxies.map((p) => p.provider!).filter(Boolean) + ); + + if (providers.size) { + Promise.allSettled( + [...providers].map((p) => providerHealthCheck(p)) + ).then(() => mutate("getProxies")); + } - await delayManager.checkListDelay({ names, groupName, skipNum: 16 }, () => - mutate("getProxies") + await delayManager.checkListDelay( + { + names: sortedProxies.filter((p) => !p.provider).map((p) => p.name), + groupName: group.name, + skipNum: 16, + }, + () => mutate("getProxies") ); }); diff --git a/src/components/proxy/proxy-item.tsx b/src/components/proxy/proxy-item.tsx index 60d8824..7c7c07d 100644 --- a/src/components/proxy/proxy-item.tsx +++ b/src/components/proxy/proxy-item.tsx @@ -46,8 +46,17 @@ const ProxyItem = (props: Props) => { const [delay, setDelay] = useState(-1); useEffect(() => { - if (proxy) { + if (!proxy) return; + + if (!proxy.provider) { setDelay(delayManager.getDelay(proxy.name, groupName)); + return; + } + + const { history = [] } = proxy; + if (history.length > 0) { + // 0ms以error显示 + setDelay(history[history.length - 1].delay || 1e6); } }, [proxy]); @@ -95,6 +104,9 @@ const ProxyItem = (props: Props) => { <> {proxy.name} + {showType && !!proxy.provider && ( + <TypeBox component="span">{proxy.provider}</TypeBox> + )} {showType && <TypeBox component="span">{proxy.type}</TypeBox>} {showType && proxy.udp && <TypeBox component="span">UDP</TypeBox>} </> @@ -104,23 +116,27 @@ const ProxyItem = (props: Props) => { <ListItemIcon sx={{ justifyContent: "flex-end", color: "primary.main" }} > - <Widget - className="the-check" - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - onDelay(); - }} - sx={(theme) => ({ - ":hover": { bgcolor: alpha(theme.palette.primary.main, 0.15) }, - })} - > - Check - </Widget> + {!proxy.provider && ( + <Widget + className="the-check" + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + onDelay(); + }} + sx={(theme) => ({ + ":hover": { bgcolor: alpha(theme.palette.primary.main, 0.15) }, + })} + > + Check + </Widget> + )} <Widget className="the-delay" onClick={(e) => { + if (proxy.provider) return; + e.preventDefault(); e.stopPropagation(); onDelay(); @@ -132,9 +148,11 @@ const ProxyItem = (props: Props) => { ? "success.main" : "text.secondary" } - sx={(theme) => ({ - ":hover": { bgcolor: alpha(theme.palette.primary.main, 0.15) }, - })} + sx={({ palette }) => + !proxy.provider + ? { ":hover": { bgcolor: alpha(palette.primary.main, 0.15) } } + : {} + } > {delay > 1e5 ? "Error" : delay > 3000 ? "Timeout" : `${delay}ms`} </Widget> diff --git a/src/services/api.ts b/src/services/api.ts index 39cb226..4794cf7 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -85,64 +85,93 @@ export async function updateProxy(group: string, proxy: string) { return instance.put(`/proxies/${encodeURIComponent(group)}`, { name: proxy }); } +// get proxy +async function getProxiesInner() { + try { + const instance = await getAxios(); + const response = await instance.get<any, any>("/proxies"); + return (response?.proxies || {}) as Record<string, ApiType.ProxyItem>; + } catch { + return {}; + } +} + /// Get the Proxy infomation export async function getProxies() { - const instance = await getAxios(); - const response = await instance.get<any, any>("/proxies"); - const records = (response?.proxies ?? {}) as Record< - string, - ApiType.ProxyItem - >; - - const global = records["GLOBAL"]; - const direct = records["DIRECT"]; - const reject = records["REJECT"]; - const order = global?.all; - - let groups: ApiType.ProxyGroupItem[] = []; + const [proxyRecord, providerRecord] = await Promise.all([ + getProxiesInner(), + getProviders(), + ]); + + // provider name map + const providerMap = Object.fromEntries( + Object.entries(providerRecord).flatMap(([provider, item]) => + item.proxies.map((p) => [p.name, { ...p, provider }]) + ) + ); // compatible with proxy-providers const generateItem = (name: string) => { - if (records[name]) return records[name]; + if (proxyRecord[name]) return proxyRecord[name]; + if (providerMap[name]) return providerMap[name]; return { name, type: "unknown", udp: false, history: [] }; }; - if (order) { - groups = order - .filter((name) => records[name]?.all) - .map((name) => records[name]) + const { GLOBAL: global, DIRECT: direct, REJECT: reject } = proxyRecord; + + let groups: ApiType.ProxyGroupItem[] = []; + + if (global?.all) { + groups = global.all + .filter((name) => proxyRecord[name]?.all) + .map((name) => proxyRecord[name]) .map((each) => ({ ...each, all: each.all!.map((item) => generateItem(item)), })); } else { - groups = Object.values(records) + groups = Object.values(proxyRecord) .filter((each) => each.name !== "GLOBAL" && each.all) .map((each) => ({ ...each, all: each.all!.map((item) => generateItem(item)), - })); - groups.sort((a, b) => b.name.localeCompare(a.name)); + })) + .sort((a, b) => b.name.localeCompare(a.name)); } const proxies = [direct, reject].concat( - Object.values(records).filter( + Object.values(proxyRecord).filter( (p) => !p.all?.length && p.name !== "DIRECT" && p.name !== "REJECT" ) ); - return { global, direct, groups, records, proxies }; + return { global, direct, groups, records: proxyRecord, proxies }; } -// todo: get proxy providers +// get proxy providers export async function getProviders() { - const instance = await getAxios(); - const response = await instance.get<any, any>("/providers/proxies"); - return response.providers as any; + try { + const instance = await getAxios(); + const response = await instance.get<any, any>("/providers/proxies"); + + const providers = (response.providers || {}) as Record< + string, + ApiType.ProviderItem + >; + + return Object.fromEntries( + Object.entries(providers).filter(([key, item]) => { + const type = item.vehicleType.toLowerCase(); + return type === "http" || type === "file"; + }) + ); + } catch { + return {}; + } } -// todo: proxy providers health check -export async function getProviderHealthCheck(name: string) { +// proxy providers health check +export async function providerHealthCheck(name: string) { const instance = await getAxios(); return instance.get( `/providers/proxies/${encodeURIComponent(name)}/healthcheck` diff --git a/src/services/types.d.ts b/src/services/types.d.ts index caf3331..37115a6 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -31,12 +31,21 @@ declare namespace ApiType { }[]; all?: string[]; now?: string; + provider?: string; // 记录是å¦æ¥è‡ªprovider } type ProxyGroupItem = Omit<ProxyItem, "all"> & { all: ProxyItem[]; }; + interface ProviderItem { + name: string; + type: string; + proxies: ProxyItem[]; + updatedAt: string; + vehicleType: string; + } + interface TrafficItem { up: number; down: number; -- GitLab