Files
CubeAdmin/app/(dashboard)/dashboard/page.tsx
2026-03-08 17:01:36 +01:00

278 lines
8.5 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Progress } from "@/components/ui/progress";
import { Skeleton } from "@/components/ui/skeleton";
import {
Users,
Puzzle,
HardDrive,
Activity,
Clock,
Zap,
TrendingUp,
AlertTriangle,
} from "lucide-react";
interface MonitoringData {
system: {
cpuPercent: number;
totalMemMb: number;
usedMemMb: number;
loadAvg: number[];
uptime: number;
};
server: {
running: boolean;
uptime?: number;
startedAt?: string;
};
timestamp: number;
}
interface StatsData {
totalPlayers: number;
onlinePlayers: number;
enabledPlugins: number;
totalPlugins: number;
pendingBackups: number;
recentAlerts: string[];
}
function StatCard({
title,
value,
subtitle,
icon: Icon,
accent = "emerald",
loading = false,
}: {
title: string;
value: string | number;
subtitle?: string;
icon: React.ElementType;
accent?: "emerald" | "blue" | "amber" | "red";
loading?: boolean;
}) {
const colors = {
emerald: "text-emerald-500 bg-emerald-500/10",
blue: "text-blue-500 bg-blue-500/10",
amber: "text-amber-500 bg-amber-500/10",
red: "text-red-500 bg-red-500/10",
};
return (
<Card className="bg-zinc-900 border-zinc-800">
<CardContent className="p-6">
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="text-sm text-zinc-400 mb-1">{title}</p>
{loading ? (
<Skeleton className="h-8 w-20 bg-zinc-800" />
) : (
<p className="text-2xl font-bold text-white">{value}</p>
)}
{subtitle && (
<p className="text-xs text-zinc-500 mt-1">{subtitle}</p>
)}
</div>
<div className={`p-2.5 rounded-lg ${colors[accent]}`}>
<Icon className="w-5 h-5" />
</div>
</div>
</CardContent>
</Card>
);
}
function formatUptime(seconds: number): string {
const d = Math.floor(seconds / 86400);
const h = Math.floor((seconds % 86400) / 3600);
const m = Math.floor((seconds % 3600) / 60);
if (d > 0) return `${d}d ${h}h ${m}m`;
if (h > 0) return `${h}h ${m}m`;
return `${m}m`;
}
export default function DashboardPage() {
const [monitoring, setMonitoring] = useState<MonitoringData | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchMonitoring = async () => {
try {
const res = await fetch("/api/monitoring");
if (res.ok) setMonitoring(await res.json());
} finally {
setLoading(false);
}
};
fetchMonitoring();
const interval = setInterval(fetchMonitoring, 5000);
return () => clearInterval(interval);
}, []);
const memPercent = monitoring
? Math.round((monitoring.system.usedMemMb / monitoring.system.totalMemMb) * 100)
: 0;
return (
<div className="p-6 space-y-6">
<div>
<h1 className="text-2xl font-bold text-white">Dashboard</h1>
<p className="text-zinc-400 text-sm mt-1">
Real-time overview of your Minecraft server
</p>
</div>
{/* Status banner */}
<div
className={`flex items-center gap-3 p-4 rounded-xl border ${
monitoring?.server.running
? "bg-emerald-500/10 border-emerald-500/30 text-emerald-400"
: "bg-red-500/10 border-red-500/30 text-red-400"
}`}
>
<div
className={`w-2.5 h-2.5 rounded-full animate-pulse ${
monitoring?.server.running ? "bg-emerald-500" : "bg-red-500"
}`}
/>
<span className="font-medium">
Server is {monitoring?.server.running ? "Online" : "Offline"}
</span>
{monitoring?.server.running && monitoring.server.uptime && (
<span className="text-sm opacity-70 ml-auto">
Up for {formatUptime(monitoring.server.uptime)}
</span>
)}
</div>
{/* Stat cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
<StatCard
title="CPU Usage"
value={loading ? "—" : `${monitoring?.system.cpuPercent ?? 0}%`}
subtitle={`Load avg: ${monitoring?.system.loadAvg[0].toFixed(2) ?? "—"}`}
icon={Activity}
accent="blue"
loading={loading}
/>
<StatCard
title="Memory"
value={loading ? "—" : `${monitoring?.system.usedMemMb ?? 0} MB`}
subtitle={`of ${monitoring?.system.totalMemMb ?? 0} MB total`}
icon={HardDrive}
accent={memPercent > 85 ? "red" : memPercent > 60 ? "amber" : "emerald"}
loading={loading}
/>
<StatCard
title="System Uptime"
value={loading ? "—" : formatUptime(monitoring?.system.uptime ?? 0)}
icon={Clock}
accent="emerald"
loading={loading}
/>
<StatCard
title="Server Status"
value={monitoring?.server.running ? "Online" : "Offline"}
icon={Zap}
accent={monitoring?.server.running ? "emerald" : "red"}
loading={loading}
/>
</div>
{/* Resource gauges */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<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">
<Activity className="w-4 h-4 text-blue-500" />
CPU Usage
</CardTitle>
</CardHeader>
<CardContent>
{loading ? (
<Skeleton className="h-4 w-full bg-zinc-800" />
) : (
<>
<div className="flex justify-between text-sm mb-2">
<span className="text-white font-medium">
{monitoring?.system.cpuPercent ?? 0}%
</span>
<span className="text-zinc-500">100%</span>
</div>
<Progress
value={monitoring?.system.cpuPercent ?? 0}
className="h-2 bg-zinc-800"
/>
</>
)}
</CardContent>
</Card>
<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
</CardTitle>
</CardHeader>
<CardContent>
{loading ? (
<Skeleton className="h-4 w-full bg-zinc-800" />
) : (
<>
<div className="flex justify-between text-sm mb-2">
<span className="text-white font-medium">
{monitoring?.system.usedMemMb ?? 0} MB
</span>
<span className="text-zinc-500">
{monitoring?.system.totalMemMb ?? 0} MB
</span>
</div>
<Progress
value={memPercent}
className="h-2 bg-zinc-800"
/>
</>
)}
</CardContent>
</Card>
</div>
{/* Quick info */}
<Card className="bg-zinc-900 border-zinc-800">
<CardHeader>
<CardTitle className="text-sm font-medium text-zinc-400 flex items-center gap-2">
<TrendingUp className="w-4 h-4 text-emerald-500" />
Quick Actions
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{[
{ label: "View Console", href: "/console", icon: "⌨️" },
{ label: "Manage Players", href: "/players", icon: "👥" },
{ label: "Plugins", href: "/plugins", icon: "🔌" },
{ label: "Create Backup", href: "/backups", icon: "💾" },
].map(({ label, href, icon }) => (
<a
key={href}
href={href}
className="flex items-center gap-2 p-3 rounded-lg bg-zinc-800 hover:bg-zinc-700 transition-colors text-sm text-zinc-300 hover:text-white"
>
<span>{icon}</span>
<span>{label}</span>
</a>
))}
</div>
</CardContent>
</Card>
</div>
);
}