# 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.ejs` duplicates 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 has `atm10-tts`)
- Designed the `short_name` system to fix this permanently
- Added Pterodactyl Admin API key to Arbiter `.env` and 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 `/createserver` and `/delserver` move 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`

```sql
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

```bash
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 channel
- `deleteMonitor(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}-status` channel

**Important:** Look at existing monitors in Uptime Kuma to match the exact format.
Run this to see existing monitor names:
```bash
# Check existing monitor names via Kuma API
```

### `src/services/pterodactyl.js`

Consolidate Pterodactyl API calls. Must handle:
- `powerAction(identifier, signal)` — start/stop/restart/kill
- `sendCommand(identifier, command)` — send console command
- `getServerResources(identifier)` — CPU, RAM, player count (via SFTP query or resources endpoint)
- Uses `PANEL_CLIENT_KEY` for client endpoints, `PANEL_ADMIN_KEY` for application endpoints

---

## Part 4: Backend Route Changes

### `src/routes/admin/servers.js` — major additions

#### POST `/:identifier/set-short-name`
- Validates `short_name` is URL-safe (lowercase, hyphens, numbers only)
- Checks uniqueness against `server_config` table
- 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_config` row (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-name` as 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.

```javascript
// 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 install` after adding `uptime-kuma-api`
- Run migration SQL before deploying new code
- Pre-populate `server_config` with seed data before first load
- Test `createserver` on 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`, restart `arbiter-3`

---

## Questions for Code Before Starting

1. Does `uptime-kuma-api` npm package support Uptime Kuma 2.1.0? Verify before using.
2. 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?
3. Farm Crossing 6 is missing only the `-status` channel — should `createserver` handle 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.*
