Security page, admin quick controls, and whitelist reload self-healing

- RCON client retries once on a fresh connection after stale-socket
  timeouts (panel idle overnight held dead sockets, so 'whitelist
  reload' silently failed and approvals needed a server restart)
- Auth watcher verifies pending players against the LIVE whitelist every
  5s and re-writes/reloads if the server missed it, refunding lost
  window time
- Log watcher records blocked join attempts (name + IP) with rate-
  limited notifications — scanner bots are now visible in the panel
- New Security page: blocked-attempt log with one-click IP ban, banned
  players/IPs management, kick-everyone lockdown
- Player detail modal: ban, op/deop, gamemode switching
- Overview quick controls: time/weather/difficulty, gamerule toggles
  (keepInventory, mobGriefing, PvP...), broadcast box
- Settings toggles for blocked-attempt notifications (panel + Discord)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Aryan SinghandClaude Fable 5 committed 2026-07-11 11:50:27 -05:00
1 parent 4fe304c5af
commit deeeaf0042
13 files changed
+809 -3

No files matched your search

@@ -9,8 +9,10 @@ import { fmtBytes } from "@/lib/format";
type Settings = {
registrationWindowMinutes: number;
notifyJoins: boolean;
notifyBlocked: boolean;
discordEnabled: boolean;
discordJoins: boolean;
discordBlocked: boolean;
discordWebhookUrl: string;
};
@@ -156,6 +158,11 @@ export default function SettingsPage() {
onChange={(v) => setValues((s) => (s ? { ...s, notifyJoins: v } : s))}
label="Notify on player joins/leaves"
/>
<Toggle
checked={values?.notifyBlocked ?? true}
onChange={(v) => setValues((s) => (s ? { ...s, notifyBlocked: v } : s))}
label="Notify on blocked join attempts (bots/strangers)"
/>
</div>
</Card>
@@ -184,6 +191,11 @@ export default function SettingsPage() {
onChange={(v) => setValues((s) => (s ? { ...s, discordJoins: v } : s))}
label="Include joins/leaves"
/>
<Toggle
checked={values?.discordBlocked ?? true}
onChange={(v) => setValues((s) => (s ? { ...s, discordBlocked: v } : s))}
label="Include blocked join attempts"
/>
<Button
size="sm"
onClick={async () => {