diff --git a/.github/get-token.png b/.github/get-token.png
deleted file mode 100644
index 4b21dda..0000000
Binary files a/.github/get-token.png and /dev/null differ
diff --git a/.github/shot-1-dark.png b/.github/shot-1-dark.png
new file mode 100644
index 0000000..630ba25
Binary files /dev/null and b/.github/shot-1-dark.png differ
diff --git a/.github/shot-1.png b/.github/shot-1.png
index 80d9343..c1ac9ff 100644
Binary files a/.github/shot-1.png and b/.github/shot-1.png differ
diff --git a/.github/shot-2-dark.png b/.github/shot-2-dark.png
new file mode 100644
index 0000000..4c3975e
Binary files /dev/null and b/.github/shot-2-dark.png differ
diff --git a/.github/shot-2.png b/.github/shot-2.png
index 9768951..8bd53a7 100644
Binary files a/.github/shot-2.png and b/.github/shot-2.png differ
diff --git a/.github/shot-3-dark.png b/.github/shot-3-dark.png
new file mode 100644
index 0000000..25288c7
Binary files /dev/null and b/.github/shot-3-dark.png differ
diff --git a/.github/shot-3.png b/.github/shot-3.png
index 9eb66a7..ba6dae5 100644
Binary files a/.github/shot-3.png and b/.github/shot-3.png differ
diff --git a/.github/shot-4.png b/.github/shot-4.png
deleted file mode 100644
index 44bf56b..0000000
Binary files a/.github/shot-4.png and /dev/null differ
diff --git a/README.md b/README.md
index f5402eb..3857359 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,9 @@
| 英语 | en | 是 |
| 日语 | ja | 是 |
-
-
-
-
+
+
+
+
+
+
diff --git a/app/[locale]/(main)/ClientComponents/ServerDetailChartClient.tsx b/app/[locale]/(main)/ClientComponents/ServerDetailChartClient.tsx
new file mode 100644
index 0000000..dad5f6e
--- /dev/null
+++ b/app/[locale]/(main)/ClientComponents/ServerDetailChartClient.tsx
@@ -0,0 +1,703 @@
+"use client";
+
+import AnimatedCircularProgressBar from "@/components/ui/animated-circular-progress-bar";
+import { Card, CardContent } from "@/components/ui/card";
+import { ChartConfig, ChartContainer } from "@/components/ui/chart";
+import getEnv from "@/lib/env-entry";
+import { formatNezhaInfo, formatRelativeTime, nezhaFetcher } from "@/lib/utils";
+import { useTranslations } from "next-intl";
+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;
+};
+
+type processChartData = {
+ timeStamp: string;
+ process: number;
+};
+
+type diskChartData = {
+ timeStamp: string;
+ disk: number;
+};
+
+type memChartData = {
+ timeStamp: string;
+ mem: number;
+ swap: number;
+};
+
+type networkChartData = {
+ timeStamp: string;
+ upload: number;
+ download: number;
+};
+
+type connectChartData = {
+ timeStamp: string;
+ tcp: number;
+ udp: number;
+};
+
+export default function ServerDetailChartClient({
+ server_id,
+}: {
+ server_id: number;
+}) {
+ const t = useTranslations("ServerDetailChartClient");
+
+ 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")}
+
+
+ >
+ );
+ }
+ if (!data) return null;
+
+ return (
+
+ );
+}
+
+function CpuChart({ data }: { data: NezhaAPISafe }) {
+ const [cpuChartData, setCpuChartData] = useState([] as cpuChartData[]);
+
+ 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]);
+
+ const chartConfig = {
+ cpu: {
+ label: "CPU",
+ },
+ } satisfies ChartConfig;
+
+ return (
+
+
+
+
+
CPU
+
+
+ {cpu.toFixed(0)}%
+
+
+
+
+
+
+
+ formatRelativeTime(value)}
+ />
+ `${value}%`}
+ />
+
+
+
+
+
+
+ );
+}
+
+function ProcessChart({ data }: { data: NezhaAPISafe }) {
+ const t = useTranslations("ServerDetailChartClient");
+
+ 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 (
+
+
+
+
+
+
+
+ formatRelativeTime(value)}
+ />
+
+
+
+
+
+
+
+ );
+}
+
+function MemChart({ data }: { data: NezhaAPISafe }) {
+ const t = useTranslations("ServerDetailChartClient");
+
+ const [memChartData, setMemChartData] = useState([] as memChartData[]);
+
+ 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]);
+
+ const chartConfig = {
+ mem: {
+ label: "Mem",
+ },
+ swap: {
+ label: "Swap",
+ },
+ } satisfies ChartConfig;
+
+ return (
+
+
+
+
+
+
+
+ formatRelativeTime(value)}
+ />
+ `${value}%`}
+ />
+
+
+
+
+
+
+
+ );
+}
+
+function DiskChart({ data }: { data: NezhaAPISafe }) {
+ const t = useTranslations("ServerDetailChartClient");
+
+ 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 (
+
+
+
+
+
{t("Disk")}
+
+
+ {disk.toFixed(0)}%
+
+
+
+
+
+
+
+ formatRelativeTime(value)}
+ />
+ `${value}%`}
+ />
+
+
+
+
+
+
+ );
+}
+
+function NetworkChart({ data }: { data: NezhaAPISafe }) {
+ const t = useTranslations("ServerDetailChartClient");
+
+ const [networkChartData, setNetworkChartData] = useState(
+ [] as networkChartData[],
+ );
+
+ 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;
+ }
+
+ const chartConfig = {
+ upload: {
+ label: "Upload",
+ },
+ download: {
+ label: "Download",
+ },
+ } satisfies ChartConfig;
+
+ return (
+
+
+
+
+
+
+
{t("Upload")}
+
+
+
{up.toFixed(2)} M/s
+
+
+
+
+ {t("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 (
+
+
+
+
+
+
+
+ formatRelativeTime(value)}
+ />
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/[locale]/(main)/ClientComponents/ServerDetailClient.tsx b/app/[locale]/(main)/ClientComponents/ServerDetailClient.tsx
new file mode 100644
index 0000000..718f814
--- /dev/null
+++ b/app/[locale]/(main)/ClientComponents/ServerDetailClient.tsx
@@ -0,0 +1,152 @@
+"use client";
+
+import { NezhaAPISafe } from "@/app/[locale]/types/nezha-api";
+import { BackIcon } from "@/components/Icon";
+import { Badge } from "@/components/ui/badge";
+import { Card, CardContent } from "@/components/ui/card";
+import getEnv from "@/lib/env-entry";
+import { cn, formatBytes, nezhaFetcher } from "@/lib/utils";
+import { useLocale, useTranslations } from "next-intl";
+import { useRouter } from "next/navigation";
+import useSWR from "swr";
+
+import ServerDetailLoading from "./ServerDetailLoading";
+
+export default function ServerDetailClient({
+ server_id,
+}: {
+ server_id: number;
+}) {
+ const t = useTranslations("ServerDetailClient");
+ 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("detail_fetch_error_message")}
+
+
+ >
+ );
+ }
+
+ if (!data) return ;
+
+ 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}
+
+
+
+
+
+ {t("status")}
+
+ {data?.online_status ? t("Online") : t("Offline")}
+
+
+
+
+
+
+
+ {t("Uptime")}
+
+ {" "}
+ {(data?.status.Uptime / 86400).toFixed(0)} {t("Days")}{" "}
+
+
+
+
+
+
+
+ {t("Version")}
+ {data?.host.Version || "Unknown"}
+
+
+
+
+
+
+ {t("Arch")}
+ {data?.host.Arch || "Unknown"}
+
+
+
+
+
+
+ {t("Mem")}
+ {formatBytes(data?.host.MemTotal)}
+
+
+
+
+
+
+ {t("Disk")}
+ {formatBytes(data?.host.DiskTotal)}
+
+
+
+
+
+
+
+
+ {t("System")}
+ {data?.host.Platform ? (
+
+ {" "}
+ {data?.host.Platform || "Unknown"} -{" "}
+ {data?.host.PlatformVersion}{" "}
+
+ ) : (
+ Unknown
+ )}
+
+
+
+
+
+
+ {t("CPU")}
+ {data?.host.CPU ? (
+ {data?.host.CPU}
+ ) : (
+ Unknown
+ )}
+
+
+
+
+
+ );
+}
diff --git a/app/[locale]/(main)/ClientComponents/ServerDetailLoading.tsx b/app/[locale]/(main)/ClientComponents/ServerDetailLoading.tsx
new file mode 100644
index 0000000..2dc538b
--- /dev/null
+++ b/app/[locale]/(main)/ClientComponents/ServerDetailLoading.tsx
@@ -0,0 +1,33 @@
+import { BackIcon } from "@/components/Icon";
+import { Separator } from "@/components/ui/separator";
+import { Skeleton } from "@/components/ui/skeleton";
+import { useLocale } from "next-intl";
+import { useRouter } from "next/navigation";
+
+export default function ServerDetailLoading() {
+ const router = useRouter();
+ const locale = useLocale();
+ 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"
+ >
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/[locale]/(main)/ClientComponents/ServerOverviewClient.tsx b/app/[locale]/(main)/ClientComponents/ServerOverviewClient.tsx
index a4ebb7e..4cd6c81 100644
--- a/app/[locale]/(main)/ClientComponents/ServerOverviewClient.tsx
+++ b/app/[locale]/(main)/ClientComponents/ServerOverviewClient.tsx
@@ -15,7 +15,7 @@ export default function ServerOverviewClient() {
const { data } = useSWR("/api/server", nezhaFetcher);
const disableCartoon = getEnv("NEXT_PUBLIC_DisableCartoon") === "true";
return (
-
+
@@ -94,9 +94,14 @@ export default function ServerOverviewClient() {
{t("p_3463-3530_Totalbandwidth")}
{data ? (
-
- {formatBytes(data?.total_bandwidth)}
-
+
+
+ ↑{formatBytes(data?.total_out_bandwidth)}
+
+
+ ↓{formatBytes(data?.total_in_bandwidth)}
+
+
) : (
diff --git a/app/[locale]/(main)/detail/[id]/page.tsx b/app/[locale]/(main)/detail/[id]/page.tsx
new file mode 100644
index 0000000..de69a5d
--- /dev/null
+++ b/app/[locale]/(main)/detail/[id]/page.tsx
@@ -0,0 +1,13 @@
+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 (
+
+
+
+
+
+ );
+}
diff --git a/app/[locale]/(main)/[id]/page.tsx b/app/[locale]/(main)/network/[id]/page.tsx
similarity index 100%
rename from app/[locale]/(main)/[id]/page.tsx
rename to app/[locale]/(main)/network/[id]/page.tsx
diff --git a/app/[locale]/types/nezha-api.ts b/app/[locale]/types/nezha-api.ts
index ecfb986..899809a 100644
--- a/app/[locale]/types/nezha-api.ts
+++ b/app/[locale]/types/nezha-api.ts
@@ -1,7 +1,8 @@
export type ServerApi = {
live_servers: number;
offline_servers: number;
- total_bandwidth: number;
+ total_out_bandwidth: number;
+ total_in_bandwidth: number;
result: NezhaAPISafe[];
};
diff --git a/app/api/detail/route.ts b/app/api/detail/route.ts
new file mode 100644
index 0000000..b0f8bc1
--- /dev/null
+++ b/app/api/detail/route.ts
@@ -0,0 +1,29 @@
+import { NezhaAPISafe } from "@/app/[locale]/types/nezha-api";
+import { GetServerDetail } from "@/lib/serverFetch";
+import { NextResponse } from "next/server";
+
+export const dynamic = "force-dynamic";
+
+interface NezhaDataResponse {
+ error?: string;
+ data?: NezhaAPISafe;
+}
+
+export async function GET(req: Request) {
+ const { searchParams } = new URL(req.url);
+ const server_id = searchParams.get("server_id");
+ if (!server_id) {
+ return NextResponse.json(
+ { error: "server_id is required" },
+ { status: 400 },
+ );
+ }
+ const response = (await GetServerDetail({
+ server_id: parseInt(server_id),
+ })) as NezhaDataResponse;
+ if (response.error) {
+ console.log(response.error);
+ return NextResponse.json({ error: response.error }, { status: 400 });
+ }
+ return NextResponse.json(response, { status: 200 });
+}
diff --git a/bun.lockb b/bun.lockb
index 1ab2b3f..35a64c1 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/bunfig.toml b/bunfig.toml
new file mode 100644
index 0000000..6517a5c
--- /dev/null
+++ b/bunfig.toml
@@ -0,0 +1,2 @@
+[install]
+registry = "https://registry.npmmirror.com/"
diff --git a/components/ServerCard.tsx b/components/ServerCard.tsx
index a1a6ec7..0e15ebf 100644
--- a/components/ServerCard.tsx
+++ b/components/ServerCard.tsx
@@ -36,38 +36,34 @@ export default function ServerCard({
"flex flex-col items-center justify-start gap-3 p-3 md:px-5 lg:flex-row"
}
>
-
-
-
-
-
- {showFlag ? : null}
-
-
- {name}
-
-
-
-
-
-
-
+
{
+ router.push(`/${locale}/detail/${id}`);
+ }}
+ >
+
+
+ {showFlag ? : null}
+
+
+ {name}
+
+
{
- router.push(`/${locale}/${id}`);
+ router.push(`/${locale}/network/${id}`);
}}
className="flex flex-col gap-2 cursor-pointer"
>
@@ -109,7 +105,7 @@ export default function ServerCard({
{showNetTransfer && (
{
- router.push(`/${locale}/${id}`);
+ router.push(`/${locale}/network/${id}`);
}}
className={"flex items-center justify-between gap-1"}
>
diff --git a/components/ui/animated-circular-progress-bar.tsx b/components/ui/animated-circular-progress-bar.tsx
new file mode 100644
index 0000000..dd96fe8
--- /dev/null
+++ b/components/ui/animated-circular-progress-bar.tsx
@@ -0,0 +1,107 @@
+import { cn } from "@/lib/utils";
+
+interface Props {
+ max: number;
+ value: number;
+ min: number;
+ className?: string;
+ primaryColor?: string;
+}
+
+export default function AnimatedCircularProgressBar({
+ max = 100,
+ min = 0,
+ value = 0,
+ primaryColor,
+ className,
+}: Props) {
+ const circumference = 2 * Math.PI * 45;
+ const percentPx = circumference / 100;
+ const currentPercent = ((value - min) / (max - min)) * 100;
+
+ return (
+
+
+
+ {currentPercent}
+
+
+ );
+}
diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx
index 2cba296..7c304ec 100644
--- a/components/ui/badge.tsx
+++ b/components/ui/badge.tsx
@@ -3,7 +3,7 @@ import { type VariantProps, cva } from "class-variance-authority";
import * as React from "react";
const badgeVariants = cva(
- "inline-flex items-center text-nowarp rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+ "inline-flex items-center text-nowarp rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors pointer-events-none focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
diff --git a/lib/serverFetch.tsx b/lib/serverFetch.tsx
index 5f3812c..480bd5a 100644
--- a/lib/serverFetch.tsx
+++ b/lib/serverFetch.tsx
@@ -36,7 +36,8 @@ export async function GetNezhaData() {
const data: ServerApi = {
live_servers: 0,
offline_servers: 0,
- total_bandwidth: 0,
+ total_out_bandwidth: 0,
+ total_in_bandwidth: 0,
result: [],
};
@@ -61,7 +62,8 @@ export async function GetNezhaData() {
data.live_servers += 1;
element.online_status = true;
}
- data.total_bandwidth += element.status.NetOutTransfer;
+ data.total_out_bandwidth += element.status.NetOutTransfer;
+ data.total_in_bandwidth += element.status.NetInTransfer;
delete element.ipv4;
delete element.ipv6;
@@ -112,3 +114,55 @@ export async function GetServerMonitor({ server_id }: { server_id: number }) {
return error;
}
}
+
+export async function GetServerDetail({ server_id }: { server_id: number }) {
+ var nezhaBaseUrl = getEnv("NezhaBaseUrl");
+ if (!nezhaBaseUrl) {
+ console.log("NezhaBaseUrl is not set");
+ return { error: "NezhaBaseUrl is not set" };
+ }
+
+ // Remove trailing slash
+ if (nezhaBaseUrl[nezhaBaseUrl.length - 1] === "/") {
+ nezhaBaseUrl = nezhaBaseUrl.slice(0, -1);
+ }
+
+ try {
+ const response = await fetch(
+ nezhaBaseUrl + `/api/v1/server/details?id=${server_id}`,
+ {
+ headers: {
+ Authorization: getEnv("NezhaAuth") as string,
+ },
+ next: {
+ revalidate: 0,
+ },
+ },
+ );
+ const resData = await response.json();
+ const detailDataList = resData.result;
+ if (!detailDataList) {
+ console.log(resData);
+ return { error: "MonitorData fetch failed" };
+ }
+
+ const timestamp = Date.now() / 1000;
+ const detailData = detailDataList.map(
+ (element: MakeOptional) => {
+ if (timestamp - element.last_active > 300) {
+ element.online_status = false;
+ } else {
+ element.online_status = true;
+ }
+ delete element.ipv4;
+ delete element.ipv6;
+ delete element.valid_ip;
+ return element;
+ },
+ )[0];
+
+ return detailData;
+ } catch (error) {
+ return error;
+ }
+}
diff --git a/lib/utils.ts b/lib/utils.ts
index a65328d..5dc9143 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -10,10 +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,
};
diff --git a/messages/en.json b/messages/en.json
index 3d09ae7..56e6d7d 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -35,6 +35,29 @@
"NetworkChart": {
"ServerMonitorCount": "Services"
},
+ "ServerDetailClient": {
+ "detail_fetch_error_message": "Please check your environment variables and review the server console",
+ "status": "Status",
+ "Online": "Online",
+ "Offline": "Offline",
+ "Uptime": "Uptime",
+ "Days": "Days",
+ "Version": "Version",
+ "Arch": "Arch",
+ "Mem": "Mem",
+ "Disk": "Disk",
+ "System": "System",
+ "CPU": "CPU"
+ },
+ "ServerDetailChartClient": {
+ "chart_fetch_error_message": "Please check your environment variables and review the server console",
+ "Process": "Process",
+ "Disk": "Disk",
+ "Mem": "Mem",
+ "Swap": "Swap",
+ "Upload": "Upload",
+ "Download": "Download"
+ },
"ThemeSwitcher": {
"Light": "Light",
"Dark": "Dark",
diff --git a/messages/ja.json b/messages/ja.json
index e308748..3f24abc 100644
--- a/messages/ja.json
+++ b/messages/ja.json
@@ -35,6 +35,29 @@
"NetworkChart": {
"ServerMonitorCount": "サービス"
},
+ "ServerDetailClient": {
+ "detail_fetch_error_message": "環境変数を確認し、サーバーコンソールを確認してください",
+ "status": "ステータス",
+ "Online": "オンライン",
+ "Offline": "オフライン",
+ "Uptime": "稼働時間",
+ "Days": "日",
+ "Version": "バージョン",
+ "Arch": "アーキテクチャ",
+ "Mem": "メモリ",
+ "Disk": "ディスク",
+ "System": "システム",
+ "CPU": "CPU"
+ },
+ "ServerDetailChartClient": {
+ "chart_fetch_error_message": "環境変数を確認し、サーバーコンソールを確認してください",
+ "Process": "進捗状況",
+ "Disk": "ディスク",
+ "Mem": "メモリ",
+ "Swap": "スワップ",
+ "Upload": "アップロード",
+ "Download": "ダウンロード"
+ },
"ThemeSwitcher": {
"Light": "ライト",
"Dark": "ダーク",
diff --git a/messages/zh-t.json b/messages/zh-t.json
index 9efe44c..50e43f6 100644
--- a/messages/zh-t.json
+++ b/messages/zh-t.json
@@ -35,6 +35,29 @@
"NetworkChart": {
"ServerMonitorCount": "個監測服務"
},
+ "ServerDetailClient": {
+ "detail_fetch_error_message": "獲取伺服器詳情失敗,請檢查您的環境變數並檢查伺服器控制台",
+ "status": "狀態",
+ "Online": "在線",
+ "Offline": "離線",
+ "Uptime": "稼働時間",
+ "Days": "天",
+ "Version": "版本",
+ "Arch": "架構",
+ "Mem": "記憶體",
+ "Disk": "磁碟",
+ "System": "系統",
+ "CPU": "CPU"
+ },
+ "ServerDetailChartClient": {
+ "chart_fetch_error_message": "獲取伺服器詳情失敗,請檢查您的環境變數並檢查伺服器控制台",
+ "Process": "進程",
+ "Disk": "磁碟",
+ "Mem": "記憶體",
+ "Swap": "虛擬記憶體",
+ "Upload": "上傳",
+ "Download": "下載"
+ },
"ThemeSwitcher": {
"Light": "亮色",
"Dark": "暗色",
diff --git a/messages/zh.json b/messages/zh.json
index c573923..2c770f5 100644
--- a/messages/zh.json
+++ b/messages/zh.json
@@ -35,6 +35,29 @@
"NetworkChart": {
"ServerMonitorCount": "个监控服务"
},
+ "ServerDetailClient": {
+ "detail_fetch_error_message": "获取服务器详情失败,请检查您的环境变量并检查服务器控制台",
+ "status": "状态",
+ "Online": "在线",
+ "Offline": "离线",
+ "Uptime": "运行时间",
+ "Days": "天",
+ "Version": "版本",
+ "Arch": "架构",
+ "Mem": "内存",
+ "Disk": "磁盘",
+ "System": "系统",
+ "CPU": "CPU"
+ },
+ "ServerDetailChartClient": {
+ "chart_fetch_error_message": "获取服务器详情失败,请检查您的环境变量并检查服务器控制台",
+ "Process": "进程",
+ "Disk": "磁盘",
+ "Mem": "内存",
+ "Swap": "虚拟内存",
+ "Upload": "上传",
+ "Download": "下载"
+ },
"ThemeSwitcher": {
"Light": "亮色",
"Dark": "暗色",
diff --git a/prettier.config.js b/prettier.config.js
index d9c7057..7d1ee54 100644
--- a/prettier.config.js
+++ b/prettier.config.js
@@ -3,6 +3,7 @@ module.exports = {
importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
importOrderSeparation: true,
importOrderSortSpecifiers: true,
+ endOfLine: "auto",
plugins: [
"prettier-plugin-tailwindcss",
"@trivago/prettier-plugin-sort-imports",