Skip to content

feat: embedded admin dashboard (Svelte 5 SPA)#132

Open
mposs00 wants to merge 11 commits into
masterfrom
feat/admin-dashboard
Open

feat: embedded admin dashboard (Svelte 5 SPA)#132
mposs00 wants to merge 11 commits into
masterfrom
feat/admin-dashboard

Conversation

@mposs00

@mposs00 mposs00 commented Apr 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Full admin dashboard served from the kagami binary at /admin/ — zero external dependencies at runtime
  • Svelte 5 + Tailwind CSS 4 + Vite, compiled to 37KB gzipped, embedded via EmbedAssets.cmake
  • Night/day mode, responsive layout (phone → desktop), Lucide icon pack, Finder-style config editor
  • Session keepalive with auto-renew, auth token → session identity binding

Pages (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 list
  • GET /admin/users — moderator account list
  • GET /admin/moderation-events — queryable audit log
  • GET /admin/mutes — active mutes with TTL
  • GET /admin/firewall — nftables rule list
  • GET /admin/asn-reputation — flagged ASNs
  • GET /admin/content — characters, music, areas from GameRoom
  • PUT /admin/content — write content files + reload
  • POST /moderation/actions — general-purpose moderation (kick/ban/unban/mute/unmute/notice)
  • SSE ic_message and ooc_message events now include area field

Build

  • KAGAMI_BUILD_ADMIN=ON cmake option auto-runs npm run build (defaults OFF for CI without Node.js)
  • Pre-built assets committed in assets/admin/ for builds without Node.js

Test plan

  • Full C++ build, all 2247 tests pass
  • Deployed to staging at https://kagami.yuurei.network/admin/
  • Login flow end-to-end (auth token → session → SUPER access)
  • Config edit + reload applies changes
  • Live traffic stream shows IC/OOC with area labels
  • All pages render on mobile viewport
  • Night/day mode toggle persists across sessions
  • Session keepalive prevents expiry during active use

🤖 Generated with Claude Code

mposs00 and others added 7 commits April 14, 2026 12:23
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

codecov Bot commented Apr 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.64955% with 95 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.60%. Comparing base (8f1b9df) to head (0e8f7d6).

Files with missing lines Patch % Lines
plugins/net/nx/endpoints/AdminDataEndpoints.cpp 82.83% 46 Missing ⚠️
...ins/net/nx/endpoints/ModerationActionsEndpoint.cpp 81.25% 27 Missing ⚠️
...ugins/net/nx/endpoints/AdminContentPutEndpoint.cpp 92.77% 13 Missing ⚠️
plugins/net/nx/endpoints/AdminConfigEndpoint.cpp 0.00% 6 Missing ⚠️
plugins/net/nx/endpoints/AdminSessionsEndpoint.cpp 0.00% 2 Missing ⚠️
plugins/net/nx/endpoints/AdminStopEndpoint.cpp 96.29% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

mposs00 and others added 4 commits April 15, 2026 18:56
…, 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant