255 lines
9.1 KiB
TypeScript
255 lines
9.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useCallback } from "react";
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { Download, RefreshCw, AlertTriangle, CheckCircle2, Server } from "lucide-react";
|
|
import { toast } from "sonner";
|
|
|
|
const SERVER_TYPES = ["vanilla", "paper", "fabric"] as const;
|
|
type ServerType = (typeof SERVER_TYPES)[number];
|
|
|
|
interface Settings {
|
|
serverType?: string;
|
|
serverVersion?: string;
|
|
serverJar?: string;
|
|
}
|
|
|
|
export default function UpdatesPage() {
|
|
const [settings, setSettings] = useState<Settings | null>(null);
|
|
const [selectedType, setSelectedType] = useState<ServerType>("paper");
|
|
const [versions, setVersions] = useState<string[]>([]);
|
|
const [selectedVersion, setSelectedVersion] = useState("");
|
|
const [loadingSettings, setLoadingSettings] = useState(true);
|
|
const [loadingVersions, setLoadingVersions] = useState(false);
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
const fetchSettings = useCallback(async () => {
|
|
try {
|
|
const res = await fetch("/api/server/settings");
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
if (data.settings) {
|
|
setSettings(data.settings);
|
|
setSelectedType((data.settings.serverType as ServerType) ?? "paper");
|
|
setSelectedVersion(data.settings.serverVersion ?? "");
|
|
}
|
|
}
|
|
} finally {
|
|
setLoadingSettings(false);
|
|
}
|
|
}, []);
|
|
|
|
const fetchVersions = useCallback(async (type: string) => {
|
|
setLoadingVersions(true);
|
|
setVersions([]);
|
|
try {
|
|
const res = await fetch(`/api/server/versions?type=${type}`);
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
setVersions(data.versions ?? []);
|
|
} else {
|
|
toast.error("Failed to fetch versions");
|
|
}
|
|
} catch {
|
|
toast.error("Network error fetching versions");
|
|
} finally {
|
|
setLoadingVersions(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchSettings();
|
|
}, [fetchSettings]);
|
|
|
|
useEffect(() => {
|
|
fetchVersions(selectedType);
|
|
}, [selectedType, fetchVersions]);
|
|
|
|
const isUpToDate =
|
|
settings?.serverVersion === selectedVersion &&
|
|
settings?.serverType === selectedType;
|
|
|
|
async function handleApply() {
|
|
if (!selectedVersion) {
|
|
toast.error("Please select a version");
|
|
return;
|
|
}
|
|
setSaving(true);
|
|
try {
|
|
const res = await fetch("/api/server/settings", {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ serverType: selectedType, serverVersion: selectedVersion }),
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({}));
|
|
throw new Error(err.error ?? "Failed to apply");
|
|
}
|
|
setSettings((prev) => ({ ...prev, serverType: selectedType, serverVersion: selectedVersion }));
|
|
toast.success(`Server version set to ${selectedType} ${selectedVersion}`);
|
|
} catch (err) {
|
|
toast.error(err instanceof Error ? err.message : "Failed to apply version");
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
}
|
|
|
|
const currentVersion = settings?.serverVersion
|
|
? `${settings.serverType ?? "unknown"} ${settings.serverVersion}`
|
|
: "Not configured";
|
|
|
|
return (
|
|
<div className="p-6 max-w-2xl space-y-6">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-white">Server Updates</h1>
|
|
<p className="text-zinc-400 text-sm mt-1">
|
|
Manage your Minecraft server version
|
|
</p>
|
|
</div>
|
|
|
|
{/* Current version */}
|
|
<Card className="bg-zinc-900 border-zinc-800">
|
|
<CardHeader>
|
|
<CardTitle className="text-base font-medium text-zinc-300 flex items-center gap-2">
|
|
<Server className="w-4 h-4 text-emerald-500" />
|
|
Current Version
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{loadingSettings ? (
|
|
<Skeleton className="h-6 w-48 bg-zinc-800" />
|
|
) : (
|
|
<div className="flex items-center gap-3">
|
|
<span className="text-white font-mono text-sm">{currentVersion}</span>
|
|
{settings?.serverVersion && (
|
|
<Badge
|
|
className={
|
|
isUpToDate
|
|
? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20"
|
|
: "bg-amber-500/10 text-amber-400 border-amber-500/20"
|
|
}
|
|
>
|
|
{isUpToDate ? "Up to date" : "Update available"}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Version picker */}
|
|
<Card className="bg-zinc-900 border-zinc-800">
|
|
<CardHeader>
|
|
<CardTitle className="text-base font-medium text-zinc-300 flex items-center gap-2">
|
|
<Download className="w-4 h-4 text-emerald-500" />
|
|
Select Version
|
|
</CardTitle>
|
|
<CardDescription className="text-zinc-500">
|
|
Choose a server type and version to apply
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-1.5">
|
|
<label className="text-sm text-zinc-300">Server Type</label>
|
|
<Select
|
|
value={selectedType}
|
|
onValueChange={(v) => { if (v) setSelectedType(v as ServerType); }}
|
|
>
|
|
<SelectTrigger className="bg-zinc-800 border-zinc-700 text-white">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent className="bg-zinc-800 border-zinc-700">
|
|
{SERVER_TYPES.map((t) => (
|
|
<SelectItem key={t} value={t} className="text-zinc-300 focus:bg-zinc-700 focus:text-white capitalize">
|
|
{t.charAt(0).toUpperCase() + t.slice(1)}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-1.5">
|
|
<label className="text-sm text-zinc-300">Version</label>
|
|
<Select
|
|
value={selectedVersion}
|
|
onValueChange={(v) => { if (v) setSelectedVersion(v); }}
|
|
disabled={loadingVersions || versions.length === 0}
|
|
>
|
|
<SelectTrigger className="bg-zinc-800 border-zinc-700 text-white">
|
|
<SelectValue
|
|
placeholder={
|
|
loadingVersions
|
|
? "Loading…"
|
|
: versions.length === 0
|
|
? "No versions found"
|
|
: "Select version"
|
|
}
|
|
/>
|
|
</SelectTrigger>
|
|
<SelectContent className="bg-zinc-800 border-zinc-700 max-h-64">
|
|
{versions.map((v) => (
|
|
<SelectItem key={v} value={v} className="text-zinc-300 focus:bg-zinc-700 focus:text-white font-mono text-sm">
|
|
{v}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
onClick={handleApply}
|
|
disabled={saving || !selectedVersion || loadingVersions}
|
|
className="bg-emerald-600 hover:bg-emerald-500 text-white"
|
|
size="sm"
|
|
>
|
|
<Download className="w-3.5 h-3.5 mr-1.5" />
|
|
{saving ? "Applying…" : "Apply Version"}
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => fetchVersions(selectedType)}
|
|
disabled={loadingVersions}
|
|
className="border-zinc-700 text-zinc-300 hover:bg-zinc-800"
|
|
>
|
|
<RefreshCw className={`w-3.5 h-3.5 mr-1.5 ${loadingVersions ? "animate-spin" : ""}`} />
|
|
Refresh
|
|
</Button>
|
|
</div>
|
|
|
|
{selectedVersion && settings?.serverVersion && selectedVersion !== settings.serverVersion && (
|
|
<div className="flex items-start gap-2 rounded-lg border border-amber-500/30 bg-amber-500/10 p-3 text-amber-400 text-sm">
|
|
<AlertTriangle className="w-4 h-4 mt-0.5 shrink-0" />
|
|
<p>
|
|
Changing from <span className="font-mono">{settings.serverVersion}</span> to{" "}
|
|
<span className="font-mono">{selectedVersion}</span> requires a server restart.
|
|
Make sure to create a backup first.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{isUpToDate && selectedVersion && (
|
|
<div className="flex items-center gap-2 rounded-lg border border-emerald-500/30 bg-emerald-500/10 p-3 text-emerald-400 text-sm">
|
|
<CheckCircle2 className="w-4 h-4 shrink-0" />
|
|
<p>This version is already configured.</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|