"use client"; import { useState } from "react"; import Link from "next/link"; import { Play, Square, RotateCw, HardDrive, Globe2, Cuboid, Map } from "lucide-react"; import { api, useApi, useSSE, toast } from "@/lib/api"; import { Card, StatTile, Button, Badge, EmptyState } from "@/components/ui"; import { Sparkline, UsageBar, type Point } from "@/components/charts"; import { QuickControls } from "@/components/quickcontrols"; import { fmtBytes, fmtDuration, timeAgo, fmtCountdown } from "@/lib/format"; type ServerData = { dockerAvailable: boolean; container: { exists: boolean; running: boolean; status: string; health?: string }; players: { online: number; max: number; names: string[] }; version: string | null; serverType: string | null; metrics: Record | null; pendingApprovals: number; map: { available: boolean; port: number }; world: { seed: string | null; sizeBytes: number | null; dimensions: string[]; levelName: string | null }; uptimeMs: number | null; }; type MetricRow = { bucket: number; cpu: number | null; mem_used: number | null; mem_limit: number | null; tps: number | null; players: number | null; disk_used: number | null; disk_free: number | null; }; type ActivityRow = { id: number; ts: number; type: string; player: string | null; detail: string | null }; type ApprovedRow = { username: string; status: string; window_expires_at: number | null }; const ACTIVITY_LABELS: Record = { join: { label: "joined", color: "good" }, leave: { label: "left", color: "muted" }, connect: { label: "connecting", color: "info" }, register: { label: "registered", color: "good" }, approve: { label: "approved", color: "info" }, expire: { label: "window expired", color: "warn" }, revoke: { label: "revoked", color: "crit" }, server_online: { label: "server online", color: "good" }, server_stopping: { label: "server stopping", color: "warn" }, backup: { label: "backup", color: "info" }, restore: { label: "restore", color: "warn" }, power: { label: "power", color: "muted" }, console: { label: "console", color: "muted" }, world_reset: { label: "world reset", color: "crit" }, blocked: { label: "blocked", color: "crit" }, ban: { label: "banned", color: "crit" }, ban_ip: { label: "IP banned", color: "crit" }, pardon: { label: "unbanned", color: "info" }, pardon_ip: { label: "IP unbanned", color: "info" }, kick_all: { label: "kicked all", color: "warn" }, admin: { label: "admin", color: "info" }, whitelist_heal: { label: "whitelist re-synced", color: "warn" }, password_reset: { label: "password reset", color: "info" }, }; export default function OverviewPage() { const { data: server, refresh } = useApi("/api/server", 10000); const { data: metricRows, refresh: refreshMetrics } = useApi("/api/metrics?range=1h", 30000); const { data: activity, refresh: refreshActivity } = useApi("/api/activity?limit=14", 20000); const { data: approvedData, refresh: refreshApproved } = useApi<{ approved: ApprovedRow[] }>( "/api/approved", 15000 ); const [busyAction, setBusyAction] = useState(null); const [, forceTick] = useState(0); useSSE("/api/stream/events", { "mc-event": () => { void refreshActivity(); void refreshApproved(); }, metrics: () => forceTick((x) => x + 1), }); const power = async (action: "start" | "stop" | "restart") => { setBusyAction(action); try { await api("/api/server/power", { body: { action } }); toast(`Server ${action} requested`, "success"); setTimeout(() => void refresh(), 1500); } catch (err) { toast((err as Error).message, "error"); } finally { setBusyAction(null); } }; const running = server?.container.running ?? false; const m = server?.metrics; const points = (key: keyof MetricRow): Point[] => (metricRows ?? []).map((r) => ({ ts: r.bucket, v: r[key] as number | null })); const pending = (approvedData?.approved ?? []).filter((a) => a.status === "pending"); return (
{/* header row */}

Overview

{server?.serverType ?? "Fabric"} · Minecraft {server?.version ?? "…"} {running && server?.uptimeMs != null && <> · up {fmtDuration(server.uptimeMs)}}

{!server?.dockerAvailable && server && (
Docker is unreachable from the panel. If you're running the dashboard outside the compose stack, server control and metrics are unavailable.
)} {/* stat tiles */}
= 18 ? "good" : Number(m.tps) >= 15 ? "warn" : "crit") : undefined} >
{/* activity feed */} {!activity || activity.length === 0 ? ( ) : (
    {activity.map((a) => { const meta = ACTIVITY_LABELS[a.type] ?? { label: a.type, color: "muted" as const }; return (
  • {meta.label} {a.player && {a.player} } {a.detail} {timeAgo(a.ts)}
  • ); })}
)}
{/* pending registrations */} {pending.length > 0 && (
    {pending.map((p) => (
  • pending {p.username} {p.window_expires_at ? fmtCountdown(p.window_expires_at) : ""}
  • ))}

They must join and /register <password> <password> before the timer runs out. Manage in{" "} Access & Auth .

)} {/* admin quick controls */} {/* world + disk */}
World {server?.world.levelName ?? "world"} · {fmtBytes(server?.world.sizeBytes)}
Dimensions {server?.world.dimensions.length ? server.world.dimensions.join(" · ") : "not generated yet"}
{server?.world.seed && (
Seed {server.world.seed}
)} {server?.map.available && ( Open the live world map BlueMap ↗ )}
{m?.disk_used != null && m?.disk_free != null ? ( ) : (
disk stats unavailable
)}
); }