diff --git a/src/components/ServerDetailChart.tsx b/src/components/ServerDetailChart.tsx index fd9ad4e..be7631b 100644 --- a/src/components/ServerDetailChart.tsx +++ b/src/components/ServerDetailChart.tsx @@ -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 ( -
+
{periods.map((period) => { // Only realtime and 1d are available for non-logged-in users const isLocked = @@ -427,6 +437,7 @@ function GpuChart({
) : ( `${value}%`} /> + { + return formatTime( + Number(payload[0]?.payload?.timeStamp), + ); + }} + formatter={(value) => ( +
+ GPU + + {Number(value).toFixed(1)}% + +
+ )} + /> + } + /> ) : ( `${value}%`} /> + { + return formatTime( + Number(payload[0]?.payload?.timeStamp), + ); + }} + formatter={(value) => ( +
+ CPU + + {Number(value).toFixed(1)}% + +
+ )} + /> + } + /> ) : ( + { + return formatTime( + Number(payload[0]?.payload?.timeStamp), + ); + }} + formatter={(value) => ( +
+ + {t("serverDetailChart.process")} + + + {Number(value).toFixed(0)} + +
+ )} + /> + } + /> ) : ( `${value}%`} /> + { + return formatTime( + Number(payload[0]?.payload?.timeStamp), + ); + }} + formatter={(value, name) => { + const label = + name === "mem" + ? t("serverDetailChart.mem") + : t("serverDetailChart.swap"); + return ( +
+ + {label} + + + {Number(value).toFixed(1)}% + +
+ ); + }} + /> + } + /> ) : ( `${value}%`} /> + { + return formatTime( + Number(payload[0]?.payload?.timeStamp), + ); + }} + formatter={(value) => ( +
+ + {t("serverDetailChart.disk")} + + + {Number(value).toFixed(1)}% + +
+ )} + /> + } + /> ) : ( `${value.toFixed(0)}M/s`} /> + { + return formatTime( + Number(payload[0]?.payload?.timeStamp), + ); + }} + formatter={(value, name) => { + const label = + name === "upload" + ? t("serverDetailChart.upload") + : t("serverDetailChart.download"); + return ( +
+ + {label} + + + {Number(value).toFixed(2)} MB/s + +
+ ); + }} + /> + } + /> ) : ( + { + return formatTime( + Number(payload[0]?.payload?.timeStamp), + ); + }} + formatter={(value, name) => { + const label = name === "tcp" ? "TCP" : "UDP"; + return ( +
+ + {label} + + + {Number(value).toFixed(0)} + +
+ ); + }} + /> + } + /> & - React.ComponentProps<"div"> & { - hideLabel?: boolean; - hideIndicator?: boolean; - indicator?: "line" | "dot" | "dashed"; - nameKey?: string; - labelKey?: string; - } + React.ComponentProps<"div"> & { + active?: boolean; + payload?: any[]; + label?: any; + hideLabel?: boolean; + hideIndicator?: boolean; + indicator?: "line" | "dot" | "dashed"; + nameKey?: string; + labelKey?: string; + labelFormatter?: (value: any, payload: any[]) => React.ReactNode; + formatter?: ( + value: any, + name: any, + item: any, + index: number, + payload: any, + ) => React.ReactNode; + color?: string; + labelClassName?: string; + } >( ( { @@ -170,18 +184,31 @@ const ChartTooltipContent = React.forwardRef< return null; } + payload.sort((a, b) => { + return Number(b.value) - Number(a.value); + }); + const nestLabel = payload.length === 1 && indicator !== "dot"; return (
- {!nestLabel ? tooltipLabel : null} -
+ {!nestLabel && ( +
+ {!nestLabel ? tooltipLabel : null} +
+ )} + +
{payload.map((item, index) => { const key = `${nameKey || item.name || item.dataKey || "value"}`; const itemConfig = getPayloadConfigFromPayload(config, item, key); @@ -236,8 +263,15 @@ const ChartTooltipContent = React.forwardRef<
{item.value && ( - - {item.value.toLocaleString()} + + {typeof item.value === "number" + ? item.value.toFixed(2).toLocaleString() + : item.value}{" "} )}
@@ -257,11 +291,12 @@ const ChartLegend = RechartsPrimitive.Legend; const ChartLegendContent = React.forwardRef< HTMLDivElement, - React.ComponentProps<"div"> & - Pick & { - hideIcon?: boolean; - nameKey?: string; - } + React.ComponentProps<"div"> & { + payload?: any[]; + verticalAlign?: "top" | "bottom" | "middle"; + hideIcon?: boolean; + nameKey?: string; + } >( ( { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey }, @@ -303,7 +338,7 @@ const ChartLegendContent = React.forwardRef< }} /> )} - {itemConfig?.label} + {key}
); })}