mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 06:53:44 +08:00
feat: url-test支持手动选择、节点组fixed节点使用角标展示 (#840)
* feat: allow manual selection of url-test group * feat: fixed proxy indicator * fix: try to fix traffic websocket no longer updating * fixup: group delay test use defined url
This commit is contained in:
parent
c0f650d7dc
commit
4f7e8116cb
@ -29,21 +29,23 @@ export const LayoutTraffic = () => {
|
||||
// setup log ws during layout
|
||||
useLogSetup();
|
||||
|
||||
const { connect, disconnect } = useWebsocket((event) => {
|
||||
const trafficWs = useWebsocket(
|
||||
(event) => {
|
||||
const data = JSON.parse(event.data) as ITrafficItem;
|
||||
trafficRef.current?.appendData(data);
|
||||
setTraffic(data);
|
||||
});
|
||||
},
|
||||
{ onError: () => setTraffic({ up: 0, down: 0 }), errorCount: 10 }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!clashInfo || !pageVisible) return;
|
||||
|
||||
const { server = "", secret = "" } = clashInfo;
|
||||
connect(`ws://${server}/traffic?token=${encodeURIComponent(secret)}`);
|
||||
|
||||
return () => {
|
||||
disconnect();
|
||||
};
|
||||
trafficWs.connect(
|
||||
`ws://${server}/traffic?token=${encodeURIComponent(secret)}`
|
||||
);
|
||||
return () => trafficWs.disconnect();
|
||||
}, [clashInfo, pageVisible]);
|
||||
|
||||
/* --------- meta memory information --------- */
|
||||
@ -54,7 +56,7 @@ export const LayoutTraffic = () => {
|
||||
(event) => {
|
||||
setMemory(JSON.parse(event.data));
|
||||
},
|
||||
{ onError: () => setMemory({ inuse: 0 }) }
|
||||
{ onError: () => setMemory({ inuse: 0 }), errorCount: 10 }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
providerHealthCheck,
|
||||
updateProxy,
|
||||
deleteConnection,
|
||||
getGroupProxyDelays,
|
||||
} from "@/services/api";
|
||||
import { Box } from "@mui/material";
|
||||
import { useProfiles } from "@/hooks/use-profiles";
|
||||
@ -33,7 +34,7 @@ export const ProxyGroups = (props: Props) => {
|
||||
// 切换分组的节点代理
|
||||
const handleChangeProxy = useLockFn(
|
||||
async (group: IProxyGroupItem, proxy: IProxyItem) => {
|
||||
if (group.type !== "Selector" && group.type !== "Fallback") return;
|
||||
if (!["Selector", "URLTest", "Fallback"].includes(group.type)) return;
|
||||
|
||||
const { name, now } = group;
|
||||
await updateProxy(name, proxy.name);
|
||||
@ -85,7 +86,11 @@ export const ProxyGroups = (props: Props) => {
|
||||
}
|
||||
|
||||
const names = proxies.filter((p) => !p!.provider).map((p) => p!.name);
|
||||
await delayManager.checkListDelay(names, groupName, timeout);
|
||||
|
||||
await Promise.race([
|
||||
delayManager.checkListDelay(names, groupName, timeout),
|
||||
getGroupProxyDelays(groupName, delayManager.getUrl(groupName), timeout), // 查询group delays 将清除fixed(不关注调用结果)
|
||||
]);
|
||||
|
||||
onProxies();
|
||||
});
|
||||
|
@ -7,7 +7,7 @@ import delayManager from "@/services/delay";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
|
||||
interface Props {
|
||||
groupName: string;
|
||||
group: IProxyGroupItem;
|
||||
proxy: IProxyItem;
|
||||
selected: boolean;
|
||||
showType?: boolean;
|
||||
@ -16,7 +16,7 @@ interface Props {
|
||||
|
||||
// 多列布局
|
||||
export const ProxyItemMini = (props: Props) => {
|
||||
const { groupName, proxy, selected, showType = true, onClick } = props;
|
||||
const { group, proxy, selected, showType = true, onClick } = props;
|
||||
|
||||
// -1/<=0 为 不显示
|
||||
// -2 为 loading
|
||||
@ -25,21 +25,21 @@ export const ProxyItemMini = (props: Props) => {
|
||||
const timeout = verge?.default_latency_timeout || 10000;
|
||||
|
||||
useEffect(() => {
|
||||
delayManager.setListener(proxy.name, groupName, setDelay);
|
||||
delayManager.setListener(proxy.name, group.name, setDelay);
|
||||
|
||||
return () => {
|
||||
delayManager.removeListener(proxy.name, groupName);
|
||||
delayManager.removeListener(proxy.name, group.name);
|
||||
};
|
||||
}, [proxy.name, groupName]);
|
||||
}, [proxy.name, group.name]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!proxy) return;
|
||||
setDelay(delayManager.getDelayFix(proxy, groupName));
|
||||
setDelay(delayManager.getDelayFix(proxy, group.name));
|
||||
}, [proxy]);
|
||||
|
||||
const onDelay = useLockFn(async () => {
|
||||
setDelay(-2);
|
||||
setDelay(await delayManager.checkDelay(proxy.name, groupName, timeout));
|
||||
setDelay(await delayManager.checkDelay(proxy.name, group.name, timeout));
|
||||
});
|
||||
|
||||
return (
|
||||
@ -65,6 +65,12 @@ export const ProxyItemMini = (props: Props) => {
|
||||
"&:hover .the-check": { display: !showDelay ? "block" : "none" },
|
||||
"&:hover .the-delay": { display: showDelay ? "block" : "none" },
|
||||
"&:hover .the-icon": { display: "none" },
|
||||
"& .the-pin, & .the-unpin": {
|
||||
position: "absolute",
|
||||
top: "-8px",
|
||||
right: "-8px",
|
||||
},
|
||||
"& .the-unpin": { filter: "grayscale(1)" },
|
||||
"&.Mui-selected": {
|
||||
width: `calc(100% + 3px)`,
|
||||
marginLeft: `-3px`,
|
||||
@ -147,14 +153,12 @@ export const ProxyItemMini = (props: Props) => {
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box sx={{ ml: 0.5, color: "primary.main" }}>
|
||||
{delay === -2 && (
|
||||
<Widget>
|
||||
<BaseLoading />
|
||||
</Widget>
|
||||
)}
|
||||
|
||||
{!proxy.provider && delay !== -2 && (
|
||||
// provider的节点不支持检测
|
||||
<Widget
|
||||
@ -193,7 +197,6 @@ export const ProxyItemMini = (props: Props) => {
|
||||
{delayManager.formatDelay(delay, timeout)}
|
||||
</Widget>
|
||||
)}
|
||||
|
||||
{delay !== -2 && delay <= 0 && selected && (
|
||||
// 展示已选择的icon
|
||||
<CheckCircleOutlineRounded
|
||||
@ -202,6 +205,13 @@ export const ProxyItemMini = (props: Props) => {
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{group.fixed && group.fixed === proxy.name && (
|
||||
// 展示fixed状态
|
||||
<span className={proxy.name === group.now ? "the-pin" : "the-unpin"}>
|
||||
📌
|
||||
</span>
|
||||
)}
|
||||
</ListItemButton>
|
||||
);
|
||||
};
|
||||
|
@ -17,7 +17,7 @@ import delayManager from "@/services/delay";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
|
||||
interface Props {
|
||||
groupName: string;
|
||||
group: IProxyGroupItem;
|
||||
proxy: IProxyItem;
|
||||
selected: boolean;
|
||||
showType?: boolean;
|
||||
@ -44,7 +44,7 @@ const TypeBox = styled(Box)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
export const ProxyItem = (props: Props) => {
|
||||
const { groupName, proxy, selected, showType = true, sx, onClick } = props;
|
||||
const { group, proxy, selected, showType = true, sx, onClick } = props;
|
||||
|
||||
// -1/<=0 为 不显示
|
||||
// -2 为 loading
|
||||
@ -52,21 +52,21 @@ export const ProxyItem = (props: Props) => {
|
||||
const { verge } = useVerge();
|
||||
const timeout = verge?.default_latency_timeout || 10000;
|
||||
useEffect(() => {
|
||||
delayManager.setListener(proxy.name, groupName, setDelay);
|
||||
delayManager.setListener(proxy.name, group.name, setDelay);
|
||||
|
||||
return () => {
|
||||
delayManager.removeListener(proxy.name, groupName);
|
||||
delayManager.removeListener(proxy.name, group.name);
|
||||
};
|
||||
}, [proxy.name, groupName]);
|
||||
}, [proxy.name, group.name]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!proxy) return;
|
||||
setDelay(delayManager.getDelayFix(proxy, groupName));
|
||||
setDelay(delayManager.getDelayFix(proxy, group.name));
|
||||
}, [proxy]);
|
||||
|
||||
const onDelay = useLockFn(async () => {
|
||||
setDelay(-2);
|
||||
setDelay(await delayManager.checkDelay(proxy.name, groupName, timeout));
|
||||
setDelay(await delayManager.checkDelay(proxy.name, group.name, timeout));
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -142,7 +142,7 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
if (type === 2 && !group.hidden) {
|
||||
return (
|
||||
<ProxyItem
|
||||
groupName={group.name}
|
||||
group={group}
|
||||
proxy={proxy!}
|
||||
selected={group.now === proxy?.name}
|
||||
showType={headState?.showType}
|
||||
@ -186,7 +186,7 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
{proxyCol?.map((proxy) => (
|
||||
<ProxyItemMini
|
||||
key={item.key + proxy.name}
|
||||
groupName={group.name}
|
||||
group={group}
|
||||
proxy={proxy!}
|
||||
selected={group.now === proxy.name}
|
||||
showType={headState?.showType}
|
||||
|
@ -5,7 +5,8 @@ export type WsMsgFn = (event: MessageEvent<any>) => void;
|
||||
export interface WsOptions {
|
||||
errorCount?: number; // default is 5
|
||||
retryInterval?: number; // default is 2500
|
||||
onError?: () => void;
|
||||
onError?: (event: Event) => void;
|
||||
onClose?: (event: CloseEvent) => void;
|
||||
}
|
||||
|
||||
export const useWebsocket = (onMessage: WsMsgFn, options?: WsOptions) => {
|
||||
@ -33,17 +34,23 @@ export const useWebsocket = (onMessage: WsMsgFn, options?: WsOptions) => {
|
||||
const ws = new WebSocket(url);
|
||||
wsRef.current = ws;
|
||||
|
||||
ws.addEventListener("message", onMessage);
|
||||
ws.addEventListener("error", () => {
|
||||
ws.addEventListener("message", (event) => {
|
||||
errorCount = 0; // reset counter
|
||||
onMessage(event);
|
||||
});
|
||||
ws.addEventListener("error", (event) => {
|
||||
errorCount -= 1;
|
||||
|
||||
if (errorCount >= 0) {
|
||||
timerRef.current = setTimeout(connectHelper, 2500);
|
||||
} else {
|
||||
disconnect();
|
||||
options?.onError?.();
|
||||
options?.onError?.(event);
|
||||
}
|
||||
});
|
||||
ws.addEventListener("close", (event) => {
|
||||
options?.onClose?.(event);
|
||||
});
|
||||
};
|
||||
|
||||
connectHelper();
|
||||
|
@ -75,9 +75,13 @@ export const getRules = async () => {
|
||||
};
|
||||
|
||||
/// Get Proxy delay
|
||||
export const getProxyDelay = async (name: string, url?: string) => {
|
||||
export const getProxyDelay = async (
|
||||
name: string,
|
||||
url?: string,
|
||||
timeout?: number
|
||||
) => {
|
||||
const params = {
|
||||
timeout: 10000,
|
||||
timeout: timeout || 10000,
|
||||
url: url || "http://1.1.1.1",
|
||||
};
|
||||
const instance = await getAxios();
|
||||
@ -237,3 +241,21 @@ export const closeAllConnections = async () => {
|
||||
const instance = await getAxios();
|
||||
await instance.delete<any, any>(`/connections`);
|
||||
};
|
||||
|
||||
// Get Group Proxy Delays
|
||||
export const getGroupProxyDelays = async (
|
||||
groupName: string,
|
||||
url?: string,
|
||||
timeout?: number
|
||||
) => {
|
||||
const params = {
|
||||
timeout: timeout || 10000,
|
||||
url: url || "http://1.1.1.1",
|
||||
};
|
||||
const instance = await getAxios();
|
||||
const result = await instance.get(
|
||||
`/group/${encodeURIComponent(groupName)}/delay`,
|
||||
{ params }
|
||||
);
|
||||
return result as any as Record<string, number>;
|
||||
};
|
||||
|
1
src/services/types.d.ts
vendored
1
src/services/types.d.ts
vendored
@ -64,6 +64,7 @@ interface IProxyItem {
|
||||
hidden?: boolean;
|
||||
icon?: string;
|
||||
provider?: string; // 记录是否来自provider
|
||||
fixed?: string; // 记录固定(优先)的节点
|
||||
}
|
||||
|
||||
type IProxyGroupItem = Omit<IProxyItem, "all"> & {
|
||||
|
Loading…
x
Reference in New Issue
Block a user