import { useEffect, useState } from 'react'; import { useParams } from 'react-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Settings2, FileText, Save } from 'lucide-react'; import { api } from '@/lib/api'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; interface ConfigFile { index: number; path: string; parser: string; editableKeys: string[] | null; } interface ConfigEntry { key: string; value: string; } interface ConfigDetail { path: string; parser: string; editableKeys: string[] | null; entries: ConfigEntry[]; raw: string; } function mergeConfigEntries( entries: ConfigEntry[], editableKeys: string[] | null, ): ConfigEntry[] { if (!editableKeys || editableKeys.length === 0) return entries; const existing = new Map(entries.map((entry) => [entry.key, entry])); const merged = [...entries]; for (const key of editableKeys) { if (!existing.has(key)) { merged.push({ key, value: '' }); } } return merged; } export function ConfigPage() { const { orgId, serverId } = useParams(); const queryClient = useQueryClient(); const { data: configsData } = useQuery({ queryKey: ['configs', orgId, serverId], queryFn: () => api.get<{ configs: ConfigFile[] }>( `/organizations/${orgId}/servers/${serverId}/config`, ), }); const configs = configsData?.configs ?? []; if (configs.length === 0) { return ( No config files available for this game ); } return ( {configs.map((cf) => ( {cf.path.split('/').pop()} ))} {configs.map((cf) => ( ))} ); } function ConfigEditor({ orgId, serverId, configIndex, configFile, }: { orgId: string; serverId: string; configIndex: number; configFile: ConfigFile; }) { const queryClient = useQueryClient(); const { data: detail } = useQuery({ queryKey: ['config-detail', orgId, serverId, configIndex], queryFn: () => api.get( `/organizations/${orgId}/servers/${serverId}/config/${configIndex}`, ), }); const [entries, setEntries] = useState([]); useEffect(() => { if (!detail) return; setEntries(mergeConfigEntries(detail.entries, configFile.editableKeys)); }, [detail, configFile.editableKeys]); const saveMutation = useMutation({ mutationFn: (data: { entries: ConfigEntry[] }) => api.put( `/organizations/${orgId}/servers/${serverId}/config/${configIndex}`, data, ), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['config-detail', orgId, serverId, configIndex], }); }, }); const updateEntry = (key: string, value: string) => { setEntries((prev) => prev.map((e) => (e.key === key ? { ...e, value } : e)), ); }; return ( {configFile.path} {configFile.parser} {configFile.editableKeys ? `${configFile.editableKeys.length} allowed additions, plus existing keys` : 'All detected keys editable'} saveMutation.mutate({ entries })} disabled={saveMutation.isPending} > {saveMutation.isPending ? 'Saving...' : 'Save'} {entries.length === 0 ? ( {detail ? 'No entries found. The server may need to be started first to generate config files.' : 'Loading...'} ) : ( {entries.map((entry) => ( {entry.key} updateEntry(entry.key, e.target.value)} className="font-mono text-sm" /> ))} )} {saveMutation.isSuccess && ( Config saved successfully )} {saveMutation.isError && ( Failed to save config )} ); }
No config files available for this game
{detail ? 'No entries found. The server may need to be started first to generate config files.' : 'Loading...'}
Config saved successfully
Failed to save config