"use client"; import type { GameMode, ServiceType as PrismaServiceType, ServerDifficulty } from "@minikura/db"; import { Info, Plus, Trash2 } from "lucide-react"; import { useState } from "react"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Textarea } from "@/components/ui/textarea"; export type ServerType = "VANILLA" | "PAPER" | "SPIGOT" | "PURPUR" | "FABRIC" | "CUSTOM"; export type ServiceType = PrismaServiceType; export type Difficulty = ServerDifficulty; export type Mode = GameMode; export interface EnvVar { id?: string; key: string; value: string; } export interface ServerFormData { // Basic Configuration id: string; description: string; memoryLimit: string; memoryRequest: string; cpuRequest: string; cpuLimit: string; // Server Type & Version type: ServerType; version?: string; customJarUrl?: string; eula: boolean; // Network Configuration listenPort: string; serviceType: ServiceType; nodePort?: string; // Server Properties motd?: string; difficulty: "peaceful" | "easy" | "normal" | "hard"; mode: "survival" | "creative" | "adventure" | "spectator"; maxPlayers: string; pvp: boolean; onlineMode: boolean; allowFlight: boolean; enableCommandBlock: boolean; spawnProtection: string; viewDistance: string; simulationDistance: string; // World Configuration levelName: string; levelSeed?: string; levelType?: string; generatorSettings?: string; hardcore: boolean; spawnAnimals: boolean; spawnMonsters: boolean; spawnNpcs: boolean; // Player Management enableWhitelist: boolean; whitelist?: string; whitelistFile?: string; ops?: string; opsFile?: string; // JVM & Performance useAikarFlags: boolean; useMeowiceFlags: boolean; jvmOpts?: string; jvmXxOpts?: string; jvmDdOpts?: string; // Resource Pack resourcePack?: string; resourcePackSha1?: string; resourcePackEnforce: boolean; // RCON Configuration enableRcon: boolean; rconPassword?: string; rconPort: string; rconCmdsStartup?: string; rconCmdsOnConnect?: string; rconCmdsFirstConnect?: string; rconCmdsOnDisconnect?: string; rconCmdsLastDisconnect?: string; // Query Protocol enableQuery: boolean; queryPort: string; // Auto-Pause enableAutopause: boolean; autopauseTimeoutEst: string; autopauseTimeoutInit: string; autopauseTimeoutKn: string; autopausePeriod: string; autopauseKnockInterface: string; // Auto-Stop enableAutostop: boolean; autostopTimeoutEst: string; autostopTimeoutInit: string; autostopPeriod: string; // Mods & Plugins plugins?: string; removeOldPlugins: boolean; spigetResources?: string; // Type-Specific Options paperBuild?: string; // Advanced timezone: string; uid: string; gid: string; enableJmx: boolean; stopDuration: string; serverIcon?: string; // Environment Variables envVars: EnvVar[]; } const toMode = (value: string): ServerFormData["mode"] => { if (value === "creative" || value === "adventure" || value === "spectator") { return value; } return "survival"; }; const toDifficulty = (value: string): ServerFormData["difficulty"] => { if (value === "peaceful" || value === "normal" || value === "hard") { return value; } return "easy"; }; interface ServerFormProps { initialData?: Partial; onSubmit: (data: ServerFormData) => Promise; onCancel: () => void; submitLabel?: string; loading?: boolean; } const InfoTooltip = ({ text }: { text: string }) => (
{text}
); export function ServerForm({ initialData, onSubmit, onCancel, submitLabel = "Create Server", loading = false, }: ServerFormProps) { const defaultType = initialData?.type || "PAPER"; const defaultVersion = defaultType === "CUSTOM" ? "" : initialData?.version || "LATEST"; const [formData, setFormData] = useState({ id: initialData?.id || "", description: initialData?.description || "", memoryLimit: initialData?.memoryLimit || "2048", memoryRequest: initialData?.memoryRequest || "1024", cpuRequest: initialData?.cpuRequest || "500m", cpuLimit: initialData?.cpuLimit || "2", type: defaultType, version: defaultVersion, eula: initialData?.eula ?? true, listenPort: initialData?.listenPort || "25565", serviceType: initialData?.serviceType || "CLUSTER_IP", difficulty: initialData?.difficulty || "easy", mode: initialData?.mode || "survival", maxPlayers: initialData?.maxPlayers || "20", pvp: initialData?.pvp ?? true, onlineMode: initialData?.onlineMode ?? true, allowFlight: initialData?.allowFlight ?? false, enableCommandBlock: initialData?.enableCommandBlock ?? false, spawnProtection: initialData?.spawnProtection || "16", viewDistance: initialData?.viewDistance || "10", simulationDistance: initialData?.simulationDistance || "10", levelName: initialData?.levelName || "world", hardcore: initialData?.hardcore ?? false, spawnAnimals: initialData?.spawnAnimals ?? true, spawnMonsters: initialData?.spawnMonsters ?? true, spawnNpcs: initialData?.spawnNpcs ?? true, enableWhitelist: initialData?.enableWhitelist ?? false, useAikarFlags: initialData?.useAikarFlags ?? false, useMeowiceFlags: initialData?.useMeowiceFlags ?? false, resourcePackEnforce: initialData?.resourcePackEnforce ?? false, enableRcon: initialData?.enableRcon ?? true, rconPort: initialData?.rconPort || "25575", enableQuery: initialData?.enableQuery ?? false, queryPort: initialData?.queryPort || "25565", enableAutopause: initialData?.enableAutopause ?? false, autopauseTimeoutEst: initialData?.autopauseTimeoutEst || "3600", autopauseTimeoutInit: initialData?.autopauseTimeoutInit || "600", autopauseTimeoutKn: initialData?.autopauseTimeoutKn || "120", autopausePeriod: initialData?.autopausePeriod || "10", autopauseKnockInterface: initialData?.autopauseKnockInterface || "eth0", enableAutostop: initialData?.enableAutostop ?? false, autostopTimeoutEst: initialData?.autostopTimeoutEst || "3600", autostopTimeoutInit: initialData?.autostopTimeoutInit || "1800", autostopPeriod: initialData?.autostopPeriod || "10", removeOldPlugins: initialData?.removeOldPlugins ?? false, timezone: initialData?.timezone || "UTC", uid: initialData?.uid || "1000", gid: initialData?.gid || "1000", enableJmx: initialData?.enableJmx ?? false, stopDuration: initialData?.stopDuration || "60", customJarUrl: initialData?.customJarUrl || "", envVars: (initialData?.envVars || []).map((envVar) => ({ id: envVar.id || crypto.randomUUID(), key: envVar.key, value: envVar.value, })), }); const [error, setError] = useState(null); const updateField = (key: K, value: ServerFormData[K]) => { setFormData((prev) => ({ ...prev, [key]: value })); }; const addEnvVar = () => { setFormData((prev) => ({ ...prev, envVars: [...prev.envVars, { id: crypto.randomUUID(), key: "", value: "" }], })); }; const removeEnvVar = (index: number) => { setFormData((prev) => ({ ...prev, envVars: prev.envVars.filter((_, i) => i !== index), })); }; const updateEnvVar = (index: number, field: "key" | "value", value: string) => { setFormData((prev) => { const updated = [...prev.envVars]; updated[index] = { ...updated[index], [field]: value }; return { ...prev, envVars: updated }; }); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); // Basic validation if (!formData.id.trim()) { setError("Server ID is required"); return; } if (!/^[a-zA-Z0-9-_]+$/.test(formData.id)) { setError("ID must be alphanumeric with - or _"); return; } if (!formData.eula) { setError("You must accept the Minecraft EULA to create a server"); return; } if (formData.type === "CUSTOM" && !formData.customJarUrl?.trim()) { setError("Custom jar URL is required for custom servers"); return; } try { await onSubmit(formData); } catch (err) { setError(err instanceof Error ? err.message : "An error occurred"); } }; return (
Basic Server World Players Performance Mods/Plugins Resources Automation Network Advanced {/* Basic Configuration */}
updateField("id", e.target.value)} placeholder="my-server" required />