mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
200 lines
8.4 KiB
Markdown
200 lines
8.4 KiB
Markdown
# DorkNet Admin Page
|
|
|
|
The admin page is the browser operations console for DorkNet. In the
|
|
microservices deployment it lives at `https://admin.<domain>/` and is
|
|
served by the `web` service from the built Vite SPA assets.
|
|
|
|
The admin backend endpoints are under `/api/admin/v1`. Requests from the
|
|
SPA are same-origin, so `https://admin.<domain>/api/admin/v1/*` is also
|
|
handled by the `web` service route on the admin host. The same
|
|
`/api/admin/*` route family is available as a path-owned moderation slice
|
|
for non-admin-host routing, so `/internal/routes` can show moderation as
|
|
the owner for that path family.
|
|
|
|
## Runtime Routing
|
|
|
|
| Item | Value |
|
|
|---|---|
|
|
| SPA source | `DorkNet.Server/admin-ui` |
|
|
| Build output | `DorkNet.Server/wwwroot/admin` |
|
|
| Container path | `/app/wwwroot/admin` |
|
|
| Public browser host | `admin.<domain>` |
|
|
| API base used by the SPA | `/api/admin/v1` |
|
|
| Login endpoint | `POST /api/admin/v1/login` |
|
|
| Same-origin admin-host service | `web` |
|
|
| Path-routed admin API slice | `moderation` |
|
|
|
|
`DorkNet.Gateway` preserves the original Host header when it proxies to
|
|
backend services. That matters for the admin page: the `admin.<domain>`
|
|
host route must reach `web`, and `web` must contain
|
|
`/app/wwwroot/admin/index.html`.
|
|
|
|
## Authentication
|
|
|
|
The first account created on a fresh database is promoted to admin so
|
|
there is always a bootstrap operator. The login form posts to
|
|
`/api/admin/v1/login`; the SPA stores the returned values in browser
|
|
`localStorage`:
|
|
|
|
| Key | Contents |
|
|
|---|---|
|
|
| `dorknet.admin.token` | DorkNet admin JWT |
|
|
| `dorknet.admin.me` | Admin identity object used by the layout |
|
|
|
|
Every admin API request sends `Authorization: Bearer <token>`. Protected
|
|
actions are guarded by `AdminOnlyAttribute`, which checks the resolved
|
|
player row still has `IsAdmin = true`. A `401` clears the local session
|
|
and sends the browser back to login.
|
|
|
|
If the admin host is public, put it behind Cloudflare Access or an
|
|
equivalent outer access control. The DorkNet JWT is still required after
|
|
that outer check.
|
|
|
|
## Navigation
|
|
|
|
| Section | Routes | Workflows |
|
|
|---|---|---|
|
|
| Overview | `/` | Live ops dashboard, online players, active sessions, quick kick/ban/broadcast actions |
|
|
| Moderation | `/players` | Player directory, bans, reports, per-player ban/grant/gift/password/avatar/account actions |
|
|
| Activity | `/activity` | Admin audit log and per-player request logs |
|
|
| Content | `/rooms`, `/rooms/:id`, `/import-room`, `/content` | Room list/detail, room import, instances, leaderboards, community board, loading tips, 3D Charades word lists |
|
|
| Operations | `/broadcast`, `/settings` | Server broadcast, server toggles (signups, everyone-is-friends, profanity filter, imported-room version clamp), signup codes, weekly challenges, Play menu tags, Rec Center doors, game config values |
|
|
|
|
Several older admin URLs are kept as redirects:
|
|
|
|
| Old route | Current route |
|
|
|---|---|
|
|
| `/bans` | `/players?tab=bans` |
|
|
| `/reports` | `/players?tab=reports` |
|
|
| `/gift`, `/passwords`, `/grants` | `/players` |
|
|
| `/audit` | `/activity?tab=audit` |
|
|
| `/logs` | `/activity?tab=logs` |
|
|
| `/rr-originals`, `/instances`, `/leaderboards` | `/rooms` |
|
|
| `/community` | `/content?tab=community` |
|
|
| `/loading-tips` | `/content?tab=tips` |
|
|
| `/signup-codes` | `/settings?tab=signup` |
|
|
|
|
`/import-room-legacy` still exists for the legacy room importer.
|
|
|
|
## 3D Charades word lists (`/content?tab=charades`)
|
|
|
|
The March 2023 client fetches a charades deck at card-box spawn from
|
|
`GET api/activities/charades/v1/words/{source}`, where `{source}` is one
|
|
of three baked `CardBox.cardSource` slots — `Charades`,
|
|
`CharadesAprilFoolsDay`, and `Icebreakers` (verified in the 2023.03.21
|
|
il2cpp dump). The response is a JSON array of
|
|
`{ "EN_US": "<phrase>", "Difficulty": <int> }` (`Difficulty` is the client
|
|
`CNMMMNJJDMM` enum: 0 easy, 1 hard, 10 very hard, 20 icebreaker).
|
|
|
|
The admin tab exposes:
|
|
|
|
- A **library** of unlimited named word lists (`CharadesWordListEntity`
|
|
rows), each with a per-card difficulty. Paste-import accepts one phrase
|
|
per line with an optional `| easy|hard|veryhard|icebreaker` suffix.
|
|
- **Live card slots** — three dropdowns binding each client slot to any
|
|
library list. Switching a slot just repoints its binding
|
|
(`ServerSettingsEntity.CharadesSlotBindingsJson`); it takes effect on the
|
|
next card-box refresh (room rejoin). An unbound slot falls back to the
|
|
built-in list seeded for it.
|
|
|
|
Three built-in lists (Default / April Fools / Icebreakers) are seeded on
|
|
first boot and bound to their slots. Seeding is idempotent — admin edits
|
|
survive restarts.
|
|
|
|
## Profanity filter toggle (`/settings`)
|
|
|
|
`ServerSettingsEntity.ProfanityFilterDisabled` gates the server-side
|
|
`api/sanitize/*` filter. When on, every sanitize route returns input
|
|
unchanged and treats all text as clean, so room/invention names and chat
|
|
are never censored. Off by default; checked per request so it takes effect
|
|
immediately.
|
|
|
|
## Imported-room version clamp (`/settings`)
|
|
|
|
`ServerSettingsEntity.RoomBlobVersionClampDisabled` gates the CDN's
|
|
`PersistedRoomData` version clamp
|
|
(`RoomDataBlobService.ClampVersionsFor2023`). Rooms imported from modern
|
|
RecNet zip exports carry room-data blobs whose top-level version varints
|
|
(field 1 `DEPRECATED_RoomPersistenceVersion`, field 30
|
|
`PersistedRoomVersion`) are far past what the March-2023 client knows —
|
|
a Sep-2025 save stamps `version=131` against the client's maximum of 16
|
|
(`RoomDataBlobService.Client2023MaxPersistedRoomVersion`; the proto's
|
|
`LatestVersion=19` came from a newer build's dump and this client rejects
|
|
it) — and the client rejects the whole room with its "update Rec Room to
|
|
visit this room" gate before spawning anything.
|
|
|
|
This clamp only addresses the room-**header** version gate. A room whose
|
|
CircuitsV2 chip graph (`circuit_data`, field 18) was saved in a newer Rec
|
|
Room is rejected by a separate CircuitsV2 version gate regardless — check
|
|
the client's `Player.log`, where the stack frame under "Booting player to
|
|
dorm" names the failing subsystem (`CircuitsV2Manager` = the circuit
|
|
graph, not the header). That case is not fixable by this clamp.
|
|
|
|
While the clamp is active (the default), the CDN serve path rewrites the
|
|
two varints down to the 2023 maxima on the way out for `.room` / `.meta` /
|
|
`.dat` blobs. The rewrite is wire-surgical: every other byte is preserved,
|
|
already-compatible blobs pass through byte-identical, and nothing stored
|
|
in S3 is modified. Toggle endpoint:
|
|
`POST api/admin/v1/settings/room-blob-version-clamp` with
|
|
`{"Disabled": bool}`. Clamped responses are served with a 60s edge TTL so
|
|
flipping the toggle lands within a minute.
|
|
|
|
## Build And Deploy
|
|
|
|
The service Dockerfile builds the admin SPA for service images that need
|
|
static assets. In the microservices stack the `web` image must include:
|
|
|
|
```text
|
|
/app/wwwroot/admin/index.html
|
|
/app/wwwroot/admin/assets/*
|
|
```
|
|
|
|
The admin SPA calls relative URLs, so it does not need a configured API
|
|
origin as long as the browser is opened on `https://admin.<domain>/`.
|
|
|
|
The native admin mobile app points at the same public admin host; see
|
|
[`../DorkNet.AdminMobile/README.md`](../DorkNet.AdminMobile/README.md).
|
|
|
|
## Smoke Checks
|
|
|
|
Check the static host:
|
|
|
|
```bash
|
|
curl -I https://admin.yourdomain.com/
|
|
```
|
|
|
|
Check that protected admin API routing is alive. A `401` is expected
|
|
without a valid admin token:
|
|
|
|
```bash
|
|
curl -i https://admin.yourdomain.com/api/admin/v1/stats
|
|
```
|
|
|
|
Check the built assets inside the running `web` container:
|
|
|
|
```bash
|
|
docker exec <web-container> sh -lc 'find /app/wwwroot/admin -maxdepth 2 -type f | sort | head -50'
|
|
```
|
|
|
|
Useful logs:
|
|
|
|
```bash
|
|
docker logs <stack>-gateway-1 --tail=200
|
|
docker logs <stack>-web-1 --tail=200
|
|
docker logs <stack>-moderation-1 --tail=200
|
|
```
|
|
|
|
For normal browser admin traffic, start with `gateway` and `web`.
|
|
Check `moderation` when you are testing `/api/admin/*` through a
|
|
non-admin host such as `api.<domain>`.
|
|
|
|
## Troubleshooting
|
|
|
|
| Symptom | Check |
|
|
|---|---|
|
|
| `admin.<domain>/` returns 404 | `web` logs for `probe="/app/wwwroot/admin/index.html" exists=False`; rebuild/redeploy the `web` image |
|
|
| Admin page loads but actions 404 | Gateway `/internal/routes`, then whether the request host is `admin.<domain>` or a path-routed host |
|
|
| Login or actions return 401 | Token expired, local session cleared, or the player no longer has `IsAdmin = true` |
|
|
| Browser spins forever | Open the browser network tab, then check `gateway` and `web` logs for the stuck URL |
|
|
| Upload/import fails near 100 MB | Use the chunked importer; the SPA uses 50 MB chunks to stay below Cloudflare request limits |
|