Surface raw RCON reply bytes + Probe RCON button
"unparseable reply (34 bytes)" told us Plutonium answered but hid the actual bytes, forcing another guess-and-check round. rconStatusResult now emits a formatRawReply() hex + ASCII dump of the raw UDP payload on every bad-reply, persisted in a new last_reply_snippet column so it survives page loads. Wrong-password detection widens beyond the "bad rcon" phrasing to "(bad|invalid|wrong)…(rcon|password|pass)"; print-body replies that don't match get their first non-header line surfaced verbatim so at least *some* server-supplied text reaches the UI. Probe RCON button (POST /api/servers/[name]/probe-rcon) fires the same code path as the periodic sweep on demand, cuts the RCON iteration loop from 30s per attempt to sub-second, and refreshes the header/roster/ health when it succeeds. Snippet <details> auto-opens on bad-reply so the bytes are one glance away. migrateServers() picks up the new column automatically via the introspection path added last commit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+25
-1
@@ -1,5 +1,10 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { parseInfoLine, parsePlayerCount, parsePlayerRoster } from '../../src/lib/server/integrations/rcon.js';
|
||||
import {
|
||||
parseInfoLine,
|
||||
parsePlayerCount,
|
||||
parsePlayerRoster,
|
||||
formatRawReply
|
||||
} from '../../src/lib/server/integrations/rcon.js';
|
||||
|
||||
describe('parsePlayerCount', () => {
|
||||
it('counts rows after --- --- separator (status dialect)', () => {
|
||||
@@ -113,3 +118,22 @@ describe('parsePlayerRoster', () => {
|
||||
expect(parsePlayerRoster('nonsense')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatRawReply', () => {
|
||||
it('renders a small buffer as hex + printable ASCII with an offset', () => {
|
||||
const buf = Buffer.from([0xff, 0xff, 0xff, 0xff, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x0a]);
|
||||
const out = formatRawReply(buf);
|
||||
expect(out).toContain('0000');
|
||||
expect(out).toContain('ff ff ff ff 70 72 69 6e 74 0a');
|
||||
// Non-printable prefix bytes render as `.`; the printable tail spells "print".
|
||||
expect(out).toContain('....print.');
|
||||
});
|
||||
|
||||
it('splits into 16-byte rows and truncates past the limit', () => {
|
||||
const buf = Buffer.alloc(600, 0x41); // "A" repeated
|
||||
const out = formatRawReply(buf);
|
||||
const lines = out.split('\n');
|
||||
expect(lines.length).toBeGreaterThan(1);
|
||||
expect(lines[lines.length - 1]).toMatch(/more bytes/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user