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 In the microservices deployment, point the app at the public admin host
for the stack, usually `https://admin.<domain>`. Cloudflare and for the stack, usually `https://admin.<domain>`. Cloudflare and
`DorkNet.Gateway` keep the public URL stable while admin API requests are `DorkNet.Gateway` keep the public URL stable. Requests made through the
routed to the owning backend slice. 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 ## Authentication
+1 -1
View File
@@ -30,7 +30,7 @@ Both were collapsed in 2026-05-25:
no per-feature patches needed. no per-feature patches needed.
- The PhotonRegion coerce (the only data transform in the block) - The PhotonRegion coerce (the only data transform in the block)
was moved into `LegacyUpgrades.CoercePhotonRegionAsync`. 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 `StorageBackfillController.cs` were deleted — they were the UI for
draining the legacy `RoomDataBlobs.Bytes` column into S3, which is draining the legacy `RoomDataBlobs.Bytes` column into S3, which is
no longer needed. 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`. 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 ## 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: 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 routing, external Postgres, and S3 setup, see
[docs/deploy.md](docs/deploy.md). [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 ## 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.Social` | Clubs, groups, announcements, player events, subscriptions | .NET 9 |
| `DorkNet.Services.Commerce` | Catalog, storefronts, econ, inventory, inventions | .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.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.Moderation` | Bug reporting, player reporting, sanitize, path-routed admin API, testcase routes | .NET 9 |
| `DorkNet.Services.Web` | Public site, admin static host, feed static host, site API | .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 | | `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 `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 .NET project, and runs that DLL in the runtime image. The root
`Dockerfile` builds only the standalone/fallback `DorkNet.Server` image. `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 ### Controllers/ groupings
20 top-level groups, each responsible for one product surface the 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 | | `social` | Clubs, groups, announcements, player events, subscriptions |
| `commerce` | Catalog, storefronts, econ, inventory, inventions | | `commerce` | Catalog, storefronts, econ, inventory, inventions |
| `platform` | Service directory, config, version checks, geo, strings | | `platform` | Service directory, config, version checks, geo, strings |
| `moderation` | Bug reports, player reports, sanitize, admin API, testcase routes | | `moderation` | Bug reports, player reports, sanitize, path-routed admin API, testcase routes |
| `web` | Apex/www/admin/feed static hosts and site API | | `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 | | `monolith` | Fallback shared server for route families not split yet |
| `postgres` | Bundled Postgres for the compose network | | `postgres` | Bundled Postgres for the compose network |
| `redis` | Bundled Redis for ephemeral state and fan-out | | `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 slice or to the monolith fallback. `/internal/services/health` reports
gateway-visible health for each backend service. 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 ### Useful Logs
```bash ```bash
docker logs <stack>-gateway-1 --tail=200 docker logs <stack>-gateway-1 --tail=200
docker logs <stack>-web-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>-platform-1 --tail=200
docker logs <stack>-cloudflared-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 old container is still running. Force a clean Dokploy rebuild on the
latest branch commit. 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 ## Local Microservices
+15 -12
View File
@@ -3,7 +3,9 @@
Cloudflare Tunnel is used in two DorkNet workflows: Cloudflare Tunnel is used in two DorkNet workflows:
- Local standalone testing, where cloudflared forwards `*.localhost` - 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 - Dokploy production microservices, where the compose sidecar forwards
the apex and wildcard hostnames to `http://gateway:8080`. 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 1. **`localhost` is on a Cloudflare account you control.** The DNS
nameservers for the domain need to be Cloudflare's. Check at nameservers for the domain need to be Cloudflare's. Check at
<https://dash.cloudflare.com/> — the zone should be Active. <https://dash.cloudflare.com/> — the zone should be Active.
2. **Local standalone server running** on `0.0.0.0:443` with the wildcard cert 2. **Local standalone server running.** The current quickstart listens
that covers `*.localhost` (re-run `tools/patch-client.ps1` if your on `http://localhost:8080`. Older local TLS setups can still run on
cert is still the `+1` rec.net-only one — it'll regenerate as `0.0.0.0:443` with a wildcard certificate that covers `*.localhost`.
`+3` covering both domains).
3. **Patched client** with `tools/patch-domain.ps1` so the in-game 3. **Patched client** with `tools/patch-domain.ps1` so the in-game
URLs resolve to `*.localhost`. (Players using the public deployment URLs resolve to `*.localhost`. (Players using the public deployment
need to run this on their own client too.) need to run this on their own client too.)
@@ -97,7 +98,7 @@ visitor -> https://api.localhost/api/versioncheck/v4
-> Cloudflare edge -> Cloudflare edge
-> cloudflared tunnel -> cloudflared tunnel
-> cloudflared running on your machine -> cloudflared running on your machine
-> https://localhost:443 (Host preserved) -> http://localhost:8080 (Host preserved)
-> DorkNet.Server -> DorkNet.Server
``` ```
@@ -112,12 +113,14 @@ visitor -> https://api.yourdomain.com/api/versioncheck/v4
-> dedicated service slice or monolith fallback -> dedicated service slice or monolith fallback
``` ```
For the local standalone template, the leg from cloudflared to localhost For default local testing, point the local cloudflared origin at
has `noTLSVerify: true` because the local server's cert is from mkcert `http://localhost:8080`. If you intentionally test a local HTTPS origin,
(private CA only trusted on your machine). The Cloudflare-edge to the leg from cloudflared to localhost can use `noTLSVerify: true`
cloudflared leg is still TLS-encrypted with Cloudflare's cert. In the because that certificate is usually from a private dev CA. The
Dokploy microservices stack, the tunnel sidecar uses plain HTTP on the Cloudflare-edge to cloudflared leg is still TLS-encrypted with
private Compose network and sends everything to the gateway. 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 ## Tearing down