diff --git a/gsm-frontend/public/openttd.png b/gsm-frontend/public/openttd.png new file mode 100644 index 0000000..0064c3d Binary files /dev/null and b/gsm-frontend/public/openttd.png differ diff --git a/gsm-frontend/public/terraria.png b/gsm-frontend/public/terraria.png new file mode 100644 index 0000000..0d301a2 Binary files /dev/null and b/gsm-frontend/public/terraria.png differ diff --git a/gsm-frontend/src/api.js b/gsm-frontend/src/api.js index 7d48674..b89092b 100644 --- a/gsm-frontend/src/api.js +++ b/gsm-frontend/src/api.js @@ -9,6 +9,13 @@ async function fetchAPI(endpoint, options = {}) { }, }) + // Auto-logout on auth errors (invalid/expired token) + if (response.status === 401 || response.status === 403) { + localStorage.removeItem('gsm_token') + window.location.href = '/' + throw new Error('Session expired') + } + if (!response.ok) { const error = await response.json().catch(() => ({ message: 'Request failed' })) throw new Error(error.message || `HTTP ${response.status}`) @@ -243,6 +250,36 @@ export async function savePalworldConfig(token, filename, content) { }) } +// Terraria Config Management +export async function getTerrariaConfig(token) { + return fetchAPI('/servers/terraria/config', { + headers: { Authorization: `Bearer ${token}` }, + }) +} + +export async function saveTerrariaConfig(token, content) { + return fetchAPI('/servers/terraria/config', { + method: 'PUT', + headers: { Authorization: `Bearer ${token}` }, + body: JSON.stringify({ content }), + }) +} + +// OpenTTD Config Management +export async function getOpenTTDConfig(token) { + return fetchAPI('/servers/openttd/config', { + headers: { Authorization: `Bearer ${token}` }, + }) +} + +export async function saveOpenTTDConfig(token, content) { + return fetchAPI('/servers/openttd/config', { + method: 'PUT', + headers: { Authorization: `Bearer ${token}` }, + body: JSON.stringify({ content }), + }) +} + // Activity Log export async function getActivityLog(token, limit = 100) { return fetchAPI(`/servers/activity-log?limit=${limit}`, { diff --git a/gsm-frontend/src/components/OpenTTDConfigEditor.jsx b/gsm-frontend/src/components/OpenTTDConfigEditor.jsx new file mode 100644 index 0000000..b30dfa1 --- /dev/null +++ b/gsm-frontend/src/components/OpenTTDConfigEditor.jsx @@ -0,0 +1,244 @@ +import { useState, useEffect, useRef } from 'react' +import { FileText, Save, RefreshCw, AlertTriangle, Check, X } from 'lucide-react' +import { getOpenTTDConfig, saveOpenTTDConfig } from '../api' + +export default function OpenTTDConfigEditor({ token }) { + const [content, setContent] = useState('') + const [originalContent, setOriginalContent] = useState('') + const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) + const [error, setError] = useState(null) + const [success, setSuccess] = useState(null) + const [hasChanges, setHasChanges] = useState(false) + const textareaRef = useRef(null) + const highlightRef = useRef(null) + + useEffect(() => { + loadConfig() + }, [token]) + + useEffect(() => { + setHasChanges(content !== originalContent) + }, [content, originalContent]) + + const handleScroll = () => { + if (highlightRef.current && textareaRef.current) { + highlightRef.current.scrollTop = textareaRef.current.scrollTop + highlightRef.current.scrollLeft = textareaRef.current.scrollLeft + } + } + + async function loadConfig() { + setLoading(true) + setError(null) + try { + const data = await getOpenTTDConfig(token) + setContent(data.content) + setOriginalContent(data.content) + } catch (err) { + setError('Fehler beim Laden der Config: ' + err.message) + } finally { + setLoading(false) + } + } + + async function handleSave() { + if (!hasChanges) return + + setSaving(true) + setError(null) + setSuccess(null) + try { + await saveOpenTTDConfig(token, content) + setOriginalContent(content) + setSuccess('Config gespeichert! Server-Neustart erforderlich.') + setTimeout(() => setSuccess(null), 5000) + } catch (err) { + setError('Fehler beim Speichern: ' + err.message) + } finally { + setSaving(false) + } + } + + function handleDiscard() { + setContent(originalContent) + setError(null) + setSuccess(null) + } + + function highlightSyntax(text) { + if (!text) return '' + + const lines = text.split('\n') + + return lines.map((line) => { + let highlighted = line + .replace(/&/g, '&') + .replace(//g, '>') + + // Section headers [section] + if (line.trim().startsWith('[') && line.trim().endsWith(']')) { + highlighted = `${highlighted}` + } + // Comments (;) + else if (line.trim().startsWith(';')) { + highlighted = `${highlighted}` + } + // key = value + else if (line.includes('=')) { + const idx = line.indexOf('=') + const key = highlighted.substring(0, idx) + const value = highlighted.substring(idx + 1) + + // Color numbers, true/false, and quoted strings + let coloredValue = value + .replace(/\b(true|false)\b/gi, '$1') + .replace(/\b(\d+)\b/g, '$1') + + highlighted = `${key}=${coloredValue}` + } + + return highlighted + }).join('\n') + } + + if (loading) { + return ( +
+ + Lade Config... +
+ ) + } + + return ( +
+ {/* Header */} +
+
+ + openttd.cfg - Server-Einstellungen +
+ +
+ + {/* Error/Success messages */} + {error && ( +
+ + {error} +
+ )} + + {success && ( +
+ + {success} +
+ )} + + {/* Editor */} +
+