/** * gameProfiles.ts — Source of truth for per-game UI adaptation. * * Every game-specific label, terminology, Steam app ID, management model, * stat field list, AND sidebar nav lives here. The dashboard, server cards, * wipe manager, sidebar, and any future multi-game surface should key off this * registry — never hard-code game-specific strings in components. * * Backend status: the backend has NO game field on licenses yet. Today every * license is implicitly Rust. This registry is ready: when the backend adds a * `game` column to `licenses` (or `server_config`), the frontend only needs to * read that field and call `useGameProfile(id)` — no component changes required. * * To add a new game: add a GameId union member and a corresponding entry in * GAME_PROFILES. Nothing else changes. */ // --------------------------------------------------------------------------- // Nav structure — drives the per-game sidebar // --------------------------------------------------------------------------- /** A single sidebar nav item. route must be an existing panel route path. */ export interface NavItemDef { label: string route: string icon: string /** Permission key required to show this item (e.g. 'plugins.view'). Null = always visible. */ permission: string | null } /** A labelled section grouping nav items in the sidebar. */ export interface NavSection { /** Section heading (eyebrow text). Empty string = no heading. */ label: string items: NavItemDef[] } // --------------------------------------------------------------------------- // Union types — exhaustive, never widen to string // --------------------------------------------------------------------------- /** Every supported game identifier. */ export type GameId = 'rust' | 'conan' | 'soulmask' | 'dune' /** How the server process is managed. */ export type ManagementModel = 'process+rcon' | 'docker-compose' /** Mod ecosystem the game uses. */ export type ModSystem = 'umod' | 'workshop' | 'none' /** Primary console / remote-admin interface. */ export type ConsoleType = 'rcon' | 'rcon+ingame' | 'rcon+gm' | 'rabbitmq' /** * How a "reset" is performed — each value maps to a distinct wipe code path. * Pipe-delimited strings intentionally encode composite operations. */ export type ResetModel = | 'map-bp-wipe' | 'wipe-world-structures+decay' | 'worlddb-delete+decay' | 'deep-desert-coriolis-seed' /** Cross-server or character-sharing mechanism. */ export type ClusteringModel = 'none' | 'character-transfer' | 'main-client' | 'battlegroup' // --------------------------------------------------------------------------- // GameProfile shape // --------------------------------------------------------------------------- export interface GameTerminology { /** What the operator calls a reset / wipe. */ reset: string /** What the operator calls plugins / mods (null if no mod system). */ mods: string | null /** What the operator calls a player group / faction. */ group: string } export interface GamePorts { game: number query: number rcon: number cluster?: number } export interface GameProfile { /** Human-readable game name. */ label: string /** CSS design-token key — maps to data-game attr and --accent token. */ accent: string managementModel: ManagementModel steamAppId: number | { windows: number; linux: number } /** Default ports (game-specific defaults; operator can override). */ ports?: GamePorts mods: ModSystem console: ConsoleType resetModel: ResetModel clustering: ClusteringModel /** Available map names, if the game ships with named maps. */ maps?: string[] terminology: GameTerminology /** Notable game-specific mechanics that affect server administration. */ special?: string[] /** * Stat field labels shown on server cards and the dashboard. * First entry is always Players; subsequent entries are game-specific. */ statFields: [string, string, string] /** * Per-game sidebar navigation. Ordered list of sections, each with items. * Items MUST use only existing panel routes (see router/index.ts). * The sidebar renders exactly these sections for the active game. */ nav: NavSection[] } // --------------------------------------------------------------------------- // Registry // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // Shared nav building blocks — reused across game nav definitions // --------------------------------------------------------------------------- const NAV_DASHBOARD: NavItemDef = { label: 'Dashboard', route: '/', icon: 'layout-dashboard', permission: null } const NAV_SERVER: NavItemDef = { label: 'Server', route: '/server', icon: 'server', permission: 'server.view' } const NAV_CONSOLE: NavItemDef = { label: 'Console', route: '/console', icon: 'terminal', permission: 'console.view' } const NAV_PLAYERS: NavItemDef = { label: 'Players', route: '/players', icon: 'users', permission: 'players.view' } const NAV_PLUGINS: NavItemDef = { label: 'Plugins (uMod)', route: '/plugins', icon: 'puzzle', permission: 'plugins.view' } const NAV_FILES: NavItemDef = { label: 'File manager', route: '/files', icon: 'folder-open', permission: 'files.view' } const NAV_PLUGIN_CONFIGS: NavItemDef = { label: 'Plugin configs', route: '/plugin-configs', icon: 'sliders', permission: null } const NAV_SCHEDULES: NavItemDef = { label: 'Schedules', route: '/schedules', icon: 'calendar-clock', permission: 'schedules.view' } const NAV_CHAT: NavItemDef = { label: 'Chat log', route: '/chat', icon: 'message-square', permission: 'chat.view' } const NAV_ANALYTICS: NavItemDef = { label: 'Analytics', route: '/analytics', icon: 'bar-chart-3', permission: 'analytics.view' } const NAV_ALERTS: NavItemDef = { label: 'Alerts', route: '/alerts', icon: 'triangle-alert', permission: 'alerts.view' } const NAV_NOTIFICATIONS: NavItemDef = { label: 'Notifications', route: '/notifications', icon: 'bell', permission: 'notifications.view' } const NAV_TEAM: NavItemDef = { label: 'Team', route: '/team', icon: 'users', permission: null } const NAV_STORE: NavItemDef = { label: 'Store', route: '/store/config', icon: 'shopping-cart', permission: 'store.view' } const NAV_MODULES: NavItemDef = { label: 'Modules', route: '/modules', icon: 'layers', permission: 'modules.view' } const NAV_CHANGELOG: NavItemDef = { label: 'Changelog', route: '/changelog', icon: 'file-text', permission: 'changelog.view' } const NAV_SETTINGS: NavItemDef = { label: 'Settings', route: '/settings', icon: 'settings', permission: 'settings.view' } const NAV_MAPS: NavItemDef = { label: 'Maps', route: '/maps', icon: 'map', permission: 'maps.view' } /** Full Rust / 'all' nav — superset used as fallback. */ const RUST_NAV: NavSection[] = [ { label: '', items: [NAV_DASHBOARD] }, { label: 'Server', items: [NAV_SERVER, NAV_CONSOLE, NAV_PLAYERS, NAV_PLUGINS, NAV_FILES], }, { label: 'Plugin configs', items: [NAV_PLUGIN_CONFIGS] }, { label: 'Operations', items: [ { label: 'Wipe', route: '/wipes', icon: 'trash-2', permission: 'wipes.view' }, NAV_MAPS, NAV_SCHEDULES, ], }, { label: 'Monitoring', items: [NAV_CHAT, NAV_ANALYTICS, NAV_ALERTS, NAV_NOTIFICATIONS], }, { label: 'Management', items: [NAV_TEAM, NAV_STORE, NAV_MODULES, NAV_CHANGELOG, NAV_SETTINGS], }, ] export const GAME_PROFILES: Record = { rust: { label: 'Rust', accent: 'rust', managementModel: 'process+rcon', steamAppId: 258550, mods: 'umod', console: 'rcon', resetModel: 'map-bp-wipe', clustering: 'none', terminology: { reset: 'Wipe', mods: 'Plugins', group: 'Team', }, statFields: ['Players', 'uMod', 'Wipe'], nav: RUST_NAV, }, conan: { label: 'Conan Exiles', accent: 'conan', managementModel: 'process+rcon', steamAppId: 443030, ports: { game: 7777, query: 27015, rcon: 25575 }, mods: 'workshop', console: 'rcon+ingame', // Player progress persists across world wipes — only structures are cleared. resetModel: 'wipe-world-structures+decay', clustering: 'character-transfer', maps: ['Exiled Lands', 'Isle of Siptah'], terminology: { reset: 'Wipe World', mods: 'Mods', group: 'Clan', }, special: ['Clans', 'Thralls', 'Avatars', 'Purge', 'PvP windows'], statFields: ['Players', 'Clans', 'Purge'], nav: [ { label: '', items: [NAV_DASHBOARD] }, { label: 'Server', // Conan: no uMod/Oxide; has RCON console, maps, players, files items: [NAV_SERVER, NAV_CONSOLE, NAV_PLAYERS, NAV_FILES], }, { label: 'Operations', items: [ { label: 'Wipe World', route: '/wipes', icon: 'trash-2', permission: 'wipes.view' }, NAV_MAPS, NAV_SCHEDULES, ], }, { label: 'Monitoring', items: [NAV_CHAT, NAV_ANALYTICS, NAV_ALERTS, NAV_NOTIFICATIONS], }, { label: 'Management', items: [NAV_TEAM, NAV_STORE, NAV_MODULES, NAV_CHANGELOG, NAV_SETTINGS], }, ], }, soulmask: { label: 'Soulmask', accent: 'soulmask', managementModel: 'process+rcon', // Different Steam app IDs per OS (uncommon — store this explicitly). steamAppId: { windows: 3017310, linux: 3017300 }, ports: { game: 8777, query: 27015, rcon: 19000, cluster: 20000 }, mods: 'workshop', console: 'rcon+gm', resetModel: 'worlddb-delete+decay', clustering: 'main-client', maps: ['Cloud Mist Forest', 'Shifting Sands'], terminology: { reset: 'World Reset', mods: 'Workshop Mods', group: 'Tribe', }, special: ['Cluster', 'Tribes'], statFields: ['Players', 'Tribe', 'Mask'], nav: [ { label: '', items: [NAV_DASHBOARD] }, { label: 'Server', // Soulmask: no uMod/Oxide; has RCON+GM console, players, files items: [NAV_SERVER, NAV_CONSOLE, NAV_PLAYERS, NAV_FILES], }, { label: 'Operations', items: [ { label: 'World Reset', route: '/wipes', icon: 'trash-2', permission: 'wipes.view' }, NAV_SCHEDULES, ], }, { label: 'Monitoring', items: [NAV_CHAT, NAV_ANALYTICS, NAV_ALERTS], }, { label: 'Management', items: [NAV_TEAM, NAV_STORE, NAV_MODULES, NAV_CHANGELOG, NAV_SETTINGS], }, ], }, dune: { label: 'Dune: Awakening', accent: 'dune', managementModel: 'docker-compose', steamAppId: 4754530, mods: 'none', // Dune uses RabbitMQ for its admin messaging — not a standard RCON port. console: 'rabbitmq', resetModel: 'deep-desert-coriolis-seed', clustering: 'battlegroup', terminology: { reset: 'Deep Desert reset', mods: null, group: 'Guild', }, special: ['Sietches', 'Deep Desert', 'Bases', 'Landsraad'], statFields: ['Players', 'Sietches', 'Control'], nav: [ { label: '', items: [NAV_DASHBOARD] }, { label: 'Server', // Dune: no RCON (uses RabbitMQ); label console "Broadcast"; no maps route; no plugins items: [ NAV_SERVER, { label: 'Broadcast', route: '/console', icon: 'radio', permission: 'console.view' }, NAV_PLAYERS, NAV_FILES, ], }, { label: 'Operations', items: [ { label: 'Deep Desert', route: '/wipes', icon: 'wind', permission: 'wipes.view' }, NAV_SCHEDULES, ], }, { label: 'Monitoring', items: [NAV_ANALYTICS, NAV_ALERTS], }, { label: 'Management', items: [NAV_TEAM, NAV_STORE, NAV_CHANGELOG, NAV_SETTINGS], }, ], }, } as const // --------------------------------------------------------------------------- // Helper // --------------------------------------------------------------------------- /** * Returns the GameProfile for the given id, falling back to Rust if the id is * unknown (forward-compatibility: unknown games show Rust defaults until their * profile is added). * * @example * const profile = useGameProfile('rust') * console.log(profile.terminology.reset) // 'Wipe' */ export function useGameProfile(id: string): GameProfile { return (GAME_PROFILES as Record)[id] ?? GAME_PROFILES.rust }