"use client"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; import { ScheduledTasks } from "@/components/ScheduledTasks"; import { StatusBadge, statusFromServer } from "@/components/StatusBadge"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; type ServerStatus = { online: boolean; starting?: boolean; players: { online: number; max: number }; version?: string; motd?: string; }; type Action = "start" | "stop" | "restart"; const ACTION_LABEL: Record = { start: "Start", stop: "Stop", restart: "Restart", }; export function ServerControls() { const queryClient = useQueryClient(); const [confirm, setConfirm] = useState(null); const { data: status, isLoading } = useQuery({ queryKey: ["status"], queryFn: () => fetch("/api/status").then((r) => r.json()), refetchInterval: 60_000, staleTime: 5000, }); const action = useMutation({ mutationFn: async (act: Action) => { const res = await fetch(`/api/server/${act}`, { method: "POST" }); if (!res.ok) { const data = await res.json(); throw new Error(data.error || "Request failed"); } return { ...(await res.json()), act }; }, onSuccess: (_, act) => { toast.success(`${ACTION_LABEL[act]} command sent`, { description: "Status will update in a few seconds.", }); setTimeout( () => queryClient.invalidateQueries({ queryKey: ["status"] }), 3000 ); }, onError: (err, act) => { toast.error(`${ACTION_LABEL[act]} failed`, { description: err.message }); }, }); const isOnline = status?.online ?? false; const trigger = (act: Action) => { if (act === "start") { action.mutate(act); } else { setConfirm(act); } }; const confirmRun = () => { if (confirm) { action.mutate(confirm); setConfirm(null); } }; return (
Server Controls Manage the Minecraft server process
{isLoading || !status ? ( ) : ( )}
{/* Stats row */}

Players

{isLoading || !status ? ( ) : (

{isOnline ? `${status.players.online}/${status.players.max}` : "-"}

)}

Version

{isLoading || !status ? ( ) : (

{status.version || "-"}

)}

Address

minecraft.hurkicorgi.com

{/* Confirmation */} {confirm && ( {confirm === "stop" ? "Stop the server? Players will be disconnected." : "Restart the server? Players will be disconnected briefly."} )} {/* Action buttons */}
{/* Feedback */} {action.isPending && ( Sending {action.variables ? ACTION_LABEL[action.variables] : ""} command... )} {/* success/error surfaced via toast */} {/* Scheduled restart */} {/* Additional scheduled tasks */}
); } function ScheduledRestart() { const { data: schedule } = useQuery<{ enabled: boolean; hour: number; minute: number }>({ queryKey: ["schedule"], queryFn: () => fetch("/api/schedule").then((r) => r.json()), staleTime: 30_000, initialData: { enabled: false, hour: 4, minute: 0 }, }); const [hour, setHour] = useState(null); const [minute, setMinute] = useState(null); const h = hour ?? schedule.hour; const m = minute ?? schedule.minute; const update = useMutation({ mutationFn: async (params: { enabled: boolean; hour: number; minute: number }) => { const res = await fetch("/api/schedule", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params), }); return res.json(); }, }); const pad = (n: number) => String(n).padStart(2, "0"); return (

Scheduled Restart

{schedule.enabled ? `Daily at ${pad(schedule.hour)}:${pad(schedule.minute)} server time — warns players before restarting` : "Set a time for daily auto-restart with player warnings (uses server's local time)"}

: server time {schedule.enabled ? ( ) : ( )}
); }