@@ -11,11 +11,21 @@ import {
1111 YAxis ,
1212} from "recharts" ;
1313import { Card , CardContent } from "@/components/ui/card" ;
14- import { type ChartConfig , ChartContainer } from "@/components/ui/chart" ;
14+ import {
15+ type ChartConfig ,
16+ ChartContainer ,
17+ ChartTooltip ,
18+ ChartTooltipContent ,
19+ } from "@/components/ui/chart" ;
1520import { useWebSocketContext } from "@/hooks/use-websocket-context" ;
1621import { formatBytes } from "@/lib/format" ;
1722import { fetchLoginUser , fetchServerMetrics } from "@/lib/nezha-api" ;
18- import { cn , formatNezhaInfo , formatRelativeTime } from "@/lib/utils" ;
23+ import {
24+ cn ,
25+ formatNezhaInfo ,
26+ formatRelativeTime ,
27+ formatTime ,
28+ } from "@/lib/utils" ;
1929import type {
2030 MetricPeriod ,
2131 NezhaServer ,
@@ -84,7 +94,7 @@ function PeriodSelector({
8494 ] ;
8595
8696 return (
87- < div className = "flex gap-1 mb-3 flex-wrap" >
97+ < div className = "flex gap-1 mb-3 flex-wrap -mt-5 " >
8898 { periods . map ( ( period ) => {
8999 // Only realtime and 1d are available for non-logged-in users
90100 const isLocked =
@@ -427,6 +437,7 @@ function GpuChart({
427437 </ div >
428438 ) : (
429439 < AreaChart
440+ syncId = "serverDetailCharts"
430441 accessibilityLayer
431442 data = { displayData }
432443 margin = { {
@@ -453,6 +464,27 @@ function GpuChart({
453464 domain = { [ 0 , 100 ] }
454465 tickFormatter = { ( value ) => `${ value } %` }
455466 />
467+ < ChartTooltip
468+ isAnimationActive = { false }
469+ content = {
470+ < ChartTooltipContent
471+ indicator = "dot"
472+ labelFormatter = { ( _ , payload ) => {
473+ return formatTime (
474+ Number ( payload [ 0 ] ?. payload ?. timeStamp ) ,
475+ ) ;
476+ } }
477+ formatter = { ( value ) => (
478+ < div className = "flex flex-1 items-center justify-between leading-none" >
479+ < span className = "text-muted-foreground" > GPU</ span >
480+ < span className = "ml-2 font-medium text-foreground tabular-nums" >
481+ { Number ( value ) . toFixed ( 1 ) } %
482+ </ span >
483+ </ div >
484+ ) }
485+ />
486+ }
487+ />
456488 < Area
457489 isAnimationActive = { false }
458490 dataKey = "gpu"
@@ -607,6 +639,7 @@ function CpuChart({
607639 </ div >
608640 ) : (
609641 < AreaChart
642+ syncId = "serverDetailCharts"
610643 accessibilityLayer
611644 data = { displayData }
612645 margin = { {
@@ -633,6 +666,27 @@ function CpuChart({
633666 domain = { [ 0 , 100 ] }
634667 tickFormatter = { ( value ) => `${ value } %` }
635668 />
669+ < ChartTooltip
670+ isAnimationActive = { false }
671+ content = {
672+ < ChartTooltipContent
673+ indicator = "dot"
674+ labelFormatter = { ( _ , payload ) => {
675+ return formatTime (
676+ Number ( payload [ 0 ] ?. payload ?. timeStamp ) ,
677+ ) ;
678+ } }
679+ formatter = { ( value ) => (
680+ < div className = "flex flex-1 items-center justify-between leading-none" >
681+ < span className = "text-muted-foreground" > CPU</ span >
682+ < span className = "ml-2 font-medium text-foreground tabular-nums" >
683+ { Number ( value ) . toFixed ( 1 ) } %
684+ </ span >
685+ </ div >
686+ ) }
687+ />
688+ }
689+ />
636690 < Area
637691 isAnimationActive = { false }
638692 dataKey = "cpu"
@@ -783,6 +837,7 @@ function ProcessChart({
783837 </ div >
784838 ) : (
785839 < AreaChart
840+ syncId = "serverDetailCharts"
786841 accessibilityLayer
787842 data = { displayData }
788843 margin = { {
@@ -807,6 +862,29 @@ function ProcessChart({
807862 mirror = { true }
808863 tickMargin = { - 15 }
809864 />
865+ < ChartTooltip
866+ isAnimationActive = { false }
867+ content = {
868+ < ChartTooltipContent
869+ indicator = { "dot" }
870+ labelFormatter = { ( _ , payload ) => {
871+ return formatTime (
872+ Number ( payload [ 0 ] ?. payload ?. timeStamp ) ,
873+ ) ;
874+ } }
875+ formatter = { ( value ) => (
876+ < div className = "flex flex-1 items-center justify-between leading-none" >
877+ < span className = "text-muted-foreground" >
878+ { t ( "serverDetailChart.process" ) }
879+ </ span >
880+ < span className = "ml-2 font-medium text-foreground tabular-nums" >
881+ { Number ( value ) . toFixed ( 0 ) }
882+ </ span >
883+ </ div >
884+ ) }
885+ />
886+ }
887+ />
810888 < Area
811889 isAnimationActive = { false }
812890 dataKey = "process"
@@ -1043,6 +1121,7 @@ function MemChart({
10431121 </ div >
10441122 ) : (
10451123 < AreaChart
1124+ syncId = "serverDetailCharts"
10461125 accessibilityLayer
10471126 data = { displayData }
10481127 margin = { {
@@ -1069,6 +1148,35 @@ function MemChart({
10691148 domain = { [ 0 , 100 ] }
10701149 tickFormatter = { ( value ) => `${ value } %` }
10711150 />
1151+ < ChartTooltip
1152+ isAnimationActive = { false }
1153+ content = {
1154+ < ChartTooltipContent
1155+ indicator = "dot"
1156+ labelFormatter = { ( _ , payload ) => {
1157+ return formatTime (
1158+ Number ( payload [ 0 ] ?. payload ?. timeStamp ) ,
1159+ ) ;
1160+ } }
1161+ formatter = { ( value , name ) => {
1162+ const label =
1163+ name === "mem"
1164+ ? t ( "serverDetailChart.mem" )
1165+ : t ( "serverDetailChart.swap" ) ;
1166+ return (
1167+ < div className = "flex flex-1 items-center justify-between leading-none" >
1168+ < span className = "text-muted-foreground" >
1169+ { label }
1170+ </ span >
1171+ < span className = "ml-2 font-medium text-foreground tabular-nums" >
1172+ { Number ( value ) . toFixed ( 1 ) } %
1173+ </ span >
1174+ </ div >
1175+ ) ;
1176+ } }
1177+ />
1178+ }
1179+ />
10721180 < Area
10731181 isAnimationActive = { false }
10741182 dataKey = "mem"
@@ -1243,6 +1351,7 @@ function DiskChart({
12431351 </ div >
12441352 ) : (
12451353 < AreaChart
1354+ syncId = "serverDetailCharts"
12461355 accessibilityLayer
12471356 data = { displayData }
12481357 margin = { {
@@ -1269,6 +1378,29 @@ function DiskChart({
12691378 domain = { [ 0 , 100 ] }
12701379 tickFormatter = { ( value ) => `${ value } %` }
12711380 />
1381+ < ChartTooltip
1382+ isAnimationActive = { false }
1383+ content = {
1384+ < ChartTooltipContent
1385+ indicator = "dot"
1386+ labelFormatter = { ( _ , payload ) => {
1387+ return formatTime (
1388+ Number ( payload [ 0 ] ?. payload ?. timeStamp ) ,
1389+ ) ;
1390+ } }
1391+ formatter = { ( value ) => (
1392+ < div className = "flex flex-1 items-center justify-between leading-none" >
1393+ < span className = "text-muted-foreground" >
1394+ { t ( "serverDetailChart.disk" ) }
1395+ </ span >
1396+ < span className = "ml-2 font-medium text-foreground tabular-nums" >
1397+ { Number ( value ) . toFixed ( 1 ) } %
1398+ </ span >
1399+ </ div >
1400+ ) }
1401+ />
1402+ }
1403+ />
12721404 < Area
12731405 isAnimationActive = { false }
12741406 dataKey = "disk"
@@ -1490,6 +1622,7 @@ function NetworkChart({
14901622 </ div >
14911623 ) : (
14921624 < LineChart
1625+ syncId = "serverDetailCharts"
14931626 accessibilityLayer
14941627 data = { displayData }
14951628 margin = { {
@@ -1519,6 +1652,35 @@ function NetworkChart({
15191652 domain = { [ 1 , maxDownload ] }
15201653 tickFormatter = { ( value ) => `${ value . toFixed ( 0 ) } M/s` }
15211654 />
1655+ < ChartTooltip
1656+ isAnimationActive = { false }
1657+ content = {
1658+ < ChartTooltipContent
1659+ indicator = "dot"
1660+ labelFormatter = { ( _ , payload ) => {
1661+ return formatTime (
1662+ Number ( payload [ 0 ] ?. payload ?. timeStamp ) ,
1663+ ) ;
1664+ } }
1665+ formatter = { ( value , name ) => {
1666+ const label =
1667+ name === "upload"
1668+ ? t ( "serverDetailChart.upload" )
1669+ : t ( "serverDetailChart.download" ) ;
1670+ return (
1671+ < div className = "flex flex-1 items-center justify-between leading-none" >
1672+ < span className = "text-muted-foreground" >
1673+ { label }
1674+ </ span >
1675+ < span className = "ml-2 font-medium text-foreground tabular-nums" >
1676+ { Number ( value ) . toFixed ( 2 ) } MB/s
1677+ </ span >
1678+ </ div >
1679+ ) ;
1680+ } }
1681+ />
1682+ }
1683+ />
15221684 < Line
15231685 isAnimationActive = { false }
15241686 dataKey = "upload"
@@ -1721,6 +1883,7 @@ function ConnectChart({
17211883 </ div >
17221884 ) : (
17231885 < LineChart
1886+ syncId = "serverDetailCharts"
17241887 accessibilityLayer
17251888 data = { displayData }
17261889 margin = { {
@@ -1747,6 +1910,32 @@ function ConnectChart({
17471910 type = "number"
17481911 interval = "preserveStartEnd"
17491912 />
1913+ < ChartTooltip
1914+ isAnimationActive = { false }
1915+ content = {
1916+ < ChartTooltipContent
1917+ indicator = "dot"
1918+ labelFormatter = { ( _ , payload ) => {
1919+ return formatTime (
1920+ Number ( payload [ 0 ] ?. payload ?. timeStamp ) ,
1921+ ) ;
1922+ } }
1923+ formatter = { ( value , name ) => {
1924+ const label = name === "tcp" ? "TCP" : "UDP" ;
1925+ return (
1926+ < div className = "flex flex-1 items-center justify-between leading-none" >
1927+ < span className = "text-muted-foreground" >
1928+ { label }
1929+ </ span >
1930+ < span className = "ml-2 font-medium text-foreground tabular-nums" >
1931+ { Number ( value ) . toFixed ( 0 ) }
1932+ </ span >
1933+ </ div >
1934+ ) ;
1935+ } }
1936+ />
1937+ }
1938+ />
17501939 < Line
17511940 isAnimationActive = { false }
17521941 dataKey = "tcp"
0 commit comments