11 KiB
Feature Request: Server Command Center Rebuild
Date: 2026-04-14 Topic: Rebuild Trinity Console server module into a full server command center Priority: HIGH — post-launch week, target build start April 15+ Filed by: Chronicler #87 Scope: Large — multiple new files, DB migration, new npm dependency
Background
The current server matrix page (/admin/servers) is functional but minimal:
- Shows online/offline, whitelist status, last sync, Discord channel status
- Sync Now + Toggle Whitelist buttons only
- Discord channel detection is broken for many servers (slug derivation from full name fails)
- No power controls, no restart scheduling, no createserver/delserver from UI
_matrix_body.ejsduplicates all card HTML inline instead of using_server_card.ejs
Michael wants this page to become a Server Command Center — the single place to manage every aspect of every game server without touching the Pterodactyl panel UI.
Session Context (Chronicler #87, April 13-14)
Tonight we:
- Discovered all Discord channel detection is broken due to slug derivation failures
(e.g. "All the Mods 10: To the Sky" → code generates
all-the-mods-10-to-the-sky, Discord hasatm10-tts) - Designed the
short_namesystem to fix this permanently - Added Pterodactyl Admin API key to Arbiter
.envand ops manual - Added Uptime Kuma API key to Arbiter
.env - Confirmed Uptime Kuma 2.1.0 uses Socket.IO (not REST) for monitor management
- Established that
/createserverand/delservermove to Trinity Console buttons (slash commands remain as fallback only) - Confirmed 5 Discord channels per server: chat, in-game, forum, status, voice
New Environment Variables (already added to /opt/arbiter-3.0/.env)
UPTIME_KUMA_URL=http://localhost:3001
UPTIME_KUMA_API_KEY=uk2_-iM8Trb4ftJCedpv2Kcz2JRSD49Zrv-gbNkVyh87
PANEL_ADMIN_KEY=ptla_4eKCnPBofAmvLDjouTGS5OagDpIra58nRetjnXOeoh5
Note: PANEL_CLIENT_KEY and PANEL_APPLICATION_KEY already exist in .env.
Use PANEL_ADMIN_KEY for power actions and application-level calls.
Part 1: Database Migration
New table: server_config
CREATE TABLE IF NOT EXISTS server_config (
server_identifier VARCHAR(36) PRIMARY KEY,
short_name VARCHAR(64) UNIQUE,
short_name_locked BOOLEAN DEFAULT false,
display_name VARCHAR(128),
restart_enabled BOOLEAN DEFAULT true,
restart_offset_minutes INTEGER DEFAULT 0,
node VARCHAR(8),
pterodactyl_name VARCHAR(128),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
restart_enabled and restart_offset_minutes are for Task #94 (Global Restart Scheduler) — include now, use later.
Part 2: New npm Dependency
npm install uptime-kuma-api
This is the official Socket.IO wrapper for Uptime Kuma 2.x. REST API only exposes status pages — monitor CRUD requires Socket.IO.
Part 3: New Service Files
src/services/uptimeKuma.js
Wrapper around uptime-kuma-api. Must handle:
- Connect/disconnect lifecycle (don't leave connections open)
createMonitor(name, hostname, port, discordChannelId)— creates a Game monitor type, sets Discord notification to the status channeldeleteMonitor(name)— finds by name, deletes- Error handling if Kuma is unreachable (don't crash createserver)
Monitor config to use:
- Type:
port(TCP port check, works for Minecraft) - Interval: 60 seconds
- Name format:
{pterodactyl_name} - {node}(matches existing monitors e.g. "Stoneblock 4 - TX") - Notification: Discord webhook to the
{short_name}-statuschannel
Important: Look at existing monitors in Uptime Kuma to match the exact format. Run this to see existing monitor names:
# Check existing monitor names via Kuma API
src/services/pterodactyl.js
Consolidate Pterodactyl API calls. Must handle:
powerAction(identifier, signal)— start/stop/restart/killsendCommand(identifier, command)— send console commandgetServerResources(identifier)— CPU, RAM, player count (via SFTP query or resources endpoint)- Uses
PANEL_CLIENT_KEYfor client endpoints,PANEL_ADMIN_KEYfor application endpoints
Part 4: Backend Route Changes
src/routes/admin/servers.js — major additions
POST /:identifier/set-short-name
- Validates
short_nameis URL-safe (lowercase, hyphens, numbers only) - Checks uniqueness against
server_configtable - If not locked: saves to DB, sets
short_name_locked = false - Returns updated card HTML via HTMX
POST /:identifier/lock-short-name
- Sets
short_name_locked = true— IRREVERSIBLE - Returns updated card HTML (lock button disappears, field becomes read-only)
POST /:identifier/createserver
- Requires
short_name_locked = true— reject if not - Creates Discord category:
🎮 {pterodactyl_name} - Creates 5 channels under category:
{short_name}-chat(text){short_name}-in-game(text){short_name}-forum(forum){short_name}-status(text — Uptime Kuma posts here){pterodactyl_name}(voice)
- Creates Uptime Kuma monitor linked to status channel
- Returns success/error to card
POST /:identifier/delserver
- Confirmation required (HTMX confirm dialog)
- Deletes all 5 channels + category from Discord
- Deletes Uptime Kuma monitor
- Does NOT delete
server_configrow (keep history) - Does NOT touch Pterodactyl (server stays in panel)
POST /:identifier/power
- Body:
{ signal: 'start' | 'stop' | 'restart' | 'kill' } - Calls
pterodactyl.powerAction() - Returns inline status to card
POST /:identifier/console
- Body:
{ command: string } - Calls
pterodactyl.sendCommand() - Used for the restart warning title/tellraw commands
Part 5: View Changes
src/views/admin/servers/_server_card.ejs — full rebuild
Each card should display:
Header row:
- Server name (bold)
- Node badge (🔥 TX1 or ❄️ NC1)
- Online/offline pill (green pulse or gray)
Short name section (shown if not locked):
- Text input: "Discord short name (e.g. atm10-tts)"
- "Save" button →
hx-post="/:id/set-short-name" - "Lock In" button (red, confirm dialog) →
hx-post="/:id/lock-short-name" - Warning: "⚠️ Once locked, this cannot be changed"
Short name section (shown if locked):
- Shows
short-nameas read-only badge - No edit option
Stats row (2-col grid):
- Whitelist: ✅ Enabled / 🔓 Disabled
- Last Sync: timestamp
Discord channels status:
- If locked: show each of 5 channels with ✅/❌
- If not locked: show "Set short name to enable channel detection"
Action buttons:
- ⚡ Sync Now
- Toggle Whitelist
- 🚀 Create Server (Discord) — only if locked, only if channels missing
- 🗑️ Delete Server (Discord) — only if channels exist, red, confirm required
- Power: ▶️ Start | ⏹ Stop | 🔄 Restart (only if online status known)
src/views/admin/servers/_matrix_body.ejs
Refactor to use <%- include('./_server_card', { server }) %> instead of duplicating card HTML.
Part 6: Discord Channel Detection Fix
Update checkServerChannels() in servers.js to use short_name from server_config instead of deriving from the full server name.
// NEW: use short_name from DB
function checkServerChannels(shortName, serverName, allChannels) {
if (!shortName) return { missing: [], found: [], complete: false, unconfigured: true };
const expectedChannels = [
{ name: `${shortName}-chat`, type: 'text' },
{ name: `${shortName}-in-game`, type: 'text' },
{ name: `${shortName}-forum`, type: 'forum' },
{ name: `${shortName}-status`, type: 'text' },
{ name: serverName.split(' - ')[0].replace(/\s*\([^)]*\)/g, '').trim(), type: 'voice' }
];
// ... rest of detection logic
}
Part 7: Existing Short Names to Pre-Populate
These are already confirmed from Discord channel audit (Chronicler #87):
| Pterodactyl Name | short_name | Node |
|---|---|---|
| All the Mods 10: To the Sky | atm10-tts | NC1 |
| All the Mons | all-the-mons | NC1 |
| Mythcraft 5 | mythcraft-5 | NC1 |
| All of Create (Creative) | all-of-create | NC1 |
| DeceasedCraft | deceasedcraft | NC1 |
| Sneak's Pirate Pack | sneaks-pirate-pack | NC1 |
| Otherworld [Dungeons & Dragons] | otherworld | NC1 |
| Farm Crossing 6 | farm-crossing-6 | NC1 |
| Homestead - A Cozy Survival Experience | homestead | NC1 |
| Stoneblock 4 | stoneblock-4 | TX1 |
| Society: Sunlit Valley | society-sunlit-valley | TX1 |
| Submerged 2 | submerged-2 | TX1 |
| Beyond Depth | beyond-depth | TX1 |
| Beyond Ascension | beyond-ascension | TX1 |
| Cottage Witch | cottage-witch | TX1 |
| All The Mons (Private) - TX | all-the-mons-private | TX1 |
| Wold's Vaults | wolds-vaults | TX1 |
Write a migration script that pre-populates server_config with these AND sets short_name_locked = true for all of them — they're already live in Discord, locking is appropriate.
Farm Crossing 6 is missing its -status channel — note this in the card UI so Michael can run createserver for just that channel (or we handle partial channel creation).
Part 8: Files to Create/Modify
| File | Action |
|---|---|
src/services/uptimeKuma.js |
NEW |
src/services/pterodactyl.js |
NEW |
src/routes/admin/servers.js |
MODIFY (major) |
src/views/admin/servers/_server_card.ejs |
REBUILD |
src/views/admin/servers/_matrix_body.ejs |
REFACTOR |
package.json |
MODIFY (add uptime-kuma-api) |
migrations/add-server-config.sql |
NEW |
migrations/seed-server-config.sql |
NEW |
Part 9: What NOT to Build Yet
These are on the roadmap but NOT in this request:
- Restart scheduler UI — Task #94, separate request after this lands
- Console command input on card — Phase 2
- RAM/CPU/player count display — Phase 2
- Partial channel creation (create only missing channels) — Phase 2
Build the foundation clean. Phase 2 adds to it.
Deployment Notes
- Run
npm installafter addinguptime-kuma-api - Run migration SQL before deploying new code
- Pre-populate
server_configwith seed data before first load - Test
createserveron a non-live server first (use All of Create Creative as test bed — it has no subscribers) - Standard deploy pattern: clone to
/tmp, rsync to/opt/arbiter-3.0, restartarbiter-3
Questions for Code Before Starting
- Does
uptime-kuma-apinpm package support Uptime Kuma 2.1.0? Verify before using. - What's the correct Discord category structure — should all 5 channels live under one
🎮 {name}category, or does the voice channel go in a separate voice category? - Farm Crossing 6 is missing only the
-statuschannel — shouldcreateserverhandle partial creation (only create missing channels) or always create all 5?
Filed by Chronicler #87 — April 14, 2026 This is the foundation of the Server Command Center. Build it clean.