Today was a full pass on Realm’s server panel: three feature threads (dashboard, AI, DNS) and a long UI-consistency sweep, capped off by one infra bug that had been silently breaking every dynamic setting in the panel.
Started with the dashboard: replaced the old card-style ServerRow list with a proper ServerTable — sortable by name and game type, with circular progress indicators for resource usage instead of flat bars. Added a public IP lookup service and a ServerInfoCard for the console page showing location, IP, and domain info at a glance.
Then built an AI provider helper: admin picks one provider (Claude, OpenAI, DeepSeek, Gemini, OpenRouter, or a custom OpenAI-compatible endpoint), drops in an API key, done. No multi-provider list to manage — one row in the DB, one config screen under Settings → Advanced. Wrote three concrete provider adapters behind a shared interface (AnthropicProvider talks the native /v1/messages API, GoogleProvider handles Gemini’s role mapping, OpenAiCompatibleProvider covers everything that speaks the OpenAI wire format) so future features can just call AiService::complete() without caring which backend is configured.
Also wired up Cloudflare zone management for subdomains, and gave the Console a log-upload + AI-summarize action.
What I built:
- ServerTable (sortable dashboard) + circular resource indicators, IP lookup + ServerInfoCard
- Single-provider AI helper: model, factory, three adapters, admin UI, wired into Console’s summarize feature
- Cloudflare integration for subdomain zone/record management
- A full step-wizard treatment (sidebar steps, numbered circles, review step) rolled out to every creation modal that used to be a plain form: Change Version, Invite Subuser, Allocation Firewall, Create Subdomain, Create Database, Create Backup
- Converted Allocations, Databases, and Backups from card grids to proper tables (header row, divide-y rows, right-click context menu on allocations instead of a button pile), with the “create new” action moved to plain text + hyperlink below each table instead of a header-bar button
- Restyled the base Modal component itself — dropped the floating X close icon, added consistent border/radius, gave it an optional title/footer API so simple confirm dialogs get the same content-pane-plus-footer-bar layout as the wizards
- Footer now shows the git commit hash + panel version with a fork icon instead of a static copyright line
The interesting part was two bugs that had nothing to do with what I was actively building.
First: Settings → Advanced looked like it saved, but nothing actually persisted — not the AI config, not the allocation port range. Chased it through three layers: my own save logic, then a native HTML required attribute silently blocking the shared form submit, and finally the real one — SettingsServiceProvider, the class responsible for merging DB-stored settings back into Laravel’s config() on every request, was never registered in config/app.php. Every dynamic setting in the entire panel — mail, OAuth, allocations, all of it — had silently never worked, completely unrelated to anything I’d touched that day.
Second: tried to delete a backup in the dev environment and got a 500. Wings was shelling out to a rustic binary that straight-up didn’t exist in the container — the Dockerfile never installed it, so every backup on this dev box had been failing since day one without anyone noticing (because dev servers don’t usually need backups). Added the install step to build/wings/Dockerfile, rebuilt the image, and the failed backup’s leftover DB row still needed a manual delete since its rustic repo was never initialized in the first place.
Neither of those two bugs was flashy, but both had been quietly breaking things for a while — the kind of thing you only find when you actually click through the feature end-to-end instead of trusting that tsc passing means it works.