feat: 新增流量限制功能,优化节点卡片和列表显示

This commit is contained in:
Montia37
2025-08-15 23:26:09 +08:00
parent 48be5c104d
commit 910f74b96d
10 changed files with 303 additions and 99 deletions

View File

@@ -1,5 +1,10 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { formatBytes, formatUptime, getOSImage } from "@/utils";
import {
formatBytes,
formatUptime,
getOSImage,
formatTrafficLimit,
} from "@/utils";
import type { NodeWithStatus } from "@/types/node";
import { Link } from "react-router-dom";
import { CpuIcon, MemoryStickIcon, HardDriveIcon } from "lucide-react";
@@ -7,6 +12,7 @@ import Flag from "./Flag";
import { Tag } from "../ui/tag";
import { useNodeCommons } from "@/hooks/useNodeCommons";
import { ProgressBar } from "../ui/progress-bar";
import { CircleProgress } from "../ui/circle-progress";
interface NodeCardProps {
node: NodeWithStatus;
@@ -23,6 +29,7 @@ export const NodeCard = ({ node }: NodeCardProps) => {
diskUsage,
load,
expired_at,
trafficPercentage,
} = useNodeCommons(node);
const getProgressBarClass = (percentage: number) => {
@@ -129,7 +136,7 @@ export const NodeCard = ({ node }: NodeCardProps) => {
</div>
<div className="border-t border-border/60 my-2"></div>
<div className="flex justify-between text-xs">
<span className="text-secondary-foreground"></span>
<span className="text-secondary-foreground"></span>
<div>
<span> {stats ? formatBytes(stats.network.up, true) : "N/A"}</span>
<span className="ml-2">
@@ -137,13 +144,38 @@ export const NodeCard = ({ node }: NodeCardProps) => {
</span>
</div>
</div>
<div className="flex justify-between text-xs">
<span className="text-secondary-foreground"></span>
<div>
<span> {stats ? formatBytes(stats.network.totalUp) : "N/A"}</span>
<span className="ml-2">
{stats ? formatBytes(stats.network.totalDown) : "N/A"}
</span>
<div className="flex items-center justify-between text-xs">
<span className="text-secondary-foreground w-1/4"></span>
<div className="flex items-center justify-between w-3/4">
<div className="flex items-center justify-center w-1/3">
{node.traffic_limit !== 0 && isOnline && stats && (
<CircleProgress
value={trafficPercentage}
maxValue={100}
size={32}
strokeWidth={4}
showPercentage={true}
/>
)}
</div>
<div className="w-2/3 text-right">
<div>
<span>
{stats ? formatBytes(stats.network.totalUp) : "N/A"}
</span>
<span className="ml-2">
{stats ? formatBytes(stats.network.totalDown) : "N/A"}
</span>
</div>
{node.traffic_limit !== 0 && isOnline && stats && (
<div className="text-right">
{formatTrafficLimit(
node.traffic_limit,
node.traffic_limit_type
)}
</div>
)}
</div>
</div>
</div>
<div className="flex justify-between text-xs">
@@ -151,13 +183,13 @@ export const NodeCard = ({ node }: NodeCardProps) => {
<span>{load}</span>
</div>
<div className="flex justify-between text-xs">
<div className="flex justify-between w-full">
<span className="text-secondary-foreground"></span>
<div className="flex justify-start w-full">
<span className="text-secondary-foreground"></span>
<div className="flex items-center gap-1">{expired_at}</div>
</div>
<div className="border-l border-border/60 mx-2"></div>
<div className="flex justify-between w-full">
<span className="text-secondary-foreground">线</span>
<div className="flex justify-start w-full">
<span className="text-secondary-foreground">线</span>
<span>
{isOnline && stats ? formatUptime(stats.uptime) : "离线"}
</span>

View File

@@ -1,12 +1,12 @@
export const NodeListHeader = () => {
return (
<div className="text-primary font-bold grid grid-cols-12 text-center shadow-md gap-4 p-2 items-center rounded-lg bg-card/50 transition-colors duration-200">
<div className="col-span-3"></div>
<div className="text-primary font-bold grid grid-cols-10 text-center shadow-md gap-4 p-2 items-center rounded-lg bg-card/50 transition-colors duration-200">
<div className="col-span-2"></div>
<div className="col-span-1">CPU</div>
<div className="col-span-1"></div>
<div className="col-span-1">SWAP</div>
<div className="col-span-1"></div>
<div className="col-span-2"></div>
<div className="col-span-1"></div>
<div className="col-span-2"></div>
<div className="col-span-1"></div>
</div>

View File

@@ -1,10 +1,11 @@
import { formatBytes, formatUptime } from "@/utils";
import { formatBytes, formatTrafficLimit, formatUptime } from "@/utils";
import type { NodeWithStatus } from "@/types/node";
import { Link } from "react-router-dom";
import { CpuIcon, MemoryStickIcon, HardDriveIcon } from "lucide-react";
import Flag from "./Flag";
import { Tag } from "../ui/tag";
import { useNodeCommons } from "@/hooks/useNodeCommons";
import { CircleProgress } from "../ui/circle-progress";
interface NodeListItemProps {
node: NodeWithStatus;
@@ -21,22 +22,23 @@ export const NodeListItem = ({ node }: NodeListItemProps) => {
diskUsage,
load,
expired_at,
trafficPercentage,
} = useNodeCommons(node);
return (
<div
className={`grid grid-cols-12 text-center shadow-md gap-4 p-2 items-center rounded-lg ${
className={`grid grid-cols-10 text-center shadow-md gap-4 p-2 items-center rounded-lg ${
isOnline
? ""
: "striped-bg-red-translucent-diagonal ring-2 ring-red-500/50"
} text-secondary-foreground transition-colors duration-200`}>
<div className="col-span-3 flex items-center text-left">
<div className="col-span-2 flex items-center text-left">
<Flag flag={node.region} />
<Link to={`/instance/${node.uuid}`}>
<div className="ml-2 w-full">
<div className="text-base font-bold">{node.name}</div>
<Tag className="text-xs" tags={tagList} />
<div className="flex text-xs">
<div className="flex text-xs text-nowrap">
<div className="flex">
<span className="text-secondary-foreground"></span>
<div className="flex items-center gap-1">{expired_at}</div>
@@ -86,17 +88,42 @@ export const NodeListItem = ({ node }: NodeListItemProps) => {
{isOnline ? `${diskUsage.toFixed(1)}%` : "N/A"}
</div>
</div>
<div className="col-span-2">
<span>
{stats ? formatBytes(stats.network.up, true) : "N/A"}{" "}
{stats ? formatBytes(stats.network.down, true) : "N/A"}
</span>
<div className="col-span-1">
<div> {stats ? formatBytes(stats.network.up, true) : "N/A"}</div>
<div> {stats ? formatBytes(stats.network.down, true) : "N/A"}</div>
</div>
<div className="col-span-2">
<span>
{stats ? formatBytes(stats.network.totalUp) : "N/A"}{" "}
{stats ? formatBytes(stats.network.totalDown) : "N/A"}
</span>
<div className="flex items-center justify-around">
{node.traffic_limit !== 0 && isOnline && stats && (
<div className="flex items-center">
<CircleProgress
value={trafficPercentage}
maxValue={100}
size={32}
strokeWidth={4}
showPercentage={true}
/>
</div>
)}
<div className={node.traffic_limit !== 0 ? "w-2/3" : "w-full"}>
<div>
<span>
{stats ? formatBytes(stats.network.totalUp) : "N/A"}
</span>
<span className="ml-2">
{stats ? formatBytes(stats.network.totalDown) : "N/A"}
</span>
</div>
{node.traffic_limit !== 0 && isOnline && stats && (
<div>
{formatTrafficLimit(
node.traffic_limit,
node.traffic_limit_type
)}
</div>
)}
</div>
</div>
</div>
<div className="col-span-1">
<span>{load}</span>