diff --git a/src/pages/instance/PingChart.tsx b/src/pages/instance/PingChart.tsx index 57782ce..6c48a39 100644 --- a/src/pages/instance/PingChart.tsx +++ b/src/pages/instance/PingChart.tsx @@ -19,7 +19,10 @@ import { Label } from "@radix-ui/react-label"; import type { NodeData } from "@/types/node"; import Loading from "@/components/loading"; import { usePingChart } from "@/hooks/usePingChart"; -import fillMissingTimePoints, { cutPeakValues } from "@/utils/RecordHelper"; +import fillMissingTimePoints, { + cutPeakValues, + calculateTaskStats, +} from "@/utils/RecordHelper"; import { useConfigItem } from "@/config"; import { CustomTooltip } from "@/components/ui/tooltip"; import Tips from "@/components/ui/tips"; @@ -164,7 +167,7 @@ const PingChart = memo(({ node, hours }: PingChartProps) => { const sortedTasks = useMemo(() => { if (!pingHistory?.tasks) return []; - return [...pingHistory.tasks].sort((a, b) => a.name.localeCompare(b.name)); + return [...pingHistory.tasks].sort((a, b) => a.id - b.id); }, [pingHistory?.tasks]); const generateColor = useCallback( @@ -224,6 +227,25 @@ const PingChart = memo(({ node, hours }: PingChartProps) => { return points; }, [chartData, sortedTasks, visiblePingTasks, generateColor, connectBreaks]); + const taskStats = useMemo(() => { + if (!pingHistory?.records || !sortedTasks.length) return []; + + return sortedTasks.map((task) => { + const { loss, latestValue, latestTime } = calculateTaskStats( + pingHistory.records, + task.id, + timeRange + ); + return { + ...task, + value: latestValue, + time: latestTime, + loss: loss, + color: generateColor(task.name, sortedTasks.length), + }; + }); + }, [pingHistory?.records, sortedTasks, generateColor, timeRange]); + return (
{loading && ( @@ -238,20 +260,19 @@ const PingChart = memo(({ node, hours }: PingChartProps) => { )} {pingHistory?.tasks && pingHistory.tasks.length > 0 && ( - + +
+ + 丢包率计算算法并不准确,谨慎参考

", + }}>
+
+
- {sortedTasks.map((task) => { - const values = chartData - .map((d) => d[task.id]) - .filter((v) => v !== null && v !== undefined) as number[]; - const loss = - chartData.length > 0 - ? (1 - values.length / chartData.length) * 100 - : 0; - const min = values.length > 0 ? Math.min(...values) : 0; + {taskStats.map((task) => { const isVisible = visiblePingTasks.includes(task.id); - const color = generateColor(task.name, sortedTasks.length); return (
{ }`} onClick={() => handleTaskVisibilityToggle(task.id)} style={{ - outlineColor: isVisible ? color : undefined, - boxShadow: isVisible ? `0 0 8px ${color}` : undefined, + outlineColor: isVisible ? task.color : undefined, + boxShadow: isVisible + ? `0 0 8px ${task.color}` + : undefined, }}>
{task.name}
- - {loss.toFixed(1)}% | {min.toFixed(0)}ms - +
+ + {task.value !== null + ? `${task.value.toFixed(1)} ms | ${task.loss.toFixed( + 1 + )}%` + : "N/A"} + +
); })} diff --git a/src/types/node.d.ts b/src/types/node.d.ts index 9e81f9c..07e0e0d 100644 --- a/src/types/node.d.ts +++ b/src/types/node.d.ts @@ -95,6 +95,7 @@ export interface PingTask { id: number; interval: number; name: string; + loss: number; } export interface PingHistoryResponse { diff --git a/src/utils/RecordHelper.tsx b/src/utils/RecordHelper.tsx index e5c9728..01716aa 100644 --- a/src/utils/RecordHelper.tsx +++ b/src/utils/RecordHelper.tsx @@ -356,3 +356,50 @@ export function sampleDataByRetention( return result; } + +/** + * 根据 ping 历史记录计算丢包率。 + * @param records - ping 历史记录数组。 + * @param taskId - 要计算丢包率的任务 ID。 + * @param timeRange - 用于筛选记录的可选时间范围 [开始, 结束]。 + * @returns 以百分比表示的丢包率。 + */ +/** + * 根据 ping 历史记录计算任务的统计数据(丢包率和最新值)。 + * @param records - ping 历史记录数组。 + * @param taskId - 要计算的任务 ID。 + * @param timeRange - 用于筛选记录的可选时间范围 [开始, 结束]。 + * @returns 包含丢包率和最新值的对象。 + */ +export function calculateTaskStats( + records: { time: string; task_id: number; value: number }[], + taskId: number, + timeRange: [number, number] | null +): { loss: number; latestValue: number | null; latestTime: string | null } { + const relevantRecords = timeRange + ? records.filter((rec) => { + const t = new Date(rec.time).getTime(); + return t >= timeRange[0] && t <= timeRange[1]; + }) + : records; + + const taskRecords = + relevantRecords?.filter((rec) => rec.task_id === taskId) || []; + + const totalPings = taskRecords.length; + const successfulPings = taskRecords.filter((rec) => rec.value >= 0); + const loss = + totalPings > 0 ? (1 - successfulPings.length / totalPings) * 100 : 0; + + let latestValue: number | null = null; + let latestTime: string | null = null; + if (successfulPings.length > 0) { + const latestRecord = successfulPings.reduce((latest, current) => { + return new Date(current.time) > new Date(latest.time) ? current : latest; + }); + latestValue = latestRecord.value; + latestTime = latestRecord.time; + } + + return { loss, latestValue, latestTime }; +}