"use client" import NetworkChartLoading from "@/app/(main)/ClientComponents/NetworkChartLoading" import { NezhaAPIMonitor, ServerMonitorChart } from "@/app/types/nezha-api" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, } from "@/components/ui/chart" import { Label } from "@/components/ui/label" import { Switch } from "@/components/ui/switch" import getEnv from "@/lib/env-entry" import { formatTime, nezhaFetcher } from "@/lib/utils" import { formatRelativeTime } from "@/lib/utils" import { useTranslations } from "next-intl" import * as React from "react" import { useCallback, useMemo } from "react" import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts" import useSWR from "swr" interface ResultItem { created_at: number [key: string]: number } export function NetworkChartClient({ server_id, show }: { server_id: number; show: boolean }) { const t = useTranslations("NetworkChartClient") const { data, error } = useSWR( `/api/monitor?server_id=${server_id}`, nezhaFetcher, { refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 15000, isVisible: () => show, }, ) if (error) { return ( <>

{error.message}

{t("chart_fetch_error_message")}

) } if (!data) return const transformedData = transformData(data) const formattedData = formatData(data) const initChartConfig = { avg_delay: { label: t("avg_delay"), }, } satisfies ChartConfig const chartDataKey = Object.keys(transformedData) return ( ) } export const NetworkChart = React.memo(function NetworkChart({ chartDataKey, chartConfig, chartData, serverName, formattedData, }: { chartDataKey: string[] chartConfig: ChartConfig chartData: ServerMonitorChart serverName: string formattedData: ResultItem[] }) { const t = useTranslations("NetworkChart") const defaultChart = "All" const [activeChart, setActiveChart] = React.useState(defaultChart) const [isPeakEnabled, setIsPeakEnabled] = React.useState(false) const handleButtonClick = useCallback( (chart: string) => { setActiveChart((prev) => (prev === chart ? defaultChart : chart)) }, [defaultChart], ) const getColorByIndex = useCallback( (chart: string) => { const index = chartDataKey.indexOf(chart) return `hsl(var(--chart-${(index % 10) + 1}))` }, [chartDataKey], ) const chartButtons = useMemo( () => chartDataKey.map((key) => ( )), [chartDataKey, activeChart, chartData, handleButtonClick], ) const chartLines = useMemo(() => { if (activeChart !== defaultChart) { return ( ) } return chartDataKey.map((key) => ( )) }, [activeChart, defaultChart, chartDataKey, getColorByIndex]) const processedData = useMemo(() => { if (!isPeakEnabled) { return activeChart === defaultChart ? formattedData : chartData[activeChart] } // 如果开启了削峰,对数据进行处理 const data = ( activeChart === defaultChart ? formattedData : chartData[activeChart] ) as ResultItem[] const windowSize = 7 // 增加到7个点的移动平均 const weights = [0.1, 0.1, 0.15, 0.3, 0.15, 0.1, 0.1] // 加权平均的权重 return data.map((point, index) => { if (index < windowSize - 1) return point const window = data.slice(index - windowSize + 1, index + 1) const smoothed = { ...point } as ResultItem if (activeChart === defaultChart) { // 处理所有线路的数据 chartDataKey.forEach((key) => { const values = window .map((w) => w[key]) .filter((v) => v !== undefined && v !== null) as number[] if (values.length === windowSize) { smoothed[key] = values.reduce((acc, val, idx) => acc + val * weights[idx], 0) } }) } else { // 处理单条线路的数据 const values = window .map((w) => w.avg_delay) .filter((v) => v !== undefined && v !== null) as number[] if (values.length === windowSize) { smoothed.avg_delay = values.reduce((acc, val, idx) => acc + val * weights[idx], 0) } } return smoothed }) }, [isPeakEnabled, activeChart, formattedData, chartData, chartDataKey, defaultChart]) return (
{serverName} {chartDataKey.length} {t("ServerMonitorCount")}
{chartButtons}
formatRelativeTime(value)} /> `${value}ms`} /> { return formatTime(payload[0].payload.created_at) }} /> } /> {activeChart === defaultChart && } />} {chartLines}
) }) const transformData = (data: NezhaAPIMonitor[]) => { const monitorData: ServerMonitorChart = {} data.forEach((item) => { const monitorName = item.monitor_name if (!monitorData[monitorName]) { monitorData[monitorName] = [] } for (let i = 0; i < item.created_at.length; i++) { monitorData[monitorName].push({ created_at: item.created_at[i], avg_delay: item.avg_delay[i], }) } }) return monitorData } const formatData = (rawData: NezhaAPIMonitor[]) => { const result: { [time: number]: ResultItem } = {} const allTimes = new Set() rawData.forEach((item) => { item.created_at.forEach((time) => allTimes.add(time)) }) const allTimeArray = Array.from(allTimes).sort((a, b) => a - b) rawData.forEach((item) => { const { monitor_name, created_at, avg_delay } = item allTimeArray.forEach((time) => { if (!result[time]) { result[time] = { created_at: time } } const timeIndex = created_at.indexOf(time) // @ts-expect-error - avg_delay is an array result[time][monitor_name] = timeIndex !== -1 ? avg_delay[timeIndex] : null }) }) return Object.values(result).sort((a, b) => a.created_at - b.created_at) }