Merge pull request #6 from kuusei/main

improve: ServerCard
This commit is contained in:
仓鼠 2024-07-28 23:03:12 +08:00 committed by GitHub
commit 7cfc5a49cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 155 additions and 76 deletions

View File

@ -10,24 +10,13 @@ export default function ServerListClient() {
refreshInterval: 3000, refreshInterval: 3000,
}); });
if (!data) return null; if (!data) return null;
const sortedResult = data.result.sort((a: any, b: any) => a.id - b.id); const sortedResult = data.result.sort((a, b) => a.id - b.id);
const timestamp = Date.now() / 1000; const timestamp = Date.now() / 1000;
return ( return (
<section className={"grid grid-cols-1 gap-2 md:grid-cols-2"}> <section className={"grid grid-cols-1 gap-2 md:grid-cols-2"}>
{sortedResult.map((server: any) => ( {sortedResult.map((serverInfo) => (
<ServerCard <ServerCard key={serverInfo.id} timestamp={timestamp} serverInfo={serverInfo} />
key={server.id}
id={server.id}
cpu={server.status.CPU}
name={server.name}
up={server.status.NetOutSpeed / 1024 / 1024}
down={server.status.NetInSpeed / 1024 / 1024}
status={timestamp - server.last_active > 300 ? "offline" : "online"}
uptime={server.status.Uptime / 86400}
mem={(server.status.MemUsed / server.host.MemTotal) * 100}
stg={(server.status.DiskUsed / server.host.DiskTotal) * 100}
/>
))} ))}
</section> </section>
); );

BIN
bun.lockb

Binary file not shown.

View File

@ -1,62 +1,43 @@
import React from "react"; import { NezhaAPISafe } from "@/app/types/nezha-api";
import ServerUsageBar from "@/components/ServerUsageBar"; import ServerUsageBar from "@/components/ServerUsageBar";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
import { import {
Tooltip, Popover,
TooltipContent, PopoverContent,
TooltipProvider, PopoverTrigger,
TooltipTrigger, } from "@/components/ui/popover";
} from "@/components/ui/tooltip"; import { formatNezhaInfo } from "@/lib/utils";
import ServerCardPopover from "./ServerCardPopover";
type ServerCardProps = {
id: number;
status: string;
name: string;
uptime: number;
cpu: number;
mem: number;
stg: number;
up: number;
down: number;
};
export default function ServerCard({ export default function ServerCard({
status, timestamp,
name, serverInfo,
uptime, }: {
cpu, timestamp: number;
mem, serverInfo: NezhaAPISafe;
stg, }) {
up, const { name, online, cpu, up, down, mem, stg, ...props } = formatNezhaInfo(
down, timestamp,
}: ServerCardProps) { serverInfo,
return status === "online" ? ( );
return online === "online" ? (
<Card <Card
className={ className={
"flex flex-col items-center justify-start gap-3 p-3 md:px-5 lg:flex-row" "flex flex-col items-center justify-start gap-3 p-3 md:px-5 lg:flex-row"
} }
> >
<TooltipProvider delayDuration={0}> <Popover>
<Tooltip> <PopoverTrigger asChild>
<TooltipTrigger asChild> <section className={"flex items-center justify-start gap-2 lg:w-28"}>
<section <span className="h-2 w-2 shrink-0 rounded-full bg-green-500"></span>
className={"flex items-center justify-start gap-2 lg:w-28"} <p className="break-all text-sm font-bold tracking-tight">{name}</p>
> </section>
<span className="h-2 w-2 shrink-0 rounded-full bg-green-500"></span> </PopoverTrigger>
<p className="break-all text-sm font-bold tracking-tight"> <PopoverContent side="top">
{name} <ServerCardPopover status={props.status} host={props.host} />
</p> </PopoverContent>
</section> </Popover>
</TooltipTrigger>
<TooltipContent>
<section>
<div>Hostname: {name}</div>
<div>Online: {uptime.toFixed(0)} Days</div>
</section>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<section className={"grid grid-cols-5 items-center gap-3"}> <section className={"grid grid-cols-5 items-center gap-3"}>
<div className={"flex flex-col"}> <div className={"flex flex-col"}>
<p className="text-xs text-muted-foreground">CPU</p> <p className="text-xs text-muted-foreground">CPU</p>
@ -89,19 +70,15 @@ export default function ServerCard({
"flex flex-col items-center justify-start gap-3 p-3 md:px-5 lg:flex-row" "flex flex-col items-center justify-start gap-3 p-3 md:px-5 lg:flex-row"
} }
> >
<TooltipProvider delayDuration={0}> <Popover>
<Tooltip> <PopoverTrigger asChild>
<TooltipTrigger asChild> <section className={"flex items-center justify-start gap-2 lg:w-28"}>
<section <span className="h-2 w-2 shrink-0 rounded-full bg-red-500"></span>
className={"flex items-center justify-start gap-2 lg:w-28"} <p className="text-sm font-bold tracking-tight">{name}</p>
> </section>
<span className="h-2 w-2 shrink-0 rounded-full bg-red-500"></span> </PopoverTrigger>
<p className="text-sm font-bold tracking-tight">{name}</p> <PopoverContent side="top">Offline</PopoverContent>
</section> </Popover>
</TooltipTrigger>
<TooltipContent>Offline</TooltipContent>
</Tooltip>
</TooltipProvider>
</Card> </Card>
); );
} }

View File

@ -0,0 +1,68 @@
import { NezhaAPISafe } from "@/app/types/nezha-api";
import { cn, formatBytes } from "@/lib/utils";
export function ServerCardPopoverCard({
className,
title,
content,
children,
}: {
className?: string;
title: string;
content?: string;
children?: React.ReactNode;
}) {
return (
<div className={cn("mb-[6px] flex w-full flex-col", className)}>
<div className="text-sm font-semibold">{title}</div>
{children ? children : <div className="break-all text-sm">{content}</div>}
<div className="h-[0.5px] w-full bg-accent"></div>
</div>
);
}
export default function ServerCardPopover({
host,
status,
}: {
host: NezhaAPISafe["host"];
status: NezhaAPISafe["status"];
}) {
return (
<section className="max-w-[300px]">
<ServerCardPopoverCard
title="System"
content={`${host.Platform}-${host.PlatformVersion} [${host.Virtualization}: ${host.Arch}]`}
/>
<ServerCardPopoverCard
title="CPU"
content={`${host.CPU.map((item) => item).join(", ")}`}
/>
<ServerCardPopoverCard
title="Mem"
content={`${formatBytes(host.MemTotal)} / ${formatBytes(status.MemUsed)}`}
/>
<ServerCardPopoverCard
title="STG"
content={`${formatBytes(status.DiskUsed)} / ${formatBytes(host.DiskTotal)}`}
/>
<ServerCardPopoverCard
title="Swap"
content={`${formatBytes(status.SwapUsed)} / ${formatBytes(host.SwapTotal)}`}
/>
<ServerCardPopoverCard
title="Network"
content={`${formatBytes(status.NetInTransfer)} / ${formatBytes(status.NetOutTransfer)}`}
/>
<ServerCardPopoverCard
title="Load"
content={`${status.Load1.toFixed(2)} / ${status.Load5.toFixed(2)} / ${status.Load15.toFixed(2)}`}
/>
<ServerCardPopoverCard
className="mb-0"
title="Online"
content={`${(status.Uptime / 86400).toFixed(0)} Days`}
/>
</section>
);
}

31
components/ui/popover.tsx Normal file
View File

@ -0,0 +1,31 @@
"use client"
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent }

View File

@ -1,3 +1,4 @@
import { NezhaAPISafe } from "@/app/types/nezha-api";
import { type ClassValue, clsx } from "clsx"; import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
@ -5,6 +6,18 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }
export function formatNezhaInfo(timestamp: number, serverInfo: NezhaAPISafe) {
return {
...serverInfo,
cpu: serverInfo.status.CPU,
up: serverInfo.status.NetOutSpeed / 1024 / 1024,
down: serverInfo.status.NetInSpeed / 1024 / 1024,
online: timestamp - serverInfo.last_active > 300 ? "offline" : "online",
mem: (serverInfo.status.MemUsed / serverInfo.host.MemTotal) * 100,
stg: (serverInfo.status.DiskUsed / serverInfo.host.DiskTotal) * 100,
}
}
export function formatBytes(bytes: number, decimals: number = 2) { export function formatBytes(bytes: number, decimals: number = 2) {
if (!+bytes) return "0 Bytes"; if (!+bytes) return "0 Bytes";

View File

@ -13,6 +13,7 @@
"@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",