feat: detail i18n

This commit is contained in:
hamster1963 2024-10-19 19:27:59 +08:00
parent 6be6e30fd7
commit 5f1d84992c
8 changed files with 168 additions and 37 deletions

View File

@ -2,19 +2,10 @@
import AnimatedCircularProgressBar from "@/components/ui/animated-circular-progress-bar"; import AnimatedCircularProgressBar from "@/components/ui/animated-circular-progress-bar";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { import { ChartConfig, ChartContainer } from "@/components/ui/chart";
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import getEnv from "@/lib/env-entry"; import getEnv from "@/lib/env-entry";
import { import { formatNezhaInfo, formatRelativeTime, nezhaFetcher } from "@/lib/utils";
formatNezhaInfo, import { useTranslations } from "next-intl";
formatRelativeTime,
formatTime,
nezhaFetcher,
} from "@/lib/utils";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { import {
Area, Area,
@ -67,6 +58,8 @@ export default function ServerDetailChartClient({
}: { }: {
server_id: number; server_id: number;
}) { }) {
const t = useTranslations("ServerDetailChartClient");
const { data, error } = useSWR<NezhaAPISafe>( const { data, error } = useSWR<NezhaAPISafe>(
`/api/detail?server_id=${server_id}`, `/api/detail?server_id=${server_id}`,
nezhaFetcher, nezhaFetcher,
@ -81,8 +74,7 @@ export default function ServerDetailChartClient({
<div className="flex flex-col items-center justify-center"> <div className="flex flex-col items-center justify-center">
<p className="text-sm font-medium opacity-40">{error.message}</p> <p className="text-sm font-medium opacity-40">{error.message}</p>
<p className="text-sm font-medium opacity-40"> <p className="text-sm font-medium opacity-40">
{/* {t("chart_fetch_error_message")} */} {t("chart_fetch_error_message")}
fetch_error_message
</p> </p>
</div> </div>
</> </>
@ -191,6 +183,8 @@ function CpuChart({ data }: { data: NezhaAPISafe }) {
} }
function ProcessChart({ data }: { data: NezhaAPISafe }) { function ProcessChart({ data }: { data: NezhaAPISafe }) {
const t = useTranslations("ServerDetailChartClient");
const [processChartData, setProcessChartData] = useState( const [processChartData, setProcessChartData] = useState(
[] as processChartData[], [] as processChartData[],
); );
@ -222,7 +216,7 @@ function ProcessChart({ data }: { data: NezhaAPISafe }) {
<CardContent className="px-6 py-3"> <CardContent className="px-6 py-3">
<section className="flex flex-col gap-1"> <section className="flex flex-col gap-1">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<p className="text-md font-medium">Process</p> <p className="text-md font-medium">{t("Process")}</p>
<section className="flex items-center gap-2"> <section className="flex items-center gap-2">
<p className="text-xs text-end w-10 font-medium">{process}</p> <p className="text-xs text-end w-10 font-medium">{process}</p>
</section> </section>
@ -273,6 +267,8 @@ function ProcessChart({ data }: { data: NezhaAPISafe }) {
} }
function MemChart({ data }: { data: NezhaAPISafe }) { function MemChart({ data }: { data: NezhaAPISafe }) {
const t = useTranslations("ServerDetailChartClient");
const [memChartData, setMemChartData] = useState([] as memChartData[]); const [memChartData, setMemChartData] = useState([] as memChartData[]);
const { mem, swap } = formatNezhaInfo(data); const { mem, swap } = formatNezhaInfo(data);
@ -307,7 +303,7 @@ function MemChart({ data }: { data: NezhaAPISafe }) {
<div className="flex items-center"> <div className="flex items-center">
<section className="flex items-center gap-4"> <section className="flex items-center gap-4">
<div className="flex flex-col"> <div className="flex flex-col">
<p className=" text-xs text-muted-foreground">Mem</p> <p className=" text-xs text-muted-foreground">{t("Mem")}</p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<AnimatedCircularProgressBar <AnimatedCircularProgressBar
className="size-3 text-[0px]" className="size-3 text-[0px]"
@ -320,7 +316,7 @@ function MemChart({ data }: { data: NezhaAPISafe }) {
</div> </div>
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
<p className=" text-xs text-muted-foreground">Swap</p> <p className=" text-xs text-muted-foreground">{t("Swap")}</p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<AnimatedCircularProgressBar <AnimatedCircularProgressBar
className="size-3 text-[0px]" className="size-3 text-[0px]"
@ -390,6 +386,8 @@ function MemChart({ data }: { data: NezhaAPISafe }) {
} }
function DiskChart({ data }: { data: NezhaAPISafe }) { function DiskChart({ data }: { data: NezhaAPISafe }) {
const t = useTranslations("ServerDetailChartClient");
const [diskChartData, setDiskChartData] = useState([] as diskChartData[]); const [diskChartData, setDiskChartData] = useState([] as diskChartData[]);
const { disk } = formatNezhaInfo(data); const { disk } = formatNezhaInfo(data);
@ -412,11 +410,11 @@ function DiskChart({ data }: { data: NezhaAPISafe }) {
} satisfies ChartConfig; } satisfies ChartConfig;
return ( return (
<Card className=" rounded-sm"> <Card className="rounded-sm">
<CardContent className="px-6 py-3"> <CardContent className="px-6 py-3">
<section className="flex flex-col gap-1"> <section className="flex flex-col gap-1">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<p className="text-md font-medium">Disk</p> <p className="text-md font-medium">{t("Disk")}</p>
<section className="flex items-center gap-2"> <section className="flex items-center gap-2">
<p className="text-xs text-end w-10 font-medium"> <p className="text-xs text-end w-10 font-medium">
{disk.toFixed(0)}% {disk.toFixed(0)}%
@ -478,6 +476,8 @@ function DiskChart({ data }: { data: NezhaAPISafe }) {
} }
function NetworkChart({ data }: { data: NezhaAPISafe }) { function NetworkChart({ data }: { data: NezhaAPISafe }) {
const t = useTranslations("ServerDetailChartClient");
const [networkChartData, setNetworkChartData] = useState( const [networkChartData, setNetworkChartData] = useState(
[] as networkChartData[], [] as networkChartData[],
); );
@ -520,14 +520,16 @@ function NetworkChart({ data }: { data: NezhaAPISafe }) {
<div className="flex items-center"> <div className="flex items-center">
<section className="flex items-center gap-4"> <section className="flex items-center gap-4">
<div className="flex flex-col w-20"> <div className="flex flex-col w-20">
<p className="text-xs text-muted-foreground">Upload</p> <p className="text-xs text-muted-foreground">{t("Upload")}</p>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<span className="relative inline-flex size-1.5 rounded-full bg-[hsl(var(--chart-1))]"></span> <span className="relative inline-flex size-1.5 rounded-full bg-[hsl(var(--chart-1))]"></span>
<p className="text-xs font-medium">{up.toFixed(2)} M/s</p> <p className="text-xs font-medium">{up.toFixed(2)} M/s</p>
</div> </div>
</div> </div>
<div className="flex flex-col w-20"> <div className="flex flex-col w-20">
<p className=" text-xs text-muted-foreground">Download</p> <p className=" text-xs text-muted-foreground">
{t("Download")}
</p>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<span className="relative inline-flex size-1.5 rounded-full bg-[hsl(var(--chart-4))]"></span> <span className="relative inline-flex size-1.5 rounded-full bg-[hsl(var(--chart-4))]"></span>
<p className="text-xs font-medium">{down.toFixed(2)} M/s</p> <p className="text-xs font-medium">{down.toFixed(2)} M/s</p>
@ -624,7 +626,7 @@ function ConnectChart({ data }: { data: NezhaAPISafe }) {
} satisfies ChartConfig; } satisfies ChartConfig;
return ( return (
<Card className=" rounded-sm"> <Card className="rounded-sm">
<CardContent className="px-6 py-3"> <CardContent className="px-6 py-3">
<section className="flex flex-col gap-1"> <section className="flex flex-col gap-1">
<div className="flex items-center"> <div className="flex items-center">

View File

@ -6,15 +6,18 @@ import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import getEnv from "@/lib/env-entry"; import getEnv from "@/lib/env-entry";
import { cn, formatBytes, nezhaFetcher } from "@/lib/utils"; import { cn, formatBytes, nezhaFetcher } from "@/lib/utils";
import { useLocale } from "next-intl"; import { useLocale, useTranslations } from "next-intl";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
import ServerDetailLoading from "./ServerDetailLoading";
export default function ServerDetailClient({ export default function ServerDetailClient({
server_id, server_id,
}: { }: {
server_id: number; server_id: number;
}) { }) {
const t = useTranslations("ServerDetailClient");
const router = useRouter(); const router = useRouter();
const locale = useLocale(); const locale = useLocale();
const { data, error } = useSWR<NezhaAPISafe>( const { data, error } = useSWR<NezhaAPISafe>(
@ -24,20 +27,22 @@ export default function ServerDetailClient({
refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 5000, refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 5000,
}, },
); );
if (error) { if (error) {
return ( return (
<> <>
<div className="flex flex-col items-center justify-center"> <div className="flex flex-col items-center justify-center">
<p className="text-sm font-medium opacity-40">{error.message}</p> <p className="text-sm font-medium opacity-40">{error.message}</p>
<p className="text-sm font-medium opacity-40"> <p className="text-sm font-medium opacity-40">
{/* {t("chart_fetch_error_message")} */} {t("detail_fetch_error_message")}
fetch_error_message
</p> </p>
</div> </div>
</> </>
); );
} }
if (!data) return null;
if (!data) return <ServerDetailLoading />;
return ( return (
<div> <div>
<div <div
@ -53,7 +58,7 @@ export default function ServerDetailClient({
<Card className="rounded-[10px] bg-transparent border-none shadow-none"> <Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1"> <CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5"> <section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">Status</p> <p className="text-xs text-muted-foreground">{t("status")}</p>
<Badge <Badge
className={cn( className={cn(
"text-[10px] rounded-[6px] w-fit px-1 py-0 dark:text-white", "text-[10px] rounded-[6px] w-fit px-1 py-0 dark:text-white",
@ -63,7 +68,7 @@ export default function ServerDetailClient({
}, },
)} )}
> >
{data?.online_status ? "Online" : "Offline"} {data?.online_status ? t("Online") : t("Offline")}
</Badge> </Badge>
</section> </section>
</CardContent> </CardContent>
@ -71,10 +76,10 @@ export default function ServerDetailClient({
<Card className="rounded-[10px] bg-transparent border-none shadow-none"> <Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1"> <CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5"> <section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">Uptime</p> <p className="text-xs text-muted-foreground">{t("Uptime")}</p>
<div className="text-xs"> <div className="text-xs">
{" "} {" "}
{(data?.status.Uptime / 86400).toFixed(0)} Days{" "} {(data?.status.Uptime / 86400).toFixed(0)} {t("Days")}{" "}
</div> </div>
</section> </section>
</CardContent> </CardContent>
@ -82,7 +87,7 @@ export default function ServerDetailClient({
<Card className="rounded-[10px] bg-transparent border-none shadow-none"> <Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1"> <CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5"> <section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">Version</p> <p className="text-xs text-muted-foreground">{t("Version")}</p>
<div className="text-xs">{data?.host.Version || "Unknown"} </div> <div className="text-xs">{data?.host.Version || "Unknown"} </div>
</section> </section>
</CardContent> </CardContent>
@ -90,7 +95,7 @@ export default function ServerDetailClient({
<Card className="rounded-[10px] bg-transparent border-none shadow-none"> <Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1"> <CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5"> <section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">Arch</p> <p className="text-xs text-muted-foreground">{t("Arch")}</p>
<div className="text-xs">{data?.host.Arch || "Unknown"} </div> <div className="text-xs">{data?.host.Arch || "Unknown"} </div>
</section> </section>
</CardContent> </CardContent>
@ -98,7 +103,7 @@ export default function ServerDetailClient({
<Card className="rounded-[10px] bg-transparent border-none shadow-none"> <Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1"> <CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5"> <section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">Mem</p> <p className="text-xs text-muted-foreground">{t("Mem")}</p>
<div className="text-xs">{formatBytes(data?.host.MemTotal)}</div> <div className="text-xs">{formatBytes(data?.host.MemTotal)}</div>
</section> </section>
</CardContent> </CardContent>
@ -106,7 +111,7 @@ export default function ServerDetailClient({
<Card className="rounded-[10px] bg-transparent border-none shadow-none"> <Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1"> <CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5"> <section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">Disk</p> <p className="text-xs text-muted-foreground">{t("Disk")}</p>
<div className="text-xs">{formatBytes(data?.host.DiskTotal)}</div> <div className="text-xs">{formatBytes(data?.host.DiskTotal)}</div>
</section> </section>
</CardContent> </CardContent>
@ -116,7 +121,7 @@ export default function ServerDetailClient({
<Card className="rounded-[10px] bg-transparent border-none shadow-none"> <Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1"> <CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5"> <section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">System</p> <p className="text-xs text-muted-foreground">{t("System")}</p>
{data?.host.Platform ? ( {data?.host.Platform ? (
<div className="text-xs"> <div className="text-xs">
{" "} {" "}
@ -132,7 +137,7 @@ export default function ServerDetailClient({
<Card className="rounded-[10px] bg-transparent border-none shadow-none"> <Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1"> <CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5"> <section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">CPU</p> <p className="text-xs text-muted-foreground">{t("CPU")}</p>
{data?.host.CPU ? ( {data?.host.CPU ? (
<div className="text-xs"> {data?.host.CPU}</div> <div className="text-xs"> {data?.host.CPU}</div>
) : ( ) : (

View File

@ -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 (
<div>
<div
onClick={() => {
router.push(`/${locale}/`);
}}
className="flex flex-none cursor-pointer font-semibold leading-none items-center break-all tracking-tight gap-0.5 text-xl"
>
<BackIcon />
<Skeleton className="h-[20px] w-24 rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
</div>
<Skeleton className="flex flex-wrap gap-2 h-[100px] w-1/2 mt-3 rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
<Separator className="my-4" />
<section className="grid md:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-3">
<Skeleton className="h-[182px] w-full rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
<Skeleton className="h-[182px] w-full rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
<Skeleton className="h-[182px] w-full rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
<Skeleton className="h-[182px] w-full rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
<Skeleton className="h-[182px] w-full rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
<Skeleton className="h-[182px] w-full rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
</section>
</div>
);
}

View File

@ -60,7 +60,6 @@ export default function ServerCard({
> >
{name} {name}
</p> </p>
</section> </section>
<div <div
onClick={() => { onClick={() => {

View File

@ -35,6 +35,29 @@
"NetworkChart": { "NetworkChart": {
"ServerMonitorCount": "Services" "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": { "ThemeSwitcher": {
"Light": "Light", "Light": "Light",
"Dark": "Dark", "Dark": "Dark",

View File

@ -35,6 +35,29 @@
"NetworkChart": { "NetworkChart": {
"ServerMonitorCount": "サービス" "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": { "ThemeSwitcher": {
"Light": "ライト", "Light": "ライト",
"Dark": "ダーク", "Dark": "ダーク",

View File

@ -35,6 +35,29 @@
"NetworkChart": { "NetworkChart": {
"ServerMonitorCount": "個監測服務" "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": { "ThemeSwitcher": {
"Light": "亮色", "Light": "亮色",
"Dark": "暗色", "Dark": "暗色",

View File

@ -35,6 +35,29 @@
"NetworkChart": { "NetworkChart": {
"ServerMonitorCount": "个监控服务" "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": { "ThemeSwitcher": {
"Light": "亮色", "Light": "亮色",
"Dark": "暗色", "Dark": "暗色",