From aa07d491664749e6998ef6530e2558a0fb5b07f2 Mon Sep 17 00:00:00 2001 From: hamster1963 <1410514192@qq.com> Date: Sat, 19 Oct 2024 12:52:24 +0800 Subject: [PATCH] feat: add detail charts --- .../ServerDetailChartClient.tsx | 1010 +++++++++++------ .../ClientComponents/ServerDetailClient.tsx | 262 +++-- app/[locale]/(main)/detail/[id]/page.tsx | 15 +- lib/utils.ts | 4 + 4 files changed, 799 insertions(+), 492 deletions(-) diff --git a/app/[locale]/(main)/ClientComponents/ServerDetailChartClient.tsx b/app/[locale]/(main)/ClientComponents/ServerDetailChartClient.tsx index 5f30858..01d49ff 100644 --- a/app/[locale]/(main)/ClientComponents/ServerDetailChartClient.tsx +++ b/app/[locale]/(main)/ClientComponents/ServerDetailChartClient.tsx @@ -1,393 +1,701 @@ -"use client" +"use client"; import AnimatedCircularProgressBar from "@/components/ui/animated-circular-progress-bar"; import { Card, CardContent } from "@/components/ui/card"; -import useSWR from "swr"; -import { NezhaAPISafe } from "../../types/nezha-api"; -import { formatNezhaInfo, formatRelativeTime, formatTime, nezhaFetcher } from "@/lib/utils"; +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; import getEnv from "@/lib/env-entry"; -import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"; -import { Area, AreaChart, CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts"; +import { + formatNezhaInfo, + formatRelativeTime, + formatTime, + nezhaFetcher, +} from "@/lib/utils"; import { useEffect, useState } from "react"; +import { + Area, + AreaChart, + CartesianGrid, + Line, + LineChart, + XAxis, + YAxis, +} from "recharts"; +import useSWR from "swr"; +import { NezhaAPISafe } from "../../types/nezha-api"; type cpuChartData = { - timeStamp: string; - cpu: number; -} + timeStamp: string; + cpu: number; +}; + +type processChartData = { + timeStamp: string; + process: number; +}; + +type diskChartData = { + timeStamp: string; + disk: number; +}; type memChartData = { - timeStamp: string; - mem: number; - swap: number; -} + timeStamp: string; + mem: number; + swap: number; +}; type networkChartData = { - timeStamp: string; - upload: number; - download: number; -} + timeStamp: string; + upload: number; + download: number; +}; + +type connectChartData = { + timeStamp: string; + tcp: number; + udp: number; +}; export default function ServerDetailChartClient({ - server_id, + server_id, }: { - server_id: number; + server_id: number; }) { - const { data, error } = useSWR( - `/api/detail?server_id=${server_id}`, - nezhaFetcher, - { - refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 5000, - }, - ); - - if (error) { - return ( - <> -
-

{error.message}

-

- {/* {t("chart_fetch_error_message")} */} - fetch_error_message -

-
- - ); - } - if (!data) return null; + const { data, error } = useSWR( + `/api/detail?server_id=${server_id}`, + nezhaFetcher, + { + refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 5000, + }, + ); + if (error) { return ( -
- - - -
- ) + <> +
+

{error.message}

+

+ {/* {t("chart_fetch_error_message")} */} + fetch_error_message +

+
+ + ); + } + if (!data) return null; + + return ( +
+ + + + + + +
+ ); } function CpuChart({ data }: { data: NezhaAPISafe }) { - const [cpuChartData, setCpuChartData] = useState([] as cpuChartData[]); + const [cpuChartData, setCpuChartData] = useState([] as cpuChartData[]); - const { cpu } = formatNezhaInfo(data); + const { cpu } = formatNezhaInfo(data); - useEffect(() => { - if (data) { - const timestamp = Date.now().toString(); - const newData = [ - ...cpuChartData, - { timeStamp: timestamp, cpu: cpu }, - ]; - if (newData.length > 30) { - newData.shift(); - } - setCpuChartData(newData); - } - }, [data]); + useEffect(() => { + if (data) { + const timestamp = Date.now().toString(); + const newData = [...cpuChartData, { timeStamp: timestamp, cpu: cpu }]; + if (newData.length > 30) { + newData.shift(); + } + setCpuChartData(newData); + } + }, [data]); - const chartConfig = { - cpu: { - label: "CPU", - }, - } satisfies ChartConfig + const chartConfig = { + cpu: { + label: "CPU", + }, + } satisfies ChartConfig; - return ( - - -
-
-

- CPU -

-
-

- {cpu.toFixed(0)}% -

- -
-
- - - - formatRelativeTime(value)} - /> - `${value}%`} - /> - - - -
-
-
- ) + return ( + + +
+
+

CPU

+
+

+ {cpu.toFixed(0)}% +

+ +
+
+ + + + formatRelativeTime(value)} + /> + `${value}%`} + /> + + + +
+
+
+ ); +} + +function ProcessChart({ data }: { data: NezhaAPISafe }) { + const [processChartData, setProcessChartData] = useState( + [] as processChartData[], + ); + + const { process } = formatNezhaInfo(data); + + useEffect(() => { + if (data) { + const timestamp = Date.now().toString(); + const newData = [ + ...processChartData, + { timeStamp: timestamp, process: process }, + ]; + if (newData.length > 30) { + newData.shift(); + } + setProcessChartData(newData); + } + }, [data]); + + const chartConfig = { + process: { + label: "Process", + }, + } satisfies ChartConfig; + + return ( + + +
+
+

Process

+
+

{process}

+
+
+ + + + formatRelativeTime(value)} + /> + + + + +
+
+
+ ); } function MemChart({ data }: { data: NezhaAPISafe }) { - const [memChartData, setMemChartData] = useState([] as memChartData[]); + const [memChartData, setMemChartData] = useState([] as memChartData[]); - const { mem, swap } = formatNezhaInfo(data); + const { mem, swap } = formatNezhaInfo(data); - useEffect(() => { - if (data) { - const timestamp = Date.now().toString(); - const newData = [ - ...memChartData, - { timeStamp: timestamp, mem: mem, swap: swap }, - ]; - if (newData.length > 30) { - newData.shift(); - } - setMemChartData(newData); - } - }, [data]); + useEffect(() => { + if (data) { + const timestamp = Date.now().toString(); + const newData = [ + ...memChartData, + { timeStamp: timestamp, mem: mem, swap: swap }, + ]; + if (newData.length > 30) { + newData.shift(); + } + setMemChartData(newData); + } + }, [data]); - const chartConfig = { - mem: { - label: "Mem", - }, - swap: { - label: "Swap", - }, - } satisfies ChartConfig - - return ( - - -
-
-
-
-

Mem

-
- -

- {mem.toFixed(0)}% -

-
-
-
-

Swap

-
- -

- {swap.toFixed(0)}% -

-
-
- -
-
- - - - formatRelativeTime(value)} - /> - `${value}%`} - /> - - - - -
-
-
- ) + const chartConfig = { + mem: { + label: "Mem", + }, + swap: { + label: "Swap", + }, + } satisfies ChartConfig; + return ( + + +
+
+
+
+

Mem

+
+ +

{mem.toFixed(0)}%

+
+
+
+

Swap

+
+ +

{swap.toFixed(0)}%

+
+
+
+
+ + + + formatRelativeTime(value)} + /> + `${value}%`} + /> + + + + +
+
+
+ ); } +function DiskChart({ data }: { data: NezhaAPISafe }) { + const [diskChartData, setDiskChartData] = useState([] as diskChartData[]); + + const { disk } = formatNezhaInfo(data); + + useEffect(() => { + if (data) { + const timestamp = Date.now().toString(); + const newData = [...diskChartData, { timeStamp: timestamp, disk: disk }]; + if (newData.length > 30) { + newData.shift(); + } + setDiskChartData(newData); + } + }, [data]); + + const chartConfig = { + disk: { + label: "Disk", + }, + } satisfies ChartConfig; + + return ( + + +
+
+

Disk

+
+

+ {disk.toFixed(0)}% +

+ +
+
+ + + + formatRelativeTime(value)} + /> + `${value}%`} + /> + + + +
+
+
+ ); +} function NetworkChart({ data }: { data: NezhaAPISafe }) { - const [networkChartData, setNetworkChartData] = useState([] as networkChartData[]); + const [networkChartData, setNetworkChartData] = useState( + [] as networkChartData[], + ); - const { up, down } = formatNezhaInfo(data); + const { up, down } = formatNezhaInfo(data); - useEffect(() => { - if (data) { - const timestamp = Date.now().toString(); - const newData = [ - ...networkChartData, - { timeStamp: timestamp, upload: up, download: down }, - ]; - if (newData.length > 30) { - newData.shift(); - } - setNetworkChartData(newData); - } - }, [data]); - - let maxDownload = Math.max(...networkChartData.map((item) => item.download)); - maxDownload = Math.ceil(maxDownload); - if (maxDownload < 1) { - maxDownload = 1; + useEffect(() => { + if (data) { + const timestamp = Date.now().toString(); + const newData = [ + ...networkChartData, + { timeStamp: timestamp, upload: up, download: down }, + ]; + if (newData.length > 30) { + newData.shift(); + } + setNetworkChartData(newData); } + }, [data]); - const chartConfig = { - upload: { - label: "Upload", - }, - download: { - label: "Download", - }, - } satisfies ChartConfig + let maxDownload = Math.max(...networkChartData.map((item) => item.download)); + maxDownload = Math.ceil(maxDownload); + if (maxDownload < 1) { + maxDownload = 1; + } - return ( - - -
-
-
-
-

Upload

-
- -

- {up.toFixed(2)} M/s -

-
-
-
-

Download

-
- -

- {down.toFixed(2)} M/s -

-
-
+ const chartConfig = { + upload: { + label: "Upload", + }, + download: { + label: "Download", + }, + } satisfies ChartConfig; -
-
- - - - formatRelativeTime(value)} - /> - `${value.toFixed(0)}M/s`} - /> - - - - -
-
-
- ) -} \ No newline at end of file + return ( + + +
+
+
+
+

Upload

+
+ +

{up.toFixed(2)} M/s

+
+
+
+

Download

+
+ +

{down.toFixed(2)} M/s

+
+
+
+
+ + + + formatRelativeTime(value)} + /> + `${value.toFixed(0)}M/s`} + /> + + + + +
+
+
+ ); +} + +function ConnectChart({ data }: { data: NezhaAPISafe }) { + const [connectChartData, setConnectChartData] = useState( + [] as connectChartData[], + ); + + const { tcp, udp } = formatNezhaInfo(data); + + useEffect(() => { + if (data) { + const timestamp = Date.now().toString(); + const newData = [ + ...connectChartData, + { timeStamp: timestamp, tcp: tcp, udp: udp }, + ]; + if (newData.length > 30) { + newData.shift(); + } + setConnectChartData(newData); + } + }, [data]); + + const chartConfig = { + tcp: { + label: "TCP", + }, + udp: { + label: "UDP", + }, + } satisfies ChartConfig; + + return ( + + +
+
+
+
+

TCP

+
+ +

{tcp}

+
+
+
+

UDP

+
+ +

{udp}

+
+
+
+
+ + + + formatRelativeTime(value)} + /> + + + + + +
+
+
+ ); +} diff --git a/app/[locale]/(main)/ClientComponents/ServerDetailClient.tsx b/app/[locale]/(main)/ClientComponents/ServerDetailClient.tsx index 02f1d37..315bfc0 100644 --- a/app/[locale]/(main)/ClientComponents/ServerDetailClient.tsx +++ b/app/[locale]/(main)/ClientComponents/ServerDetailClient.tsx @@ -2,7 +2,6 @@ import { NezhaAPISafe } from "@/app/[locale]/types/nezha-api"; import { BackIcon } from "@/components/Icon"; -import AnimatedCircularProgressBar from "@/components/ui/animated-circular-progress-bar"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent } from "@/components/ui/card"; import getEnv from "@/lib/env-entry"; @@ -12,142 +11,137 @@ import { useRouter } from "next/navigation"; import useSWR from "swr"; export default function ServerDetailClient({ - server_id, + server_id, }: { - server_id: number; + server_id: number; }) { - const router = useRouter(); - const locale = useLocale(); - const { data, error } = useSWR( - `/api/detail?server_id=${server_id}`, - nezhaFetcher, - { - refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 5000, - }, - ); - if (error) { - return ( - <> -
-

{error.message}

-

- {/* {t("chart_fetch_error_message")} */} - fetch_error_message -

-
- - ); - } - if (!data) return null; + const router = useRouter(); + const locale = useLocale(); + const { data, error } = useSWR( + `/api/detail?server_id=${server_id}`, + nezhaFetcher, + { + refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 5000, + }, + ); + if (error) { return ( -
-
{ - router.push(`/${locale}/`); - }} - className="flex flex-none cursor-pointer font-semibold leading-none items-center break-all tracking-tight gap-0.5 text-xl" - > - - {data?.name} -
-
- - -
-

Status

- - {data?.online_status ? "Online" : "Offline"} - -
-
-
- - -
-

Uptime

-
- {" "} - {(data?.status.Uptime / 86400).toFixed(0)} Days{" "} -
-
-
-
- - -
-

Version

-
- {data?.host.Version || "Unknown"}{" "} -
-
-
-
- - -
-

Arch

-
- {data?.host.Arch || "Unknown"}{" "} -
-
-
-
- - -
-

Mem

-
- {formatBytes(data?.host.MemTotal)} -
-
-
-
- - -
-

Disk

-
- {formatBytes(data?.host.DiskTotal)} -
-
-
-
-
-
- - -
-

System

- {data?.host.Platform ? ( -
- {" "} - {data?.host.Platform || "Unknown"} -{" "} - {data?.host.PlatformVersion}{" "} -
) :
Unknown
} -
-
-
- - -
-

CPU

- {data?.host.CPU ? ( -
- {" "} - {data?.host.CPU} -
) :
Unknown
} -
-
-
-
+ <> +
+

{error.message}

+

+ {/* {t("chart_fetch_error_message")} */} + fetch_error_message +

+ ); + } + if (!data) return null; + return ( +
+
{ + router.push(`/${locale}/`); + }} + className="flex flex-none cursor-pointer font-semibold leading-none items-center break-all tracking-tight gap-0.5 text-xl" + > + + {data?.name} +
+
+ + +
+

Status

+ + {data?.online_status ? "Online" : "Offline"} + +
+
+
+ + +
+

Uptime

+
+ {" "} + {(data?.status.Uptime / 86400).toFixed(0)} Days{" "} +
+
+
+
+ + +
+

Version

+
{data?.host.Version || "Unknown"}
+
+
+
+ + +
+

Arch

+
{data?.host.Arch || "Unknown"}
+
+
+
+ + +
+

Mem

+
{formatBytes(data?.host.MemTotal)}
+
+
+
+ + +
+

Disk

+
{formatBytes(data?.host.DiskTotal)}
+
+
+
+
+
+ + +
+

System

+ {data?.host.Platform ? ( +
+ {" "} + {data?.host.Platform || "Unknown"} -{" "} + {data?.host.PlatformVersion}{" "} +
+ ) : ( +
Unknown
+ )} +
+
+
+ + +
+

CPU

+ {data?.host.CPU ? ( +
{data?.host.CPU}
+ ) : ( +
Unknown
+ )} +
+
+
+
+
+ ); } diff --git a/app/[locale]/(main)/detail/[id]/page.tsx b/app/[locale]/(main)/detail/[id]/page.tsx index 21e17b6..de69a5d 100644 --- a/app/[locale]/(main)/detail/[id]/page.tsx +++ b/app/[locale]/(main)/detail/[id]/page.tsx @@ -1,12 +1,13 @@ -import ServerDetailClient from "@/app/[locale]/(main)/ClientComponents/ServerDetailClient"; import ServerDetailChartClient from "@/app/[locale]/(main)/ClientComponents/ServerDetailChartClient"; +import ServerDetailClient from "@/app/[locale]/(main)/ClientComponents/ServerDetailClient"; import { Separator } from "@/components/ui/separator"; export default function Page({ params }: { params: { id: string } }) { - return
- - - -
- ; + return ( +
+ + + +
+ ); } diff --git a/lib/utils.ts b/lib/utils.ts index 4987b0f..5dc9143 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -10,11 +10,15 @@ export function formatNezhaInfo(serverInfo: NezhaAPISafe) { return { ...serverInfo, cpu: serverInfo.status.CPU, + process: serverInfo.status.ProcessCount, up: serverInfo.status.NetOutSpeed / 1024 / 1024, down: serverInfo.status.NetInSpeed / 1024 / 1024, online: serverInfo.online_status, + tcp: serverInfo.status.TcpConnCount, + udp: serverInfo.status.UdpConnCount, mem: (serverInfo.status.MemUsed / serverInfo.host.MemTotal) * 100, swap: (serverInfo.status.SwapUsed / serverInfo.host.SwapTotal) * 100, + disk: (serverInfo.status.DiskUsed / serverInfo.host.DiskTotal) * 100, stg: (serverInfo.status.DiskUsed / serverInfo.host.DiskTotal) * 100, country_code: serverInfo.host.CountryCode, };