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>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
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.';
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,12 @@ export interface RosterPlayer {
|
||||
}
|
||||
|
||||
export type Health = 'online' | 'stale' | 'offline';
|
||||
export type RconFailure = 'no-config' | 'no-port' | 'send-error' | 'timeout' | 'bad-reply' | 'no-players';
|
||||
|
||||
export interface RconEndpoint {
|
||||
host: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export interface ServerStatus {
|
||||
server: string;
|
||||
@@ -20,6 +26,8 @@ export interface ServerStatus {
|
||||
health: Health;
|
||||
lastSeenAt: string | null;
|
||||
lastError: string | null;
|
||||
endpoint: RconEndpoint | null;
|
||||
reason: RconFailure | null;
|
||||
}
|
||||
|
||||
export const statusByServer = writable<Map<string, ServerStatus>>(new Map());
|
||||
|
||||
Reference in New Issue
Block a user