Admin UI¶
Status: π§ Partial β Admin-UI page/component inventory, live-status dashboard, universe editor, and the regional governance tabs are real. (Re-verified 2026-08-21 vs Sectorwars2102 origin/feat
96c968ad.) Tip delta from prior46bce720stamp is Admin #794 MedalAdmin Player collection β #794 β Admin Soft-HOLD unlock for LanguageSwitcher / Beacon / Admin-UI bulk-moderate / acceptΒ·redactΒ·block callers / Fibril Soft-ORDER #1561/#1563/#1564. Prior Admin #604 TradeDockAdmin read-only v1 remains tip-PRESENT (Soft-HOLD #1146 / LEG-71) β #604 β Soft-ORDER #1561 force-cancel unlock. β tip-PRESENT: #604TradeDockAdmin.tsxApp mount +/tradedocksroute (read-only v1); #789 SectorDetail EditableFields description / radiation_level / resource_regeneration; #788 StationsManager / PlanetDetailModal / FleetManagement; #793 ReEngagementQueuePanelformatAdminApiError+ Sidebar/review-queue; #794 MedalAdmin Player collection tab + GETβ¦/medals/admin/players/{id}/collection. OPEN Soft-ORDER tip-pending (not tip-shipped): #778 AI secondary + LanguageSwitcher 403/429; #791 Station fuel/luxury qty + Planet residual PATCH (Fibril #1564/#1563); #792 TradeDockAdmin force-cancel + formatAdminApiError reads (Fibril #1561/#1711); #795 FleetManagement emergency repair/refuel + Playwright/tradedocks+//medalssmoke β claim only when tip ancestor of land SHA. Fibril Soft-ORDER wires #1561 TradeDockAdmin force-cancel / #1563 PlanetDetail residual PATCH / #1564 StationDetail fuel/luxury editors stay tip-pending. Fibril Soft-ORDER #1758 Admin UIPOST β¦/ships/registry/backfillconsumer + #1759 DroneOperationsTab PATCH+DELETE stay tip-pending (gap MET on tip) β claim only when tip ancestor. Prior Admin #786 StationDetail/PlanetDetail PATCH honesty + Admin #787 Soft-ORDER create-station/planet + GS #784 force-cancel + Admin #775 universe detail 403/429 + GS #756 combat/history + Admin #783 EconomyDashboard alerts/summary honesty + GS #710 medal collection GET remain tip-true. GS #711 bulk-moderate route remains tip-PRESENT; Admin UI callers still Soft-HOLD. β GS canon moderation routes tip-PRESENT via #709 β do not claim Admin-UI accept/redact/block buttons tip-shipped. Found and corrected an overstated claim about the Culture/Diplomacy tabs (see Β§ below β they're labeled-limited and navigable, not literally disabled; the "no mock data" substance held up).
The administrative web app at services/admin-ui/. Used by operators to manage the universe, players, economy, security, multi-regional deployment, and the AI dialogue subsystem.
1. Tech stack¶
From services/admin-ui/package.json on tip 96c968ad:
| Layer | Choice |
|---|---|
| Framework | React 19.2 + TypeScript 7.0 |
| Build | Vite 8.2 |
| Routing | react-router-dom 7.18 |
| HTTP | axios 1.19 |
| WebSockets | socket.io-client 4.8 |
| Charting | chart.js 4.4 + react-chartjs-2; d3 7.8 for custom viz |
| i18n | i18next 26.3 + react-i18next 17.0 + browser-languagedetector + http-backend |
| Tests | Vitest 4.1 (npm test); Playwright 1.62 (E2E) |
| Lint | ESLint 10 + @typescript-eslint 8 |
State management: React Context (no external store).
Auth: JWT, integrated via contexts/AuthContext.tsx and utils/auth.ts.
Default ports: admin-ui on 3001, gameserver on 8080.
2. Project layout¶
services/admin-ui/src/
βββ App.tsx, main.tsx, i18n.ts
βββ components/
β βββ admin/ Player asset / detail editors, intervention panels, bulk ops
β βββ ai/ Market prediction, route optimization, behavior analytics
β βββ analytics/ Custom report builder, performance metrics, predictive
β βββ auth/ LoginForm, MFASetup, MFAVerification, ProtectedRoute, UserProfile
β βββ charts/ Combat activity, fleet health, market health, price, team strength
β βββ colonization/ Colony overview, genesis tracking, planetary mgmt, production
β βββ combat/ CombatFeed, DisputePanel
β βββ common/ LanguageSwitcher, PageLoader
β βββ first-login/ ConversationDetailModal, ConversationFilters, ConversationTable
β βββ layouts/ AppLayout, Sidebar
β βββ pages/ top-level page components incl. ScopesManager (see Section 3)
β βββ security/ AuditLogViewer
β βββ fleet/ FleetOperationsTab
β βββ ui/ PageHeader, SystemHealthStatus
β βββ universe/ SectorEditModal, PlanetDetail(Modal), StationDetail, ColonyDetailModal, UniverseEditor
βββ contexts/ AdminContext, AuthContext, WebSocketContext
βββ services/ bangGalaxyApi.ts, resourceCatalog.ts, websocket.ts
βββ styles/, types/, utils/
utils/ holds auth.ts (plus auth.test.ts) and β
tip-shipped adminApiError.ts (plus adminApiError.test.ts) via #760 / board #1113 (mergeCommit d82b7990; live tip 96c968ad). There is no player-client-style services/api.ts. On Sectorwars2102 origin/feat 96c968ad, src/services/ has three files (bangGalaxyApi.ts, resourceCatalog.ts, websocket.ts) β not websocket-only. Most REST calls go through the shared axios instance exported as api from utils/auth.ts. Residual raw import axios on origin/feat 96c968ad (non-test; git grep -n 'import axios' <tip> -- services/admin-ui/src | grep -v test):
services/admin-ui/src/contexts/AuthContext.tsx:2:import axios from 'axios';
services/admin-ui/src/utils/auth.ts:5:import axios from 'axios';
utils/auth.ts is the transport root (intentionally raw). AuthContext.tsx remains raw for auth bootstrap. AdminContext.tsx and bangGalaxyApi.ts no longer import axios on tip 96c968ad β they use the shared api from utils/auth. Dashboard.tsx remains on the shared api (b28bf981 / LEG-233 / #674 is a tip ancestor). #760 (Admin LEG-1044 / board #1113) is MERGED β tip ancestor; do not treat older WO-branch HEADs (e.g. 31b90026 / local wo/LEG-917) as the tip. Never stamp a WO-branch HEAD as the tip.
Comparison note: prior docs falsely claimed AdminContext/bangGalaxyApi were axios-free on c2522953 by confusing a WO-branch tip with origin/feat, then later Soft-HOLD-stamped them as still raw on 3cfb1d5c. On current tip 96c968ad those two are migrated; only AuthContext + auth.ts remain raw. components/users/ is absent (UsersManager lives under components/pages/).
3. Pages¶
App.tsx on tip 96c968ad lazy-imports 32 routed page modules under components/pages/ β not 26 files each routed. Additional page-level files not their own routes include EconomyLeversPanel.tsx (embedded in EconomyDashboard) and, on tip 1f6acc0e, RankingLeaderboardPanel.tsx + β
tip-shipped ReEngagementQueuePanel.tsx (both embedded in PlayerAnalytics; land #772 / mergeCommit 3fbb6073). β
TradeDockAdmin.tsx tip-PRESENT via #604 / Soft-HOLD #1146 @ 96c968ad β App lazy import + /tradedocks route + Vitest (read-only v1; no force-cancel write path). Soft-ORDER #1561 force-cancel Admin-UI consumer stays tip-pending. GS admin_construction remains tip-shipped / #598. The routed list:
| Page | Domain |
|---|---|
LoginPage.tsx |
Auth |
Dashboard.tsx |
Landing / overview |
UniverseManager.tsx, BangGalaxyPage.tsx |
Universe editor + Bang-engine generation |
SectorsManager.tsx, StationsManager.tsx, PlanetsManager.tsx, WarpTunnelsManager.tsx |
Universe drill-downs |
UsersManager.tsx, PlayerAnalytics.tsx |
Users / players |
AdvancedAnalytics.tsx |
Analytics |
EconomyDashboard.tsx |
Economy |
CombatOverview.tsx, FleetManagement.tsx |
Combat / fleets |
ContractDisputeArbitration.tsx |
Contracts (/contract-disputes) |
ColonizationManagement.tsx |
Planetary |
TeamManagement.tsx |
Teams |
EventManagement.tsx |
Game events |
FactionManagement.tsx |
Factions |
MedalAdmin.tsx |
Medals (/medals) |
TradeDockAdmin.tsx |
TradeDock / construction (/tradedocks) β read-only v1 |
MessageModeration.tsx |
Messaging / moderation |
MultiAccountReview.tsx |
Multi-account (/multi-account) |
CentralNexusManager.tsx, RegionalGovernorDashboard.tsx |
Multi-regional |
AITradingDashboard.tsx, FirstLoginConversations.tsx |
AI ops |
SecurityDashboard.tsx, ScopesManager.tsx, AdminActionLogPage.tsx |
Security / admin scopes (RBAC) / audit (/audit) |
TranslationManagement.tsx |
Internationalization |
NotFound.tsx |
Catch-all (*) |
Tip honesty (#769 MERGED, still true on 96c968ad): MultiAccountReview retires the stale βdetection sweep not shippedβ banner β the hourly detection sweep is tip-shipped; an empty queue means no open clusters awaiting review. The same land added inline 403/429 (scope / admin rate-limit) copy on colonization panels (GenesisDeviceTracking, PlanetaryManagement, ProductionMonitoring), DisputePanel, BalanceAnalytics, Dashboard audit feed, and PerformanceMetrics. Shared adminApiError.ts coverage landed later via #760 β see Β§ Authorization model.
Tip honesty (#774 MERGED, still true on 96c968ad): additional inline 403/429 honesty on AI MarketPrediction / PlayerBehavior / RouteOptimization, PlayerDetailEditor / PlayerBountyPanel / PlayerAssetManager, AdvancedAnalytics generate/export, AuditLogViewer, and FleetHealthReport. Shared helper tip-shipped separately via #760 / #1113 β residuals in Β§ Authorization model.
Tip honesty (#783 MERGED / tip 96c968ad; Fibril #1446 DONE): EconomyDashboard.tsx β alertsRes (price-alerts) and summaryRes (dashboard-summary) settled rejections now push formatAdminApiError into the page error strip (same helper market/metrics already used). Empty alerts array / null summary only on honest empty payloads β not on 403/429 silent clears.
Tip honesty (#756 MERGED / tip 96c968ad; GS LEG-862): β
GS GET /api/v1/combat/history tip-PRESENT (player_combat.py:215 β authenticated player CombatLog list). Do not claim an Admin-UI consumer of this route as tip-shipped β #756 is gameserver-only; Soft-HOLD residuals above unchanged.
Tip honesty (#775 MERGED / tip 96c968ad; Admin LEG-1213/1214 + LEG-1233): β
universe detail editors + SystemHealthStatus β RBAC/rate-limit failures no longer collapse to bare Failed / console-only Offline. Shared helper utils/universeAdminError.ts tip-PRESENT. Soft-HOLD residuals above unchanged.
Tip honesty (#784 MERGED / tip 96c968ad; GS LEG-339): β
GS POST /api/v1/admin/construction/reservations/{id}/force-cancel tip-PRESENT. Do not claim any Admin-UI force-cancel consumer as tip-shipped β Soft-ORDER Fibril #1561 owns that residual. (TradeDockAdmin panel is tip-PRESENT read-only via #604; that is not the force-cancel write path.)
Tip honesty (#787 MERGED / tip 96c968ad; Admin LEG-1451/1452/1453): Soft-ORDER create-station/planet payload honesty on Admin universe create flows. Soft-HOLD residuals above unchanged β #787 β unlock LanguageSwitcher / Beacon / bulk-moderate callers / Soft-ORDER #1561 force-cancel.
Tip honesty (#786 MERGED / tip 96c968ad; Admin LEG-1373/1443/1444/1445): Soft-ORDER StationDetail/PlanetDetail PATCH honesty tip-PRESENT. Do not treat Fibril READY product wires #1563/#1564 (residual PlanetDetail PATCH / StationDetail fuelΒ·luxury editors) as unlocked β those stay tip-pending. Soft-HOLD residuals above unchanged β #786 β unlock LanguageSwitcher / Beacon / bulk-moderate / acceptΒ·redactΒ·block callers / Soft-ORDER #1561 force-cancel.
Tip honesty (#788 MERGED / tip 96c968ad; Admin Soft-ORDER): β
tip-PRESENT β StationsManager.tsx (station_class POST), PlanetDetailModal.tsx, FleetManagement.tsx (+ tests) on tip. Soft-HOLD residuals above unchanged β #788 β unlock LanguageSwitcher / Beacon / bulk-moderate / acceptΒ·redactΒ·block callers / Fibril READY #1561/#1563/#1564.
Tip honesty (#789 MERGED / tip 96c968ad; Admin Soft-ORDER LEG-1488): β
tip-PRESENT β SectorDetail.tsx EditableFields description / radiation_level / resource_regeneration (+ createPayload tests) on tip. Soft-HOLD residuals above unchanged β #789 β unlock LanguageSwitcher / Beacon / bulk-moderate / acceptΒ·redactΒ·block callers / Fibril READY #1561/#1563/#1564. Do not claim #778 tip-shipped (still OPEN).
Tip honesty (#604 MERGED / tip 96c968ad; Soft-HOLD #1146 / LEG-71): β
tip-PRESENT β TradeDockAdmin.tsx + App lazy mount + /tradedocks route + tests (read-only v1; no force-cancel write path). Soft-ORDER #1561 force-cancel Admin-UI consumer stays tip-pending. Soft-HOLD residuals above unchanged for LanguageSwitcher / Beacon / MessageModeration Admin callers / #1563/#1564.
Tip honesty (#710 MERGED / tip 96c968ad; GS LEG-264): β
GS GET /api/v1/medals/admin/players/{player_id}/collection tip-PRESENT β gameserver route. Admin UI collection consumer tip-PRESENT separately via #794. Soft-HOLD residuals unchanged β #710 β Admin Soft-HOLD unlock.
Tip honesty (#793 MERGED / tip 96c968ad; Admin Soft-ORDER LEG-1639/#1715 + LEG-1640/#1716): β
tip-PRESENT β ReEngagementQueuePanel.tsx imports+uses formatAdminApiError on load/update catches; Sidebar.tsx { to: '/review-queue', label: 'Review Queue' } tip-PRESENT (canonical surface remains /audit?tab=review via navigate). Soft-HOLD residuals above unchanged β #793 β unlock LanguageSwitcher / Beacon / bulk-moderate / acceptΒ·redactΒ·block callers / Fibril READY #1561/#1563/#1564. Do not claim #795/#778 tip-shipped (still OPEN).
Tip honesty (#794 MERGED / tip 96c968ad; Admin Soft-ORDER LEG-1643/#1719): β
tip-PRESENT β MedalAdmin.tsx Player collection tab (TabId includes collection) + GET /api/v1/medals/admin/players/{player_id}/collection caller + Vitest 403/429 paths. Soft-HOLD residuals above unchanged β #794 β Soft-HOLD unlock LanguageSwitcher / Beacon / bulk-moderate / acceptΒ·redactΒ·block callers / Fibril READY #1561/#1563/#1564. Do not claim #795/#778/#791/#792 tip-shipped (still OPEN). Fibril Soft-ORDER #1758/#1759 stay tip-pending.
Tip honesty (OPEN Soft-ORDER PRs, tip-pending): #791 Station fuel/luxury + Planet residual PATCH (Fibril #1564/#1563); #792 TradeDockAdmin force-cancel + formatAdminApiError reads (Fibril #1561/#1711); #795 FleetManagement emergency repair/refuel + Playwright /tradedocks+//medals smoke; keep #778 tip-pending. Fibril Soft-ORDER #1758 Admin UI POST β¦/ships/registry/backfill (FleetManagement; gap MET: zero registry/backfill under admin-ui) + #1759 DroneOperationsTab PATCH+DELETE (gap MET: tip get/post force-recall/restore only) stay tip-pending. Claim only when tip ancestor of land SHA.
4. Key features¶
Universe editor¶
UniverseManager.tsx (plus BangGalaxyPage.tsx for Bang-engine generation and the universe/ modal components) offers galaxy generation (configurable parameters, region distribution sliders, sector-by-sector edits) and clickable drill-downs to planets, ports, and warp tunnels. Backed by /api/v1/admin/galaxy/* and /api/v1/admin/sectors/* endpoints.
Bang wipe / add-region error honesty (tip 96c968ad): BangGalaxyPage.tsx wipe/add-region catches call formatAdminApiError (scopeHint admin.universe.manage; landed with #760). β
Context-layer MET: AdminContext.wipeGalaxy also uses formatAdminApiError with the same fallback/scopeHint (LEG-1315 / #780 / mergeCommit 1f6acc0e; still true on tip 96c968ad) β do not read wipeGalaxy as still collapsing to a bare Failed to wipe galaxy string. See Β§ Authorization model.
Dashboard with live status¶
SystemHealthStatus.tsx (in components/ui/) renders three live cards β Database, AI providers (OpenAI / Anthropic), Game Server β pulling from /api/v1/status/database, /api/v1/status/ai/providers, /api/v1/status/. Auto-refreshes; shows pool metrics, response times, configuration vs reachability state. β
Tip honesty (#775): probe catch paths surface 403/429 to operators via role=alert instead of console-only Offline.
Messaging moderation¶
MessageModeration.tsx is the flagged-message / flagged-beacon review queue (page-table row above). On HEAD 96c968ad it live-refreshes when a player flags a message: the page subscribes to WebSocket event flagged:message:alert (GS type flagged_message_alert) and reloads the queue after a debounce, with a toast (MessageModeration.tsx ~8, 224β246). Landed f36ff5f1 / LEG-414 / #705 β not #706 (player FLAG UI, LEG-412).
GS path: player POST /api/v1/messages/{message_id}/flag (messages.py flag_message) β message_service.py ~426β444 builds {type: "flagged_message_alert", ...} and await manager.broadcast_to_admins(...). Operators do not need a manual refresh for new flags. Player FLAG flow: ../FEATURES/gameplay/messaging.md Β§ Player-facing affordances. β
GS POST /admin/messages/bulk-moderate is tip-PRESENT via #711 / LEG-266 (admin_messages.py:100; tip 96c968ad). β
GS canon accept / redact / block tip-PRESENT via #709 / LEG-263 (admin_moderation_messages.py). Do not claim Admin-UI bulk-moderate callers or Admin-UI accept/redact/block buttons as shipped (#1114/#1115 Soft-HOLD β Admin UI still tip-absent; MessageModeration.tsx remains delete|unflag only).
AI ops¶
AITradingDashboard.tsxβ monitors AI trading interactions.FirstLoginConversations.tsx+first-login/modals/filters/table β review the AI-driven first-login dialogues, useful for tuning the guard personality and ship-outcome thresholds (recent migrationsec92f8afd44a,c5e32c313020,6b1d95a38c98,2e78250f47bc,6acc65ee7a72all touched this area).ai/components (MarketPredictionInterface, RouteOptimizationDisplay, PlayerBehaviorAnalytics) surface backend ML output.
Analytics¶
PlayerAnalytics.tsx (large) and AdvancedAnalytics.tsx, plus analytics/ (CustomReportBuilder, PerformanceMetrics, PredictiveAnalytics) and the charts/ widgets. (Re-verified 2026-08-20 vs Sectorwars2102 HEAD 96c968ad.) The Player Metrics block on PlayerAnalytics surfaces two shipped stat cards (also present on origin/feat 48d014ee):
- 7-day retention rate β
data-testid="player-metrics-retention-rate"; description'7-day retention'; landed6f25007f/ LEG-376 / #690. Methodology:retention.mdΒ§ Retention rate. When the value is null the card shows an em-dash plus an unavailable empty state β not a stub slogan. β Re-engagement OPEN queue βReEngagementQueuePanel.tsxmounted onPlayerAnalytics(import + render ~287); tip-shipped on1f6acc0evia #772 / mergeCommit3fbb6073(LEG-880). Seeretention.mdΒ§ Retention rate. - Average session time (hours) β
data-testid="player-metrics-session-time"; rendersaverage_session_timeas hours; landed118cae0f/ LEG-386 / #717. Methodology:retention.mdΒ§ Session metrics. Same honest empty-state pattern when null.
Do not invent formulas in this page β those live in retention.md.
Admin Reports / Analytics API (backend contract)¶
β
Shipped β five read-only endpoints in services/gameserver/src/api/routes/admin_reports.py (router prefix /admin, mounted under /api/v1). All gated by admin.audit.view (AUDIT_VIEW). Consumed by the admin-ui Advanced Analytics page. Expensive generate/export calls are the intended load class for the Reports / exports tier in Β§ Admin REST rate limits below (5 / hour via classify_admin_tier β ADMIN_TIER_REPORTS, enforced by RateLimitingMiddleware in services/gameserver/src/api/middleware/security.py).
| Method | Path | Purpose |
|---|---|---|
GET |
/api/v1/admin/reports/metrics |
Metric catalog (schema-driven; no DB reads). |
GET |
/api/v1/admin/reports/templates |
Built-in report templates. |
POST |
/api/v1/admin/reports/generate |
Run a custom report: compute live DB aggregates for each requested metric id. |
GET |
/api/v1/admin/analytics/export |
Bulk dataset export (json or csv). |
GET |
/api/v1/admin/performance/metrics |
System / DB / application performance snapshot. |
Pydantic shapes (must stay aligned with admin-ui TypeScript interfaces):
ReportFilterβ{ id, field, operator, value }ReportTemplateβ{ id, name, description, metrics[], filters[], groupBy[], sortBy[], visualization, chartType?, schedule? }ReportResultβ{ id, name, generatedAt (ISO-8601), data (metric_id β value), template }
Metric catalog categories (ids are stable; values computed by _compute_metric):
| Category | Metric ids |
|---|---|
| Players | player_total_count, player_active_count (7d login), player_avg_credits, player_total_credits, player_avg_turns |
| Economy | market_total_transactions, market_total_volume, market_avg_profit_margin |
| Galaxy | sector_total_count |
| Combat | combat_total_encounters |
| Fleet | ship_total_count |
| Teams | team_total_count |
Player-facing aggregates exclude soft-deleted users (User.deleted == false), matching the players export filter.
Built-in templates: tpl-economy-overview, tpl-player-health, tpl-galaxy-status (each is a ReportTemplate with a fixed metric list and visualization: "table").
POST /reports/generate: body = ReportTemplate; requires 1β50 metric ids; returns ReportResult with a fresh report-{12-hex} id. Unknown metric ids resolve to 0 (never fabricated narrative data).
GET /analytics/export:
| Query | Values |
|---|---|
dataset |
players Β· economy Β· combat Β· teams Β· ships Β· performance |
format |
json (default) Β· csv β excel/pdf rejected with 400 (deps not installed) |
Row cap 10β―000 per dataset. performance returns a pointer row directing clients to GET /performance/metrics rather than duplicating that payload. Attachment filenames: {dataset}-export.{json\|csv}.
GET /performance/metrics: query timeRange β 1h Β· 6h Β· 24h (default) Β· 7d. Response blocks:
systemβactiveConnections+ Postgres uptime % are real; CPU/memory/disk/network/errorRateare 0.0 without psutil / in-band error tracking;requestsPerSecondis a market-txn-last-minute proxy.databaseβpg_stat_activitypool breakdown, slow-query count (>1s),pg_statio_all_tablescache hit rate;queryTimestays 0 withoutpg_stat_statements.applicationβ throughput fromMarketTransactionover the window; p50/p95/p99 response times are 0 (no in-band latency).historicalβ 12 evenly spaced buckets of transaction volume over the window (load/error series padded with zeros).suggestionsβ static, stack-specific recommendations (not live ML).
(Drafted 2026-08-07 from live admin_reports.py for WO-CANON-DRAFT-ADMIN-REPORTS-ANALYTICS-API-CONTRACT.)
Combat analytics & intervention¶
CombatAnalyticsService (services/gameserver/src/services/combat_analytics_service.py) backs the /admin/combat/* routes (admin_combat.py) with a live combat dashboard, gated by PLAYERS_VIEW (reads) / COMBAT_INTERVENE (writes):
GET /admin/combat/liveβ recent/activeCombatLogrows plus activeFleetBattlerows merged into one feed, with participant names, sector, damage/drone stats, and aneeds_interventionflag (heuristics: >100 rounds, a >20-round stalemate under 100 damage each side, or a >50-round one-sided fight at >95%/<5% damage split).POST /admin/combat/{combat_id}/interveneβ admin interventions on an in-progress combat:stop_combat,adjust_damage(multiplier),restore_shields(tip-shipped#763β mutates liveShip.combatJSONB shields toshield_percentofmax_shields; not CombatLog-only),declare_winner. Every intervention (success or failure) is written to the audit log viaAuditService.- β
Admin UI tip-PRESENT (#782 / tip
96c968ad; Fibril #1422β#1425 DONE):CombatOverview.tsxpostsrestore_shieldswithtarget+shield_percent, plusadjust_damage/declare_winner, against that intervene API. Restore is a live shield write, not audit-only UI. Pause/Reset remain unavailable on the page. GET /admin/combat/balanceβ win-rate/balance analytics over completed combats, grouped byship_type/player_level/combat_type/ overall, with a 0β100 balance score (variance from an ideal 50% win rate) and outlier detection (>70% win rate = overpowered, <30% = underpowered, min 10-sample floor) feeding generated buff/nerf recommendations.GET /admin/combat/disputesβ heuristic dispute surfacing: completed combats with a >10:1 damage ratio in the last 24h, plus "repeat combat" pattern detection (>5 combats between the same attacker/defender pair within an hour, flagged as potential farming/harassment).GET /admin/combat/stats,GET /admin/combat/logs,POST /admin/combat/{combat_id}/resolve,GET /admin/combat/dashboard-summaryβ ported-forward legacy endpoints (aggregate stats by time window, paginated combat-log history, manual dispute resolution, and a combined dashboard payload merging live feed + balance + disputes).
Multi-regional¶
CentralNexusManager.tsx and RegionalGovernorDashboard.tsx expose the Central Nexus generation + regional governance APIs (/api/v1/nexus/*, /api/v1/regions/*). The latter is one of the larger pages in the app β 7-tab interface (Overview, Governance, Economy, Policies, Elections, Diplomacy, Culture).
Governance write scope and honest-disabled tabs (canon, blessed by Max
2026-06-14). Governance writes are keyed by region id β an operator acts on a
named region's governance state, not a global one β gated by admin.regions.*
scopes. The Culture and Diplomacy tabs are honest, not faked β not
literally disabled/unclickable (both are navigable, labeled "Diplomacy β limited"
/ "Culture β read-only" in the tab bar), but they render only real data (e.g. real
treaties rows) with no fabricated write actions: an explicit inline note
states the write endpoints don't exist rather than showing an Actions bar that
does nothing. (Re-verified 2026-08-20 vs Sectorwars2102 HEAD 96c968ad.) against RegionalGovernorDashboard.tsx
β corrected the "visibly inert" wording, which overstated how disabled the tabs
actually are; the "never wired to mock data" claim held up exactly as described.
Diplomacy in particular is intended to graduate into a real gameplay feature
(inter-region relations), not a permanent admin-only panel β so its tab stays
limited rather than stubbed until that feature lands.
Security & audit¶
SecurityDashboard.tsx for monitoring (live metrics over the audit-log feed); security/AuditLogViewer.tsx for the audit trail. MFA setup and verification components live in auth/ (MFASetup.tsx, MFAVerification.tsx). Gameserver TOTP/MFA routes: ../ARCHITECTURE/auth.md Β§ Multi-factor authentication (TOTP).
Authorization model¶
(Re-verified 2026-08-21 vs Sectorwars2102 HEAD 96c968ad.)
β
Shipped β the scope-based gate described below is live, not just the Release target. User.is_admin is Phase-C3 grant-derived (a correlated EXISTS expression against AdminScopeGrant), superseding the flat User.is_admin == true gate from ADR-0027 β see ../DATA_MODELS/admin.md for the full schema/status detail.
Per ADR-0058 A-F2 (supersedes ADR-0027), greenlit as the RBAC foundation chain by ADR-0093 item 19 (2026-07-10): admin capabilities live as a scope set, each scope assignable per-admin via AdminScopeGrant (user_id, scope, granted_by, granted_at) β table + 4 migrations shipped, per-endpoint scope checks wired across 32 route files.
Scope families: admin.players.* (view, suspend, adjust_rep, transfer_assets), admin.subscriptions.* (view, modify, refund), admin.webhooks.* (view, replay), admin.regions.* (view, create, terminate, transfer_ownership), admin.aria.audit, admin.multi_account.review, admin.bang.regenerate, admin.scopes.* (grant, revoke), admin.audit.view. Full list in ADR-0058.
UI surfaces (β Shipped unless noted):
/scopesβ scope-grant management viaScopesManager.tsx, gated byadmin.scopes.grant/admin.scopes.revoke. Wired to/api/v1/admin/scopes/holders,/catalog,/grant,/revokeagainstAdminScopeGrant. Legacy/permissionsroute redirects here. The old scaffolding (PermissionsDashboard.tsx,permissions/PermissionMatrix.tsx,permissions/RoleManagement.tsx) was deleted after the live scopes API landed β do not document those filenames as live./auditβ β Shipped β append-onlyAdminActionLogledger and the HIGH_IMPACT retrospective review queue, both inAdminActionLogPage.tsx(App.tsxrouteaudit). Ledger tab:GET /api/v1/admin/audit(admin.audit.view). Review tab (?tab=review):GET /api/v1/admin/audit/review-queue+ acknowledge via the audit review POST (admin.audit.review) writingreviewed_by/reviewed_at. Gameserver:services/gameserver/src/api/routes/audit.py. No pre-action two-person gate β accountability through scope-precision + audit + review.- Canonical path residual: older prose named this surface
/admin/review-queue(or bare/review-queue). The Admin UI now mountsRoute path="review-queue"βNavigate to="/audit?tab=review"(LEG-103 / PR #659 / SHAedfdc35c). Primary surface remains/audit?tab=reviewβ do not invent a duplicate review page.
A sweep-test alarm fires on the review tab (AdminActionLogPage.tsx, /audit?tab=review) when the count of 30-day-old unacknowledged HIGH_IMPACT rows meets REVIEW_QUEUE_STALE_ALARM_THRESHOLD (provisional N=3 in Admin UI PR #621 / SHA 99b10a81 β not a ratified canon number; code comment keeps N β€ 5). Target remains: review acknowledged within 7 days. β
Shipped β persistent review-tab banner (role="alert", data-testid="review-queue-sweep-alarm") + Vitest on/off coverage.
Scope-gated routes will return 403 with the missing scope name in the response body β clients render "you need scope X" rather than a generic permission error.
Shared client helper (services/admin-ui/src/utils/adminApiError.ts) β β
tip-shipped on origin/feat 96c968ad via #760 / board #1113 (mergeCommit d82b7990; live tip 96c968ad). Helper + Vitest land with tip callers (formatAdminApiError / import of adminApiError) including PlayerDetailEditor, ColonyOverview, MessageModeration, BangGalaxyPage wipe/add-region, AdminContext.wipeGalaxy (LEG-1315 / #780 / 1f6acc0e), CombatOverview intervene paths (#782), EconomyDashboard alerts/summary (#783 / Fibril #1446), and many pages inventory surfaces. Do not claim universal coverage: residuals include other AdminContext generic Failed to β¦ setError strings (wipeGalaxy is not among them), LanguageSwitcher empty progress catch (see Β§ i18n / Fibril #1336), and pages still without the helper (e.g. PlayerAnalytics.tsx generic catch β Admin LEG-962). UniverseManager.tsx is not a page-local formatAdmin residual on tip 96c968ad: it has no local API catch β it only surfaces useAdmin().error from AdminContext (Soft-ORDER / context-layer honesty owns remaining AdminContext generic strings; Fibril Soft-ORDER #1325 CLAIMED). Tip-PRESENT ReEngagementQueuePanel.tsx (mounted on PlayerAnalytics via #772 / mergeCommit 3fbb6073) now uses formatAdminApiError on load/update catches via #793 / tip 96c968ad β do not read #772 alone as full 403/429 coverage; #793 closed that residual. β
TradeDockAdmin.tsx tip-PRESENT read-only via #604 / Fibril #1146 @ 96c968ad (force-cancel Soft-ORDER #1561 still tip-pending). β
GS POST /admin/messages/bulk-moderate tip-PRESENT (#711 / LEG-266). β
GS canon accept / redact / block tip-PRESENT (#709 / LEG-263). Admin-UI bulk-moderate callers + Admin-UI accept/redact/block buttons stay Soft-HOLD unshipped (#1114/#1115 β zero Admin UI tip matches). Admin UI Soft-HOLD for BeaconSectorCapEditor stays on PR #764 / message-beacons.md β not claimed tip-shipped here.
ARIA per-player security-ops UI (UIADM-SECURITY-PLAYER-OPS) β π Design-only, gated behind the Wave-12 RBAC scope rollout (ADR-0093 item 10; Max overrode the build-now recommendation). The per-player ARIA security-ops surface β reviewing a specific player's ARIASecurityLog history, trust score, and violation count, and acting on it (block / unblock / reset trust) β is born scope-gated under an admin.aria.* scope family, never shipped flat-admin the way the legacy /admin/security/player/{id}/* endpoints in ./aria.md are today. It sequences behind the same 19-scope rollout as item 19.
Orphaned GC admin methods (manual grant/revoke) β π Design-only, gated behind the same scope rollout (ADR-0093 item 21). Manual Galactic-Citizen grant/revoke β support tooling for comping or clawing back GC status outside the normal PayPal subscription flow β wires as admin.subscriptions.*-scoped admin routes, sequenced behind item 19's scope rollout. See ./monetization.md Β§ Manual GC grant/revoke (admin support tooling) for the player-state side.
Admin REST rate limits¶
β
Shipped β admin endpoints are rate-limited per authenticated admin user (separate from the player-facing limits enforced on the realtime bus) via classify_admin_tier + ADMIN_TIER_LIMITS in services/gameserver/src/api/middleware/security.py, mounted as RateLimitingMiddleware:
| Endpoint class | Limit | Classifier |
|---|---|---|
Read (GET / HEAD / OPTIONS on /admin) |
100 / minute | default non-mutating admin path |
Write (POST / PUT / PATCH / DELETE) |
30 / minute | mutating admin path (non-bulk, non-report) |
Bulk operations (mutating + /bulk / /batch / /mass) |
10 / minute | _ADMIN_BULK_MARKERS |
Reports / exports (any method + /report(s) / /export(s)) |
5 / hour | _ADMIN_REPORT_MARKERS β ADMIN_TIER_REPORTS |
Bulk operations include market interventions (price cap / floor / supply injection), combat resolution overrides, mass message moderation, and asset transfers β actions that touch many rows in one call. The 5-per-hour reports cap covers expensive analytics queries that scan denormalized aggregates.
i18n¶
common/LanguageSwitcher.tsx with full i18next integration. Backed by six-language translation files (English / Spanish / French / Portuguese / Chinese launch-complete; German Partial β see ./i18n.md). On tip 96c968ad the switcher seeds from static SUPPORTED_LANGUAGES filtered by LAUNCH_COMPLETE_CODES (en/es/fr/zh/pt β static completionPercentage 100%, never the old non-en 0% fallback) and then refreshes each row from GET /api/v1/i18n/admin/progress/{code} when the API returns a finite percent (LanguageSwitcher.tsx:24-74; PR #720 / LEG-488 landed). German stays Partial in canon and is not in the launch-complete active set. Do not read this page as claiming Complete locales still show 0% in the Admin picker. Residual (tip 96c968ad): progress GET uses an empty catch that keeps the static 100% fallback on any failure β including 403 scope denial and 429 admin rate-limit β so RBAC/rate-limit errors can masquerade as a green completion bar (LanguageSwitcher.tsx ~68β70). That is not tip-shipped progress RBAC honesty. Product land path: Admin Fibril #1336 / LEG-1265. Shared adminApiError.ts is tip-shipped (Β§ Authorization model) but this switcher does not use it.
5. Build / test¶
# from services/admin-ui/
npm run dev # vite dev
npm run dev:watch # with live-reload watcher
npm run build # tsc + vite build
npm run lint # eslint
npm test # vitest run
npm run test:e2e # playwright via scripts/simple-run-tests.js
E2E specs live in repo-root e2e_tests/admin/ui/ β 13 *.spec.ts files on tip 96c968ad: the original six (login, dashboard, universe-generation, sector-editing, sector-editing-authenticated, user-management) plus deep-link-basename, economy-dashboard-mutations, economy-levers-panel, faction-mutations, player-detail-emergency, rbac-d-scopes-audit, and translation-key-edit.