import { useState, useEffect, useRef, useCallback } from "react"; import { Editor as ReactMonacoEditor } from "@monaco-editor/react"; import { api } from "../services/api"; import { useWebSocket } from "../hooks/useWebSocket"; type ServerState = "running" | "exited" | "not created" | string; function StatusBadge({ label, state }: { label: string; state: ServerState }) { const color = state === "running" ? "bg-green-500/20 text-green-400" : state === "exited" ? "bg-red-500/20 text-red-400" : "bg-gray-500/20 text-gray-400"; return (
{label}: {state}
); } function ControlsPanel() { const [status, setStatus] = useState<{ nwserver: string; mariadb: string }>({ nwserver: "unknown", mariadb: "unknown", }); const [loading, setLoading] = useState(null); const fetchStatus = useCallback(async () => { try { const s = await api.server.status(); setStatus(s); } catch { /* server might not be reachable */ } }, []); useEffect(() => { fetchStatus(); const interval = setInterval(fetchStatus, 10000); return () => clearInterval(interval); }, [fetchStatus]); const handleAction = async (action: string) => { setLoading(action); try { if (action === "start") await api.server.start(); else if (action === "stop") await api.server.stop(); else if (action === "restart") await api.server.restart(); else if (action === "config") await api.server.generateConfig(); await fetchStatus(); } catch { /* error handled by status refresh */ } setLoading(null); }; return (

Server Controls

{(["start", "stop", "restart", "config"] as const).map((action) => ( ))}
); } function LogViewer() { const { subscribe } = useWebSocket(); const [lines, setLines] = useState([]); const [autoScroll, setAutoScroll] = useState(true); const [filter, setFilter] = useState(""); const scrollRef = useRef(null); useEffect(() => { return subscribe("log:server", (event) => { const data = event.data as { text?: string }; if (!data?.text) return; setLines((prev) => [ ...prev, ...data.text!.split("\n").filter(Boolean), ]); }); }, [subscribe]); useEffect(() => { if (autoScroll && scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [lines, autoScroll]); const filteredLines = filter ? lines.filter((l) => l.toLowerCase().includes(filter.toLowerCase())) : lines; return (

Server Logs

setFilter(e.target.value)} placeholder="Filter logs..." className="rounded border px-2 py-1 text-xs" style={{ backgroundColor: "var(--forge-bg)", borderColor: "var(--forge-border)", color: "var(--forge-text)", width: "200px", }} />
{filteredLines.length === 0 ? ( Waiting for log output... ) : ( filteredLines.map((line, i) => (
{line}
)) )}
); } function SQLConsole() { const [query, setQuery] = useState("SELECT * FROM player_tracking LIMIT 10;"); const [result, setResult] = useState<{ columns: string[]; rows: Record[]; } | null>(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const [history, setHistory] = useState([]); const execute = async () => { if (!query.trim()) return; setLoading(true); setError(null); try { const res = await api.server.sql(query); setResult(res); setHistory((prev) => [query, ...prev.filter((q) => q !== query)].slice(0, 10)); } catch (err: any) { setError(err.message); setResult(null); } setLoading(false); }; return (

SQL Console

{history.length > 0 && ( )}
setQuery(v ?? "")} options={{ minimap: { enabled: false }, fontSize: 13, lineNumbers: "off", scrollBeyondLastLine: false, automaticLayout: true, wordWrap: "on", padding: { top: 4, bottom: 4 }, renderLineHighlight: "none", overviewRulerLanes: 0, hideCursorInOverviewRuler: true, scrollbar: { vertical: "hidden", horizontal: "hidden" }, }} />
{error && (
{error}
)} {result && (
{result.columns.length === 0 ? (
Query executed successfully (no results)
) : ( {result.columns.map((col) => ( ))} {result.rows.map((row, i) => ( {result.columns.map((col) => ( ))} ))}
{col}
{row[col]}
)}
{result.rows.length} row{result.rows.length !== 1 ? "s" : ""}
)}
); } export function Server() { return (

Server Management

); }