Initial push
This commit is contained in:
269
app/(dashboard)/monitoring/page.tsx
Normal file
269
app/(dashboard)/monitoring/page.tsx
Normal file
@@ -0,0 +1,269 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { io } from "socket.io-client";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
LineChart,
|
||||
Line,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
AreaChart,
|
||||
Area,
|
||||
} from "recharts";
|
||||
import { Activity, HardDrive, Clock, Cpu, Server } from "lucide-react";
|
||||
|
||||
interface DataPoint {
|
||||
time: string;
|
||||
cpu: number;
|
||||
memory: number;
|
||||
players: number;
|
||||
}
|
||||
|
||||
interface MonitoringData {
|
||||
system: {
|
||||
cpuPercent: number;
|
||||
totalMemMb: number;
|
||||
usedMemMb: number;
|
||||
loadAvg: number[];
|
||||
uptime: number;
|
||||
};
|
||||
server: { running: boolean; uptime?: number };
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
const MAX_HISTORY = 60; // 60 data points (e.g. 2 minutes at 2s intervals)
|
||||
|
||||
function formatUptime(s: number) {
|
||||
const d = Math.floor(s / 86400);
|
||||
const h = Math.floor((s % 86400) / 3600);
|
||||
const m = Math.floor((s % 3600) / 60);
|
||||
if (d > 0) return `${d}d ${h}h ${m}m`;
|
||||
if (h > 0) return `${h}h ${m}m`;
|
||||
return `${m}m ${s % 60}s`;
|
||||
}
|
||||
|
||||
export default function MonitoringPage() {
|
||||
const [data, setData] = useState<MonitoringData | null>(null);
|
||||
const [history, setHistory] = useState<DataPoint[]>([]);
|
||||
const socketRef = useRef<ReturnType<typeof io> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Use REST polling (Socket.io monitoring namespace is optional here)
|
||||
const poll = async () => {
|
||||
try {
|
||||
const res = await fetch("/api/monitoring");
|
||||
if (!res.ok) return;
|
||||
const json: MonitoringData = await res.json();
|
||||
setData(json);
|
||||
const time = new Date(json.timestamp).toLocaleTimeString("en", {
|
||||
hour12: false,
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
});
|
||||
setHistory((prev) => {
|
||||
const updated = [
|
||||
...prev,
|
||||
{
|
||||
time,
|
||||
cpu: json.system.cpuPercent,
|
||||
memory: Math.round(
|
||||
(json.system.usedMemMb / json.system.totalMemMb) * 100,
|
||||
),
|
||||
players: 0,
|
||||
},
|
||||
];
|
||||
return updated.length > MAX_HISTORY
|
||||
? updated.slice(-MAX_HISTORY)
|
||||
: updated;
|
||||
});
|
||||
} catch {}
|
||||
};
|
||||
|
||||
poll();
|
||||
const interval = setInterval(poll, 2000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const memPercent = data
|
||||
? Math.round((data.system.usedMemMb / data.system.totalMemMb) * 100)
|
||||
: 0;
|
||||
|
||||
const chartTheme = {
|
||||
grid: "#27272a",
|
||||
axis: "#52525b",
|
||||
tooltip: { bg: "#18181b", border: "#3f3f46", text: "#fff" },
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">Monitoring</h1>
|
||||
<p className="text-zinc-400 text-sm mt-1">
|
||||
Real-time system and server performance metrics
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Summary cards */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{[
|
||||
{
|
||||
title: "CPU",
|
||||
value: `${data?.system.cpuPercent ?? 0}%`,
|
||||
icon: Cpu,
|
||||
color: "text-blue-500",
|
||||
bg: "bg-blue-500/10",
|
||||
},
|
||||
{
|
||||
title: "Memory",
|
||||
value: `${memPercent}%`,
|
||||
icon: HardDrive,
|
||||
color: "text-emerald-500",
|
||||
bg: "bg-emerald-500/10",
|
||||
sub: `${data?.system.usedMemMb ?? 0} / ${data?.system.totalMemMb ?? 0} MB`,
|
||||
},
|
||||
{
|
||||
title: "Load Avg",
|
||||
value: data?.system.loadAvg[0].toFixed(2) ?? "—",
|
||||
icon: Activity,
|
||||
color: "text-amber-500",
|
||||
bg: "bg-amber-500/10",
|
||||
},
|
||||
{
|
||||
title: "Uptime",
|
||||
value: data ? formatUptime(data.system.uptime) : "—",
|
||||
icon: Clock,
|
||||
color: "text-violet-500",
|
||||
bg: "bg-violet-500/10",
|
||||
},
|
||||
].map(({ title, value, icon: Icon, color, bg, sub }) => (
|
||||
<Card key={title} className="bg-zinc-900 border-zinc-800">
|
||||
<CardContent className="p-5">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<p className="text-sm text-zinc-400">{title}</p>
|
||||
<div className={`p-2 rounded-lg ${bg}`}>
|
||||
<Icon className={`w-4 h-4 ${color}`} />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-white">{value}</p>
|
||||
{sub && <p className="text-xs text-zinc-500 mt-1">{sub}</p>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* CPU chart */}
|
||||
<Card className="bg-zinc-900 border-zinc-800">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm font-medium text-zinc-400 flex items-center gap-2">
|
||||
<Cpu className="w-4 h-4 text-blue-500" />
|
||||
CPU Usage Over Time
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<AreaChart data={history}>
|
||||
<defs>
|
||||
<linearGradient id="cpuGrad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.3} />
|
||||
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid stroke={chartTheme.grid} strokeDasharray="3 3" vertical={false} />
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
stroke={chartTheme.axis}
|
||||
tick={{ fontSize: 11, fill: "#71717a" }}
|
||||
interval="preserveStartEnd"
|
||||
/>
|
||||
<YAxis
|
||||
domain={[0, 100]}
|
||||
stroke={chartTheme.axis}
|
||||
tick={{ fontSize: 11, fill: "#71717a" }}
|
||||
tickFormatter={(v) => `${v}%`}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: chartTheme.tooltip.bg,
|
||||
border: `1px solid ${chartTheme.tooltip.border}`,
|
||||
borderRadius: 8,
|
||||
color: chartTheme.tooltip.text,
|
||||
fontSize: 12,
|
||||
}}
|
||||
formatter={(v) => [`${v}%`, "CPU"]}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="cpu"
|
||||
stroke="#3b82f6"
|
||||
strokeWidth={2}
|
||||
fill="url(#cpuGrad)"
|
||||
dot={false}
|
||||
activeDot={{ r: 4, fill: "#3b82f6" }}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Memory chart */}
|
||||
<Card className="bg-zinc-900 border-zinc-800">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-sm font-medium text-zinc-400 flex items-center gap-2">
|
||||
<HardDrive className="w-4 h-4 text-emerald-500" />
|
||||
Memory Usage Over Time
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<AreaChart data={history}>
|
||||
<defs>
|
||||
<linearGradient id="memGrad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#10b981" stopOpacity={0.3} />
|
||||
<stop offset="95%" stopColor="#10b981" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid stroke={chartTheme.grid} strokeDasharray="3 3" vertical={false} />
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
stroke={chartTheme.axis}
|
||||
tick={{ fontSize: 11, fill: "#71717a" }}
|
||||
interval="preserveStartEnd"
|
||||
/>
|
||||
<YAxis
|
||||
domain={[0, 100]}
|
||||
stroke={chartTheme.axis}
|
||||
tick={{ fontSize: 11, fill: "#71717a" }}
|
||||
tickFormatter={(v) => `${v}%`}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: chartTheme.tooltip.bg,
|
||||
border: `1px solid ${chartTheme.tooltip.border}`,
|
||||
borderRadius: 8,
|
||||
color: chartTheme.tooltip.text,
|
||||
fontSize: 12,
|
||||
}}
|
||||
formatter={(v) => [`${v}%`, "Memory"]}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="memory"
|
||||
stroke="#10b981"
|
||||
strokeWidth={2}
|
||||
fill="url(#memGrad)"
|
||||
dot={false}
|
||||
activeDot={{ r: 4, fill: "#10b981" }}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user