diff --git a/README.md b/README.md index be43ba9..84d28ee 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,34 @@ reconciliation; this change does not immediately rewrite or restart them. Servers with an unknown map retain their existing config behavior. Rollback restores the exact prior config bytes. -## Tests +## Round telemetry experiment + +Send `POST /api/telemetry` with `Content-Type: application/json` to the Electric +Cherry host (port 3000 in local development): + +```bash +curl http://localhost:3000/api/telemetry \ + -H 'Content-Type: application/json' \ + -d '{"event":"round_change","round":17,"server":"t5_zombie_prototype_1","players":4}' +``` + +The server must already be registered in Electric Cherry. `round` and `players` +are required nonnegative integers. Success returns HTTP 200 with +`{ "ok": true, "telemetry": { "server": "...", "round": 17, "players": 4, "receivedAt": "..." } }`. +Invalid payloads return 400, unknown/destroyed servers 404, and non-JSON content +types 415. This endpoint uses the app's existing LAN/Tailscale access model; +there is no API key requirement. + +The latest report is saved across app restarts and streamed immediately to the +dashboard and server detail page, including after browser reconnection. The UI +labels it as the last reported round with its age: round-change events alone +cannot detect a stopped script or a match ending. Send the initial round on each +new match, then each round change. The latest received request wins (including +lower rounds for new matches); there is no event ordering or history yet. +Telemetry player counts are displayed with the report and never replace RCON +occupancy or affect automated restart gates. + +## Running tests ```bash npm test diff --git a/src/lib/server/db/schema.sql b/src/lib/server/db/schema.sql index 9410983..8a21957 100644 --- a/src/lib/server/db/schema.sql +++ b/src/lib/server/db/schema.sql @@ -242,3 +242,9 @@ CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); +CREATE TABLE IF NOT EXISTS server_telemetry ( + server TEXT PRIMARY KEY REFERENCES servers(name) ON DELETE CASCADE, + round INTEGER NOT NULL, + players INTEGER NOT NULL, + received_at TEXT NOT NULL +); diff --git a/src/lib/server/fleet/telemetry.ts b/src/lib/server/fleet/telemetry.ts new file mode 100644 index 0000000..3d64ef3 --- /dev/null +++ b/src/lib/server/fleet/telemetry.ts @@ -0,0 +1,15 @@ +import { db } from '../db/index.js'; +import type { RoundTelemetry } from '../../telemetry.js'; + +export function listTelemetry(): RoundTelemetry[] { + return db().prepare(`SELECT t.server, t.round, t.players, t.received_at AS receivedAt + FROM server_telemetry t JOIN servers s ON s.name = t.server + WHERE s.destroyed_at IS NULL`).all() as RoundTelemetry[]; +} + +export function saveTelemetry(report: RoundTelemetry): void { + db().prepare(`INSERT INTO server_telemetry (server, round, players, received_at) + VALUES (?, ?, ?, ?) ON CONFLICT(server) DO UPDATE SET + round = excluded.round, players = excluded.players, received_at = excluded.received_at`) + .run(report.server, report.round, report.players, report.receivedAt); +} diff --git a/src/lib/server/pipeline/bus.ts b/src/lib/server/pipeline/bus.ts index b408b3b..b4f08b1 100644 --- a/src/lib/server/pipeline/bus.ts +++ b/src/lib/server/pipeline/bus.ts @@ -1,4 +1,5 @@ import { EventEmitter } from 'node:events'; +import type { RoundTelemetry } from '../../telemetry.js'; import type { RconEndpoint, RconFailure, RconPlayer } from '../integrations/rcon.js'; import type { Health } from '../fleet/health.js'; @@ -36,6 +37,7 @@ export type BetaStage = export type BetaState = 'running' | 'ready-promote' | 'promoted' | 'rolled-back'; export type PipelineEvent = + | { type: 'server-telemetry'; entries: RoundTelemetry[] } | { type: 'deployment-opened'; deploymentId: string; action: string; servers: string[] } | { type: 'server-changed'; deploymentId: string; server: string; files: number } | { diff --git a/src/lib/telemetry.ts b/src/lib/telemetry.ts new file mode 100644 index 0000000..e1c2f3c --- /dev/null +++ b/src/lib/telemetry.ts @@ -0,0 +1,6 @@ +export interface RoundTelemetry { + server: string; + round: number; + players: number; + receivedAt: string; +} diff --git a/src/lib/ui/components/RoundTelemetry.svelte b/src/lib/ui/components/RoundTelemetry.svelte new file mode 100644 index 0000000..3a5cd20 --- /dev/null +++ b/src/lib/ui/components/RoundTelemetry.svelte @@ -0,0 +1,19 @@ + + +{#if report} +
+ Round {report.round} + Last reported {elapsedSince(report.receivedAt, $uiNow)} ago ยท {report.players} players +
+{/if} + + diff --git a/src/lib/ui/components/ServerCard.svelte b/src/lib/ui/components/ServerCard.svelte index 5d01c64..ceff928 100644 --- a/src/lib/ui/components/ServerCard.svelte +++ b/src/lib/ui/components/ServerCard.svelte @@ -1,4 +1,5 @@