diff --git a/frontend/src/components/ds/data/PlayersChart.vue b/frontend/src/components/ds/data/PlayersChart.vue index 834cb9e..8ee8c3c 100644 --- a/frontend/src/components/ds/data/PlayersChart.vue +++ b/frontend/src/components/ds/data/PlayersChart.vue @@ -1,10 +1,15 @@ + + diff --git a/frontend/src/config/gameProfiles.ts b/frontend/src/config/gameProfiles.ts new file mode 100644 index 0000000..7512692 --- /dev/null +++ b/frontend/src/config/gameProfiles.ts @@ -0,0 +1,191 @@ +/** + * gameProfiles.ts — Source of truth for per-game UI adaptation. + * + * Every game-specific label, terminology, Steam app ID, management model, + * and stat field list lives here. The dashboard, server cards, wipe manager, + * 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. + */ + +// --------------------------------------------------------------------------- +// 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] +} + +// --------------------------------------------------------------------------- +// Registry +// --------------------------------------------------------------------------- + +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'], + }, + + 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'], + }, + + 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'], + }, + + 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'], + }, +} 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 +} diff --git a/frontend/src/views/admin/DashboardView.vue b/frontend/src/views/admin/DashboardView.vue index a59023a..075b57e 100644 --- a/frontend/src/views/admin/DashboardView.vue +++ b/frontend/src/views/admin/DashboardView.vue @@ -1,136 +1,92 @@