mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-06 01:13:43 +08:00
fix: sync proxy node selection
This commit is contained in:
parent
f4cb978118
commit
79bb0f29f9
@ -2,7 +2,6 @@ import { useTranslation } from "react-i18next";
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
Stack,
|
|
||||||
Chip,
|
Chip,
|
||||||
Button,
|
Button,
|
||||||
alpha,
|
alpha,
|
||||||
@ -16,7 +15,6 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
RouterOutlined,
|
|
||||||
SignalWifi4Bar as SignalStrong,
|
SignalWifi4Bar as SignalStrong,
|
||||||
SignalWifi3Bar as SignalGood,
|
SignalWifi3Bar as SignalGood,
|
||||||
SignalWifi2Bar as SignalMedium,
|
SignalWifi2Bar as SignalMedium,
|
||||||
@ -28,8 +26,14 @@ import {
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useCurrentProxy } from "@/hooks/use-current-proxy";
|
import { useCurrentProxy } from "@/hooks/use-current-proxy";
|
||||||
import { EnhancedCard } from "@/components/home/enhanced-card";
|
import { EnhancedCard } from "@/components/home/enhanced-card";
|
||||||
import { getProxies, updateProxy } from "@/services/api";
|
import {
|
||||||
|
getProxies,
|
||||||
|
updateProxy,
|
||||||
|
getConnections,
|
||||||
|
deleteConnection,
|
||||||
|
} from "@/services/api";
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
|
|
||||||
// 本地存储的键名
|
// 本地存储的键名
|
||||||
const STORAGE_KEY_GROUP = "clash-verge-selected-proxy-group";
|
const STORAGE_KEY_GROUP = "clash-verge-selected-proxy-group";
|
||||||
@ -121,6 +125,7 @@ export const CurrentProxyCard = () => {
|
|||||||
useCurrentProxy();
|
useCurrentProxy();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { verge } = useVerge();
|
||||||
|
|
||||||
// 判断模式
|
// 判断模式
|
||||||
const isGlobalMode = mode === "global";
|
const isGlobalMode = mode === "global";
|
||||||
@ -341,11 +346,23 @@ export const CurrentProxyCard = () => {
|
|||||||
const refreshProxyData = async () => {
|
const refreshProxyData = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await getProxies();
|
const data = await getProxies();
|
||||||
|
// 更新所有代理记录
|
||||||
|
setRecords(data.records);
|
||||||
|
|
||||||
|
// 更新代理组信息
|
||||||
|
const filteredGroups = data.groups
|
||||||
|
.filter((g) => g.name !== "DIRECT" && g.name !== "REJECT")
|
||||||
|
.map((g) => ({
|
||||||
|
name: g.name,
|
||||||
|
now: g.now || "",
|
||||||
|
all: g.all.map((p) => p.name),
|
||||||
|
}));
|
||||||
|
|
||||||
|
setGroups(filteredGroups);
|
||||||
|
|
||||||
// 检查并更新全局代理信息
|
// 检查并更新全局代理信息
|
||||||
if (isGlobalMode && data.global) {
|
if (isGlobalMode && data.global) {
|
||||||
const globalNow = data.global.now || "";
|
const globalNow = data.global.now || "";
|
||||||
|
|
||||||
setSelectedProxy(globalNow);
|
setSelectedProxy(globalNow);
|
||||||
|
|
||||||
if (globalNow && data.records[globalNow]) {
|
if (globalNow && data.records[globalNow]) {
|
||||||
@ -359,24 +376,48 @@ export const CurrentProxyCard = () => {
|
|||||||
|
|
||||||
setProxyOptions(options);
|
setProxyOptions(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新直连代理信息
|
// 更新直连代理信息
|
||||||
if (isDirectMode && data.records["DIRECT"]) {
|
else if (isDirectMode && data.records["DIRECT"]) {
|
||||||
setDirectProxy(data.records["DIRECT"]);
|
setDirectProxy(data.records["DIRECT"]);
|
||||||
setDisplayProxy(data.records["DIRECT"]);
|
setDisplayProxy(data.records["DIRECT"]);
|
||||||
}
|
}
|
||||||
|
// 更新普通模式下当前选中组的信息
|
||||||
|
else {
|
||||||
|
const currentGroup = filteredGroups.find(
|
||||||
|
(g) => g.name === selectedGroup,
|
||||||
|
);
|
||||||
|
if (currentGroup) {
|
||||||
|
// 如果当前选中的代理节点与组中的now不一致,则需要更新
|
||||||
|
if (currentGroup.now !== selectedProxy) {
|
||||||
|
setSelectedProxy(currentGroup.now);
|
||||||
|
|
||||||
|
if (data.records[currentGroup.now]) {
|
||||||
|
setDisplayProxy(data.records[currentGroup.now]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新代理选项
|
||||||
|
const options = currentGroup.all.map((proxyName) => ({
|
||||||
|
name: proxyName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setProxyOptions(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("刷新代理信息失败", error);
|
console.error("刷新代理信息失败", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 每隔一段时间刷新特殊模式下的代理信息
|
// 每隔一段时间刷新代理信息 - 修改为在所有模式下都刷新
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isGlobalMode && !isDirectMode) return;
|
// 初始刷新一次
|
||||||
|
refreshProxyData();
|
||||||
|
|
||||||
const refreshInterval = setInterval(refreshProxyData, 3000);
|
// 定期刷新所有模式下的代理信息
|
||||||
|
const refreshInterval = setInterval(refreshProxyData, 2000);
|
||||||
return () => clearInterval(refreshInterval);
|
return () => clearInterval(refreshInterval);
|
||||||
}, [isGlobalMode, isDirectMode]);
|
}, [isGlobalMode, isDirectMode, selectedGroup]); // 依赖项添加selectedGroup以便在切换组时重新设置定时器
|
||||||
|
|
||||||
// 处理代理组变更
|
// 处理代理组变更
|
||||||
const handleGroupChange = (event: SelectChangeEvent) => {
|
const handleGroupChange = (event: SelectChangeEvent) => {
|
||||||
@ -393,6 +434,8 @@ export const CurrentProxyCard = () => {
|
|||||||
if (isDirectMode) return;
|
if (isDirectMode) return;
|
||||||
|
|
||||||
const newProxy = event.target.value;
|
const newProxy = event.target.value;
|
||||||
|
const previousProxy = selectedProxy; // 保存变更前的代理节点名称
|
||||||
|
|
||||||
setSelectedProxy(newProxy);
|
setSelectedProxy(newProxy);
|
||||||
|
|
||||||
// 更新显示的代理节点信息
|
// 更新显示的代理节点信息
|
||||||
@ -403,6 +446,18 @@ export const CurrentProxyCard = () => {
|
|||||||
try {
|
try {
|
||||||
// 更新代理设置
|
// 更新代理设置
|
||||||
await updateProxy(selectedGroup, newProxy);
|
await updateProxy(selectedGroup, newProxy);
|
||||||
|
|
||||||
|
// 添加断开连接逻辑 - 与proxy-groups.tsx中的逻辑相同
|
||||||
|
if (verge?.auto_close_connection && previousProxy) {
|
||||||
|
getConnections().then(({ connections }) => {
|
||||||
|
connections.forEach((conn) => {
|
||||||
|
if (conn.chains.includes(previousProxy)) {
|
||||||
|
deleteConnection(conn.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
refreshProxy();
|
refreshProxy();
|
||||||
if (isGlobalMode || isDirectMode) {
|
if (isGlobalMode || isDirectMode) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef, useCallback, memo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
Typography,
|
Typography,
|
||||||
@ -60,157 +60,19 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EnhancedTrafficStats = () => {
|
// 控制更新频率
|
||||||
const { t } = useTranslation();
|
const CONNECTIONS_UPDATE_INTERVAL = 5000; // 5秒更新一次连接数据
|
||||||
|
|
||||||
|
// 统计卡片组件 - 使用memo优化
|
||||||
|
const CompactStatCard = memo(({
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
value,
|
||||||
|
unit,
|
||||||
|
color,
|
||||||
|
onClick,
|
||||||
|
}: StatCardProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { clashInfo } = useClashInfo();
|
|
||||||
const { verge } = useVerge();
|
|
||||||
const trafficRef = useRef<EnhancedTrafficGraphRef>(null);
|
|
||||||
const pageVisible = useVisibility();
|
|
||||||
const [isDebug, setIsDebug] = useState(false);
|
|
||||||
const [trafficStats, setTrafficStats] = useState<TrafficStatData>({
|
|
||||||
uploadTotal: 0,
|
|
||||||
downloadTotal: 0,
|
|
||||||
activeConnections: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 是否显示流量图表
|
|
||||||
const trafficGraph = verge?.traffic_graph ?? true;
|
|
||||||
|
|
||||||
// 获取连接数据
|
|
||||||
const fetchConnections = async () => {
|
|
||||||
try {
|
|
||||||
const connections = await getConnections();
|
|
||||||
if (connections && connections.connections) {
|
|
||||||
const uploadTotal = connections.connections.reduce(
|
|
||||||
(sum, conn) => sum + conn.upload,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
const downloadTotal = connections.connections.reduce(
|
|
||||||
(sum, conn) => sum + conn.download,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
setTrafficStats({
|
|
||||||
uploadTotal,
|
|
||||||
downloadTotal,
|
|
||||||
activeConnections: connections.connections.length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to fetch connections:", err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 定期更新连接数据
|
|
||||||
useEffect(() => {
|
|
||||||
if (pageVisible) {
|
|
||||||
fetchConnections();
|
|
||||||
const intervalId = setInterval(fetchConnections, 5000);
|
|
||||||
return () => clearInterval(intervalId);
|
|
||||||
}
|
|
||||||
}, [pageVisible]);
|
|
||||||
|
|
||||||
// 检查是否支持调试
|
|
||||||
useEffect(() => {
|
|
||||||
isDebugEnabled().then((flag) => setIsDebug(flag));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 为流量数据和内存数据准备状态
|
|
||||||
const [trafficData, setTrafficData] = useState<ITrafficItem>({
|
|
||||||
up: 0,
|
|
||||||
down: 0,
|
|
||||||
});
|
|
||||||
const [memoryData, setMemoryData] = useState<MemoryUsage>({ inuse: 0 });
|
|
||||||
|
|
||||||
// 使用 WebSocket 连接获取流量数据
|
|
||||||
useEffect(() => {
|
|
||||||
if (!clashInfo || !pageVisible) return;
|
|
||||||
|
|
||||||
const { server, secret = "" } = clashInfo;
|
|
||||||
if (!server) return;
|
|
||||||
|
|
||||||
const socket = createAuthSockette(`${server}/traffic`, secret, {
|
|
||||||
onmessage(event) {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(event.data) as ITrafficItem;
|
|
||||||
if (
|
|
||||||
data &&
|
|
||||||
typeof data.up === "number" &&
|
|
||||||
typeof data.down === "number"
|
|
||||||
) {
|
|
||||||
setTrafficData({
|
|
||||||
up: isNaN(data.up) ? 0 : data.up,
|
|
||||||
down: isNaN(data.down) ? 0 : data.down,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (trafficRef.current) {
|
|
||||||
const lastData = {
|
|
||||||
up: isNaN(data.up) ? 0 : data.up,
|
|
||||||
down: isNaN(data.down) ? 0 : data.down,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!window.lastTrafficData) {
|
|
||||||
window.lastTrafficData = { ...lastData };
|
|
||||||
}
|
|
||||||
|
|
||||||
trafficRef.current.appendData({
|
|
||||||
up: lastData.up,
|
|
||||||
down: lastData.down,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
window.lastTrafficData = { ...lastData };
|
|
||||||
|
|
||||||
if (window.animationFrameId) {
|
|
||||||
cancelAnimationFrame(window.animationFrameId);
|
|
||||||
window.animationFrameId = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[Traffic] 解析数据错误:", err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => socket.close();
|
|
||||||
}, [clashInfo, pageVisible]);
|
|
||||||
|
|
||||||
// 使用 WebSocket 连接获取内存数据
|
|
||||||
useEffect(() => {
|
|
||||||
if (!clashInfo || !pageVisible) return;
|
|
||||||
|
|
||||||
const { server, secret = "" } = clashInfo;
|
|
||||||
if (!server) return;
|
|
||||||
|
|
||||||
const socket = createAuthSockette(`${server}/memory`, secret, {
|
|
||||||
onmessage(event) {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(event.data) as MemoryUsage;
|
|
||||||
if (data && typeof data.inuse === "number") {
|
|
||||||
setMemoryData({
|
|
||||||
inuse: isNaN(data.inuse) ? 0 : data.inuse,
|
|
||||||
oslimit: data.oslimit,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[Memory] 解析数据错误:", err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => socket.close();
|
|
||||||
}, [clashInfo, pageVisible]);
|
|
||||||
|
|
||||||
// 解析流量数据
|
|
||||||
const [up, upUnit] = parseTraffic(trafficData.up);
|
|
||||||
const [down, downUnit] = parseTraffic(trafficData.down);
|
|
||||||
const [inuse, inuseUnit] = parseTraffic(memoryData.inuse);
|
|
||||||
const [uploadTotal, uploadTotalUnit] = parseTraffic(trafficStats.uploadTotal);
|
|
||||||
const [downloadTotal, downloadTotalUnit] = parseTraffic(
|
|
||||||
trafficStats.downloadTotal,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 获取调色板颜色
|
// 获取调色板颜色
|
||||||
const getColorFromPalette = (colorName: string) => {
|
const getColorFromPalette = (colorName: string) => {
|
||||||
@ -225,14 +87,7 @@ export const EnhancedTrafficStats = () => {
|
|||||||
return palette.primary.main;
|
return palette.primary.main;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 统计卡片组件
|
return (
|
||||||
const CompactStatCard = ({
|
|
||||||
icon,
|
|
||||||
title,
|
|
||||||
value,
|
|
||||||
unit,
|
|
||||||
color,
|
|
||||||
}: StatCardProps) => (
|
|
||||||
<Paper
|
<Paper
|
||||||
elevation={0}
|
elevation={0}
|
||||||
sx={{
|
sx={{
|
||||||
@ -241,16 +96,16 @@ export const EnhancedTrafficStats = () => {
|
|||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
bgcolor: alpha(getColorFromPalette(color), 0.05),
|
bgcolor: alpha(getColorFromPalette(color), 0.05),
|
||||||
border: `1px solid ${alpha(getColorFromPalette(color), 0.15)}`,
|
border: `1px solid ${alpha(getColorFromPalette(color), 0.15)}`,
|
||||||
//height: "80px",
|
|
||||||
padding: "8px",
|
padding: "8px",
|
||||||
transition: "all 0.2s ease-in-out",
|
transition: "all 0.2s ease-in-out",
|
||||||
cursor: "pointer",
|
cursor: onClick ? "pointer" : "default",
|
||||||
"&:hover": {
|
"&:hover": onClick ? {
|
||||||
bgcolor: alpha(getColorFromPalette(color), 0.1),
|
bgcolor: alpha(getColorFromPalette(color), 0.1),
|
||||||
border: `1px solid ${alpha(getColorFromPalette(color), 0.3)}`,
|
border: `1px solid ${alpha(getColorFromPalette(color), 0.3)}`,
|
||||||
boxShadow: `0 4px 8px rgba(0,0,0,0.05)`,
|
boxShadow: `0 4px 8px rgba(0,0,0,0.05)`,
|
||||||
},
|
} : {},
|
||||||
}}
|
}}
|
||||||
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{/* 图标容器 */}
|
{/* 图标容器 */}
|
||||||
<Grid
|
<Grid
|
||||||
@ -287,9 +142,206 @@ export const EnhancedTrafficStats = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加显示名称
|
||||||
|
CompactStatCard.displayName = "CompactStatCard";
|
||||||
|
|
||||||
|
export const EnhancedTrafficStats = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const theme = useTheme();
|
||||||
|
const { clashInfo } = useClashInfo();
|
||||||
|
const { verge } = useVerge();
|
||||||
|
const trafficRef = useRef<EnhancedTrafficGraphRef>(null);
|
||||||
|
const pageVisible = useVisibility();
|
||||||
|
const [isDebug, setIsDebug] = useState(false);
|
||||||
|
|
||||||
|
// 为流量数据和内存数据准备状态
|
||||||
|
const [trafficData, setTrafficData] = useState<ITrafficItem>({
|
||||||
|
up: 0,
|
||||||
|
down: 0,
|
||||||
|
});
|
||||||
|
const [memoryData, setMemoryData] = useState<MemoryUsage>({ inuse: 0 });
|
||||||
|
const [trafficStats, setTrafficStats] = useState<TrafficStatData>({
|
||||||
|
uploadTotal: 0,
|
||||||
|
downloadTotal: 0,
|
||||||
|
activeConnections: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 是否显示流量图表
|
||||||
|
const trafficGraph = verge?.traffic_graph ?? true;
|
||||||
|
|
||||||
|
// WebSocket引用
|
||||||
|
const trafficSocketRef = useRef<ReturnType<typeof createAuthSockette> | null>(null);
|
||||||
|
const memorySocketRef = useRef<ReturnType<typeof createAuthSockette> | null>(null);
|
||||||
|
|
||||||
|
// 获取连接数据
|
||||||
|
const fetchConnections = useCallback(async () => {
|
||||||
|
if (!pageVisible) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const connections = await getConnections();
|
||||||
|
if (connections && connections.connections) {
|
||||||
|
const uploadTotal = connections.connections.reduce(
|
||||||
|
(sum, conn) => sum + conn.upload,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const downloadTotal = connections.connections.reduce(
|
||||||
|
(sum, conn) => sum + conn.download,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
setTrafficStats({
|
||||||
|
uploadTotal,
|
||||||
|
downloadTotal,
|
||||||
|
activeConnections: connections.connections.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to fetch connections:", err);
|
||||||
|
}
|
||||||
|
}, [pageVisible]);
|
||||||
|
|
||||||
|
// 定期更新连接数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (pageVisible) {
|
||||||
|
fetchConnections();
|
||||||
|
const intervalId = setInterval(fetchConnections, CONNECTIONS_UPDATE_INTERVAL);
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
}, [pageVisible, fetchConnections]);
|
||||||
|
|
||||||
|
// 检查是否支持调试
|
||||||
|
useEffect(() => {
|
||||||
|
isDebugEnabled().then((flag) => setIsDebug(flag));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 处理流量数据更新
|
||||||
|
const handleTrafficUpdate = useCallback((event: MessageEvent) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data) as ITrafficItem;
|
||||||
|
if (
|
||||||
|
data &&
|
||||||
|
typeof data.up === "number" &&
|
||||||
|
typeof data.down === "number"
|
||||||
|
) {
|
||||||
|
// 验证数据有效性,防止NaN
|
||||||
|
const safeUp = isNaN(data.up) ? 0 : data.up;
|
||||||
|
const safeDown = isNaN(data.down) ? 0 : data.down;
|
||||||
|
|
||||||
|
setTrafficData({
|
||||||
|
up: safeUp,
|
||||||
|
down: safeDown,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新图表数据
|
||||||
|
if (trafficRef.current) {
|
||||||
|
trafficRef.current.appendData({
|
||||||
|
up: safeUp,
|
||||||
|
down: safeDown,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清除之前可能存在的动画帧
|
||||||
|
if (window.animationFrameId) {
|
||||||
|
cancelAnimationFrame(window.animationFrameId);
|
||||||
|
window.animationFrameId = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[Traffic] 解析数据错误:", err);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 处理内存数据更新
|
||||||
|
const handleMemoryUpdate = useCallback((event: MessageEvent) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data) as MemoryUsage;
|
||||||
|
if (data && typeof data.inuse === "number") {
|
||||||
|
setMemoryData({
|
||||||
|
inuse: isNaN(data.inuse) ? 0 : data.inuse,
|
||||||
|
oslimit: data.oslimit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[Memory] 解析数据错误:", err);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 使用 WebSocket 连接获取流量数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (!clashInfo || !pageVisible) return;
|
||||||
|
|
||||||
|
const { server, secret = "" } = clashInfo;
|
||||||
|
if (!server) return;
|
||||||
|
|
||||||
|
// 关闭现有连接
|
||||||
|
if (trafficSocketRef.current) {
|
||||||
|
trafficSocketRef.current.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新连接
|
||||||
|
trafficSocketRef.current = createAuthSockette(`${server}/traffic`, secret, {
|
||||||
|
onmessage: handleTrafficUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (trafficSocketRef.current) {
|
||||||
|
trafficSocketRef.current.close();
|
||||||
|
trafficSocketRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [clashInfo, pageVisible, handleTrafficUpdate]);
|
||||||
|
|
||||||
|
// 使用 WebSocket 连接获取内存数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (!clashInfo || !pageVisible) return;
|
||||||
|
|
||||||
|
const { server, secret = "" } = clashInfo;
|
||||||
|
if (!server) return;
|
||||||
|
|
||||||
|
// 关闭现有连接
|
||||||
|
if (memorySocketRef.current) {
|
||||||
|
memorySocketRef.current.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新连接
|
||||||
|
memorySocketRef.current = createAuthSockette(`${server}/memory`, secret, {
|
||||||
|
onmessage: handleMemoryUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (memorySocketRef.current) {
|
||||||
|
memorySocketRef.current.close();
|
||||||
|
memorySocketRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [clashInfo, pageVisible, handleMemoryUpdate]);
|
||||||
|
|
||||||
|
// 解析流量数据
|
||||||
|
const [up, upUnit] = parseTraffic(trafficData.up);
|
||||||
|
const [down, downUnit] = parseTraffic(trafficData.down);
|
||||||
|
const [inuse, inuseUnit] = parseTraffic(memoryData.inuse);
|
||||||
|
const [uploadTotal, uploadTotalUnit] = parseTraffic(trafficStats.uploadTotal);
|
||||||
|
const [downloadTotal, downloadTotalUnit] = parseTraffic(
|
||||||
|
trafficStats.downloadTotal,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 执行垃圾回收
|
||||||
|
const handleGarbageCollection = useCallback(async () => {
|
||||||
|
if (isDebug) {
|
||||||
|
try {
|
||||||
|
await gc();
|
||||||
|
console.log("[Debug] 垃圾回收已执行");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[Debug] 垃圾回收失败:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isDebug]);
|
||||||
|
|
||||||
// 渲染流量图表
|
// 渲染流量图表
|
||||||
const renderTrafficGraph = () => {
|
const renderTrafficGraph = useCallback(() => {
|
||||||
if (!trafficGraph || !pageVisible) return null;
|
if (!trafficGraph || !pageVisible) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -328,7 +380,7 @@ export const EnhancedTrafficStats = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
};
|
}, [trafficGraph, pageVisible, theme.palette.divider, isDebug]);
|
||||||
|
|
||||||
// 统计卡片配置
|
// 统计卡片配置
|
||||||
const statCards = [
|
const statCards = [
|
||||||
@ -373,7 +425,7 @@ export const EnhancedTrafficStats = () => {
|
|||||||
value: inuse,
|
value: inuse,
|
||||||
unit: inuseUnit,
|
unit: inuseUnit,
|
||||||
color: "error" as const,
|
color: "error" as const,
|
||||||
onClick: isDebug ? async () => await gc() : undefined,
|
onClick: isDebug ? handleGarbageCollection : undefined,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -385,7 +437,7 @@ export const EnhancedTrafficStats = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
{/* 统计卡片区域 */}
|
{/* 统计卡片区域 */}
|
||||||
{statCards.map((card, index) => (
|
{statCards.map((card, index) => (
|
||||||
<Grid size={4}>
|
<Grid key={index} size={4}>
|
||||||
<CompactStatCard {...card} />
|
<CompactStatCard {...card} />
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user