feat: detail overview i18n

This commit is contained in:
hamster1963 2024-11-24 21:44:12 +08:00
parent 8812cd1d3f
commit ee03928a56
9 changed files with 157 additions and 36 deletions

View File

@ -1,7 +1,9 @@
// src/components/Footer.tsx
import React from "react";
import { useTranslation } from "react-i18next";
const Footer: React.FC = () => {
const { t } = useTranslation();
return (
<footer className="mx-auto w-full max-w-5xl px-4 lg:px-0 pb-4">
<section className="flex flex-col">
@ -13,7 +15,7 @@ const Footer: React.FC = () => {
</a>
</p>
<p>
Theme by{" "}
{t("footer.themeBy")}
<a
href={"https://github.com/hamster1963/nezha-dash-react"}
target="_blank"

View File

@ -5,10 +5,11 @@ import { fetchLoginUser } from "@/lib/nezha-api";
import { useQuery } from "@tanstack/react-query";
import { DateTime } from "luxon";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { LanguageSwitcher } from "./LanguageSwitcher";
import { useTranslation } from "react-i18next";
function Header() {
const { t } = useTranslation();
return (
<div className="mx-auto w-full max-w-5xl">
<section className="flex items-center justify-between">
@ -28,7 +29,7 @@ function Header() {
className="mx-2 hidden h-4 w-[1px] md:block"
/>
<p className="hidden text-sm font-medium opacity-40 md:block">
{t("nezha")}
</p>
</section>
<section className="flex items-center gap-2">
@ -98,7 +99,7 @@ function Overview() {
<section className={"mt-10 flex flex-col md:mt-16"}>
<p className="text-base font-semibold">👋 {t("overview")}</p>
<div className="flex items-center gap-1.5">
<p className="text-sm font-medium opacity-50">where the time is</p>
<p className="text-sm font-medium opacity-50">{t("whereTheTimeIs")}</p>
{mouted ? (
<p className="text-sm font-medium">{timeString}</p>
) : (

View File

@ -5,8 +5,10 @@ import { cn, formatNezhaInfo } from "@/lib/utils";
import { NezhaAPI } from "@/types/nezha-api";
import { Card } from "./ui/card";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
export default function ServerCard({ serverInfo }: { serverInfo: NezhaAPI }) {
const { t } = useTranslation();
const navigate = useNavigate();
const { name, country_code, online, cpu, up, down, mem, stg } =
formatNezhaInfo(serverInfo);
@ -55,21 +57,27 @@ export default function ServerCard({ serverInfo }: { serverInfo: NezhaAPI }) {
<ServerUsageBar value={cpu} />
</div>
<div className={"flex w-14 flex-col"}>
<p className="text-xs text-muted-foreground">{"MEM"}</p>
<p className="text-xs text-muted-foreground">
{t("serverCard.mem")}
</p>
<div className="flex items-center text-xs font-semibold">
{mem.toFixed(2)}%
</div>
<ServerUsageBar value={mem} />
</div>
<div className={"flex w-14 flex-col"}>
<p className="text-xs text-muted-foreground">{"STG"}</p>
<p className="text-xs text-muted-foreground">
{t("serverCard.stg")}
</p>
<div className="flex items-center text-xs font-semibold">
{stg.toFixed(2)}%
</div>
<ServerUsageBar value={stg} />
</div>
<div className={"flex w-14 flex-col"}>
<p className="text-xs text-muted-foreground">{"Upload"}</p>
<p className="text-xs text-muted-foreground">
{t("serverCard.upload")}
</p>
<div className="flex items-center text-xs font-semibold">
{up >= 1024
? `${(up / 1024).toFixed(2)}G/s`
@ -77,7 +85,9 @@ export default function ServerCard({ serverInfo }: { serverInfo: NezhaAPI }) {
</div>
</div>
<div className={"flex w-14 flex-col"}>
<p className="text-xs text-muted-foreground">{"Download"}</p>
<p className="text-xs text-muted-foreground">
{t("serverCard.download")}
</p>
<div className="flex items-center text-xs font-semibold">
{down >= 1024
? `${(down / 1024).toFixed(2)}G/s`

View File

@ -7,18 +7,18 @@ import { useWebSocketContext } from "@/hooks/use-websocket-context";
import { cn, formatBytes, formatNezhaInfo } from "@/lib/utils";
import { NezhaAPIResponse } from "@/types/nezha-api";
import { useNavigate, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
export default function ServerDetailOverview() {
const { t } = useTranslation();
const navigate = useNavigate();
const { id } = useParams();
const { lastMessage, readyState } = useWebSocketContext();
// 检查连接状态
if (readyState !== 1) {
return <ServerDetailLoading />;
}
// 解析消息
const nezhaWsData = lastMessage
? (JSON.parse(lastMessage.data) as NezhaAPIResponse)
: null;
@ -48,7 +48,9 @@ export default function ServerDetailOverview() {
<Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1">
<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("serverDetail.status")}
</p>
<Badge
className={cn(
"text-[9px] rounded-[6px] w-fit px-1 py-0 -mt-[0.3px] dark:text-white",
@ -58,7 +60,7 @@ export default function ServerDetailOverview() {
},
)}
>
{online ? "Online" : "Offline"}
{online ? t("serverDetail.online") : t("serverDetail.offline")}
</Badge>
</section>
</CardContent>
@ -66,7 +68,9 @@ export default function ServerDetailOverview() {
<Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1">
<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("serverDetail.uptime")}
</p>
<div className="text-xs">
{" "}
{online ? (uptime / 86400).toFixed(0) : "N/A"} {"Days"}{" "}
@ -77,23 +81,33 @@ export default function ServerDetailOverview() {
<Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">{"Version"}</p>
<div className="text-xs">{version || "Unknown"} </div>
<p className="text-xs text-muted-foreground">
{t("serverDetail.version")}
</p>
<div className="text-xs">
{version || t("serverDetail.unknown")}{" "}
</div>
</section>
</CardContent>
</Card>
<Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">{"Arch"}</p>
<div className="text-xs">{server.host.arch || "Unknown"} </div>
<p className="text-xs text-muted-foreground">
{t("serverDetail.arch")}
</p>
<div className="text-xs">
{server.host.arch || t("serverDetail.unknown")}{" "}
</div>
</section>
</CardContent>
</Card>
<Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1">
<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("serverDetail.mem")}
</p>
<div className="text-xs">
{formatBytes(server.host.mem_total)}
</div>
@ -103,7 +117,9 @@ export default function ServerDetailOverview() {
<Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1">
<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("serverDetail.disk")}
</p>
<div className="text-xs">
{formatBytes(server.host.disk_total)}
</div>
@ -113,10 +129,13 @@ export default function ServerDetailOverview() {
<Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">{"Region"}</p>
<p className="text-xs text-muted-foreground">
{t("serverDetail.region")}
</p>
<section className="flex items-start gap-1">
<div className="text-xs text-start">
{server.host.country_code?.toUpperCase() || "Unknown"}
{server.host.country_code?.toUpperCase() ||
t("serverDetail.unknown")}
</div>
{server.host.country_code && (
<ServerFlag
@ -133,15 +152,17 @@ export default function ServerDetailOverview() {
<Card className="rounded-[10px] bg-transparent border-none shadow-none">
<CardContent className="px-1.5 py-1">
<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("serverDetail.system")}
</p>
{server.host.platform ? (
<div className="text-xs">
{" "}
{server.host.platform || "Unknown"} -{" "}
{server.host.platform || t("serverDetail.unknown")} -{" "}
{server.host.platform_version}{" "}
</div>
) : (
<div className="text-xs">Unknown</div>
<div className="text-xs"> {t("serverDetail.unknown")}</div>
)}
</section>
</CardContent>
@ -153,7 +174,7 @@ export default function ServerDetailOverview() {
{server.host.cpu ? (
<div className="text-xs"> {server.host.cpu}</div>
) : (
<div className="text-xs">Unknown</div>
<div className="text-xs"> {t("serverDetail.unknown")}</div>
)}
</section>
</CardContent>

View File

@ -1,5 +1,6 @@
import { Card, CardContent } from "@/components/ui/card";
import { cn, formatBytes } from "@/lib/utils";
import { useTranslation } from "react-i18next";
type ServerOverviewProps = {
online: number;
@ -16,6 +17,8 @@ export default function ServerOverview({
up,
down,
}: ServerOverviewProps) {
const { t } = useTranslation();
return (
<>
<section className="grid grid-cols-2 gap-4 lg:grid-cols-4">
@ -23,7 +26,7 @@ export default function ServerOverview({
<CardContent className="px-6 py-3">
<section className="flex flex-col gap-1">
<p className="text-sm font-medium md:text-base">
{"Total servers"}
{t("serverOverview.totalServers")}
</p>
<div className="flex items-center gap-2">
<span className="relative flex h-2 w-2">
@ -42,7 +45,7 @@ export default function ServerOverview({
<CardContent className="px-6 py-3">
<section className="flex flex-col gap-1">
<p className="text-sm font-medium md:text-base">
{"Online servers"}
{t("serverOverview.onlineServers")}
</p>
<div className="flex items-center gap-2">
<span className="relative flex h-2 w-2">
@ -63,7 +66,7 @@ export default function ServerOverview({
<CardContent className="px-6 py-3">
<section className="flex flex-col gap-1">
<p className="text-sm font-medium md:text-base">
{"Offline servers"}
{t("serverOverview.offlineServers")}
</p>
<div className="flex items-center gap-2">
<span className="relative flex h-2 w-2">
@ -83,7 +86,7 @@ export default function ServerOverview({
<CardContent className="relative px-6 py-3">
<section className="flex flex-col gap-1">
<p className="text-sm font-medium md:text-base">
{"Total bandwidth"}
{t("serverOverview.totalBandwidth")}
</p>
<section className="flex flex-col sm:flex-row pt-[8px] sm:items-center items-start gap-1">

View File

@ -1,5 +1,35 @@
{
"nezha": "Nezha Monitoring",
"overview": "Overview",
"whereTheTimeIs": "Where the time is",
"serverOverview": {
"totalServers": "Total Servers",
"onlineServers": "Online Servers",
"offlineServers": "Offline Servers",
"totalBandwidth": "Total Bandwidth"
},
"serverCard": {
"mem": "MEM",
"stg": "STG",
"upload": "Upload",
"download": "Download"
},
"serverDetail": {
"status": "Status",
"online": "Online",
"offline": "Offline",
"unknown": "Unknown",
"uptime": "Uptime",
"version": "Version",
"arch": "Arch",
"mem": "Mem",
"disk": "Disk",
"region": "Region",
"system": "System"
},
"footer": {
"themeBy": "Theme by "
},
"language": {
"zh-CN": "简体中文",
"zh-TW": "繁體中文",

View File

@ -1,5 +1,35 @@
{
"nezha": "哪吒监控",
"overview": "概览",
"whereTheTimeIs": "当前时间",
"serverOverview": {
"totalServers": "服务器总数",
"onlineServers": "在线服务器",
"offlineServers": "离线服务器",
"totalBandwidth": "总流量"
},
"serverCard": {
"mem": "内存",
"stg": "存储",
"upload": "上传",
"download": "下载"
},
"serverDetail": {
"status": "状态",
"online": "在线",
"offline": "离线",
"unknown": "未知",
"uptime": "运行时间",
"version": "版本",
"arch": "架构",
"mem": "内存",
"disk": "磁盘",
"region": "区域",
"system": "系统"
},
"footer": {
"themeBy": "主题-"
},
"language": {
"zh-CN": "简体中文",
"zh-TW": "繁體中文",

View File

@ -1,5 +1,35 @@
{
"nezha": "哪吒監控",
"overview": "概覽",
"whereTheTimeIs": "目前時間",
"serverOverview": {
"totalServers": "總服務器",
"onlineServers": "線上服務器",
"offlineServers": "離線服務器",
"totalBandwidth": "總帶寬"
},
"serverCard": {
"mem": "內存",
"stg": "存儲",
"upload": "上傳",
"download": "下載"
},
"serverDetail": {
"status": "狀態",
"online": "線上",
"offline": "離線",
"unknown": "未知",
"uptime": "運行時間",
"version": "版本",
"arch": "架構",
"mem": "內存",
"disk": "磁盤",
"region": "地區",
"system": "系統"
},
"footer": {
"themeBy": "主題-"
},
"language": {
"zh-CN": "简体中文",
"zh-TW": "繁體中文",
@ -8,6 +38,6 @@
"theme": {
"light": "亮色",
"dark": "暗色",
"system": "跟系統"
"system": "跟系統"
}
}

View File

@ -17,10 +17,8 @@ export default function Servers() {
});
const { lastMessage, readyState } = useWebSocketContext();
// 添加分组状态
const [currentGroup, setCurrentGroup] = useState<string>("All");
// 获取所有分组名称
const groupTabs = [
"All",
...(groupData?.data?.map((item: ServerGroup) => item.group.name) || []),
@ -32,7 +30,6 @@ export default function Servers() {
}
}, [readyState]);
// 检查连接状态
if (readyState !== 1) {
return (
<div className="flex flex-col items-center justify-center ">
@ -41,7 +38,6 @@ export default function Servers() {
);
}
// 解析消息
const nezhaWsData = lastMessage
? (JSON.parse(lastMessage.data) as NezhaAPIResponse)
: null;
@ -54,7 +50,6 @@ export default function Servers() {
);
}
// 计算所有服务器的统计数据(用于 Overview
const totalServers = nezhaWsData?.servers?.length || 0;
const onlineServers =
nezhaWsData?.servers?.filter((server) => formatNezhaInfo(server).online)
@ -73,7 +68,6 @@ export default function Servers() {
0,
) || 0;
// 根据当前选中的分组筛选服务器(用于显示列表)
const filteredServers =
nezhaWsData?.servers?.filter((server) => {
if (currentGroup === "All") return true;