mirror of
https://github.com/fankes/komari-theme-purcarte.git
synced 2025-10-19 03:49:22 +08:00
fix: 修复延迟以及丢包率的计算
This commit is contained in:
@@ -19,7 +19,10 @@ import { Label } from "@radix-ui/react-label";
|
|||||||
import type { NodeData } from "@/types/node";
|
import type { NodeData } from "@/types/node";
|
||||||
import Loading from "@/components/loading";
|
import Loading from "@/components/loading";
|
||||||
import { usePingChart } from "@/hooks/usePingChart";
|
import { usePingChart } from "@/hooks/usePingChart";
|
||||||
import fillMissingTimePoints, { cutPeakValues } from "@/utils/RecordHelper";
|
import fillMissingTimePoints, {
|
||||||
|
cutPeakValues,
|
||||||
|
calculateTaskStats,
|
||||||
|
} from "@/utils/RecordHelper";
|
||||||
import { useConfigItem } from "@/config";
|
import { useConfigItem } from "@/config";
|
||||||
import { CustomTooltip } from "@/components/ui/tooltip";
|
import { CustomTooltip } from "@/components/ui/tooltip";
|
||||||
import Tips from "@/components/ui/tips";
|
import Tips from "@/components/ui/tips";
|
||||||
@@ -164,7 +167,7 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
|
|||||||
|
|
||||||
const sortedTasks = useMemo(() => {
|
const sortedTasks = useMemo(() => {
|
||||||
if (!pingHistory?.tasks) return [];
|
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]);
|
}, [pingHistory?.tasks]);
|
||||||
|
|
||||||
const generateColor = useCallback(
|
const generateColor = useCallback(
|
||||||
@@ -224,6 +227,25 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
|
|||||||
return points;
|
return points;
|
||||||
}, [chartData, sortedTasks, visiblePingTasks, generateColor, connectBreaks]);
|
}, [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 (
|
return (
|
||||||
<div className="relative space-y-4">
|
<div className="relative space-y-4">
|
||||||
{loading && (
|
{loading && (
|
||||||
@@ -238,20 +260,19 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{pingHistory?.tasks && pingHistory.tasks.length > 0 && (
|
{pingHistory?.tasks && pingHistory.tasks.length > 0 && (
|
||||||
<Card>
|
<Card className="relative">
|
||||||
|
<div className="absolute top-2 right-2">
|
||||||
|
<Tips>
|
||||||
|
<span
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: "<p>丢包率计算算法并不准确,谨慎参考</p>",
|
||||||
|
}}></span>
|
||||||
|
</Tips>
|
||||||
|
</div>
|
||||||
<CardContent className="p-2">
|
<CardContent className="p-2">
|
||||||
<div className="flex flex-wrap gap-2 items-center justify-center">
|
<div className="flex flex-wrap gap-2 items-center justify-center">
|
||||||
{sortedTasks.map((task) => {
|
{taskStats.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;
|
|
||||||
const isVisible = visiblePingTasks.includes(task.id);
|
const isVisible = visiblePingTasks.includes(task.id);
|
||||||
const color = generateColor(task.name, sortedTasks.length);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -261,13 +282,21 @@ const PingChart = memo(({ node, hours }: PingChartProps) => {
|
|||||||
}`}
|
}`}
|
||||||
onClick={() => handleTaskVisibilityToggle(task.id)}
|
onClick={() => handleTaskVisibilityToggle(task.id)}
|
||||||
style={{
|
style={{
|
||||||
outlineColor: isVisible ? color : undefined,
|
outlineColor: isVisible ? task.color : undefined,
|
||||||
boxShadow: isVisible ? `0 0 8px ${color}` : undefined,
|
boxShadow: isVisible
|
||||||
|
? `0 0 8px ${task.color}`
|
||||||
|
: undefined,
|
||||||
}}>
|
}}>
|
||||||
<div className="font-semibold">{task.name}</div>
|
<div className="font-semibold">{task.name}</div>
|
||||||
<span className="text-xs font-normal">
|
<div className="flex text-xs font-normal">
|
||||||
{loss.toFixed(1)}% | {min.toFixed(0)}ms
|
<span>
|
||||||
</span>
|
{task.value !== null
|
||||||
|
? `${task.value.toFixed(1)} ms | ${task.loss.toFixed(
|
||||||
|
1
|
||||||
|
)}%`
|
||||||
|
: "N/A"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
1
src/types/node.d.ts
vendored
1
src/types/node.d.ts
vendored
@@ -95,6 +95,7 @@ export interface PingTask {
|
|||||||
id: number;
|
id: number;
|
||||||
interval: number;
|
interval: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
loss: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PingHistoryResponse {
|
export interface PingHistoryResponse {
|
||||||
|
@@ -356,3 +356,50 @@ export function sampleDataByRetention(
|
|||||||
|
|
||||||
return result;
|
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 };
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user