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 };
+}