Files
electric-cherry/src/lib/ui/rconError.ts
T
joshandClaude Opus 4.7 7b4e6e0565 Surface structured RCON failure reasons in the fleet UI
rconGetStatus previously collapsed at least five distinct failure modes
(no password, no port, socket error, timeout, bad reply) into a single
null return, so the UI could only say "rcon unreachable" — useless when
diagnosing a real Alcatraz outage.

Refactored to rconStatusResult() returning a discriminated union with a
RconFailure reason and human-facing detail. The worker persists these as
"<reason>:<detail>" in last_error (no schema change). /api/servers now
returns the derived endpoint (host:port) per server plus a top-level
integrations block (rcon/komodo configured, timeout, default host).

Server detail page gains an RCON diagnostic panel when health is not
online: shows the exact endpoint being dialed, integration status,
timeout, a color-coded reason tag, the detail, and a targeted fix hint.
Wrong-password replies are detected via "bad rcon" text in the print
body.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-09-12 09:06:27 -04:00

44 lines
1.8 KiB
TypeScript

import type { RconFailure } from '$ui/stores/servers.js';
export interface DecodedRconError {
reason: RconFailure;
detail: string;
}
const KNOWN: RconFailure[] = ['no-config', 'no-port', 'send-error', 'timeout', 'bad-reply', 'no-players'];
/**
* Decode the "<reason>:<detail>" string the worker persists into
* `servers.last_error`. Returns null for empty/legacy strings so the UI can
* fall back to a generic message.
*/
export function decodeRconError(raw: string | null | undefined): DecodedRconError | null {
if (!raw) return null;
const idx = raw.indexOf(':');
if (idx <= 0) return null;
const reason = raw.slice(0, idx);
if (!KNOWN.includes(reason as RconFailure)) return null;
return { reason: reason as RconFailure, detail: raw.slice(idx + 1).trim() };
}
/**
* Human-facing hint about how to fix each RCON failure. Kept short — the
* detail already carries the specifics (host, port, byte counts, etc.).
*/
export function rconFixHint(reason: RconFailure): string {
switch (reason) {
case 'no-config':
return 'Set RCON_PASSWORD in the container env and restart.';
case 'no-port':
return 'Set game_port on the server row, or configure Komodo so it can resolve the port.';
case 'send-error':
return 'DNS or route problem — check the RCON host is reachable from this container.';
case 'timeout':
return 'Plutonium is not replying on the resolved UDP port. Verify the server is running and the port matches.';
case 'bad-reply':
return 'Something is listening but not speaking Plutonium RCON. Check the port and password.';
case 'no-players':
return 'Reply parsed but no player table was found — likely a server-side dialect mismatch.';
}
}