mirror of
https://github.com/hamster1963/nezha-dash.git
synced 2025-04-24 21:10:45 +08:00
feat: detail info
This commit is contained in:
parent
dbe67ab205
commit
b83219bbf2
@ -4,10 +4,10 @@ import AnimatedCircularProgressBar from "@/components/ui/animated-circular-progr
|
|||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { NezhaAPISafe } from "../../types/nezha-api";
|
import { NezhaAPISafe } from "../../types/nezha-api";
|
||||||
import { formatRelativeTime, formatTime, nezhaFetcher } from "@/lib/utils";
|
import { formatNezhaInfo, formatRelativeTime, formatTime, nezhaFetcher } from "@/lib/utils";
|
||||||
import getEnv from "@/lib/env-entry";
|
import getEnv from "@/lib/env-entry";
|
||||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
||||||
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
|
import { Area, AreaChart, CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
@ -16,6 +16,18 @@ type cpuChartData = {
|
|||||||
cpu: number;
|
cpu: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type memChartData = {
|
||||||
|
timeStamp: string;
|
||||||
|
mem: number;
|
||||||
|
swap: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type networkChartData = {
|
||||||
|
timeStamp: string;
|
||||||
|
upload: number;
|
||||||
|
download: number;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ServerDetailChartClient({
|
export default function ServerDetailChartClient({
|
||||||
server_id,
|
server_id,
|
||||||
}: {
|
}: {
|
||||||
@ -29,22 +41,6 @@ export default function ServerDetailChartClient({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const [chartData, setChartData] = useState([] as cpuChartData[]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
const timestamp = Date.now().toString();
|
|
||||||
const newData = [
|
|
||||||
...chartData,
|
|
||||||
{ timeStamp: timestamp, cpu: data.status.CPU },
|
|
||||||
];
|
|
||||||
if (newData.length > 30) {
|
|
||||||
newData.shift();
|
|
||||||
}
|
|
||||||
setChartData(newData);
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -60,6 +56,33 @@ export default function ServerDetailChartClient({
|
|||||||
}
|
}
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="grid md:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-3">
|
||||||
|
<CpuChart data={data} />
|
||||||
|
<MemChart data={data} />
|
||||||
|
<NetworkChart data={data} />
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = {
|
const chartConfig = {
|
||||||
cpu: {
|
cpu: {
|
||||||
@ -68,73 +91,303 @@ export default function ServerDetailChartClient({
|
|||||||
} satisfies ChartConfig
|
} satisfies ChartConfig
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="grid md:grid-cols-3 grid-cols-1">
|
<Card className=" rounded-sm">
|
||||||
<Card className=" rounded-sm">
|
<CardContent className="px-6 py-3">
|
||||||
<CardContent className="px-6 py-3 h-[217px]">
|
<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">
|
||||||
<p className="text-sm font-medium md:text-base">
|
CPU
|
||||||
CPU
|
</p>
|
||||||
|
<section className="flex items-center gap-2">
|
||||||
|
<p className="text-xs text-end w-10 font-medium">
|
||||||
|
{cpu.toFixed(0)}%
|
||||||
</p>
|
</p>
|
||||||
<section className="flex items-center gap-2">
|
<AnimatedCircularProgressBar
|
||||||
<p className="text-xs text-end w-10 font-medium">
|
className="size-3 text-[0px]"
|
||||||
{data?.status.CPU.toFixed(0)}%
|
max={100}
|
||||||
</p>
|
min={0}
|
||||||
<AnimatedCircularProgressBar
|
value={cpu}
|
||||||
className="size-3 text-[0px]"
|
primaryColor="hsl(var(--chart-1))"
|
||||||
max={100}
|
/>
|
||||||
min={0}
|
</section>
|
||||||
value={data?.status.CPU}
|
</div>
|
||||||
/>
|
<ChartContainer config={chartConfig} className="aspect-auto h-[130px] w-full">
|
||||||
</section>
|
<AreaChart
|
||||||
</div>
|
accessibilityLayer
|
||||||
|
data={cpuChartData}
|
||||||
<ChartContainer config={chartConfig}>
|
margin={{
|
||||||
<AreaChart
|
top: 12,
|
||||||
accessibilityLayer
|
left: 12,
|
||||||
data={chartData}
|
right: 12,
|
||||||
margin={{
|
}}
|
||||||
top: 12,
|
>
|
||||||
left: 12,
|
<CartesianGrid vertical={false} />
|
||||||
right: 12,
|
<XAxis
|
||||||
}}
|
dataKey="timeStamp"
|
||||||
>
|
tickLine={false}
|
||||||
<CartesianGrid vertical={false} />
|
axisLine={false}
|
||||||
<XAxis
|
tickMargin={8}
|
||||||
dataKey="timeStamp"
|
minTickGap={200}
|
||||||
tickLine={false}
|
interval="preserveStartEnd"
|
||||||
axisLine={false}
|
tickFormatter={(value) => formatRelativeTime(value)}
|
||||||
ticks={[chartData[0]?.timeStamp, chartData[chartData.length - 1]?.timeStamp]}
|
/>
|
||||||
tickFormatter={(value) => formatRelativeTime(value)}
|
<YAxis
|
||||||
/>
|
tickLine={false}
|
||||||
<YAxis
|
axisLine={false}
|
||||||
tickLine={false}
|
mirror={true}
|
||||||
axisLine={false}
|
tickMargin={-15}
|
||||||
mirror={true}
|
domain={[0, 100]}
|
||||||
tickMargin={-15}
|
tickFormatter={(value) => `${value}%`}
|
||||||
domain={[0, 100]}
|
/>
|
||||||
tickFormatter={(value) => `${value}%`}
|
<Area
|
||||||
/>
|
isAnimationActive={false}
|
||||||
{/* <ChartTooltip
|
dataKey="cpu"
|
||||||
isAnimationActive={false}
|
type="step"
|
||||||
cursor={false}
|
fill="hsl(var(--chart-1))"
|
||||||
content={<ChartTooltipContent indicator={"line"} labelFormatter={(_, payload) => {
|
fillOpacity={0.3}
|
||||||
return formatTime(Number(payload[0].payload.timeStamp));
|
stroke="hsl(var(--chart-1))"
|
||||||
}} />}
|
/>
|
||||||
/> */}
|
</AreaChart>
|
||||||
<Area
|
</ChartContainer>
|
||||||
isAnimationActive={false}
|
</section>
|
||||||
dataKey="cpu"
|
</CardContent>
|
||||||
type="step"
|
</Card>
|
||||||
fill="hsl(var(--chart-1))"
|
)
|
||||||
fillOpacity={0.3}
|
}
|
||||||
stroke="hsl(var(--chart-1))"
|
|
||||||
/>
|
function MemChart({ data }: { data: NezhaAPISafe }) {
|
||||||
</AreaChart>
|
const [memChartData, setMemChartData] = useState([] as memChartData[]);
|
||||||
</ChartContainer>
|
|
||||||
</section>
|
const { mem, swap } = formatNezhaInfo(data);
|
||||||
</CardContent>
|
|
||||||
</Card>
|
useEffect(() => {
|
||||||
</section>
|
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 (
|
||||||
|
<Card className=" rounded-sm">
|
||||||
|
<CardContent className="px-6 py-3">
|
||||||
|
<section className="flex flex-col gap-1">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<section className="flex items-center gap-4">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<p className=" text-xs text-muted-foreground">Mem</p>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<AnimatedCircularProgressBar
|
||||||
|
className="size-3 text-[0px]"
|
||||||
|
max={100}
|
||||||
|
min={0}
|
||||||
|
value={mem}
|
||||||
|
primaryColor="hsl(var(--chart-1))"
|
||||||
|
/>
|
||||||
|
<p className="text-xs font-medium">
|
||||||
|
{mem.toFixed(0)}%
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<p className=" text-xs text-muted-foreground">Swap</p>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<AnimatedCircularProgressBar
|
||||||
|
className="size-3 text-[0px]"
|
||||||
|
max={100}
|
||||||
|
min={0}
|
||||||
|
value={swap}
|
||||||
|
primaryColor="hsl(var(--chart-4))"
|
||||||
|
/>
|
||||||
|
<p className="text-xs font-medium">
|
||||||
|
{swap.toFixed(0)}%
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<ChartContainer config={chartConfig} className="aspect-auto h-[130px] w-full">
|
||||||
|
<AreaChart
|
||||||
|
accessibilityLayer
|
||||||
|
data={memChartData}
|
||||||
|
margin={{
|
||||||
|
top: 12,
|
||||||
|
left: 12,
|
||||||
|
right: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<XAxis
|
||||||
|
dataKey="timeStamp"
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={false}
|
||||||
|
tickMargin={8}
|
||||||
|
minTickGap={200}
|
||||||
|
interval="preserveStartEnd"
|
||||||
|
tickFormatter={(value) => formatRelativeTime(value)}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={false}
|
||||||
|
mirror={true}
|
||||||
|
tickMargin={-15}
|
||||||
|
domain={[0, 100]}
|
||||||
|
tickFormatter={(value) => `${value}%`}
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
isAnimationActive={false}
|
||||||
|
dataKey="mem"
|
||||||
|
type="step"
|
||||||
|
fill="hsl(var(--chart-1))"
|
||||||
|
fillOpacity={0.3}
|
||||||
|
stroke="hsl(var(--chart-1))"
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
isAnimationActive={false}
|
||||||
|
dataKey="swap"
|
||||||
|
type="step"
|
||||||
|
fill="hsl(var(--chart-4))"
|
||||||
|
fillOpacity={0.3}
|
||||||
|
stroke="hsl(var(--chart-4))"
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ChartContainer>
|
||||||
|
</section>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function NetworkChart({ data }: { data: NezhaAPISafe }) {
|
||||||
|
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 (
|
||||||
|
<Card className=" rounded-sm">
|
||||||
|
<CardContent className="px-6 py-3">
|
||||||
|
<section className="flex flex-col gap-1">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<section className="flex items-center gap-4">
|
||||||
|
<div className="flex flex-col w-20">
|
||||||
|
<p className="text-xs text-muted-foreground">Upload</p>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col w-20">
|
||||||
|
<p className=" text-xs text-muted-foreground">Download</p>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<ChartContainer config={chartConfig} className="aspect-auto h-[130px] w-full">
|
||||||
|
<LineChart
|
||||||
|
accessibilityLayer
|
||||||
|
data={networkChartData}
|
||||||
|
margin={{
|
||||||
|
top: 12,
|
||||||
|
left: 12,
|
||||||
|
right: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<XAxis
|
||||||
|
dataKey="timeStamp"
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={false}
|
||||||
|
tickMargin={8}
|
||||||
|
minTickGap={200}
|
||||||
|
interval="preserveStartEnd"
|
||||||
|
tickFormatter={(value) => formatRelativeTime(value)}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={false}
|
||||||
|
mirror={true}
|
||||||
|
tickMargin={-15}
|
||||||
|
type="number"
|
||||||
|
minTickGap={50}
|
||||||
|
interval="preserveStartEnd"
|
||||||
|
domain={[1, maxDownload]}
|
||||||
|
tickFormatter={(value) => `${value.toFixed(0)}M/s`}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
isAnimationActive={false}
|
||||||
|
dataKey="upload"
|
||||||
|
type="linear"
|
||||||
|
stroke="hsl(var(--chart-1))"
|
||||||
|
strokeWidth={1}
|
||||||
|
dot={false}
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
isAnimationActive={false}
|
||||||
|
dataKey="download"
|
||||||
|
type="linear"
|
||||||
|
stroke="hsl(var(--chart-4))"
|
||||||
|
strokeWidth={1}
|
||||||
|
dot={false}
|
||||||
|
/>
|
||||||
|
</LineChart>
|
||||||
|
</ChartContainer>
|
||||||
|
</section>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -6,7 +6,7 @@ import AnimatedCircularProgressBar from "@/components/ui/animated-circular-progr
|
|||||||
import { Badge } from "@/components/ui/badge";
|
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, nezhaFetcher } from "@/lib/utils";
|
import { cn, formatBytes, nezhaFetcher } from "@/lib/utils";
|
||||||
import { useLocale } from "next-intl";
|
import { useLocale } from "next-intl";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
@ -51,10 +51,10 @@ export default function ServerDetailClient({
|
|||||||
{data?.name}
|
{data?.name}
|
||||||
</div>
|
</div>
|
||||||
<section className="flex flex-wrap gap-2 mt-2">
|
<section className="flex flex-wrap gap-2 mt-2">
|
||||||
<Card className="rounded-[10px]">
|
<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 items-center gap-2">
|
<section className="flex flex-col items-start gap-0.5">
|
||||||
<p className="text-xs font-semibold">Status</p>
|
<p className="text-xs text-muted-foreground">Status</p>
|
||||||
<Badge
|
<Badge
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-xs rounded-[6px] w-fit px-1 py-0 dark:text-white",
|
"text-xs rounded-[6px] w-fit px-1 py-0 dark:text-white",
|
||||||
@ -69,160 +69,85 @@ export default function ServerDetailClient({
|
|||||||
</section>
|
</section>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="rounded-[10px]">
|
<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 items-center gap-2">
|
<section className="flex flex-col items-start gap-0.5">
|
||||||
<p className="text-xs font-semibold">Uptime</p>
|
<p className="text-xs text-muted-foreground">Uptime</p>
|
||||||
<Badge
|
<div className="text-xs">
|
||||||
className="text-xs rounded-[6px] w-fit px-1 py-0"
|
|
||||||
variant="secondary"
|
|
||||||
>
|
|
||||||
{" "}
|
{" "}
|
||||||
{(data?.status.Uptime / 86400).toFixed(0)} Days{" "}
|
{(data?.status.Uptime / 86400).toFixed(0)} Days{" "}
|
||||||
</Badge>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="rounded-[10px]">
|
<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 items-center gap-2">
|
<section className="flex flex-col items-start gap-0.5">
|
||||||
<p className="text-xs font-semibold">Arch</p>
|
<p className="text-xs text-muted-foreground">Version</p>
|
||||||
<Badge
|
<div className="text-xs">
|
||||||
className="text-xs rounded-[6px] w-fit px-1 py-0"
|
|
||||||
variant="secondary"
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
{data?.host.Arch || "Unknown"}{" "}
|
|
||||||
</Badge>
|
|
||||||
</section>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card className="rounded-[10px]">
|
|
||||||
<CardContent className="px-1.5 py-1">
|
|
||||||
<section className="flex items-center gap-2">
|
|
||||||
<p className="text-xs font-semibold">Version</p>
|
|
||||||
<Badge
|
|
||||||
className="text-xs rounded-[6px] w-fit px-1 py-0"
|
|
||||||
variant="secondary"
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
{data?.host.Version || "Unknown"}{" "}
|
{data?.host.Version || "Unknown"}{" "}
|
||||||
</Badge>
|
</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">
|
||||||
|
{data?.host.Arch || "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>
|
||||||
|
<div className="text-xs">
|
||||||
|
{formatBytes(data?.host.MemTotal)}
|
||||||
|
</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">Disk</p>
|
||||||
|
<div className="text-xs">
|
||||||
|
{formatBytes(data?.host.DiskTotal)}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</section>
|
</section>
|
||||||
<section className="flex flex-wrap gap-2 mt-2">
|
<section className="flex flex-wrap gap-2 mt-1">
|
||||||
<Card className="rounded-[10px] flex flex-col justify-center">
|
<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 items-center gap-2">
|
<section className="flex flex-col items-start gap-0.5">
|
||||||
<p className="text-xs font-semibold">System</p>
|
<p className="text-xs text-muted-foreground">System</p>
|
||||||
{data?.host.Platform ? (
|
{data?.host.Platform ? (
|
||||||
<div className="text-xs w-fit font-medium">
|
<div className="text-xs">
|
||||||
{" "}
|
{" "}
|
||||||
{data?.host.Platform || "Unknown"} -{" "}
|
{data?.host.Platform || "Unknown"} -{" "}
|
||||||
{data?.host.PlatformVersion}{" "}
|
{data?.host.PlatformVersion}{" "}
|
||||||
</div>
|
</div>) : <div className="text-xs">Unknown</div>}
|
||||||
) : (
|
|
||||||
<div className="text-xs w-fit font-medium"> Unknown </div>
|
|
||||||
)}
|
|
||||||
</section>
|
</section>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className="rounded-[10px] flex flex-col justify-center">
|
<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 items-center gap-2">
|
<section className="flex flex-col items-start gap-0.5">
|
||||||
<p className="text-xs font-semibold">CPU</p>
|
<p className="text-xs text-muted-foreground">CPU</p>
|
||||||
{data?.host.CPU ? (
|
{data?.host.CPU ? (
|
||||||
<div className="text-xs w-fit font-medium">
|
<div className="text-xs">
|
||||||
{" "}
|
{" "}
|
||||||
{data?.host.CPU || "Unknown"}
|
{data?.host.CPU}
|
||||||
</div>
|
</div>) : <div className="text-xs">Unknown</div>}
|
||||||
) : (
|
|
||||||
<div className="text-xs w-fit font-medium"> Unknown </div>
|
|
||||||
)}
|
|
||||||
</section>
|
</section>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</section>
|
</section>
|
||||||
{/* <section className="flex flex-wrap gap-2 mt-1">
|
|
||||||
<Card className="rounded-[10px]">
|
|
||||||
<CardContent className="px-1.5 py-1">
|
|
||||||
<section className="flex items-center gap-2">
|
|
||||||
<p className="text-xs font-semibold">CPU</p>
|
|
||||||
<p className="text-xs text-end w-10 font-medium">
|
|
||||||
{data?.status.CPU.toFixed(0)}%
|
|
||||||
</p>
|
|
||||||
<AnimatedCircularProgressBar
|
|
||||||
className="size-3 text-[0px]"
|
|
||||||
max={100}
|
|
||||||
min={0}
|
|
||||||
value={data?.status.CPU}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card className="rounded-[10px]">
|
|
||||||
<CardContent className="px-1.5 py-1">
|
|
||||||
<section className="flex items-center gap-2">
|
|
||||||
<p className="text-xs font-semibold">Mem</p>
|
|
||||||
<p className="text-xs w-10 text-end font-medium">
|
|
||||||
{((data?.status.MemUsed / data?.host.MemTotal) * 100).toFixed(
|
|
||||||
0,
|
|
||||||
)}
|
|
||||||
%
|
|
||||||
</p>
|
|
||||||
<AnimatedCircularProgressBar
|
|
||||||
className="size-3 text-[0px]"
|
|
||||||
max={100}
|
|
||||||
min={0}
|
|
||||||
value={(data?.status.MemUsed / data?.host.MemTotal) * 100}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card className="rounded-[10px]">
|
|
||||||
<CardContent className="px-1.5 py-1">
|
|
||||||
<section className="flex items-center gap-2">
|
|
||||||
<p className="text-xs font-semibold">Swap</p>
|
|
||||||
<p className="text-xs w-10 text-end font-medium">
|
|
||||||
{data?.status.SwapUsed
|
|
||||||
? (
|
|
||||||
(data?.status.SwapUsed / data?.host.SwapTotal) *
|
|
||||||
100
|
|
||||||
).toFixed(0)
|
|
||||||
: 0}
|
|
||||||
%
|
|
||||||
</p>
|
|
||||||
<AnimatedCircularProgressBar
|
|
||||||
className="size-3 text-[0px]"
|
|
||||||
max={100}
|
|
||||||
min={0}
|
|
||||||
value={(data?.status.SwapUsed / data?.host.SwapTotal) * 100}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card className="rounded-[10px]">
|
|
||||||
<CardContent className="px-1.5 py-1">
|
|
||||||
<section className="flex items-center gap-2">
|
|
||||||
<p className="text-xs font-semibold">Disk</p>
|
|
||||||
<p className="text-xs w-10 text-end font-medium">
|
|
||||||
{((data?.status.DiskUsed / data?.host.DiskTotal) * 100).toFixed(
|
|
||||||
0,
|
|
||||||
)}
|
|
||||||
%
|
|
||||||
</p>
|
|
||||||
<AnimatedCircularProgressBar
|
|
||||||
className="size-3 text-[0px]"
|
|
||||||
max={100}
|
|
||||||
min={0}
|
|
||||||
value={(data?.status.DiskUsed / data?.host.DiskTotal) * 100}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</section> */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import ServerDetailClient from "@/app/[locale]/(main)/ClientComponents/ServerDetailClient";
|
import ServerDetailClient from "@/app/[locale]/(main)/ClientComponents/ServerDetailClient";
|
||||||
import ServerDetailChartClient from "@/app/[locale]/(main)/ClientComponents/ServerDetailChartClient";
|
import ServerDetailChartClient from "@/app/[locale]/(main)/ClientComponents/ServerDetailChartClient";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
export default function Page({ params }: { params: { id: string } }) {
|
export default function Page({ params }: { params: { id: string } }) {
|
||||||
return <div className="mx-auto grid w-full max-w-5xl gap-2">
|
return <div className="mx-auto grid w-full max-w-5xl gap-2">
|
||||||
<ServerDetailClient server_id={Number(params.id)} />
|
<ServerDetailClient server_id={Number(params.id)} />
|
||||||
|
<Separator className="mt-1 mb-2" />
|
||||||
<ServerDetailChartClient server_id={Number(params.id)} />
|
<ServerDetailChartClient server_id={Number(params.id)} />
|
||||||
</div>
|
</div>
|
||||||
;
|
;
|
||||||
|
@ -5,12 +5,14 @@ interface Props {
|
|||||||
value: number;
|
value: number;
|
||||||
min: number;
|
min: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
primaryColor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AnimatedCircularProgressBar({
|
export default function AnimatedCircularProgressBar({
|
||||||
max = 100,
|
max = 100,
|
||||||
min = 0,
|
min = 0,
|
||||||
value = 0,
|
value = 0,
|
||||||
|
primaryColor,
|
||||||
className,
|
className,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const circumference = 2 * Math.PI * 45;
|
const circumference = 2 * Math.PI * 45;
|
||||||
@ -74,9 +76,12 @@ export default function AnimatedCircularProgressBar({
|
|||||||
strokeDashoffset="0"
|
strokeDashoffset="0"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
className="opacity-100 stroke-current"
|
className={cn("opacity-100 stroke-current", {
|
||||||
|
"stroke-[var(--stroke-primary-color)]": primaryColor,
|
||||||
|
})}
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
|
"--stroke-primary-color": primaryColor,
|
||||||
"--stroke-percent": currentPercent,
|
"--stroke-percent": currentPercent,
|
||||||
strokeDasharray:
|
strokeDasharray:
|
||||||
"calc(var(--stroke-percent) * var(--percent-to-px)) var(--circumference)",
|
"calc(var(--stroke-percent) * var(--percent-to-px)) var(--circumference)",
|
||||||
|
@ -14,6 +14,7 @@ export function formatNezhaInfo(serverInfo: NezhaAPISafe) {
|
|||||||
down: serverInfo.status.NetInSpeed / 1024 / 1024,
|
down: serverInfo.status.NetInSpeed / 1024 / 1024,
|
||||||
online: serverInfo.online_status,
|
online: serverInfo.online_status,
|
||||||
mem: (serverInfo.status.MemUsed / serverInfo.host.MemTotal) * 100,
|
mem: (serverInfo.status.MemUsed / serverInfo.host.MemTotal) * 100,
|
||||||
|
swap: (serverInfo.status.SwapUsed / serverInfo.host.SwapTotal) * 100,
|
||||||
stg: (serverInfo.status.DiskUsed / serverInfo.host.DiskTotal) * 100,
|
stg: (serverInfo.status.DiskUsed / serverInfo.host.DiskTotal) * 100,
|
||||||
country_code: serverInfo.host.CountryCode,
|
country_code: serverInfo.host.CountryCode,
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user