Document admin page and refresh architecture docs

This commit is contained in:
Alexa
2026-06-15 23:44:48 +03:00
parent c6a4357c6f
commit e2ec948c74
8 changed files with 185 additions and 19 deletions
+7 -2
View File
@@ -4,8 +4,13 @@ Native Android/iOS admin app for DorkNet, built with .NET MAUI.
In the microservices deployment, point the app at the public admin host
for the stack, usually `https://admin.<domain>`. Cloudflare and
`DorkNet.Gateway` keep the public URL stable while admin API requests are
routed to the owning backend slice.
`DorkNet.Gateway` keep the public URL stable. Requests made through the
admin host are handled by the `web` service's same-origin admin route;
the `/api/admin/*` path family is also exposed by the moderation slice
for non-admin-host routing.
The browser admin page, route map, and troubleshooting flow are
documented in [`../docs/admin.md`](../docs/admin.md).
## Authentication
+1 -1
View File
@@ -30,7 +30,7 @@ Both were collapsed in 2026-05-25:
no per-feature patches needed.
- The PhotonRegion coerce (the only data transform in the block)
was moved into `LegacyUpgrades.CoercePhotonRegionAsync`.
- The Storage admin page (`admin-ui/src/pages/Storage.tsx`) and
- The legacy storage backfill page (`admin-ui/src/pages/Storage.tsx`) and
`StorageBackfillController.cs` were deleted — they were the UI for
draining the legacy `RoomDataBlobs.Bytes` column into S3, which is
no longer needed.
+7
View File
@@ -2,6 +2,13 @@
Generated: 2026-05-09. Source: client URLs from `Cpp2IL_ISIL` ISIL dump (curated list provided to this report); server routes harvested via grep of `[HttpGet]`/`[HttpPost]`/`[HttpPut]`/`[HttpDelete]`/`[Route]` attributes across `Controllers/**/*.cs` plus the namespace-wildcard fall-throughs in `ApiNamespaceStubsController` and the global `ApiCatchAllController` / `GlobalCatchAllController`.
> Snapshot scope: this file is a historical controller-coverage matrix,
> not the live gateway route table. The dedicated microservice hosts
> still reuse the shared controller stack behind route guards, so the
> endpoint shape notes remain useful. For current service ownership, use
> `DorkNet.Contracts/DorkNetRouteOwnership.cs` or the gateway
> `/internal/routes` endpoint.
## How this matrix is built
Each client URL is matched against the server's full route table. ASP.NET Core routes resolve "specific wins over wildcard", so a route literally registered as `api/foo/v1/bar` always wins over `api/foo/{*path}`. The `Status` column reflects the route that actually wins:
+3
View File
@@ -245,6 +245,9 @@ For deployment details, including Dokploy env vars, Cloudflare Tunnel
routing, external Postgres, and S3 setup, see
[docs/deploy.md](docs/deploy.md).
For the browser admin console, its routes, auth model, and production
troubleshooting, see [docs/admin.md](docs/admin.md).
---
## Database & migrations
+136
View File
@@ -0,0 +1,136 @@
# 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 |
| Operations | `/broadcast`, `/settings` | Server broadcast, server toggles, 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.
## 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 |
+6 -2
View File
@@ -45,8 +45,8 @@ DorkNet.sln
| `DorkNet.Services.Social` | Clubs, groups, announcements, player events, subscriptions | .NET 9 |
| `DorkNet.Services.Commerce` | Catalog, storefronts, econ, inventory, inventions | .NET 9 |
| `DorkNet.Services.Platform` | Service URLs, config, version checks, geo, strings, telemetry-style surfaces | .NET 9 |
| `DorkNet.Services.Moderation` | Bug reporting, player reporting, sanitize, admin API, testcase routes | .NET 9 |
| `DorkNet.Services.Web` | Public site, admin static host, feed static host, site API | .NET 9 |
| `DorkNet.Services.Moderation` | Bug reporting, player reporting, sanitize, path-routed admin API, testcase routes | .NET 9 |
| `DorkNet.Services.Web` | Public site, admin static host, same-origin admin browser API, feed static host, site API | .NET 9 |
| `DorkNet.ClientMod` | MelonLoader 0.6.x IL2CPP mod — the client patcher, JSON config | .NET 6 |
`ClientMod` applies the client-side patches needed to point the 2020
@@ -119,6 +119,10 @@ SPAs for service images that need static assets, publishes the selected
.NET project, and runs that DLL in the runtime image. The root
`Dockerfile` builds only the standalone/fallback `DorkNet.Server` image.
For the admin browser console specifically, including the host routing
split between the admin static host and `/api/admin/v1`, see
[`admin.md`](admin.md).
### Controllers/ groupings
20 top-level groups, each responsible for one product surface the
+10 -2
View File
@@ -29,8 +29,8 @@ while route slices move out of the fallback server.
| `social` | Clubs, groups, announcements, player events, subscriptions |
| `commerce` | Catalog, storefronts, econ, inventory, inventions |
| `platform` | Service directory, config, version checks, geo, strings |
| `moderation` | Bug reports, player reports, sanitize, admin API, testcase routes |
| `web` | Apex/www/admin/feed static hosts and site API |
| `moderation` | Bug reports, player reports, sanitize, path-routed admin API, testcase routes |
| `web` | Apex/www/admin/feed static hosts, same-origin admin browser API, and site API |
| `monolith` | Fallback shared server for route families not split yet |
| `postgres` | Bundled Postgres for the compose network |
| `redis` | Bundled Redis for ephemeral state and fan-out |
@@ -240,11 +240,15 @@ Expected key files:
slice or to the monolith fallback. `/internal/services/health` reports
gateway-visible health for each backend service.
For the admin page's browser routes, auth flow, same-origin API calls,
and focused troubleshooting, see [`admin.md`](admin.md).
### Useful Logs
```bash
docker logs <stack>-gateway-1 --tail=200
docker logs <stack>-web-1 --tail=200
docker logs <stack>-moderation-1 --tail=200
docker logs <stack>-platform-1 --tail=200
docker logs <stack>-cloudflared-1 --tail=200
```
@@ -260,6 +264,10 @@ That means the `web` image did not include the built SPA assets or the
old container is still running. Force a clean Dokploy rebuild on the
latest branch commit.
Admin browser traffic on `admin.<domain>` is routed to `web`. The
`moderation` service is still useful when testing the `/api/admin/*`
path family through non-admin hosts.
---
## Local Microservices
+15 -12
View File
@@ -3,7 +3,9 @@
Cloudflare Tunnel is used in two DorkNet workflows:
- Local standalone testing, where cloudflared forwards `*.localhost`
traffic to a locally running `DorkNet.Server`.
traffic to a locally running `DorkNet.Server`. The default origin is
`http://localhost:8080`; use local HTTPS only when you intentionally
run the standalone server with a wildcard dev certificate.
- Dokploy production microservices, where the compose sidecar forwards
the apex and wildcard hostnames to `http://gateway:8080`.
@@ -26,10 +28,9 @@ friends to reach it through Cloudflare.
1. **`localhost` is on a Cloudflare account you control.** The DNS
nameservers for the domain need to be Cloudflare's. Check at
<https://dash.cloudflare.com/> — the zone should be Active.
2. **Local standalone server running** on `0.0.0.0:443` with the wildcard cert
that covers `*.localhost` (re-run `tools/patch-client.ps1` if your
cert is still the `+1` rec.net-only one — it'll regenerate as
`+3` covering both domains).
2. **Local standalone server running.** The current quickstart listens
on `http://localhost:8080`. Older local TLS setups can still run on
`0.0.0.0:443` with a wildcard certificate that covers `*.localhost`.
3. **Patched client** with `tools/patch-domain.ps1` so the in-game
URLs resolve to `*.localhost`. (Players using the public deployment
need to run this on their own client too.)
@@ -97,7 +98,7 @@ visitor -> https://api.localhost/api/versioncheck/v4
-> Cloudflare edge
-> cloudflared tunnel
-> cloudflared running on your machine
-> https://localhost:443 (Host preserved)
-> http://localhost:8080 (Host preserved)
-> DorkNet.Server
```
@@ -112,12 +113,14 @@ visitor -> https://api.yourdomain.com/api/versioncheck/v4
-> dedicated service slice or monolith fallback
```
For the local standalone template, the leg from cloudflared to localhost
has `noTLSVerify: true` because the local server's cert is from mkcert
(private CA only trusted on your machine). The Cloudflare-edge to
cloudflared leg is still TLS-encrypted with Cloudflare's cert. In the
Dokploy microservices stack, the tunnel sidecar uses plain HTTP on the
private Compose network and sends everything to the gateway.
For default local testing, point the local cloudflared origin at
`http://localhost:8080`. If you intentionally test a local HTTPS origin,
the leg from cloudflared to localhost can use `noTLSVerify: true`
because that certificate is usually from a private dev CA. The
Cloudflare-edge to cloudflared leg is still TLS-encrypted with
Cloudflare's cert. In the Dokploy microservices stack, the tunnel
sidecar uses plain HTTP on the private Compose network and sends
everything to the gateway.
## Tearing down