chore: remove unused file and improve traffic monitor
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { useTheme } from "@mui/material";
|
import { useTheme } from "@mui/material";
|
||||||
import { useEffect, useImperativeHandle, useRef, type Ref } from "react";
|
import { useEffect, useImperativeHandle, useRef, type Ref } from "react";
|
||||||
|
import { Traffic } from "tauri-plugin-mihomo-api";
|
||||||
|
|
||||||
const maxPoint = 30;
|
const maxPoint = 30;
|
||||||
|
|
||||||
@@ -14,10 +15,8 @@ const downLineWidth = 4;
|
|||||||
|
|
||||||
const defaultList = Array(maxPoint + 2).fill({ up: 0, down: 0 });
|
const defaultList = Array(maxPoint + 2).fill({ up: 0, down: 0 });
|
||||||
|
|
||||||
type TrafficData = { up: number; down: number };
|
|
||||||
|
|
||||||
export interface TrafficRef {
|
export interface TrafficRef {
|
||||||
appendData: (data: TrafficData) => void;
|
appendData: (data: Traffic) => void;
|
||||||
toggleStyle: () => void;
|
toggleStyle: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,15 +26,15 @@ export interface TrafficRef {
|
|||||||
export function TrafficGraph({ ref }: { ref?: Ref<TrafficRef> }) {
|
export function TrafficGraph({ ref }: { ref?: Ref<TrafficRef> }) {
|
||||||
const countRef = useRef(0);
|
const countRef = useRef(0);
|
||||||
const styleRef = useRef(true);
|
const styleRef = useRef(true);
|
||||||
const listRef = useRef<TrafficData[]>(defaultList);
|
const listRef = useRef<Traffic[]>(defaultList);
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null!);
|
const canvasRef = useRef<HTMLCanvasElement>(null!);
|
||||||
|
|
||||||
const cacheRef = useRef<TrafficData | null>(null);
|
const cacheRef = useRef<Traffic | null>(null);
|
||||||
|
|
||||||
const { palette } = useTheme();
|
const { palette } = useTheme();
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
appendData: (data: TrafficData) => {
|
appendData: (data: Traffic) => {
|
||||||
cacheRef.current = data;
|
cacheRef.current = data;
|
||||||
},
|
},
|
||||||
toggleStyle: () => {
|
toggleStyle: () => {
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
import dayjs from "dayjs";
|
|
||||||
import { useLocalStorage } from "foxact/use-local-storage";
|
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
import { mutate } from "swr";
|
|
||||||
import useSWRSubscription from "swr/subscription";
|
|
||||||
import { MihomoWebSocket } from "tauri-plugin-mihomo-api";
|
|
||||||
|
|
||||||
import { getClashLogs } from "@/services/cmds";
|
|
||||||
import { useClashLog } from "@/services/states";
|
|
||||||
|
|
||||||
const MAX_LOG_NUM = 1000;
|
|
||||||
|
|
||||||
export const useLogData = () => {
|
|
||||||
const [clashLog] = useClashLog();
|
|
||||||
const enableLog = clashLog.enable;
|
|
||||||
const logLevel = clashLog.logLevel;
|
|
||||||
|
|
||||||
const [date, setDate] = useLocalStorage("mihomo_logs_date", Date.now());
|
|
||||||
const subscriptKey = enableLog ? `getClashLog-${date}` : null;
|
|
||||||
|
|
||||||
const ws = useRef<MihomoWebSocket | null>(null);
|
|
||||||
const wsFirstConnection = useRef<boolean>(true);
|
|
||||||
const timeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
|
|
||||||
|
|
||||||
const response = useSWRSubscription<ILogItem[], any, string | null>(
|
|
||||||
subscriptKey,
|
|
||||||
(_key, { next }) => {
|
|
||||||
const reconnect = async () => {
|
|
||||||
await ws.current?.close();
|
|
||||||
ws.current = null;
|
|
||||||
timeoutRef.current = setTimeout(async () => await connect(), 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
const connect = () =>
|
|
||||||
MihomoWebSocket.connect_logs(logLevel)
|
|
||||||
.then(async (ws_) => {
|
|
||||||
ws.current = ws_;
|
|
||||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
||||||
|
|
||||||
const logs = await getClashLogs();
|
|
||||||
let filterLogs: ILogItem[] = [];
|
|
||||||
switch (logLevel) {
|
|
||||||
case "debug":
|
|
||||||
filterLogs = logs.filter((i) =>
|
|
||||||
["debug", "info", "warning", "error"].includes(i.type),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "info":
|
|
||||||
filterLogs = logs.filter((i) =>
|
|
||||||
["info", "warning", "error"].includes(i.type),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "warning":
|
|
||||||
filterLogs = logs.filter((i) =>
|
|
||||||
["warning", "error"].includes(i.type),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "error":
|
|
||||||
filterLogs = logs.filter((i) => i.type === "error");
|
|
||||||
break;
|
|
||||||
case "silent":
|
|
||||||
filterLogs = [];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
filterLogs = logs;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
next(null, filterLogs);
|
|
||||||
|
|
||||||
const buffer: ILogItem[] = [];
|
|
||||||
let flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
||||||
const flush = () => {
|
|
||||||
if (buffer.length > 0) {
|
|
||||||
next(null, (l) => {
|
|
||||||
let newList = [...(l ?? []), ...buffer.splice(0)];
|
|
||||||
if (newList.length > MAX_LOG_NUM) {
|
|
||||||
newList = newList.slice(
|
|
||||||
-Math.min(MAX_LOG_NUM, newList.length),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return newList;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
flushTimer = null;
|
|
||||||
};
|
|
||||||
ws_.addListener(async (msg) => {
|
|
||||||
if (msg.type === "Text") {
|
|
||||||
if (msg.data.startsWith("Websocket error")) {
|
|
||||||
next(msg.data);
|
|
||||||
await reconnect();
|
|
||||||
} else {
|
|
||||||
const data = JSON.parse(msg.data) as ILogItem;
|
|
||||||
data.time = dayjs().format("MM-DD HH:mm:ss");
|
|
||||||
buffer.push(data);
|
|
||||||
|
|
||||||
// flush data
|
|
||||||
if (!flushTimer) {
|
|
||||||
flushTimer = setTimeout(flush, 50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((_) => {
|
|
||||||
if (!ws.current) {
|
|
||||||
timeoutRef.current = setTimeout(async () => await connect(), 500);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
wsFirstConnection.current ||
|
|
||||||
(ws.current && !wsFirstConnection.current)
|
|
||||||
) {
|
|
||||||
wsFirstConnection.current = false;
|
|
||||||
if (ws.current) {
|
|
||||||
ws.current.close();
|
|
||||||
ws.current = null;
|
|
||||||
}
|
|
||||||
connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
ws.current?.close();
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fallbackData: [],
|
|
||||||
keepPreviousData: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
mutate(`$sub$${subscriptKey}`);
|
|
||||||
}, [date, subscriptKey]);
|
|
||||||
|
|
||||||
const previousLogLevel = useRef<string | undefined>(undefined);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!logLevel) {
|
|
||||||
previousLogLevel.current = logLevel ?? undefined;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (previousLogLevel.current === logLevel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
previousLogLevel.current = logLevel;
|
|
||||||
ws.current?.close();
|
|
||||||
setDate(Date.now());
|
|
||||||
}, [logLevel, setDate]);
|
|
||||||
|
|
||||||
const refreshGetClashLog = (clear = false) => {
|
|
||||||
if (clear) {
|
|
||||||
mutate(`$sub$${subscriptKey}`, []);
|
|
||||||
} else {
|
|
||||||
setDate(Date.now());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { response, refreshGetClashLog };
|
|
||||||
};
|
|
||||||
@@ -1,12 +1,162 @@
|
|||||||
import {
|
import dayjs from "dayjs";
|
||||||
useGlobalLogData,
|
import { useLocalStorage } from "foxact/use-local-storage";
|
||||||
clearGlobalLogs,
|
import { useEffect, useRef } from "react";
|
||||||
// LogLevel,
|
import { mutate } from "swr";
|
||||||
} from "@/services/global-log-service";
|
import useSWRSubscription from "swr/subscription";
|
||||||
|
import { MihomoWebSocket } from "tauri-plugin-mihomo-api";
|
||||||
|
|
||||||
// 为了向后兼容,导出相同的类型
|
import { getClashLogs } from "@/services/cmds";
|
||||||
// export type { LogLevel };
|
import { useClashLog } from "@/services/states";
|
||||||
|
|
||||||
export const useLogData = useGlobalLogData;
|
const MAX_LOG_NUM = 1000;
|
||||||
|
|
||||||
export const clearLogs = clearGlobalLogs;
|
export const useLogData = () => {
|
||||||
|
const [clashLog] = useClashLog();
|
||||||
|
const enableLog = clashLog.enable;
|
||||||
|
const logLevel = clashLog.logLevel;
|
||||||
|
|
||||||
|
const [date, setDate] = useLocalStorage("mihomo_logs_date", Date.now());
|
||||||
|
const subscriptKey = enableLog ? `getClashLog-${date}` : null;
|
||||||
|
|
||||||
|
const ws = useRef<MihomoWebSocket | null>(null);
|
||||||
|
const wsFirstConnection = useRef<boolean>(true);
|
||||||
|
const timeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
|
||||||
|
|
||||||
|
const response = useSWRSubscription<ILogItem[], any, string | null>(
|
||||||
|
subscriptKey,
|
||||||
|
(_key, { next }) => {
|
||||||
|
const reconnect = async () => {
|
||||||
|
await ws.current?.close();
|
||||||
|
ws.current = null;
|
||||||
|
timeoutRef.current = setTimeout(async () => await connect(), 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const connect = () =>
|
||||||
|
MihomoWebSocket.connect_logs(logLevel)
|
||||||
|
.then(async (ws_) => {
|
||||||
|
ws.current = ws_;
|
||||||
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||||
|
|
||||||
|
const logs = await getClashLogs();
|
||||||
|
let filterLogs: ILogItem[] = [];
|
||||||
|
switch (logLevel) {
|
||||||
|
case "debug":
|
||||||
|
filterLogs = logs.filter((i) =>
|
||||||
|
["debug", "info", "warning", "error"].includes(i.type),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "info":
|
||||||
|
filterLogs = logs.filter((i) =>
|
||||||
|
["info", "warning", "error"].includes(i.type),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "warning":
|
||||||
|
filterLogs = logs.filter((i) =>
|
||||||
|
["warning", "error"].includes(i.type),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
filterLogs = logs.filter((i) => i.type === "error");
|
||||||
|
break;
|
||||||
|
case "silent":
|
||||||
|
filterLogs = [];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
filterLogs = logs;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
next(null, filterLogs);
|
||||||
|
|
||||||
|
const buffer: ILogItem[] = [];
|
||||||
|
let flushTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
const flush = () => {
|
||||||
|
if (buffer.length > 0) {
|
||||||
|
next(null, (l) => {
|
||||||
|
let newList = [...(l ?? []), ...buffer.splice(0)];
|
||||||
|
if (newList.length > MAX_LOG_NUM) {
|
||||||
|
newList = newList.slice(
|
||||||
|
-Math.min(MAX_LOG_NUM, newList.length),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return newList;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
flushTimer = null;
|
||||||
|
};
|
||||||
|
ws_.addListener(async (msg) => {
|
||||||
|
if (msg.type === "Text") {
|
||||||
|
if (msg.data.startsWith("Websocket error")) {
|
||||||
|
next(msg.data);
|
||||||
|
await reconnect();
|
||||||
|
} else {
|
||||||
|
const data = JSON.parse(msg.data) as ILogItem;
|
||||||
|
data.time = dayjs().format("MM-DD HH:mm:ss");
|
||||||
|
buffer.push(data);
|
||||||
|
|
||||||
|
// flush data
|
||||||
|
if (!flushTimer) {
|
||||||
|
flushTimer = setTimeout(flush, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((_) => {
|
||||||
|
if (!ws.current) {
|
||||||
|
timeoutRef.current = setTimeout(async () => await connect(), 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
wsFirstConnection.current ||
|
||||||
|
(ws.current && !wsFirstConnection.current)
|
||||||
|
) {
|
||||||
|
wsFirstConnection.current = false;
|
||||||
|
if (ws.current) {
|
||||||
|
ws.current.close();
|
||||||
|
ws.current = null;
|
||||||
|
}
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ws.current?.close();
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fallbackData: [],
|
||||||
|
keepPreviousData: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
mutate(`$sub$${subscriptKey}`);
|
||||||
|
}, [date, subscriptKey]);
|
||||||
|
|
||||||
|
const previousLogLevel = useRef<string | undefined>(undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!logLevel) {
|
||||||
|
previousLogLevel.current = logLevel ?? undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousLogLevel.current === logLevel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
previousLogLevel.current = logLevel;
|
||||||
|
ws.current?.close();
|
||||||
|
setDate(Date.now());
|
||||||
|
}, [logLevel, setDate]);
|
||||||
|
|
||||||
|
const refreshGetClashLog = (clear = false) => {
|
||||||
|
if (clear) {
|
||||||
|
mutate(`$sub$${subscriptKey}`, []);
|
||||||
|
} else {
|
||||||
|
setDate(Date.now());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { response, refreshGetClashLog };
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,15 +2,20 @@ import { useLocalStorage } from "foxact/use-local-storage";
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
import useSWRSubscription from "swr/subscription";
|
import useSWRSubscription from "swr/subscription";
|
||||||
import { MihomoWebSocket } from "tauri-plugin-mihomo-api";
|
import { MihomoWebSocket, Traffic } from "tauri-plugin-mihomo-api";
|
||||||
|
|
||||||
import { TrafficRef } from "@/components/layout/traffic-graph";
|
import { TrafficRef } from "@/components/layout/traffic-graph";
|
||||||
|
|
||||||
|
import { useTrafficMonitorEnhanced } from "./use-traffic-monitor";
|
||||||
|
|
||||||
export const useTrafficData = () => {
|
export const useTrafficData = () => {
|
||||||
const [date, setDate] = useLocalStorage("mihomo_traffic_date", Date.now());
|
const [date, setDate] = useLocalStorage("mihomo_traffic_date", Date.now());
|
||||||
const subscriptKey = `getClashTraffic-${date}`;
|
const subscriptKey = `getClashTraffic-${date}`;
|
||||||
|
|
||||||
const trafficRef = useRef<TrafficRef>(null);
|
const trafficRef = useRef<TrafficRef>(null);
|
||||||
|
const {
|
||||||
|
graphData: { appendData },
|
||||||
|
} = useTrafficMonitorEnhanced();
|
||||||
const ws = useRef<MihomoWebSocket | null>(null);
|
const ws = useRef<MihomoWebSocket | null>(null);
|
||||||
const wsFirstConnection = useRef<boolean>(true);
|
const wsFirstConnection = useRef<boolean>(true);
|
||||||
const timeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
|
const timeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
|
||||||
@@ -36,8 +41,9 @@ export const useTrafficData = () => {
|
|||||||
next(msg.data, { up: 0, down: 0 });
|
next(msg.data, { up: 0, down: 0 });
|
||||||
await reconnect();
|
await reconnect();
|
||||||
} else {
|
} else {
|
||||||
const data = JSON.parse(msg.data) as ITrafficItem;
|
const data = JSON.parse(msg.data) as Traffic;
|
||||||
trafficRef.current?.appendData(data);
|
trafficRef.current?.appendData(data);
|
||||||
|
appendData(data);
|
||||||
next(null, data);
|
next(null, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { useEffect, useRef, useCallback, useReducer } from "react";
|
import { useEffect, useRef, useCallback, useReducer } from "react";
|
||||||
|
import { Traffic } from "tauri-plugin-mihomo-api";
|
||||||
// import { useClashInfo } from "@/hooks/use-clash";
|
|
||||||
// import { useVisibility } from "@/hooks/use-visibility";
|
|
||||||
|
|
||||||
import { useTrafficData } from "./use-traffic-data";
|
|
||||||
|
|
||||||
// 增强的流量数据点接口
|
// 增强的流量数据点接口
|
||||||
export interface ITrafficDataPoint {
|
export interface ITrafficDataPoint {
|
||||||
@@ -175,18 +171,11 @@ class TrafficDataSampler {
|
|||||||
// 全局单例
|
// 全局单例
|
||||||
const refCounter = new ReferenceCounter();
|
const refCounter = new ReferenceCounter();
|
||||||
let globalSampler: TrafficDataSampler | null = null;
|
let globalSampler: TrafficDataSampler | null = null;
|
||||||
// let lastValidData: ISystemMonitorOverview | null = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 增强的流量监控Hook - 支持数据压缩、采样和引用计数
|
* 增强的流量监控Hook - 支持数据压缩、采样和引用计数
|
||||||
*/
|
*/
|
||||||
export const useTrafficMonitorEnhanced = () => {
|
export const useTrafficMonitorEnhanced = () => {
|
||||||
// const { clashInfo } = useClashInfo();
|
|
||||||
// const pageVisible = useVisibility();
|
|
||||||
const {
|
|
||||||
response: { data: traffic },
|
|
||||||
} = useTrafficData();
|
|
||||||
|
|
||||||
// 初始化采样器
|
// 初始化采样器
|
||||||
if (!globalSampler) {
|
if (!globalSampler) {
|
||||||
globalSampler = new TrafficDataSampler({
|
globalSampler = new TrafficDataSampler({
|
||||||
@@ -232,8 +221,17 @@ export const useTrafficMonitorEnhanced = () => {
|
|||||||
refCounter.onCountChange(handleCountChange);
|
refCounter.onCountChange(handleCountChange);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// const monitorData = useRef<ISystemMonitorOverview | null>(null);
|
// 获取指定时间范围的数据
|
||||||
useEffect(() => {
|
const getDataForTimeRange = useCallback(
|
||||||
|
(minutes: number): ITrafficDataPoint[] => {
|
||||||
|
if (!globalSampler) return [];
|
||||||
|
return globalSampler.getDataForTimeRange(minutes);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 添加流量数据
|
||||||
|
const appendData = useCallback((traffic: Traffic) => {
|
||||||
if (globalSampler) {
|
if (globalSampler) {
|
||||||
// 添加到采样器
|
// 添加到采样器
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
@@ -250,77 +248,7 @@ export const useTrafficMonitorEnhanced = () => {
|
|||||||
};
|
};
|
||||||
globalSampler.addDataPoint(dataPoint);
|
globalSampler.addDataPoint(dataPoint);
|
||||||
}
|
}
|
||||||
}, [traffic]);
|
}, []);
|
||||||
|
|
||||||
// const { data: monitorData, error } = useSWR<ISystemMonitorOverview>(
|
|
||||||
// shouldFetch ? "getSystemMonitorOverviewSafe" : null,
|
|
||||||
// getSystemMonitorOverviewSafe,
|
|
||||||
// {
|
|
||||||
// refreshInterval: shouldFetch ? 1000 : 0, // 只有在需要时才刷新
|
|
||||||
// keepPreviousData: true,
|
|
||||||
// onSuccess: (data) => {
|
|
||||||
// // console.log("[TrafficMonitorEnhanced] 获取到监控数据:", data);
|
|
||||||
|
|
||||||
// if (data?.traffic?.raw && globalSampler) {
|
|
||||||
// // 保存最后有效数据
|
|
||||||
// lastValidData = data;
|
|
||||||
|
|
||||||
// // 添加到采样器
|
|
||||||
// const timestamp = Date.now();
|
|
||||||
// const dataPoint: ITrafficDataPoint = {
|
|
||||||
// up: data.traffic.raw.up_rate || 0,
|
|
||||||
// down: data.traffic.raw.down_rate || 0,
|
|
||||||
// timestamp,
|
|
||||||
// name: new Date(timestamp).toLocaleTimeString("en-US", {
|
|
||||||
// hour12: false,
|
|
||||||
// hour: "2-digit",
|
|
||||||
// minute: "2-digit",
|
|
||||||
// second: "2-digit",
|
|
||||||
// }),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// globalSampler.addDataPoint(dataPoint);
|
|
||||||
// triggerUpdate();
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// onError: (error) => {
|
|
||||||
// console.error(
|
|
||||||
// "[TrafficMonitorEnhanced] 网络错误,使用最后有效数据. 错误详情:",
|
|
||||||
// {
|
|
||||||
// message: error?.message || "未知错误",
|
|
||||||
// stack: error?.stack || "无堆栈信息",
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// // 网络错误时不清空数据,继续使用最后有效值
|
|
||||||
// // 但是添加一个错误标记的数据点(流量为0)
|
|
||||||
// if (globalSampler) {
|
|
||||||
// const timestamp = Date.now();
|
|
||||||
// const errorPoint: ITrafficDataPoint = {
|
|
||||||
// up: 0,
|
|
||||||
// down: 0,
|
|
||||||
// timestamp,
|
|
||||||
// name: new Date(timestamp).toLocaleTimeString("en-US", {
|
|
||||||
// hour12: false,
|
|
||||||
// hour: "2-digit",
|
|
||||||
// minute: "2-digit",
|
|
||||||
// second: "2-digit",
|
|
||||||
// }),
|
|
||||||
// };
|
|
||||||
// globalSampler.addDataPoint(errorPoint);
|
|
||||||
// triggerUpdate();
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
|
|
||||||
// 获取指定时间范围的数据
|
|
||||||
const getDataForTimeRange = useCallback(
|
|
||||||
(minutes: number): ITrafficDataPoint[] => {
|
|
||||||
if (!globalSampler) return [];
|
|
||||||
return globalSampler.getDataForTimeRange(minutes);
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
// 清空数据
|
// 清空数据
|
||||||
const clearData = useCallback(() => {
|
const clearData = useCallback(() => {
|
||||||
@@ -342,64 +270,20 @@ export const useTrafficMonitorEnhanced = () => {
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 构建返回的监控数据,优先使用当前数据,fallback到最后有效数据
|
|
||||||
// const currentData = monitorData.current || lastValidData;
|
|
||||||
// const trafficMonitorData = {
|
|
||||||
// traffic: currentData?.traffic || {
|
|
||||||
// raw: { up: 0, down: 0, up_rate: 0, down_rate: 0 },
|
|
||||||
// formatted: {
|
|
||||||
// up_rate: "0B",
|
|
||||||
// down_rate: "0B",
|
|
||||||
// total_up: "0B",
|
|
||||||
// total_down: "0B",
|
|
||||||
// },
|
|
||||||
// is_fresh: false,
|
|
||||||
// },
|
|
||||||
// memory: currentData?.memory || {
|
|
||||||
// raw: { inuse: 0, oslimit: 0, usage_percent: 0 },
|
|
||||||
// formatted: { inuse: "0B", oslimit: "0B", usage_percent: 0 },
|
|
||||||
// is_fresh: false,
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 监控数据
|
|
||||||
// monitorData: trafficMonitorData,
|
|
||||||
|
|
||||||
// 图表数据管理
|
// 图表数据管理
|
||||||
graphData: {
|
graphData: {
|
||||||
dataPoints: globalSampler?.getDataForTimeRange(60) || [], // 默认获取1小时数据
|
dataPoints: globalSampler?.getDataForTimeRange(60) || [], // 默认获取1小时数据
|
||||||
getDataForTimeRange,
|
getDataForTimeRange,
|
||||||
|
appendData,
|
||||||
clearData,
|
clearData,
|
||||||
},
|
},
|
||||||
|
|
||||||
// 状态信息
|
|
||||||
// isLoading: !currentData,
|
|
||||||
// isDataFresh: currentData?.traffic?.is_fresh || false,
|
|
||||||
// hasValidData: !!lastValidData,
|
|
||||||
|
|
||||||
// 性能统计
|
// 性能统计
|
||||||
samplerStats: getSamplerStats(),
|
samplerStats: getSamplerStats(),
|
||||||
referenceCount: refCounter.getCount(),
|
referenceCount: refCounter.getCount(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 轻量级流量数据Hook
|
|
||||||
*/
|
|
||||||
// export const useTrafficDataEnhanced = () => {
|
|
||||||
// const { monitorData, isLoading, isDataFresh, hasValidData } =
|
|
||||||
// useTrafficMonitorEnhanced();
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// traffic: monitorData.traffic,
|
|
||||||
// memory: monitorData.memory,
|
|
||||||
// isLoading,
|
|
||||||
// isDataFresh,
|
|
||||||
// hasValidData,
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图表数据Hook
|
* 图表数据Hook
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { useCustomTheme } from "@/components/layout/use-custom-theme";
|
|||||||
import { useConnectionData } from "@/hooks/use-connection-data";
|
import { useConnectionData } from "@/hooks/use-connection-data";
|
||||||
import { useI18n } from "@/hooks/use-i18n";
|
import { useI18n } from "@/hooks/use-i18n";
|
||||||
import { useListen } from "@/hooks/use-listen";
|
import { useListen } from "@/hooks/use-listen";
|
||||||
import { useLogData } from "@/hooks/use-log-data-new";
|
import { useLogData } from "@/hooks/use-log-data";
|
||||||
import { useMemoryData } from "@/hooks/use-memory-data";
|
import { useMemoryData } from "@/hooks/use-memory-data";
|
||||||
import { useTrafficData } from "@/hooks/use-traffic-data";
|
import { useTrafficData } from "@/hooks/use-traffic-data";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ import { BaseSearchBox } from "@/components/base/base-search-box";
|
|||||||
import { SearchState } from "@/components/base/base-search-box";
|
import { SearchState } from "@/components/base/base-search-box";
|
||||||
import { BaseStyledSelect } from "@/components/base/base-styled-select";
|
import { BaseStyledSelect } from "@/components/base/base-styled-select";
|
||||||
import LogItem from "@/components/log/log-item";
|
import LogItem from "@/components/log/log-item";
|
||||||
import { useLogData } from "@/hooks/use-log-data-new";
|
import { useLogData } from "@/hooks/use-log-data";
|
||||||
import { toggleLogEnabled } from "@/services/global-log-service";
|
|
||||||
import { LogFilter, useClashLog } from "@/services/states";
|
import { LogFilter, useClashLog } from "@/services/states";
|
||||||
|
|
||||||
const LogPage = () => {
|
const LogPage = () => {
|
||||||
@@ -52,11 +51,9 @@ const LogPage = () => {
|
|||||||
|
|
||||||
const handleLogLevelChange = (newLevel: string) => {
|
const handleLogLevelChange = (newLevel: string) => {
|
||||||
setClashLog((pre: any) => ({ ...pre, logFilter: newLevel }));
|
setClashLog((pre: any) => ({ ...pre, logFilter: newLevel }));
|
||||||
// changeLogLevel(newLevel);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleLog = async () => {
|
const handleToggleLog = async () => {
|
||||||
await toggleLogEnabled();
|
|
||||||
setClashLog((pre: any) => ({ ...pre, enable: !enableLog }));
|
setClashLog((pre: any) => ({ ...pre, enable: !enableLog }));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,7 +87,6 @@ const LogPage = () => {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
refreshGetClashLog(true);
|
refreshGetClashLog(true);
|
||||||
// clearGlobalLogs();
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("Clear")}
|
{t("Clear")}
|
||||||
|
|||||||
@@ -1,191 +0,0 @@
|
|||||||
// 全局日志服务,使应用在任何页面都能收集日志
|
|
||||||
import { create } from "zustand";
|
|
||||||
|
|
||||||
import {
|
|
||||||
fetchLogsViaIPC,
|
|
||||||
stopLogsStreaming,
|
|
||||||
clearLogs as clearLogsIPC,
|
|
||||||
} from "@/services/ipc-log-service";
|
|
||||||
|
|
||||||
// 最大日志数量
|
|
||||||
const MAX_LOG_NUM = 1000;
|
|
||||||
|
|
||||||
export type LogLevel = "debug" | "info" | "warning" | "error" | "all";
|
|
||||||
|
|
||||||
interface ILogItem {
|
|
||||||
time?: string;
|
|
||||||
type: string;
|
|
||||||
payload: string;
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GlobalLogStore {
|
|
||||||
logs: ILogItem[];
|
|
||||||
enabled: boolean;
|
|
||||||
isConnected: boolean;
|
|
||||||
currentLevel: LogLevel;
|
|
||||||
setEnabled: (enabled: boolean) => void;
|
|
||||||
setCurrentLevel: (level: LogLevel) => void;
|
|
||||||
clearLogs: () => void;
|
|
||||||
appendLog: (log: ILogItem) => void;
|
|
||||||
setLogs: (logs: ILogItem[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建全局状态存储
|
|
||||||
export const useGlobalLogStore = create<GlobalLogStore>((set) => ({
|
|
||||||
logs: [],
|
|
||||||
enabled: false,
|
|
||||||
isConnected: false,
|
|
||||||
currentLevel: "info",
|
|
||||||
setEnabled: (enabled) => set({ enabled }),
|
|
||||||
setCurrentLevel: (currentLevel) => set({ currentLevel }),
|
|
||||||
clearLogs: () => set({ logs: [] }),
|
|
||||||
appendLog: (log: ILogItem) =>
|
|
||||||
set((state) => {
|
|
||||||
const newLogs =
|
|
||||||
state.logs.length >= MAX_LOG_NUM
|
|
||||||
? [...state.logs.slice(1), log]
|
|
||||||
: [...state.logs, log];
|
|
||||||
return { logs: newLogs };
|
|
||||||
}),
|
|
||||||
setLogs: (logs: ILogItem[]) => set({ logs }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// IPC 日志获取函数
|
|
||||||
export const fetchLogsViaIPCPeriodically = async () => {
|
|
||||||
try {
|
|
||||||
const logs = await fetchLogsViaIPC();
|
|
||||||
useGlobalLogStore.getState().setLogs(logs);
|
|
||||||
console.log(`[GlobalLog-IPC] 成功获取 ${logs.length} 条日志`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[GlobalLog-IPC] 获取日志失败:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化全局日志服务 (仅IPC模式)
|
|
||||||
let ipcPollingInterval: ReturnType<typeof setInterval> | null = null;
|
|
||||||
let isInitializing = false; // 添加初始化标志
|
|
||||||
|
|
||||||
export const initGlobalLogService = (
|
|
||||||
enabled: boolean = false,
|
|
||||||
logLevel: LogLevel = "info",
|
|
||||||
) => {
|
|
||||||
// 防止重复初始化
|
|
||||||
if (isInitializing) {
|
|
||||||
console.log("[GlobalLog-IPC] 正在初始化中,跳过重复调用");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { setEnabled, setCurrentLevel } = useGlobalLogStore.getState();
|
|
||||||
|
|
||||||
// 更新启用状态
|
|
||||||
setEnabled(enabled);
|
|
||||||
setCurrentLevel(logLevel);
|
|
||||||
|
|
||||||
// 如果不启用,则不初始化
|
|
||||||
if (!enabled) {
|
|
||||||
clearIpcPolling();
|
|
||||||
useGlobalLogStore.setState({ isConnected: false });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isInitializing = true;
|
|
||||||
|
|
||||||
// 使用IPC流式模式
|
|
||||||
console.log("[GlobalLog-IPC] 启用IPC流式日志服务");
|
|
||||||
|
|
||||||
// 启动流式监控
|
|
||||||
// startLogsStreaming(logLevel);
|
|
||||||
|
|
||||||
// 立即获取一次日志
|
|
||||||
fetchLogsViaIPCPeriodically();
|
|
||||||
|
|
||||||
// 设置定期轮询来同步流式缓存的数据
|
|
||||||
clearIpcPolling();
|
|
||||||
ipcPollingInterval = setInterval(() => {
|
|
||||||
fetchLogsViaIPCPeriodically();
|
|
||||||
}, 1000); // 每1秒同步一次流式缓存
|
|
||||||
|
|
||||||
// 设置连接状态
|
|
||||||
useGlobalLogStore.setState({ isConnected: true });
|
|
||||||
|
|
||||||
isInitializing = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 清除IPC轮询
|
|
||||||
const clearIpcPolling = () => {
|
|
||||||
if (ipcPollingInterval) {
|
|
||||||
clearInterval(ipcPollingInterval);
|
|
||||||
ipcPollingInterval = null;
|
|
||||||
console.log("[GlobalLog-IPC] 轮询已停止");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 停止日志监控 (仅IPC模式)
|
|
||||||
export const stopGlobalLogMonitoring = async () => {
|
|
||||||
clearIpcPolling();
|
|
||||||
isInitializing = false; // 重置初始化标志
|
|
||||||
|
|
||||||
// 调用后端停止监控
|
|
||||||
await stopLogsStreaming();
|
|
||||||
|
|
||||||
useGlobalLogStore.setState({ isConnected: false });
|
|
||||||
console.log("[GlobalLog-IPC] 日志监控已停止");
|
|
||||||
};
|
|
||||||
|
|
||||||
// 关闭全局日志连接 (仅IPC模式) - 保持向后兼容
|
|
||||||
export const closeGlobalLogConnection = async () => {
|
|
||||||
await stopGlobalLogMonitoring();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 切换日志级别 (仅IPC模式)
|
|
||||||
export const changeLogLevel = (level: LogLevel) => {
|
|
||||||
const { enabled } = useGlobalLogStore.getState();
|
|
||||||
useGlobalLogStore.setState({ currentLevel: level });
|
|
||||||
|
|
||||||
// 如果正在初始化,则跳过,避免重复启动
|
|
||||||
if (isInitializing) {
|
|
||||||
console.log("[GlobalLog-IPC] 正在初始化中,跳过级别变更流启动");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enabled) {
|
|
||||||
// IPC流式模式下重新启动监控
|
|
||||||
// startLogsStreaming(level);
|
|
||||||
fetchLogsViaIPCPeriodically();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 切换启用状态 (仅IPC模式)
|
|
||||||
export const toggleLogEnabled = async () => {
|
|
||||||
const { enabled, currentLevel } = useGlobalLogStore.getState();
|
|
||||||
const newEnabled = !enabled;
|
|
||||||
|
|
||||||
useGlobalLogStore.setState({ enabled: newEnabled });
|
|
||||||
|
|
||||||
if (newEnabled) {
|
|
||||||
// IPC模式下直接启动
|
|
||||||
initGlobalLogService(newEnabled, currentLevel);
|
|
||||||
} else {
|
|
||||||
await stopGlobalLogMonitoring();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取日志清理函数 - 只清理前端日志,不停止监控
|
|
||||||
export const clearGlobalLogs = () => {
|
|
||||||
useGlobalLogStore.getState().clearLogs();
|
|
||||||
// 同时清理后端缓存的日志,但不停止监控
|
|
||||||
clearLogsIPC();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 自定义钩子,用于获取过滤后的日志数据
|
|
||||||
export const useGlobalLogData = (logLevel: LogLevel = "info") => {
|
|
||||||
const logs = useGlobalLogStore((state) => state.logs);
|
|
||||||
|
|
||||||
// 根据当前选择的日志等级过滤日志
|
|
||||||
if (logLevel === "info") {
|
|
||||||
return logs;
|
|
||||||
} else {
|
|
||||||
return logs.filter((log) => log.type.toLowerCase() === logLevel);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
// IPC-based log service using Tauri commands with streaming support
|
|
||||||
|
|
||||||
import { clearLogs as clearLogsCmd } from "@/services/cmds";
|
|
||||||
|
|
||||||
type LogLevel = "debug" | "info" | "warning" | "error" | "all";
|
|
||||||
|
|
||||||
interface ILogItem {
|
|
||||||
time?: string;
|
|
||||||
type: string;
|
|
||||||
payload: string;
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start logs monitoring with specified level
|
|
||||||
export const startLogsStreaming = async (logLevel: LogLevel = "info") => {
|
|
||||||
try {
|
|
||||||
// await startLogsMonitoring(logLevel === "all" ? undefined : logLevel);
|
|
||||||
console.log(
|
|
||||||
`[IPC-LogService] Started logs monitoring with level: ${logLevel}`,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[IPC-LogService] Failed to start logs monitoring:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Stop logs monitoring
|
|
||||||
export const stopLogsStreaming = async () => {
|
|
||||||
try {
|
|
||||||
// await stopLogsMonitoring();
|
|
||||||
console.log("[IPC-LogService] Stopped logs monitoring");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[IPC-LogService] Failed to stop logs monitoring:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fetch logs using IPC command (now from streaming cache)
|
|
||||||
export const fetchLogsViaIPC = async (): Promise<ILogItem[]> => {
|
|
||||||
try {
|
|
||||||
// Server-side filtering handles the level via /logs?level={level}
|
|
||||||
// We just fetch all cached logs regardless of the logLevel parameter
|
|
||||||
// const response = await getClashLogs();
|
|
||||||
|
|
||||||
// // The response should be in the format expected by the frontend
|
|
||||||
// // Transform the logs to match the expected format
|
|
||||||
// if (Array.isArray(response)) {
|
|
||||||
// return response.map((log: any) => ({
|
|
||||||
// ...log,
|
|
||||||
// time: log.time || dayjs().format("HH:mm:ss"),
|
|
||||||
// }));
|
|
||||||
// }
|
|
||||||
|
|
||||||
return [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[IPC-LogService] Failed to fetch logs:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Clear logs
|
|
||||||
export const clearLogs = async () => {
|
|
||||||
try {
|
|
||||||
await clearLogsCmd();
|
|
||||||
console.log("[IPC-LogService] Logs cleared");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[IPC-LogService] Failed to clear logs:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user