feat: display delay check result timely
This commit is contained in:
parent
cc5b33a8ec
commit
a6ac75e97b
@ -5,9 +5,8 @@ import { Virtuoso } from "react-virtuoso";
|
|||||||
import { providerHealthCheck, updateProxy } from "@/services/api";
|
import { providerHealthCheck, updateProxy } from "@/services/api";
|
||||||
import { getProfiles, patchProfile } from "@/services/cmds";
|
import { getProfiles, patchProfile } from "@/services/cmds";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
import useSortProxy from "./use-sort-proxy";
|
|
||||||
import useHeadState from "./use-head-state";
|
import useHeadState from "./use-head-state";
|
||||||
import useFilterProxy from "./use-filter-proxy";
|
import useFilterSort from "./use-filter-sort";
|
||||||
import ProxyHead from "./proxy-head";
|
import ProxyHead from "./proxy-head";
|
||||||
import ProxyItem from "./proxy-item";
|
import ProxyItem from "./proxy-item";
|
||||||
|
|
||||||
@ -27,14 +26,10 @@ const ProxyGlobal = (props: Props) => {
|
|||||||
const [headState, setHeadState] = useHeadState(groupName);
|
const [headState, setHeadState] = useHeadState(groupName);
|
||||||
|
|
||||||
const virtuosoRef = useRef<any>();
|
const virtuosoRef = useRef<any>();
|
||||||
const filterProxies = useFilterProxy(
|
const sortedProxies = useFilterSort(
|
||||||
proxies,
|
proxies,
|
||||||
groupName,
|
groupName,
|
||||||
headState.filterText
|
headState.filterText,
|
||||||
);
|
|
||||||
const sortedProxies = useSortProxy(
|
|
||||||
filterProxies,
|
|
||||||
groupName,
|
|
||||||
headState.sortType
|
headState.sortType
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -85,13 +80,12 @@ const ProxyGlobal = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await delayManager.checkListDelay(
|
await delayManager.checkListDelay(
|
||||||
{
|
sortedProxies.filter((p) => !p.provider).map((p) => p.name),
|
||||||
names: sortedProxies.filter((p) => !p.provider).map((p) => p.name),
|
groupName,
|
||||||
groupName,
|
16
|
||||||
skipNum: 16,
|
|
||||||
},
|
|
||||||
() => mutate("getProxies")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
mutate("getProxies");
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => onLocation(false), [groupName]);
|
useEffect(() => onLocation(false), [groupName]);
|
||||||
|
@ -18,9 +18,8 @@ import {
|
|||||||
import { providerHealthCheck, updateProxy } from "@/services/api";
|
import { providerHealthCheck, updateProxy } from "@/services/api";
|
||||||
import { getProfiles, patchProfile } from "@/services/cmds";
|
import { getProfiles, patchProfile } from "@/services/cmds";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
import useSortProxy from "./use-sort-proxy";
|
|
||||||
import useHeadState from "./use-head-state";
|
import useHeadState from "./use-head-state";
|
||||||
import useFilterProxy from "./use-filter-proxy";
|
import useFilterSort from "./use-filter-sort";
|
||||||
import ProxyHead from "./proxy-head";
|
import ProxyHead from "./proxy-head";
|
||||||
import ProxyItem from "./proxy-item";
|
import ProxyItem from "./proxy-item";
|
||||||
|
|
||||||
@ -35,14 +34,10 @@ const ProxyGroup = ({ group }: Props) => {
|
|||||||
const [headState, setHeadState] = useHeadState(group.name);
|
const [headState, setHeadState] = useHeadState(group.name);
|
||||||
|
|
||||||
const virtuosoRef = useRef<any>();
|
const virtuosoRef = useRef<any>();
|
||||||
const filterProxies = useFilterProxy(
|
const sortedProxies = useFilterSort(
|
||||||
group.all,
|
group.all,
|
||||||
group.name,
|
group.name,
|
||||||
headState.filterText
|
headState.filterText,
|
||||||
);
|
|
||||||
const sortedProxies = useSortProxy(
|
|
||||||
filterProxies,
|
|
||||||
group.name,
|
|
||||||
headState.sortType
|
headState.sortType
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -105,13 +100,12 @@ const ProxyGroup = ({ group }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await delayManager.checkListDelay(
|
await delayManager.checkListDelay(
|
||||||
{
|
sortedProxies.filter((p) => !p.provider).map((p) => p.name),
|
||||||
names: sortedProxies.filter((p) => !p.provider).map((p) => p.name),
|
group.name,
|
||||||
groupName: group.name,
|
16
|
||||||
skipNum: 16,
|
|
||||||
},
|
|
||||||
() => mutate("getProxies")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
mutate("getProxies");
|
||||||
});
|
});
|
||||||
|
|
||||||
// auto scroll to current index
|
// auto scroll to current index
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
import type { HeadState } from "./use-head-state";
|
import type { HeadState } from "./use-head-state";
|
||||||
import type { ProxySortType } from "./use-sort-proxy";
|
import type { ProxySortType } from "./use-filter-sort";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
|
@ -49,6 +49,14 @@ const ProxyItem = (props: Props) => {
|
|||||||
// -2 为 loading
|
// -2 为 loading
|
||||||
const [delay, setDelay] = useState(-1);
|
const [delay, setDelay] = useState(-1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
delayManager.setListener(proxy.name, groupName, setDelay);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
delayManager.removeListener(proxy.name, groupName);
|
||||||
|
};
|
||||||
|
}, [proxy.name, groupName]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!proxy) return;
|
if (!proxy) return;
|
||||||
|
|
||||||
@ -66,10 +74,7 @@ const ProxyItem = (props: Props) => {
|
|||||||
|
|
||||||
const onDelay = useLockFn(async () => {
|
const onDelay = useLockFn(async () => {
|
||||||
setDelay(-2);
|
setDelay(-2);
|
||||||
return delayManager
|
setDelay(await delayManager.checkDelay(proxy.name, groupName));
|
||||||
.checkDelay(proxy.name, groupName)
|
|
||||||
.then((result) => setDelay(result))
|
|
||||||
.catch(() => setDelay(1e6));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
import { useMemo } from "react";
|
|
||||||
import delayManager from "@/services/delay";
|
|
||||||
|
|
||||||
const regex1 = /delay([=<>])(\d+|timeout|error)/i;
|
|
||||||
const regex2 = /type=(.*)/i;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* filter the proxy
|
|
||||||
* according to the regular conditions
|
|
||||||
*/
|
|
||||||
export default function useFilterProxy(
|
|
||||||
proxies: ApiType.ProxyItem[],
|
|
||||||
groupName: string,
|
|
||||||
filterText: string
|
|
||||||
) {
|
|
||||||
return useMemo(() => {
|
|
||||||
if (!proxies) return [];
|
|
||||||
if (!filterText) return proxies;
|
|
||||||
|
|
||||||
const res1 = regex1.exec(filterText);
|
|
||||||
if (res1) {
|
|
||||||
const symbol = res1[1];
|
|
||||||
const symbol2 = res1[2].toLowerCase();
|
|
||||||
const value =
|
|
||||||
symbol2 === "error" ? 1e5 : symbol2 === "timeout" ? 3000 : +symbol2;
|
|
||||||
|
|
||||||
return proxies.filter((p) => {
|
|
||||||
const delay = delayManager.getDelay(p.name, groupName);
|
|
||||||
|
|
||||||
if (delay < 0) return false;
|
|
||||||
if (symbol === "=" && symbol2 === "error") return delay >= 1e5;
|
|
||||||
if (symbol === "=" && symbol2 === "timeout")
|
|
||||||
return delay < 1e5 && delay >= 3000;
|
|
||||||
if (symbol === "=") return delay == value;
|
|
||||||
if (symbol === "<") return delay <= value;
|
|
||||||
if (symbol === ">") return delay >= value;
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const res2 = regex2.exec(filterText);
|
|
||||||
if (res2) {
|
|
||||||
const type = res2[1].toLowerCase();
|
|
||||||
return proxies.filter((p) => p.type.toLowerCase().includes(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
return proxies.filter((p) => p.name.includes(filterText.trim()));
|
|
||||||
}, [proxies, groupName, filterText]);
|
|
||||||
}
|
|
114
src/components/proxy/use-filter-sort.ts
Normal file
114
src/components/proxy/use-filter-sort.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import delayManager from "@/services/delay";
|
||||||
|
|
||||||
|
// default | delay | alphabet
|
||||||
|
export type ProxySortType = 0 | 1 | 2;
|
||||||
|
|
||||||
|
export default function useFilterSort(
|
||||||
|
proxies: ApiType.ProxyItem[],
|
||||||
|
groupName: string,
|
||||||
|
filterText: string,
|
||||||
|
sortType: ProxySortType
|
||||||
|
) {
|
||||||
|
const [refresh, setRefresh] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let last = 0;
|
||||||
|
|
||||||
|
delayManager.setGroupListener(groupName, () => {
|
||||||
|
// 简单节流
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - last > 666) {
|
||||||
|
last = now;
|
||||||
|
setRefresh({});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
delayManager.removeGroupListener(groupName);
|
||||||
|
};
|
||||||
|
}, [groupName]);
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const fp = filterProxies(proxies, groupName, filterText);
|
||||||
|
const sp = sortProxies(fp, groupName, sortType);
|
||||||
|
return sp;
|
||||||
|
}, [proxies, groupName, filterText, sortType, refresh]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可以通过延迟数/节点类型 过滤
|
||||||
|
*/
|
||||||
|
const regex1 = /delay([=<>])(\d+|timeout|error)/i;
|
||||||
|
const regex2 = /type=(.*)/i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* filter the proxy
|
||||||
|
* according to the regular conditions
|
||||||
|
*/
|
||||||
|
function filterProxies(
|
||||||
|
proxies: ApiType.ProxyItem[],
|
||||||
|
groupName: string,
|
||||||
|
filterText: string
|
||||||
|
) {
|
||||||
|
if (!filterText) return proxies;
|
||||||
|
|
||||||
|
const res1 = regex1.exec(filterText);
|
||||||
|
if (res1) {
|
||||||
|
const symbol = res1[1];
|
||||||
|
const symbol2 = res1[2].toLowerCase();
|
||||||
|
const value =
|
||||||
|
symbol2 === "error" ? 1e5 : symbol2 === "timeout" ? 3000 : +symbol2;
|
||||||
|
|
||||||
|
return proxies.filter((p) => {
|
||||||
|
const delay = delayManager.getDelay(p.name, groupName);
|
||||||
|
|
||||||
|
if (delay < 0) return false;
|
||||||
|
if (symbol === "=" && symbol2 === "error") return delay >= 1e5;
|
||||||
|
if (symbol === "=" && symbol2 === "timeout")
|
||||||
|
return delay < 1e5 && delay >= 3000;
|
||||||
|
if (symbol === "=") return delay == value;
|
||||||
|
if (symbol === "<") return delay <= value;
|
||||||
|
if (symbol === ">") return delay >= value;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const res2 = regex2.exec(filterText);
|
||||||
|
if (res2) {
|
||||||
|
const type = res2[1].toLowerCase();
|
||||||
|
return proxies.filter((p) => p.type.toLowerCase().includes(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxies.filter((p) => p.name.includes(filterText.trim()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sort the proxy
|
||||||
|
*/
|
||||||
|
function sortProxies(
|
||||||
|
proxies: ApiType.ProxyItem[],
|
||||||
|
groupName: string,
|
||||||
|
sortType: ProxySortType
|
||||||
|
) {
|
||||||
|
if (!proxies) return [];
|
||||||
|
if (sortType === 0) return proxies;
|
||||||
|
|
||||||
|
const list = proxies.slice();
|
||||||
|
|
||||||
|
if (sortType === 1) {
|
||||||
|
list.sort((a, b) => {
|
||||||
|
const ad = delayManager.getDelay(a.name, groupName);
|
||||||
|
const bd = delayManager.getDelay(b.name, groupName);
|
||||||
|
|
||||||
|
if (ad === -1 || ad === -2) return 1;
|
||||||
|
if (bd === -1 || bd === -2) return -1;
|
||||||
|
|
||||||
|
return ad - bd;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
list.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useRecoilValue } from "recoil";
|
import { useRecoilValue } from "recoil";
|
||||||
import { atomCurrentProfile } from "@/services/states";
|
import { atomCurrentProfile } from "@/services/states";
|
||||||
import { ProxySortType } from "./use-sort-proxy";
|
import { ProxySortType } from "./use-filter-sort";
|
||||||
|
|
||||||
export interface HeadState {
|
export interface HeadState {
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
import { useMemo } from "react";
|
|
||||||
import delayManager from "@/services/delay";
|
|
||||||
|
|
||||||
// default | delay | alpha
|
|
||||||
export type ProxySortType = 0 | 1 | 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sort the proxy
|
|
||||||
*/
|
|
||||||
export default function useSortProxy(
|
|
||||||
proxies: ApiType.ProxyItem[],
|
|
||||||
groupName: string,
|
|
||||||
sortType: ProxySortType
|
|
||||||
) {
|
|
||||||
return useMemo(() => {
|
|
||||||
if (!proxies) return [];
|
|
||||||
if (sortType === 0) return proxies;
|
|
||||||
|
|
||||||
const list = proxies.slice();
|
|
||||||
|
|
||||||
if (sortType === 1) {
|
|
||||||
list.sort((a, b) => {
|
|
||||||
const ad = delayManager.getDelay(a.name, groupName);
|
|
||||||
const bd = delayManager.getDelay(b.name, groupName);
|
|
||||||
|
|
||||||
if (ad === -1 || ad === -2) return 1;
|
|
||||||
if (bd === -1 || bd === -2) return -1;
|
|
||||||
|
|
||||||
return ad - bd;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
list.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}, [proxies, groupName, sortType]);
|
|
||||||
}
|
|
@ -6,6 +6,12 @@ class DelayManager {
|
|||||||
private cache = new Map<string, [number, number]>();
|
private cache = new Map<string, [number, number]>();
|
||||||
private urlMap = new Map<string, string>();
|
private urlMap = new Map<string, string>();
|
||||||
|
|
||||||
|
// 每个item的监听
|
||||||
|
private listenerMap = new Map<string, (time: number) => void>();
|
||||||
|
|
||||||
|
// 每个分组的监听
|
||||||
|
private groupListenerMap = new Map<string, () => void>();
|
||||||
|
|
||||||
setUrl(group: string, url: string) {
|
setUrl(group: string, url: string) {
|
||||||
this.urlMap.set(group, url);
|
this.urlMap.set(group, url);
|
||||||
}
|
}
|
||||||
@ -14,8 +20,29 @@ class DelayManager {
|
|||||||
return this.urlMap.get(group);
|
return this.urlMap.get(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setListener(name: string, group: string, listener: (time: number) => void) {
|
||||||
|
const key = hashKey(name, group);
|
||||||
|
this.listenerMap.set(key, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeListener(name: string, group: string) {
|
||||||
|
const key = hashKey(name, group);
|
||||||
|
this.listenerMap.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
setGroupListener(group: string, listener: () => void) {
|
||||||
|
this.groupListenerMap.set(group, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeGroupListener(group: string) {
|
||||||
|
this.groupListenerMap.delete(group);
|
||||||
|
}
|
||||||
|
|
||||||
setDelay(name: string, group: string, delay: number) {
|
setDelay(name: string, group: string, delay: number) {
|
||||||
this.cache.set(hashKey(name, group), [Date.now(), delay]);
|
const key = hashKey(name, group);
|
||||||
|
this.cache.set(key, [Date.now(), delay]);
|
||||||
|
this.listenerMap.get(key)?.(delay);
|
||||||
|
this.groupListenerMap.get(group)?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
getDelay(name: string, group: string) {
|
getDelay(name: string, group: string) {
|
||||||
@ -44,19 +71,13 @@ class DelayManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkListDelay(
|
async checkListDelay(
|
||||||
options: {
|
nameList: readonly string[],
|
||||||
names: readonly string[];
|
groupName: string,
|
||||||
groupName: string;
|
concurrency: number
|
||||||
skipNum: number;
|
|
||||||
},
|
|
||||||
callback: Function
|
|
||||||
) {
|
) {
|
||||||
const { groupName, skipNum } = options;
|
const names = [...nameList];
|
||||||
|
|
||||||
const names = [...options.names];
|
let total = names.length;
|
||||||
const total = names.length;
|
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
let current = 0;
|
let current = 0;
|
||||||
|
|
||||||
// 设置正在延迟测试中
|
// 设置正在延迟测试中
|
||||||
@ -64,7 +85,7 @@ class DelayManager {
|
|||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const help = async (): Promise<void> => {
|
const help = async (): Promise<void> => {
|
||||||
if (current >= skipNum) return;
|
if (current >= concurrency) return;
|
||||||
|
|
||||||
const task = names.shift();
|
const task = names.shift();
|
||||||
if (!task) return;
|
if (!task) return;
|
||||||
@ -72,14 +93,13 @@ class DelayManager {
|
|||||||
current += 1;
|
current += 1;
|
||||||
await this.checkDelay(task, groupName);
|
await this.checkDelay(task, groupName);
|
||||||
current -= 1;
|
current -= 1;
|
||||||
|
total -= 1;
|
||||||
|
|
||||||
if (count++ % skipNum === 0 || count === total) callback();
|
if (total <= 0) resolve(null);
|
||||||
if (count === total) resolve(null);
|
else return help();
|
||||||
|
|
||||||
return help();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let i = 0; i < skipNum; ++i) help();
|
for (let i = 0; i < concurrency; ++i) help();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user