feat: enhance ServerDetailChart with new chart tooltips and sync functionality

This commit is contained in:
hamster1963
2026-01-30 09:30:50 +08:00
parent 65b32ec81e
commit 6dae6cce8f
2 changed files with 247 additions and 23 deletions
+192 -3
View File
@@ -11,11 +11,21 @@ import {
YAxis,
} from "recharts";
import { Card, CardContent } from "@/components/ui/card";
import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
import {
type ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { useWebSocketContext } from "@/hooks/use-websocket-context";
import { formatBytes } from "@/lib/format";
import { fetchLoginUser, fetchServerMetrics } from "@/lib/nezha-api";
import { cn, formatNezhaInfo, formatRelativeTime } from "@/lib/utils";
import {
cn,
formatNezhaInfo,
formatRelativeTime,
formatTime,
} from "@/lib/utils";
import type {
MetricPeriod,
NezhaServer,
@@ -84,7 +94,7 @@ function PeriodSelector({
];
return (
<div className="flex gap-1 mb-3 flex-wrap">
<div className="flex gap-1 mb-3 flex-wrap -mt-5">
{periods.map((period) => {
// Only realtime and 1d are available for non-logged-in users
const isLocked =
@@ -427,6 +437,7 @@ function GpuChart({
</div>
) : (
<AreaChart
syncId="serverDetailCharts"
accessibilityLayer
data={displayData}
margin={{
@@ -453,6 +464,27 @@ function GpuChart({
domain={[0, 100]}
tickFormatter={(value) => `${value}%`}
/>
<ChartTooltip
isAnimationActive={false}
content={
<ChartTooltipContent
indicator="dot"
labelFormatter={(_, payload) => {
return formatTime(
Number(payload[0]?.payload?.timeStamp),
);
}}
formatter={(value) => (
<div className="flex flex-1 items-center justify-between leading-none">
<span className="text-muted-foreground">GPU</span>
<span className="ml-2 font-medium text-foreground tabular-nums">
{Number(value).toFixed(1)}%
</span>
</div>
)}
/>
}
/>
<Area
isAnimationActive={false}
dataKey="gpu"
@@ -607,6 +639,7 @@ function CpuChart({
</div>
) : (
<AreaChart
syncId="serverDetailCharts"
accessibilityLayer
data={displayData}
margin={{
@@ -633,6 +666,27 @@ function CpuChart({
domain={[0, 100]}
tickFormatter={(value) => `${value}%`}
/>
<ChartTooltip
isAnimationActive={false}
content={
<ChartTooltipContent
indicator="dot"
labelFormatter={(_, payload) => {
return formatTime(
Number(payload[0]?.payload?.timeStamp),
);
}}
formatter={(value) => (
<div className="flex flex-1 items-center justify-between leading-none">
<span className="text-muted-foreground">CPU</span>
<span className="ml-2 font-medium text-foreground tabular-nums">
{Number(value).toFixed(1)}%
</span>
</div>
)}
/>
}
/>
<Area
isAnimationActive={false}
dataKey="cpu"
@@ -783,6 +837,7 @@ function ProcessChart({
</div>
) : (
<AreaChart
syncId="serverDetailCharts"
accessibilityLayer
data={displayData}
margin={{
@@ -807,6 +862,29 @@ function ProcessChart({
mirror={true}
tickMargin={-15}
/>
<ChartTooltip
isAnimationActive={false}
content={
<ChartTooltipContent
indicator={"dot"}
labelFormatter={(_, payload) => {
return formatTime(
Number(payload[0]?.payload?.timeStamp),
);
}}
formatter={(value) => (
<div className="flex flex-1 items-center justify-between leading-none">
<span className="text-muted-foreground">
{t("serverDetailChart.process")}
</span>
<span className="ml-2 font-medium text-foreground tabular-nums">
{Number(value).toFixed(0)}
</span>
</div>
)}
/>
}
/>
<Area
isAnimationActive={false}
dataKey="process"
@@ -1043,6 +1121,7 @@ function MemChart({
</div>
) : (
<AreaChart
syncId="serverDetailCharts"
accessibilityLayer
data={displayData}
margin={{
@@ -1069,6 +1148,35 @@ function MemChart({
domain={[0, 100]}
tickFormatter={(value) => `${value}%`}
/>
<ChartTooltip
isAnimationActive={false}
content={
<ChartTooltipContent
indicator="dot"
labelFormatter={(_, payload) => {
return formatTime(
Number(payload[0]?.payload?.timeStamp),
);
}}
formatter={(value, name) => {
const label =
name === "mem"
? t("serverDetailChart.mem")
: t("serverDetailChart.swap");
return (
<div className="flex flex-1 items-center justify-between leading-none">
<span className="text-muted-foreground">
{label}
</span>
<span className="ml-2 font-medium text-foreground tabular-nums">
{Number(value).toFixed(1)}%
</span>
</div>
);
}}
/>
}
/>
<Area
isAnimationActive={false}
dataKey="mem"
@@ -1243,6 +1351,7 @@ function DiskChart({
</div>
) : (
<AreaChart
syncId="serverDetailCharts"
accessibilityLayer
data={displayData}
margin={{
@@ -1269,6 +1378,29 @@ function DiskChart({
domain={[0, 100]}
tickFormatter={(value) => `${value}%`}
/>
<ChartTooltip
isAnimationActive={false}
content={
<ChartTooltipContent
indicator="dot"
labelFormatter={(_, payload) => {
return formatTime(
Number(payload[0]?.payload?.timeStamp),
);
}}
formatter={(value) => (
<div className="flex flex-1 items-center justify-between leading-none">
<span className="text-muted-foreground">
{t("serverDetailChart.disk")}
</span>
<span className="ml-2 font-medium text-foreground tabular-nums">
{Number(value).toFixed(1)}%
</span>
</div>
)}
/>
}
/>
<Area
isAnimationActive={false}
dataKey="disk"
@@ -1490,6 +1622,7 @@ function NetworkChart({
</div>
) : (
<LineChart
syncId="serverDetailCharts"
accessibilityLayer
data={displayData}
margin={{
@@ -1519,6 +1652,35 @@ function NetworkChart({
domain={[1, maxDownload]}
tickFormatter={(value) => `${value.toFixed(0)}M/s`}
/>
<ChartTooltip
isAnimationActive={false}
content={
<ChartTooltipContent
indicator="dot"
labelFormatter={(_, payload) => {
return formatTime(
Number(payload[0]?.payload?.timeStamp),
);
}}
formatter={(value, name) => {
const label =
name === "upload"
? t("serverDetailChart.upload")
: t("serverDetailChart.download");
return (
<div className="flex flex-1 items-center justify-between leading-none">
<span className="text-muted-foreground">
{label}
</span>
<span className="ml-2 font-medium text-foreground tabular-nums">
{Number(value).toFixed(2)} MB/s
</span>
</div>
);
}}
/>
}
/>
<Line
isAnimationActive={false}
dataKey="upload"
@@ -1721,6 +1883,7 @@ function ConnectChart({
</div>
) : (
<LineChart
syncId="serverDetailCharts"
accessibilityLayer
data={displayData}
margin={{
@@ -1747,6 +1910,32 @@ function ConnectChart({
type="number"
interval="preserveStartEnd"
/>
<ChartTooltip
isAnimationActive={false}
content={
<ChartTooltipContent
indicator="dot"
labelFormatter={(_, payload) => {
return formatTime(
Number(payload[0]?.payload?.timeStamp),
);
}}
formatter={(value, name) => {
const label = name === "tcp" ? "TCP" : "UDP";
return (
<div className="flex flex-1 items-center justify-between leading-none">
<span className="text-muted-foreground">
{label}
</span>
<span className="ml-2 font-medium text-foreground tabular-nums">
{Number(value).toFixed(0)}
</span>
</div>
);
}}
/>
}
/>
<Line
isAnimationActive={false}
dataKey="tcp"