import { db } from '../db/index.js'; import { getServer, listServers } from './servers.js'; import { observationReader, serverObservation } from './status.js'; import { config, rconConfigured } from '../config.js'; import { runningVersions } from '../integrations/plutonium.js'; import { listHosts } from '../hosts/index.js'; import type { NativeInstance } from '../backends/native.js'; import { serverOperations } from './overview.js'; import { hostListStatus } from '../../ui/host-overview.js'; import { playerCapacityReader } from './player-capacity.js'; export function fleetSnapshot(serverName?: string) { const bindings = serverName === undefined ? [] : [serverName]; const rconHost = config.rcon.host; const hosts = listHosts(); const serverNow = Date.now(); const hostsById = new Map(hosts.map(({ inventory: _inventory, ...host }) => [host.id, host])); const versions = new Map( runningVersions(serverName).map((version) => [version.server, version]) ); const instances = new Map( ( db() .prepare( `SELECT * FROM native_instances WHERE retired_at IS NULL ${serverName === undefined ? '' : 'AND server = ?'}` ) .all(...bindings) as NativeInstance[] ).map((instance) => [instance.server, instance]) ); const holds = new Map(); for (const row of db() .prepare( `SELECT bsv.server, s.target_rel_path AS target, b.id AS betaId FROM beta_servers bsv JOIN betas b ON b.id=bsv.beta_id JOIN beta_server_scripts bs ON bs.beta_id=b.id AND bs.server=bsv.server JOIN scripts s ON s.id=bs.script_id WHERE bsv.server_deleted_at IS NULL AND b.state IN ('running','ready-promote') ${serverName === undefined ? '' : 'AND bsv.server = ?'}` ) .all(...bindings) as { server: string; target: string; betaId: number }[]) { const entries = holds.get(row.server) ?? []; entries.push({ target: row.target, betaId: row.betaId }); holds.set(row.server, entries); } const observe = serverName === undefined ? observationReader() : serverObservation; const capacity = playerCapacityReader(serverName); const selected = serverName === undefined ? undefined : getServer(serverName); const rows = serverName === undefined ? listServers() : selected && !selected.destroyed_at ? [selected] : []; const servers = rows.map((s) => { const instance = s.native_instance_id ? instances.get(s.name) : null; return { ...observe(s), ...capacity(s.name), host: s.host_id ? (hostsById.get(s.host_id) ?? null) : null, network: instance ? { bindAddress: instance.bind_address, localPort: instance.port, publicAddress: instance.public_address, publicPort: instance.public_port } : null, plutonium: versions.get(s.name), // The host agent owns the game connection. endpoint: instance ? { host: `agent:${instance.host_id}`, port: instance.port } : s.game_port ? { host: s.rcon_host ?? rconHost, port: s.game_port } : null, held: holds.get(s.name) ?? [] }; }); return { servers, serverNow, // Include hosts without assigned servers. Re-evaluate heartbeat age on // every existing snapshot refresh rather than adding a navigation poll. hostAttention: hosts.filter((host) => ['attention', 'offline'].includes(hostListStatus(host, serverNow).key) ).length, reservedNames: serverName === undefined ? ( db() .prepare( "SELECT j.server FROM server_creation_jobs j JOIN server_deployments d ON d.id = j.deployment_id WHERE d.state IN ('queued', 'running', 'awaiting-health')" ) .all() as { server: string }[] ).map((row) => row.server) : [], integrations: { native: true, hostsReady: hosts.filter((host) => host.readiness === 'ready').length, hostsTotal: hosts.length, rcon: rconConfigured(), rconTimeoutMs: config.rcon.timeoutMs, defaultRconHost: rconHost } }; } export function serverSnapshot(name: string) { return { ...fleetSnapshot(name), operations: serverOperations(name) }; }