mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-11 19:56:37 +08:00
feat: redesign monitor page to nezha-style server grid
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
# Monitor Nezha Redesign
|
||||||
|
## Summary
|
||||||
|
Redesign the monitor overview to resemble Nezha dashboard by introducing a Server Matrix (Grid) containing cards with metrics visualisations.
|
||||||
|
|
||||||
|
## Objectives
|
||||||
|
- [x] Analyze `monitor-view.tsx` layout boundaries.
|
||||||
|
- [x] Create `ServerCard` utilizing `Progress` component.
|
||||||
|
- [x] Render metrics (CPU, RAM, Disk, System Load, Connections, Network speeeds).
|
||||||
|
- [x] Implement robust real-time updates and seamless state linkage via existing hooks.
|
||||||
|
- [x] Adjust layout positioning for impact at top-of-page.
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
Injected a `ServerCard` inline component mapping over unified node metric states. Used the tailwind and hero UI components matching visual designs like progress bars and small monospace typograhy. Extracted utility color scales.
|
||||||
|
|
||||||
|
## Status: Complete
|
||||||
@@ -26,6 +26,12 @@ import {
|
|||||||
Edit,
|
Edit,
|
||||||
Activity,
|
Activity,
|
||||||
Play,
|
Play,
|
||||||
|
Server,
|
||||||
|
Zap,
|
||||||
|
HardDrive,
|
||||||
|
Cpu,
|
||||||
|
Clock,
|
||||||
|
Globe,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
@@ -69,6 +75,7 @@ import {
|
|||||||
DropdownItem,
|
DropdownItem,
|
||||||
} from "@/shadcn-bridge/heroui/dropdown";
|
} from "@/shadcn-bridge/heroui/dropdown";
|
||||||
import { Card, CardBody, CardHeader } from "@/shadcn-bridge/heroui/card";
|
import { Card, CardBody, CardHeader } from "@/shadcn-bridge/heroui/card";
|
||||||
|
import { Progress } from "@/shadcn-bridge/heroui/progress";
|
||||||
import { useNodeRealtime } from "@/pages/node/use-node-realtime";
|
import { useNodeRealtime } from "@/pages/node/use-node-realtime";
|
||||||
|
|
||||||
interface MonitorViewProps {
|
interface MonitorViewProps {
|
||||||
@@ -145,6 +152,117 @@ const formatBytesPerSecond = (bytesPerSecond: number): string => {
|
|||||||
return `${parseFloat((bytesPerSecond / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
return `${parseFloat((bytesPerSecond / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatUptime = (seconds: number) => {
|
||||||
|
if (!seconds) return "-";
|
||||||
|
const days = Math.floor(seconds / 86400);
|
||||||
|
const hours = Math.floor((seconds % 86400) / 3600);
|
||||||
|
if (days > 0) return `${days} 天 ${hours} 小时`;
|
||||||
|
return `${hours} 小时`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getColorByUsage = (usage?: number) => {
|
||||||
|
if (usage === undefined || usage === null) return "default";
|
||||||
|
if (usage >= 90) return "danger";
|
||||||
|
if (usage >= 75) return "warning";
|
||||||
|
if (usage >= 50) return "primary";
|
||||||
|
return "success";
|
||||||
|
};
|
||||||
|
|
||||||
|
function ServerCard({ node, metric }: { node: any; metric: RealtimeNodeMetric | null }) {
|
||||||
|
const isOnline = node.connectionStatus === "online";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={`border-t-4 ${isOnline ? "border-t-success" : "border-t-danger"}`}>
|
||||||
|
<CardHeader className="pb-2 flex justify-between items-center">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Server className={`w-5 h-5 ${isOnline ? "text-success" : "text-default-400"}`} />
|
||||||
|
<div className="font-semibold text-base truncate max-w-[150px]" title={node.name}>{node.name}</div>
|
||||||
|
</div>
|
||||||
|
<Chip size="sm" color={isOnline ? "success" : "danger"} variant="flat">
|
||||||
|
{isOnline ? "在线" : "离线"}
|
||||||
|
</Chip>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardBody className="pt-0 space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-y-2 gap-x-4 text-xs mt-1">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-1 text-default-500">
|
||||||
|
<Clock className="w-3 h-3" /> 运行
|
||||||
|
</div>
|
||||||
|
<span className="font-mono text-[11px]">{metric ? formatUptime(metric.uptime) : "-"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-1 text-default-500">
|
||||||
|
<Activity className="w-3 h-3" /> 负载
|
||||||
|
</div>
|
||||||
|
<span className="font-mono text-[11px]">{metric ? metric.load1.toFixed(2) : "-"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-1 text-default-500">
|
||||||
|
<Globe className="w-3 h-3" /> 连接
|
||||||
|
</div>
|
||||||
|
<span className="font-mono text-[11px]">{metric ? metric.tcpConns : "-"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-1 text-default-500">
|
||||||
|
<Zap className="w-3 h-3" /> UDP
|
||||||
|
</div>
|
||||||
|
<span className="font-mono text-[11px]">{metric ? metric.udpConns : "-"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between text-xs mb-1">
|
||||||
|
<div className="flex items-center gap-1 text-default-600"><Cpu className="w-3.5 h-3.5" /> CPU</div>
|
||||||
|
<span className="font-mono text-[11px]">{metric ? metric.cpuUsage.toFixed(1) : "0.0"}%</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={metric ? metric.cpuUsage : 0} color={getColorByUsage(metric?.cpuUsage)} className="h-1.5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between text-xs mb-1">
|
||||||
|
<div className="flex items-center gap-1 text-default-600"><HardDrive className="w-3.5 h-3.5" /> RAM</div>
|
||||||
|
<span className="font-mono text-[11px]">{metric ? metric.memoryUsage.toFixed(1) : "0.0"}%</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={metric ? metric.memoryUsage : 0} color={getColorByUsage(metric?.memoryUsage)} className="h-1.5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between text-xs mb-1">
|
||||||
|
<div className="flex items-center gap-1 text-default-600"><HardDrive className="w-3.5 h-3.5" /> Disk</div>
|
||||||
|
<span className="font-mono text-[11px]">{metric ? metric.diskUsage.toFixed(1) : "0.0"}%</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={metric ? metric.diskUsage : 0} color={getColorByUsage(metric?.diskUsage)} className="h-1.5" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center pt-3 border-t border-default-200/50 text-xs">
|
||||||
|
<div className="flex flex-col gap-0.5 w-[50%] border-r border-default-200/50 pr-2">
|
||||||
|
<div className="text-default-500 flex justify-between">
|
||||||
|
<span>网络 ↓</span>
|
||||||
|
<span className="font-mono text-success">{metric ? formatBytesPerSecond(metric.netInSpeed) : "0 B/s"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-default-400 font-mono flex justify-between">
|
||||||
|
<span>总计</span>
|
||||||
|
<span className="text-[11px]">{metric ? formatBytes(metric.netInBytes) : "0 B"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-0.5 w-[50%] pl-2">
|
||||||
|
<div className="text-default-500 flex justify-between">
|
||||||
|
<span>网络 ↑</span>
|
||||||
|
<span className="font-mono text-primary">{metric ? formatBytesPerSecond(metric.netOutSpeed) : "0 B/s"}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-default-400 font-mono flex justify-between">
|
||||||
|
<span>总计</span>
|
||||||
|
<span className="text-[11px]">{metric ? formatBytes(metric.netOutBytes) : "0 B"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type MetricType =
|
type MetricType =
|
||||||
| "cpu"
|
| "cpu"
|
||||||
| "memory"
|
| "memory"
|
||||||
@@ -1059,6 +1177,13 @@ export function MonitorView({ nodeMap }: MonitorViewProps) {
|
|||||||
|
|
||||||
{!accessDenied && (
|
{!accessDenied && (
|
||||||
<>
|
<>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||||
|
{nodes.map((node) => {
|
||||||
|
const metric = realtimeNodeMetrics[node.id] || null;
|
||||||
|
return <ServerCard key={node.id} node={node} metric={metric} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<h3 className="text-lg font-semibold">监控概览</h3>
|
<h3 className="text-lg font-semibold">监控概览</h3>
|
||||||
|
|||||||
Reference in New Issue
Block a user