feat: embedded admin dashboard (Svelte 5 SPA)#132
Open
mposs00 wants to merge 11 commits into
Open
Conversation
Add a full admin dashboard served directly from the kagami binary at /admin/. Built with Svelte 5 (runes mode) + Tailwind CSS 4 + Vite, compiled to 28KB gzipped, embedded via EmbedAssets.cmake. Phase 1 pages: - Login: auth token + session creation flow - Dashboard: server info, live gauges from /metrics, area/session summary - Config: collapsible tree editor with save + reload server button - Sessions: sortable table with clickable detail panel - Traffic: live IC/OOC message stream via fetch-based SSE Infrastructure: - SPA serving handler in main.cpp with MIME type mapping - Hash-based routing (#/login, #/dashboard, etc.) - Responsive sidebar nav with mobile hamburger menu - API client library with Prometheus /metrics parser - Auth state management with localStorage persistence Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add read-only admin endpoints: - GET /admin/bans (searchable ban list via BanManager) - GET /admin/users (user list from DB) - GET /admin/moderation-events (queryable audit log) - GET /admin/mutes (active mutes with time remaining) - GET /admin/firewall (nftables rule list) - GET /admin/asn-reputation (flagged ASNs with status) - GET /admin/content (characters, music, areas from GameRoom) Implement POST /moderation/actions (already in OpenAPI spec): General-purpose moderation endpoint dispatching kick, ban, unban, mute, unmute, and notice (global broadcast) actions. Includes SSE session_ended notification on kick/ban. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…wall, content) Complete the dashboard with all remaining page views: - Bans: searchable ban list with unban action - Moderation: audit log viewer, active mutes, quick kick/mute/ban actions - Areas: card grid with detail panel (background, music, HP) - Users: user list with role badges - Firewall: nftables rules + ASN reputation table with status badges - Content: tabbed view of areas, characters, music lists All pages consume the new admin data viewer endpoints. The full SPA compiles to 32KB gzipped with all 11 pages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add PUT /admin/content endpoint that writes content files (characters.txt, areas.ini, backgrounds.txt) to disk and triggers a hot-reload. Allows content management from the admin dashboard. Add optional cmake build integration for the admin SPA: cmake -DKAGAMI_BUILD_ADMIN=ON .. Runs npm install + npm run build, outputs to assets/admin/ which is automatically embedded by EmbedAssets.cmake. Defaults to OFF so CI/builds without Node.js aren't affected. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
$state() rune only works in .svelte and .svelte.js files, not plain .js. The auth module was failing silently at runtime, leaving the app div empty. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Design: - Sharp corners, B/W base with gray accents, saturated color highlights - Night/day mode toggle (persisted to localStorage) - Lucide icon pack replaces emoji in navigation - CSS custom properties for all surface/text/border colors - Consistent typography: uppercase tracking-wider labels, monospace data Functional: - Session keepalive: auto-renew every 60s, auto-recreate from auth token - Traffic feed now shows area for each message - Audit log maps numeric action enums to human-readable labels - Music list detects category headers (sticky section dividers) - Config editor uses Finder-style column navigation instead of nested tree - Nav labels updated: Sessions→Players, Users→Accounts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The area parameter was only used for SSE routing (scoping delivery to clients in that area) but not included in the JSON data payload. The admin traffic feed needs it to show which area each message came from. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #132 +/- ##
==========================================
+ Coverage 64.37% 65.60% +1.22%
==========================================
Files 323 327 +4
Lines 19095 19748 +653
Branches 2982 3079 +97
==========================================
+ Hits 12293 12956 +663
+ Misses 6802 6792 -10 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…, spectator sessions Config editor: - Merged save+reload into single "Save & Apply" button - Search across all config keys with dropdown navigation - Array editing with add/delete per item, typed entries Content page: - Full CRUD: add, remove, reorder items via PUT /admin/content - Save & Apply button triggers hot-reload after writing Server management: - POST /admin/stop endpoint (SUPER only, 500ms delay for response flush) - Shutdown button in nav footer with confirmation dialog - stop_func on GameRoom, wired to stop_source in main.cpp Session improvements: - spectator_admin flag on sessions created with SUPER auth tokens - Excluded from player counts (stats.joined) and master server advertising - Auto-logout with reason message when session is unrenewable (server restart) - "Connecting..." intermediate state in traffic SSE feed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oderator attribution, 43 tests
Correctness
- AdminContentPutEndpoint: read-modify-write areas.ini preserves per-area
settings instead of overwriting them with default background. Writes
music.json in akashi format (ContentConfig never read the old
music.txt). Atomic write-rename for every file. 10K-entry size caps
return 413 before any I/O.
- AdminDataEndpoints: guarded stoi/stoll on moderation-events query
params (since/until/limit) — malformed values now return 400 instead
of propagating an uncaught exception as 500.
- ModerationActionsEndpoint: mutes and bans now attribute to the
acting moderator (session->moderator_name) instead of "REST API".
perform_kick() collapses the old two-pass session scan into one,
eliminating the TOCTOU window between count and destroy. do_ban
composes perform_kick cleanly.
- SessionCreateEndpoint + NXServer: spectator flag passed into
create_session upfront; no more stats.joined fetch_sub(1) dance.
XSS hardening (/admin/)
- Strict CSP: default-src 'none', script-src 'self', frame-ancestors
'none', plus X-Content-Type-Options: nosniff, X-Frame-Options: DENY,
Referrer-Policy: no-referrer, Cache-Control: no-store.
- Tokens moved from localStorage to sessionStorage so stolen tokens
die on tab close. Legacy localStorage entries purged on load.
Per-endpoint CORS tiers
- New CorsPolicy { Default, Public, Restricted } on RestEndpoint.
Restricted strips Access-Control-Allow-Origin on dispatch + preflight
regardless of router config, so wildcard ops configs don't expose
admin routes to cross-origin callers.
- Marked Restricted: all admin/* GETs, admin/content PUT, admin/stop,
admin/sessions, admin/config GET/PATCH/DELETE, moderation/actions.
Tests (+43)
- tests/net/test_AdminEndpoints.cpp covers auth/SUPER gating, malformed
query param regressions, content PUT size caps, atomicity, music.json
format, areas.ini round-trip preservation, moderator attribution
regressions, TOCTOU-safe ban, Restricted CORS behavior.
- 2290/2290 tests pass (was 2247).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… actually strips httplib's set_default_headers merges defaults AFTER the handler runs, so dispatch-time erase() of Allow-Origin was silently reverted before the response went out. Caught on staging: /aonx/v1/admin/* preflight still responded with Access-Control-Allow-Origin: *. Fix: set Allow-Origin exclusively in dispatch() and the OPTIONS preflight handler, based on the endpoint's CorsPolicy. Defaults now only carry Allow-Methods + Allow-Headers (informational, no authorization on their own). For multi-origin mode, Vary: Origin is set per response in apply_cors_origin(). Behavior change: unmatched routes (httplib built-in 404) no longer carry Allow-Origin. Safer posture — browsers surface a CORS error for probing instead of returning a same-origin-looking 404. Added RestRouterTest coverage for all three tiers under a wildcard router: - RestrictedEndpointOverridesWildcard - RestrictedPreflightOverridesWildcard - PublicEndpointForcesWildcard - DefaultPolicyEchoesWildcardConfig 2294/2294 tests pass (was 2290). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…esponse unconditionally Comment claimed the engine merges default_headers after the handler runs (cpp-httplib semantics). Our custom HTTP server actually emits both default_headers and res.headers sequentially in serialize_response with no "merge if absent" step, so a default always lands on the wire regardless of what the handler does. Fix is still correct (keep Allow-Origin out of defaults) but the reasoning in the comment was wrong. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/admin/— zero external dependencies at runtimeEmbedAssets.cmakePages (11)
Login, Dashboard (live gauges from /metrics), Config (Finder column-view editor with save/reload), Players (sortable session table with detail panel), Traffic (live IC/OOC stream via SSE), Bans (searchable with unban), Moderation (audit log + active mutes + quick kick/ban/mute), Areas (card grid with detail), Accounts (user/role management), Firewall (nftables rules + ASN reputation), Content (areas/characters/music with category parsing)
Backend (11 new endpoints)
GET /admin/bans— searchable ban listGET /admin/users— moderator account listGET /admin/moderation-events— queryable audit logGET /admin/mutes— active mutes with TTLGET /admin/firewall— nftables rule listGET /admin/asn-reputation— flagged ASNsGET /admin/content— characters, music, areas from GameRoomPUT /admin/content— write content files + reloadPOST /moderation/actions— general-purpose moderation (kick/ban/unban/mute/unmute/notice)ic_messageandooc_messageevents now includeareafieldBuild
KAGAMI_BUILD_ADMIN=ONcmake option auto-runsnpm run build(defaults OFF for CI without Node.js)assets/admin/for builds without Node.jsTest plan
🤖 Generated with Claude Code