feat: migrate mihomo to use kode-bridge IPC on Windows and Unix (#4051)
* Refactor Mihomo API integration and remove crate_mihomo_api - Removed the `mihomo_api` crate and its dependencies from the project. - Introduced `IpcManager` for handling IPC communication with Mihomo. - Implemented IPC methods for managing proxies, connections, and configurations. - Updated `MihomoManager` to utilize `IpcManager` instead of the removed crate. - Added platform-specific IPC socket path handling for macOS, Linux, and Windows. - Cleaned up related tests and configuration files. * fix: remove duplicate permission entry in desktop capabilities * refactor: replace MihomoManager with IpcManager and remove Mihomo module * fix: restore tempfile dependency in dev-dependencies * fix: update kode-bridge dependency to use git source from the dev branch * feat: migrate mihomo to use kode-bridge IPC on Windows This commit implements a comprehensive migration from legacy service IPC to the kode-bridge library for Windows IPC communication. Key changes include: Replace service_ipc with kode-bridge IpcManager for all mihomo communications Simplify proxy commands using new caching mechanism with ProxyRequestCache Add Windows named pipe (\.\pipe\mihomo) and Unix socket IPC endpoint configuration Update Tauri permissions and dependencies (dashmap, tauri-plugin-notification) Add IPC logging support and improve error handling Fix Windows IPC path handling in directory utilities This migration enables better cross-platform IPC support and improved performance for mihomo proxy core communication. * doc: add IPC communication with Mihomo kernel, removing Restful API dependency * fix: standardize logging type naming from IPC to Ipc for consistency * refactor: clean up and optimize code structure across multiple components and services - Removed unnecessary comments and whitespace in various files. - Improved code readability and maintainability by restructuring functions and components. - Updated localization files for consistency and accuracy. - Enhanced performance by optimizing hooks and utility functions. - General code cleanup in settings, pages, and services to adhere to best practices. * fix: simplify URL formatting in test_proxy_delay method * fix: update kode-bridge dependency to version 0.1.3 and change source to crates.io * fix: update macOS target versions in development workflow * Revert "fix: update macOS target versions in development workflow" This reverts commit b9831357e462e0f308d11a9a53cb718f98ae1295. * feat: enhance IPC path handling for Unix systems and improve directory safety checks * feat: add conditional compilation for Unix-specific IPC path handling * chore: update cagro.lock * feat: add external controller configuration and UI support * Refactor proxy and connection management to use IPC-based commands - Updated `get_proxies` function in `proxy.rs` to call the new IPC command. - Renamed `get_refresh_proxies` to `get_proxies` in `ipc/general.rs` for consistency. - Added new IPC commands for managing proxies, connections, and configurations in `cmds.ts`. - Refactored API calls in various components to use the new IPC commands instead of HTTP requests. - Improved error handling and response management in the new IPC functions. - Cleaned up unused API functions in `api.ts` and redirected relevant calls to `cmds.ts`. - Enhanced connection management features including health checks and updates for proxy providers. * chore: update dependencies and improve error handling in IPC manager * fix: downgrade zip dependency from 4.3.0 to 4.2.0 * feat: Implement traffic and memory data monitoring service - Added `TrafficService` and `TrafficManager` to manage traffic and memory data collection. - Introduced commands to get traffic and memory data, start and stop the traffic service. - Integrated IPC calls for traffic and memory data retrieval in the frontend. - Updated `AppDataProvider` and `EnhancedTrafficStats` components to utilize new data fetching methods. - Removed WebSocket connections for traffic and memory data, replaced with IPC polling. - Added logging for better traceability of data fetching and service status. * refactor: unify external controller handling and improve IPC path resolution * fix: replace direct IPC path retrieval with guard function for external controller * fix: convert external controller IPC path to string for proper insertion in config map * fix: update dependencies and improve IPC response handling * fix: remove unnecessary unix conditional for ipc path import * Refactor traffic and memory monitoring to use IPC stream; remove TrafficService and TrafficManager. Introduce new IPC-based data retrieval methods for traffic and memory, including formatted data and system overview. Update frontend components to utilize new APIs for enhanced data display and management. * chore: bump crate rand version to 0.9.2 * feat: Implement enhanced traffic monitoring system with data compression and sampling - Introduced `useTrafficMonitorEnhanced` hook for advanced traffic data management. - Added `TrafficDataSampler` class for handling raw and compressed traffic data. - Implemented reference counting to manage data collection based on component usage. - Enhanced data validation with `SystemMonitorValidator` for API responses. - Created diagnostic tools for monitoring performance and error tracking. - Updated existing hooks to utilize the new enhanced monitoring features. - Added utility functions for generating and formatting diagnostic reports. * feat(ipc): improve URL encoding and error handling for IPC requests - Add percent-encoding for URL paths to handle special characters properly - Enhance error handling in update_proxy with proper logging - Remove excessive debug logging to reduce noise - Update kode-bridge dependency to v0.1.5 - Fix JSON parsing error handling in PUT requests Changes include: - Proper URL encoding for connection IDs, proxy names, and test URLs - Enhanced error handling with fallback responses in updateProxy - Comment out verbose debug logs in traffic monitoring and data validation - Update dependency version for improved IPC functionality * feat: major improvements in architecture, traffic monitoring, and data validation * Refactor traffic graph components: Replace EnhancedTrafficGraph with EnhancedCanvasTrafficGraph, improve rendering performance, and enhance visual elements. Remove deprecated code and ensure compatibility with global data management. * chore: update UPDATELOG.md for v2.4.0 release, refine traffic monitoring system details, and enhance IPC functionality * chore: update UPDATELOG.md to reflect removal of deprecated MihomoManager and unify IPC control * refactor: remove global traffic service testing method from cmds.ts * Update src/components/home/enhanced-canvas-traffic-graph.tsx * Update src/hooks/use-traffic-monitor-enhanced.ts * Update src/components/layout/layout-traffic.tsx * refactor: remove debug state management from LayoutTraffic component ---------
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { getAxios, getVersion } from "@/services/api";
|
||||
import { getVersion } from "@/services/cmds";
|
||||
import {
|
||||
getClashInfo,
|
||||
patchClashConfig,
|
||||
@@ -122,8 +122,7 @@ export const useClashInfo = () => {
|
||||
await patchClashConfig(patch);
|
||||
mutateInfo();
|
||||
mutate("getClashConfig");
|
||||
// 刷新接口
|
||||
getAxios(true);
|
||||
// IPC调用不需要刷新axios实例
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
patchProfilesConfig,
|
||||
forceRefreshProxies,
|
||||
} from "@/services/cmds";
|
||||
import { getProxies, updateProxy } from "@/services/api";
|
||||
import { getProxies, updateProxy } from "@/services/cmds";
|
||||
|
||||
export const useProfiles = () => {
|
||||
const { data: profiles, mutate: mutateProfiles } = useSWR(
|
||||
|
||||
@@ -2,7 +2,7 @@ import useSWR, { mutate } from "swr";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { getAutotemProxy } from "@/services/cmds";
|
||||
import { useAppData } from "@/providers/app-data-provider";
|
||||
import { closeAllConnections } from "@/services/api";
|
||||
import { closeAllConnections } from "@/services/cmds";
|
||||
|
||||
// 系统代理状态检测统一逻辑
|
||||
export const useSystemProxyState = () => {
|
||||
|
||||
398
src/hooks/use-traffic-monitor-enhanced.ts
Normal file
398
src/hooks/use-traffic-monitor-enhanced.ts
Normal file
@@ -0,0 +1,398 @@
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import useSWR from "swr";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { useVisibility } from "@/hooks/use-visibility";
|
||||
import { getSystemMonitorOverviewSafe } from "@/services/cmds";
|
||||
|
||||
// 增强的流量数据点接口
|
||||
export interface ITrafficDataPoint {
|
||||
up: number;
|
||||
down: number;
|
||||
timestamp: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// 压缩的数据点(用于长期存储)
|
||||
interface ICompressedDataPoint {
|
||||
up: number;
|
||||
down: number;
|
||||
timestamp: number;
|
||||
samples: number; // 压缩了多少个原始数据点
|
||||
}
|
||||
|
||||
// 数据采样器配置
|
||||
interface ISamplingConfig {
|
||||
// 原始数据保持时间(分钟)
|
||||
rawDataMinutes: number;
|
||||
// 压缩数据保持时间(分钟)
|
||||
compressedDataMinutes: number;
|
||||
// 压缩比例(多少个原始点压缩成1个)
|
||||
compressionRatio: number;
|
||||
}
|
||||
|
||||
// 引用计数管理器
|
||||
class ReferenceCounter {
|
||||
private count = 0;
|
||||
private callbacks: (() => void)[] = [];
|
||||
|
||||
increment(): () => void {
|
||||
this.count++;
|
||||
console.log(`[ReferenceCounter] 引用计数增加: ${this.count}`);
|
||||
|
||||
if (this.count === 1) {
|
||||
// 从0到1,开始数据收集
|
||||
this.callbacks.forEach((cb) => cb());
|
||||
}
|
||||
|
||||
return () => {
|
||||
this.count--;
|
||||
console.log(`[ReferenceCounter] 引用计数减少: ${this.count}`);
|
||||
|
||||
if (this.count === 0) {
|
||||
// 从1到0,停止数据收集
|
||||
this.callbacks.forEach((cb) => cb());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onCountChange(callback: () => void) {
|
||||
this.callbacks.push(callback);
|
||||
}
|
||||
|
||||
getCount(): number {
|
||||
return this.count;
|
||||
}
|
||||
}
|
||||
|
||||
// 智能数据采样器
|
||||
class TrafficDataSampler {
|
||||
private rawBuffer: ITrafficDataPoint[] = [];
|
||||
private compressedBuffer: ICompressedDataPoint[] = [];
|
||||
private config: ISamplingConfig;
|
||||
private compressionQueue: ITrafficDataPoint[] = [];
|
||||
|
||||
constructor(config: ISamplingConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
addDataPoint(point: ITrafficDataPoint): void {
|
||||
// 添加到原始缓冲区
|
||||
this.rawBuffer.push(point);
|
||||
|
||||
// 清理过期的原始数据
|
||||
const rawCutoff = Date.now() - this.config.rawDataMinutes * 60 * 1000;
|
||||
this.rawBuffer = this.rawBuffer.filter((p) => p.timestamp > rawCutoff);
|
||||
|
||||
// 添加到压缩队列
|
||||
this.compressionQueue.push(point);
|
||||
|
||||
// 当压缩队列达到压缩比例时,执行压缩
|
||||
if (this.compressionQueue.length >= this.config.compressionRatio) {
|
||||
this.compressData();
|
||||
}
|
||||
|
||||
// 清理过期的压缩数据
|
||||
const compressedCutoff =
|
||||
Date.now() - this.config.compressedDataMinutes * 60 * 1000;
|
||||
this.compressedBuffer = this.compressedBuffer.filter(
|
||||
(p) => p.timestamp > compressedCutoff,
|
||||
);
|
||||
}
|
||||
|
||||
private compressData(): void {
|
||||
if (this.compressionQueue.length === 0) return;
|
||||
|
||||
// 计算平均值进行压缩
|
||||
const totalUp = this.compressionQueue.reduce((sum, p) => sum + p.up, 0);
|
||||
const totalDown = this.compressionQueue.reduce((sum, p) => sum + p.down, 0);
|
||||
const avgTimestamp =
|
||||
this.compressionQueue.reduce((sum, p) => sum + p.timestamp, 0) /
|
||||
this.compressionQueue.length;
|
||||
|
||||
const compressedPoint: ICompressedDataPoint = {
|
||||
up: totalUp / this.compressionQueue.length,
|
||||
down: totalDown / this.compressionQueue.length,
|
||||
timestamp: avgTimestamp,
|
||||
samples: this.compressionQueue.length,
|
||||
};
|
||||
|
||||
this.compressedBuffer.push(compressedPoint);
|
||||
this.compressionQueue = [];
|
||||
|
||||
console.log(`[DataSampler] 压缩了 ${compressedPoint.samples} 个数据点`);
|
||||
}
|
||||
|
||||
getDataForTimeRange(minutes: number): ITrafficDataPoint[] {
|
||||
const cutoff = Date.now() - minutes * 60 * 1000;
|
||||
|
||||
// 如果请求的时间范围在原始数据范围内,直接返回原始数据
|
||||
if (minutes <= this.config.rawDataMinutes) {
|
||||
return this.rawBuffer.filter((p) => p.timestamp > cutoff);
|
||||
}
|
||||
|
||||
// 否则组合原始数据和压缩数据
|
||||
const rawData = this.rawBuffer.filter((p) => p.timestamp > cutoff);
|
||||
const compressedData = this.compressedBuffer
|
||||
.filter(
|
||||
(p) =>
|
||||
p.timestamp > cutoff &&
|
||||
p.timestamp <= Date.now() - this.config.rawDataMinutes * 60 * 1000,
|
||||
)
|
||||
.map((p) => ({
|
||||
up: p.up,
|
||||
down: p.down,
|
||||
timestamp: p.timestamp,
|
||||
name: new Date(p.timestamp).toLocaleTimeString("en-US", {
|
||||
hour12: false,
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
}),
|
||||
}));
|
||||
|
||||
return [...compressedData, ...rawData].sort(
|
||||
(a, b) => a.timestamp - b.timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
getStats() {
|
||||
return {
|
||||
rawBufferSize: this.rawBuffer.length,
|
||||
compressedBufferSize: this.compressedBuffer.length,
|
||||
compressionQueueSize: this.compressionQueue.length,
|
||||
totalMemoryPoints: this.rawBuffer.length + this.compressedBuffer.length,
|
||||
};
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.rawBuffer = [];
|
||||
this.compressedBuffer = [];
|
||||
this.compressionQueue = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 全局单例
|
||||
const refCounter = new ReferenceCounter();
|
||||
let globalSampler: TrafficDataSampler | null = null;
|
||||
let lastValidData: ISystemMonitorOverview | null = null;
|
||||
|
||||
/**
|
||||
* 增强的流量监控Hook - 支持数据压缩、采样和引用计数
|
||||
*/
|
||||
export const useTrafficMonitorEnhanced = () => {
|
||||
const { clashInfo } = useClashInfo();
|
||||
const pageVisible = useVisibility();
|
||||
|
||||
// 初始化采样器
|
||||
if (!globalSampler) {
|
||||
globalSampler = new TrafficDataSampler({
|
||||
rawDataMinutes: 10, // 原始数据保持10分钟
|
||||
compressedDataMinutes: 60, // 压缩数据保持1小时
|
||||
compressionRatio: 5, // 每5个原始点压缩成1个
|
||||
});
|
||||
}
|
||||
|
||||
const [, forceUpdate] = useState({});
|
||||
const cleanupRef = useRef<(() => void) | null>(null);
|
||||
|
||||
// 强制组件更新
|
||||
const triggerUpdate = useCallback(() => {
|
||||
forceUpdate({});
|
||||
}, []);
|
||||
|
||||
// 注册引用计数
|
||||
useEffect(() => {
|
||||
console.log("[TrafficMonitorEnhanced] 组件挂载,注册引用计数");
|
||||
const cleanup = refCounter.increment();
|
||||
cleanupRef.current = cleanup;
|
||||
|
||||
return () => {
|
||||
console.log("[TrafficMonitorEnhanced] 组件卸载,清理引用计数");
|
||||
cleanup();
|
||||
cleanupRef.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 设置引用计数变化回调
|
||||
useEffect(() => {
|
||||
const handleCountChange = () => {
|
||||
console.log(
|
||||
`[TrafficMonitorEnhanced] 引用计数变化: ${refCounter.getCount()}`,
|
||||
);
|
||||
if (refCounter.getCount() === 0) {
|
||||
console.log("[TrafficMonitorEnhanced] 所有组件已卸载,暂停数据收集");
|
||||
} else {
|
||||
console.log("[TrafficMonitorEnhanced] 开始数据收集");
|
||||
}
|
||||
};
|
||||
|
||||
refCounter.onCountChange(handleCountChange);
|
||||
}, []);
|
||||
|
||||
// 只有在有引用时才启用SWR
|
||||
const shouldFetch = clashInfo && pageVisible && refCounter.getCount() > 0;
|
||||
|
||||
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(() => {
|
||||
if (globalSampler) {
|
||||
globalSampler.clear();
|
||||
triggerUpdate();
|
||||
}
|
||||
}, [triggerUpdate]);
|
||||
|
||||
// 获取采样器统计信息
|
||||
const getSamplerStats = useCallback(() => {
|
||||
return (
|
||||
globalSampler?.getStats() || {
|
||||
rawBufferSize: 0,
|
||||
compressedBufferSize: 0,
|
||||
compressionQueueSize: 0,
|
||||
totalMemoryPoints: 0,
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
// 构建返回的监控数据,优先使用当前数据,fallback到最后有效数据
|
||||
const currentData = monitorData || 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 {
|
||||
// 监控数据
|
||||
monitorData: trafficMonitorData,
|
||||
|
||||
// 图表数据管理
|
||||
graphData: {
|
||||
dataPoints: globalSampler?.getDataForTimeRange(60) || [], // 默认获取1小时数据
|
||||
getDataForTimeRange,
|
||||
clearData,
|
||||
},
|
||||
|
||||
// 状态信息
|
||||
isLoading: !currentData && !error,
|
||||
error,
|
||||
isDataFresh: currentData?.traffic?.is_fresh || false,
|
||||
hasValidData: !!lastValidData,
|
||||
|
||||
// 性能统计
|
||||
samplerStats: getSamplerStats(),
|
||||
referenceCount: refCounter.getCount(),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 轻量级流量数据Hook
|
||||
*/
|
||||
export const useTrafficDataEnhanced = () => {
|
||||
const { monitorData, isLoading, error, isDataFresh, hasValidData } =
|
||||
useTrafficMonitorEnhanced();
|
||||
|
||||
return {
|
||||
traffic: monitorData.traffic,
|
||||
memory: monitorData.memory,
|
||||
isLoading,
|
||||
error,
|
||||
isDataFresh,
|
||||
hasValidData,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 图表数据Hook
|
||||
*/
|
||||
export const useTrafficGraphDataEnhanced = () => {
|
||||
const { graphData, isDataFresh, samplerStats, referenceCount } =
|
||||
useTrafficMonitorEnhanced();
|
||||
|
||||
return {
|
||||
...graphData,
|
||||
isDataFresh,
|
||||
samplerStats,
|
||||
referenceCount,
|
||||
};
|
||||
};
|
||||
245
src/hooks/use-traffic-monitor.ts
Normal file
245
src/hooks/use-traffic-monitor.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import useSWR from "swr";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { useVisibility } from "@/hooks/use-visibility";
|
||||
import { getSystemMonitorOverview } from "@/services/cmds";
|
||||
|
||||
// 流量数据项接口
|
||||
export interface ITrafficDataPoint {
|
||||
up: number;
|
||||
down: number;
|
||||
timestamp: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// 流量监控数据接口
|
||||
export interface ITrafficMonitorData {
|
||||
traffic: {
|
||||
raw: { up_rate: number; down_rate: number };
|
||||
formatted: { up_rate: string; down_rate: string };
|
||||
is_fresh: boolean;
|
||||
};
|
||||
memory: {
|
||||
raw: { inuse: number; oslimit?: number };
|
||||
formatted: { inuse: string; usage_percent?: number };
|
||||
is_fresh: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// 图表数据管理接口
|
||||
export interface ITrafficGraphData {
|
||||
dataPoints: ITrafficDataPoint[];
|
||||
addDataPoint: (data: {
|
||||
up: number;
|
||||
down: number;
|
||||
timestamp?: number;
|
||||
}) => void;
|
||||
clearData: () => void;
|
||||
getDataForTimeRange: (minutes: number) => ITrafficDataPoint[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局流量监控数据管理Hook
|
||||
* 提供统一的流量数据获取和图表数据管理
|
||||
*/
|
||||
export const useTrafficMonitor = () => {
|
||||
const { clashInfo } = useClashInfo();
|
||||
const pageVisible = useVisibility();
|
||||
|
||||
// 图表数据缓冲区 - 使用ref保持数据持久性
|
||||
const dataBufferRef = useRef<ITrafficDataPoint[]>([]);
|
||||
const [, forceUpdate] = useState({});
|
||||
|
||||
// 强制组件更新的函数
|
||||
const triggerUpdate = useCallback(() => {
|
||||
forceUpdate({});
|
||||
}, []);
|
||||
|
||||
// 最大缓冲区大小 (10分钟 * 60秒 = 600个数据点)
|
||||
const MAX_BUFFER_SIZE = 600;
|
||||
|
||||
// 初始化数据缓冲区
|
||||
useEffect(() => {
|
||||
if (dataBufferRef.current.length === 0) {
|
||||
const now = Date.now();
|
||||
const tenMinutesAgo = now - 10 * 60 * 1000;
|
||||
|
||||
const initialBuffer = Array.from(
|
||||
{ length: MAX_BUFFER_SIZE },
|
||||
(_, index) => {
|
||||
const pointTime =
|
||||
tenMinutesAgo + index * ((10 * 60 * 1000) / MAX_BUFFER_SIZE);
|
||||
const date = new Date(pointTime);
|
||||
|
||||
let nameValue: string;
|
||||
try {
|
||||
if (isNaN(date.getTime())) {
|
||||
nameValue = "??:??:??";
|
||||
} else {
|
||||
nameValue = date.toLocaleTimeString("en-US", {
|
||||
hour12: false,
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
nameValue = "Err:Time";
|
||||
}
|
||||
|
||||
return {
|
||||
up: 0,
|
||||
down: 0,
|
||||
timestamp: pointTime,
|
||||
name: nameValue,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
dataBufferRef.current = initialBuffer;
|
||||
}
|
||||
}, [MAX_BUFFER_SIZE]);
|
||||
|
||||
// 使用SWR获取监控数据
|
||||
const { data: monitorData, error } = useSWR<ISystemMonitorOverview>(
|
||||
clashInfo && pageVisible ? "getSystemMonitorOverview" : null,
|
||||
getSystemMonitorOverview,
|
||||
{
|
||||
refreshInterval: 1000, // 1秒刷新一次
|
||||
keepPreviousData: true,
|
||||
onSuccess: (data) => {
|
||||
console.log("[TrafficMonitor] 获取到监控数据:", data);
|
||||
|
||||
if (data?.traffic) {
|
||||
// 为图表添加新数据点
|
||||
addDataPoint({
|
||||
up: data.traffic.raw.up_rate || 0,
|
||||
down: data.traffic.raw.down_rate || 0,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("[TrafficMonitor] 获取数据错误:", error);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// 添加数据点到缓冲区
|
||||
const addDataPoint = useCallback(
|
||||
(data: { up: number; down: number; timestamp?: number }) => {
|
||||
const timestamp = data.timestamp || Date.now();
|
||||
const date = new Date(timestamp);
|
||||
|
||||
let nameValue: string;
|
||||
try {
|
||||
if (isNaN(date.getTime())) {
|
||||
nameValue = "??:??:??";
|
||||
} else {
|
||||
nameValue = date.toLocaleTimeString("en-US", {
|
||||
hour12: false,
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
nameValue = "Err:Time";
|
||||
}
|
||||
|
||||
const newPoint: ITrafficDataPoint = {
|
||||
up: typeof data.up === "number" && !isNaN(data.up) ? data.up : 0,
|
||||
down:
|
||||
typeof data.down === "number" && !isNaN(data.down) ? data.down : 0,
|
||||
timestamp,
|
||||
name: nameValue,
|
||||
};
|
||||
|
||||
// 更新缓冲区,保持固定大小
|
||||
const newBuffer = [...dataBufferRef.current.slice(1), newPoint];
|
||||
dataBufferRef.current = newBuffer;
|
||||
|
||||
// 触发使用该数据的组件更新
|
||||
triggerUpdate();
|
||||
},
|
||||
[triggerUpdate],
|
||||
);
|
||||
|
||||
// 清空数据
|
||||
const clearData = useCallback(() => {
|
||||
dataBufferRef.current = [];
|
||||
triggerUpdate();
|
||||
}, [triggerUpdate]);
|
||||
|
||||
// 根据时间范围获取数据
|
||||
const getDataForTimeRange = useCallback(
|
||||
(minutes: number): ITrafficDataPoint[] => {
|
||||
const pointsToShow = minutes * 60; // 每分钟60个数据点
|
||||
return dataBufferRef.current.slice(-pointsToShow);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// 构建图表数据管理对象
|
||||
const graphData: ITrafficGraphData = {
|
||||
dataPoints: dataBufferRef.current,
|
||||
addDataPoint,
|
||||
clearData,
|
||||
getDataForTimeRange,
|
||||
};
|
||||
|
||||
// 构建监控数据对象
|
||||
const trafficMonitorData: ITrafficMonitorData = {
|
||||
traffic: monitorData?.traffic || {
|
||||
raw: { up_rate: 0, down_rate: 0 },
|
||||
formatted: { up_rate: "0B", down_rate: "0B" },
|
||||
is_fresh: false,
|
||||
},
|
||||
memory: monitorData?.memory || {
|
||||
raw: { inuse: 0 },
|
||||
formatted: { inuse: "0B" },
|
||||
is_fresh: false,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
// 原始监控数据
|
||||
monitorData: trafficMonitorData,
|
||||
// 图表数据管理
|
||||
graphData,
|
||||
// 数据获取状态
|
||||
isLoading: !monitorData && !error,
|
||||
error,
|
||||
// 数据新鲜度
|
||||
isDataFresh: monitorData?.overall_status === "active",
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 仅获取流量数据的轻量级Hook
|
||||
* 适用于不需要图表数据的组件
|
||||
*/
|
||||
export const useTrafficData = () => {
|
||||
const { monitorData, isLoading, error, isDataFresh } = useTrafficMonitor();
|
||||
|
||||
return {
|
||||
traffic: monitorData.traffic,
|
||||
memory: monitorData.memory,
|
||||
isLoading,
|
||||
error,
|
||||
isDataFresh,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 仅获取图表数据的Hook
|
||||
* 适用于图表组件
|
||||
*/
|
||||
export const useTrafficGraphData = () => {
|
||||
const { graphData, isDataFresh } = useTrafficMonitor();
|
||||
|
||||
return {
|
||||
...graphData,
|
||||
isDataFresh,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user