Compare commits

...

3 Commits

Author SHA1 Message Date
hamster1963
bc0886e8c0 v2.2.0 2025-01-12 22:58:42 +08:00
hamster1963
957c679a90 chore: deps 2025-01-12 22:57:31 +08:00
hamster1963
0dd8bf7bb7 feat: show last active 2025-01-12 22:56:37 +08:00
8 changed files with 93 additions and 44 deletions

View File

@ -6,7 +6,7 @@ import ServerFlag from "@/components/ServerFlag"
import { ServerDetailLoading } from "@/components/loading/ServerDetailLoading"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent } from "@/components/ui/card"
import { cn, formatBytes } from "@/lib/utils"
import { cn, formatBytes, formatNezhaInfo } from "@/lib/utils"
import countries from "i18n-iso-countries"
import enLocale from "i18n-iso-countries/langs/en.json"
import { useTranslations } from "next-intl"
@ -45,9 +45,9 @@ export default function ServerDetailClient({
}
const { data: serverList, error, isLoading } = useServerData()
const data = serverList?.result?.find((item) => item.id === server_id)
const serverData = serverList?.result?.find((item) => item.id === server_id)
if (!data && !isLoading) {
if (!serverData && !isLoading) {
notFound()
}
@ -62,7 +62,28 @@ export default function ServerDetailClient({
)
}
if (!data) return <ServerDetailLoading />
if (!serverData) return <ServerDetailLoading />
const {
name,
online,
uptime,
version,
arch,
mem_total,
disk_total,
country_code,
platform,
platform_version,
cpu_info,
gpu_info,
load_1,
load_5,
load_15,
net_out_transfer,
net_in_transfer,
last_active_time_string,
} = formatNezhaInfo(serverData)
return (
<div>
@ -71,7 +92,7 @@ export default function ServerDetailClient({
className="flex flex-none cursor-pointer font-semibold leading-none items-center break-all tracking-tight gap-0.5 text-xl hover:opacity-50 transition-opacity duration-300"
>
<BackIcon />
{data?.name}
{name}
</div>
<section className="flex flex-wrap gap-2 mt-3">
<Card className="rounded-[10px] bg-transparent border-none shadow-none">
@ -82,12 +103,12 @@ export default function ServerDetailClient({
className={cn(
"text-[9px] rounded-[6px] w-fit px-1 py-0 -mt-[0.3px] dark:text-white",
{
" bg-green-800": data?.online_status,
" bg-red-600": !data?.online_status,
" bg-green-800": online,
" bg-red-600": !online,
},
)}
>
{data?.online_status ? t("Online") : t("Offline")}
{online ? t("Online") : t("Offline")}
</Badge>
</section>
</CardContent>
@ -98,29 +119,29 @@ export default function ServerDetailClient({
<p className="text-xs text-muted-foreground">{t("Uptime")}</p>
<div className="text-xs">
{" "}
{data?.status.Uptime / 86400 >= 1
? (data?.status.Uptime / 86400).toFixed(0) + " " + t("Days")
: (data?.status.Uptime / 3600).toFixed(0) + " " + t("Hours")}{" "}
{uptime / 86400 >= 1
? (uptime / 86400).toFixed(0) + " " + t("Days")
: (uptime / 3600).toFixed(0) + " " + t("Hours")}{" "}
</div>
</section>
</CardContent>
</Card>
{data?.host.Version && (
{version && (
<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">{t("Version")}</p>
<div className="text-xs">{data?.host.Version} </div>
<div className="text-xs">{version} </div>
</section>
</CardContent>
</Card>
)}
{data?.host.Arch && (
{arch && (
<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">{t("Arch")}</p>
<div className="text-xs">{data?.host.Arch} </div>
<div className="text-xs">{arch} </div>
</section>
</CardContent>
</Card>
@ -130,7 +151,7 @@ export default function ServerDetailClient({
<CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">{t("Mem")}</p>
<div className="text-xs">{formatBytes(data?.host.MemTotal)}</div>
<div className="text-xs">{formatBytes(mem_total)}</div>
</section>
</CardContent>
</Card>
@ -138,23 +159,18 @@ export default function ServerDetailClient({
<CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">{t("Disk")}</p>
<div className="text-xs">{formatBytes(data?.host.DiskTotal)}</div>
<div className="text-xs">{formatBytes(disk_total)}</div>
</section>
</CardContent>
</Card>
{data?.host.CountryCode && (
{country_code && (
<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">{t("Region")}</p>
<section className="flex items-start gap-1">
<div className="text-xs text-start">
{countries.getName(data?.host.CountryCode, "en")}
</div>
<ServerFlag
className="text-[11px] -mt-[1px]"
country_code={data?.host.CountryCode}
/>
<div className="text-xs text-start">{countries.getName(country_code, "en")}</div>
<ServerFlag className="text-[11px] -mt-[1px]" country_code={country_code} />
</section>
</section>
</CardContent>
@ -162,7 +178,7 @@ export default function ServerDetailClient({
)}
</section>
<section className="flex flex-wrap gap-2 mt-1">
{data?.host.Platform && (
{platform && (
<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">
@ -170,29 +186,29 @@ export default function ServerDetailClient({
<div className="text-xs">
{" "}
{data?.host.Platform} - {data?.host.PlatformVersion}{" "}
{platform} - {platform_version}{" "}
</div>
</section>
</CardContent>
</Card>
)}
{data?.host.CPU && (
{cpu_info && cpu_info.length > 0 && (
<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">{t("CPU")}</p>
<div className="text-xs"> {data?.host.CPU.join(", ")}</div>
<div className="text-xs"> {cpu_info.join(", ")}</div>
</section>
</CardContent>
</Card>
)}
{data?.host.GPU && (
{gpu_info && gpu_info.length > 0 && (
<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">{"GPU"}</p>
<div className="text-xs"> {data?.host.GPU.join(", ")}</div>
<div className="text-xs"> {gpu_info.join(", ")}</div>
</section>
</CardContent>
</Card>
@ -204,8 +220,7 @@ export default function ServerDetailClient({
<section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">{t("Load")}</p>
<div className="text-xs">
{data.status.Load1.toFixed(2) || "0.00"} / {data.status.Load5.toFixed(2) || "0.00"}{" "}
/ {data.status.Load15.toFixed(2) || "0.00"}
{load_1 || "0.00"} / {load_5 || "0.00"} / {load_15 || "0.00"}
</div>
</section>
</CardContent>
@ -214,8 +229,8 @@ export default function ServerDetailClient({
<CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">{t("Upload")}</p>
{data.status.NetOutTransfer ? (
<div className="text-xs"> {formatBytes(data.status.NetOutTransfer)} </div>
{net_out_transfer ? (
<div className="text-xs"> {formatBytes(net_out_transfer)} </div>
) : (
<div className="text-xs">Unknown</div>
)}
@ -226,8 +241,8 @@ export default function ServerDetailClient({
<CardContent className="px-1.5 py-1">
<section className="flex flex-col items-start gap-0.5">
<p className="text-xs text-muted-foreground">{t("Download")}</p>
{data.status.NetInTransfer ? (
<div className="text-xs"> {formatBytes(data.status.NetInTransfer)} </div>
{net_in_transfer ? (
<div className="text-xs"> {formatBytes(net_in_transfer)} </div>
) : (
<div className="text-xs">Unknown</div>
)}
@ -235,6 +250,18 @@ export default function ServerDetailClient({
</CardContent>
</Card>
</section>
<section className="flex flex-wrap gap-2 mt-1">
<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">{t("LastActive")}</p>
<div className="text-xs">
{last_active_time_string ? last_active_time_string : "N/A"}
</div>
</section>
</CardContent>
</Card>
</section>
</div>
)
}

BIN
bun.lockb

Binary file not shown.

View File

@ -13,14 +13,32 @@ export function formatNezhaInfo(serverInfo: NezhaAPISafe) {
process: serverInfo.status.ProcessCount || 0,
up: serverInfo.status.NetOutSpeed / 1024 / 1024 || 0,
down: serverInfo.status.NetInSpeed / 1024 / 1024 || 0,
last_active_time_string: serverInfo.last_active
? new Date(serverInfo.last_active * 1000).toLocaleString()
: "",
online: serverInfo.online_status,
uptime: serverInfo.status.Uptime || 0,
version: serverInfo.host.Version || null,
tcp: serverInfo.status.TcpConnCount || 0,
udp: serverInfo.status.UdpConnCount || 0,
arch: serverInfo.host.Arch || "",
mem_total: serverInfo.host.MemTotal || 0,
swap_total: serverInfo.host.SwapTotal || 0,
disk_total: serverInfo.host.DiskTotal || 0,
platform: serverInfo.host.Platform || "",
platform_version: serverInfo.host.PlatformVersion || "",
mem: (serverInfo.status.MemUsed / serverInfo.host.MemTotal) * 100 || 0,
swap: (serverInfo.status.SwapUsed / serverInfo.host.SwapTotal) * 100 || 0,
disk: (serverInfo.status.DiskUsed / serverInfo.host.DiskTotal) * 100 || 0,
stg: (serverInfo.status.DiskUsed / serverInfo.host.DiskTotal) * 100 || 0,
net_out_transfer: serverInfo.status.NetOutTransfer || 0,
net_in_transfer: serverInfo.status.NetInTransfer || 0,
country_code: serverInfo.host.CountryCode,
cpu_info: serverInfo.host.CPU || [],
gpu_info: serverInfo.host.GPU || [],
load_1: serverInfo.status.Load1?.toFixed(2) || 0.0,
load_5: serverInfo.status.Load5?.toFixed(2) || 0.0,
load_15: serverInfo.status.Load15?.toFixed(2) || 0.0,
}
}

View File

@ -85,7 +85,8 @@
"CPU": "CPU",
"Upload": "Upload",
"Download": "Download",
"Load": "Load"
"Load": "Load",
"LastActive": "Last Active"
},
"ServerDetailChartClient": {
"chart_fetch_error_message": "Please check your environment variables and review the server console",

View File

@ -85,7 +85,8 @@
"CPU": "CPU",
"Load": "負荷",
"Upload": "Upload",
"Download": "Download"
"Download": "Download",
"LastActive": "Last Active"
},
"ServerDetailChartClient": {
"chart_fetch_error_message": "環境変数を確認し、サーバーコンソールを確認してください",

View File

@ -85,7 +85,8 @@
"CPU": "CPU",
"Upload": "上傳",
"Download": "下載",
"Load": "負載"
"Load": "負載",
"LastActive": "最後上報時間"
},
"ServerDetailChartClient": {
"chart_fetch_error_message": "獲取伺服器詳情失敗,請檢查您的環境變數並檢查伺服器控制台",

View File

@ -85,7 +85,8 @@
"CPU": "CPU",
"Upload": "上传",
"Download": "下载",
"Load": "负载"
"Load": "负载",
"LastActive": "最后上报时间"
},
"ServerDetailChartClient": {
"chart_fetch_error_message": "获取服务器详情失败,请检查您的环境变量并检查服务器控制台",

View File

@ -1,6 +1,6 @@
{
"name": "nezha-dash",
"version": "2.1.3",
"version": "2.2.0",
"private": true,
"scripts": {
"dev": "next dev -p 3040",
@ -66,8 +66,8 @@
"@next/bundle-analyzer": "^15.1.4",
"@tailwindcss/postcss": "^4.0.0-beta.9",
"@types/node": "^22.10.5",
"@types/react": "^19.0.4",
"@types/react-dom": "^19.0.2",
"@types/react": "^19.0.5",
"@types/react-dom": "^19.0.3",
"eslint-config-next": "^15.1.4",
"eslint-plugin-turbo": "^2.3.3",
"eslint-plugin-unused-imports": "^4.1.4",