"use client";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
ChartConfig,
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { fetchMonitor } from "@/lib/nezha-api";
import { formatTime } from "@/lib/utils";
import { formatRelativeTime } from "@/lib/utils";
import { useQuery } from "@tanstack/react-query";
import * as React from "react";
import { useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
import NetworkChartLoading from "./NetworkChartLoading";
import { NezhaMonitor, ServerMonitorChart } from "@/types/nezha-api";
interface ResultItem {
created_at: number;
[key: string]: number | null;
}
export function NetworkChart({
server_id,
show,
}: {
server_id: number;
show: boolean;
}) {
const { t } = useTranslation();
const { data: monitorData } = useQuery({
queryKey: ["monitor", server_id],
queryFn: () => fetchMonitor(server_id),
enabled: show,
refetchOnMount: true,
refetchOnWindowFocus: true,
refetchInterval: 10000,
});
if (!monitorData) return ;
if (monitorData?.success && !monitorData.data) {
return (
<>
>
);
}
const transformedData = transformData(monitorData.data);
const formattedData = formatData(monitorData.data);
const chartDataKey = Object.keys(transformedData);
const initChartConfig = {
avg_delay: {
label: t("monitor.avgDelay"),
},
...chartDataKey.reduce((acc, key) => {
acc[key] = {
label: key,
};
return acc;
}, {} as ChartConfig),
} satisfies ChartConfig;
return (
);
}
export const NetworkChartClient = React.memo(function NetworkChart({
chartDataKey,
chartConfig,
chartData,
serverName,
formattedData,
}: {
chartDataKey: string[];
chartConfig: ChartConfig;
chartData: ServerMonitorChart;
serverName: string;
formattedData: ResultItem[];
}) {
const { t } = useTranslation();
const defaultChart = "All";
const [activeCharts, setActiveCharts] = React.useState([defaultChart]);
const handleButtonClick = useCallback(
(chart: string) => {
setActiveCharts((prev) => {
if (chart === defaultChart) {
return [defaultChart];
}
const newCharts = prev.filter(c => c !== defaultChart);
const chartIndex = newCharts.indexOf(chart);
if (chartIndex === -1) {
return newCharts.length === 0 ? [chart] : [...newCharts, chart];
} else {
const result = newCharts.filter(c => c !== chart);
return result.length === 0 ? [defaultChart] : result;
}
});
},
[],
);
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, activeCharts, chartData, handleButtonClick],
);
const chartLines = useMemo(() => {
if (activeCharts.includes(defaultChart)) {
return chartDataKey.map((key) => (
));
}
return activeCharts.map((chart) => (
));
}, [activeCharts, chartDataKey, getColorByIndex]);
return (
{serverName}
{chartDataKey.length} {t("monitor.monitorCount")}
{chartButtons}
formatRelativeTime(value)}
/>
`${value}ms`}
/>
{
return formatTime(payload[0].payload.created_at);
}}
/>
}
/>
{activeCharts.includes(defaultChart) && (
} />
)}
{chartLines}
);
});
const transformData = (data: NezhaMonitor[]) => {
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: NezhaMonitor[]) => {
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);
result[time][monitor_name] =
timeIndex !== -1 ? avg_delay[timeIndex] : null;
});
});
return Object.values(result).sort((a, b) => a.created_at - b.created_at);
};