- Checks for 4 channels per server: chat, in-game, forum, voice
- Shows 'All 4 channels configured' or lists missing channels
- Caches Discord channel data for 5 minutes to reduce API calls
Deletes complete server setup:
- All channels in category
- The category itself
- The server role
Requires confirm:True to execute.
Without confirm, shows preview of what would be deleted.
Reminds to clean up Carl-bot reaction roles.
Staff only.
Chronicler: #71
- New sidebar entry for Discord
- Full server structure visualization
- Channel tree with expandable categories
- Role hierarchy with color badges
- Health checks (orphan channels, empty roles, bot roles)
- Search/filter across channels and roles
- Click channel to see permission overwrites
- Click role to see explicit channel access
- Responsive design with modal details view
Chronicler: #70
New endpoints for Trinity Console:
- GET /admin/discord/audit — Full server audit (channels, roles, structure)
- GET /admin/discord/channels — Just channels
- GET /admin/discord/roles — Just roles
Returns:
- Server info (name, member count, features)
- Categories with nested children
- Orphan channels (not in categories)
- Role hierarchy with positions and member counts
- Permission overwrites per channel
Uses existing Discord.js client from app.locals.
Chronicler #70
When hourly sync encounters servers that fail (e.g., mid-restart):
- Logs the failure count
- Schedules automatic retry in 10 minutes
- Retry only targets previously failed servers
- Clears error state on successful retry
Fixes issue where servers in daily restart would stay in error state
until manual intervention.
Chronicler #69
- Queries server_sync_log for most recent successful sync
- Displays date (e.g., 'Apr 8') and time (e.g., '3:28 AM')
- Shows yellow dash with 'Never' if no syncs recorded
Chronicler #69
The subscriptions table uses:
- tier_level (integer) not tier_id
- mrr_value (pre-calculated) not joined to subscription_tiers
- is_lifetime (boolean) not status='lifetime'
Chronicler #69
Dashboard was showing hardcoded values:
- Servers Online: 12 (should be 22)
- Active Subscribers: 0
- Total MRR: $0
Now fetches live data:
- Server count from Pterodactyl API via getMinecraftServers()
- Subscriber count and MRR from arbiter_db subscriptions table
Files changed:
- src/routes/admin/index.js: async dashboard route with data fetching
- src/views/admin/dashboard.ejs: EJS variables instead of hardcoded values
Chronicler #69
WHAT THIS ADDS:
- Discord role sync on new subscriptions (checkout.session.completed)
- Discord role removal on chargebacks (charge.dispute.created)
- Grace period expiration job (hourly cron check)
- Automatic downgrade to Awakened when grace period expires
NEW FILES:
- src/services/discordRoleSync.js - Role add/remove/sync functions
- src/sync/graceExpiration.js - Grace period expiration processor
MODIFIED FILES:
- src/routes/stripe.js - Added role sync calls to webhook handlers
- src/discord/events.js - Initialize role sync service on bot ready
- src/sync/cron.js - Added grace period check to hourly job
- src/index.js - Import discordRoleSync service
PHILOSOPHY:
'We Don't Kick People Out' - expired grace periods downgrade to
permanent Awakened tier (tier 1, lifetime). Users keep community
access, just lose premium perks.
ROLE MAPPING (tier_level -> role key):
1=the-awakened, 2=fire-elemental, 3=frost-elemental,
4=fire-knight, 5=frost-knight, 6=fire-master, 7=frost-master,
8=fire-legend, 9=frost-legend, 10=the-sovereign
CHARGEBACKS:
- Immediate role removal
- Added to banned_users table
- Full audit logging
Signed-off-by: Claude (Chronicler #62) <claude@firefrostgaming.com>
Replaces MemoryStore with connect-pg-simple.
Sessions now persist across Arbiter restarts.
Table 'session' auto-created if missing.
Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
Removed power task filter — Pterodactyl doesn't include task
relationships by default. Now catches any schedule not prefixed
with [Trinity].
Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
express-ejs-layouts doesn't support nested includes.
Changed scheduler.ejs to inline the table HTML.
Changed routes to return raw HTML for HTMX partials instead of rendering.
Signed-off-by: Claude (Chronicler #61) <claude@firefrostgaming.com>
WHAT WAS DONE:
- Added POST /admin/servers/sync-all/:node endpoint
- Accepts 'tx1' or 'nc1' as node parameter
- Syncs whitelist to all servers on that node
- Returns count of synced/errors
- Wired up buttons in index.ejs with htmx
- hx-post to the new endpoint
- Results display in #sync-result span
Files changed:
- services/arbiter-3.0/src/routes/admin/servers.js (+45 lines)
- services/arbiter-3.0/src/views/admin/servers/index.ejs
Signed-off-by: Claude (Chronicler #60) <claude@firefrostgaming.com>
WHAT WAS DONE:
- Re-added MINECRAFT_NEST_IDS filtering
- Keeps the node ID mapping fix (2→NC1, 3→TX1)
WHY:
Non-Minecraft servers were appearing in the matrix.
We need to filter to only show Minecraft servers.
Signed-off-by: Claude (Chronicler #60) <claude@firefrostgaming.com>
WHAT WAS DONE:
- Removed MINECRAFT_NEST_IDS filtering
- Now shows ALL servers from Pterodactyl, not just Minecraft nests
WHY:
Trinity Console should show all servers for management,
not just those in specific nests.
Signed-off-by: Claude (Chronicler #60) <claude@firefrostgaming.com>
WHAT WAS DONE:
- discovery.js: Added node field to server objects
- Maps Pterodactyl node ID 2 → NC1
- Maps Pterodactyl node ID 3 → TX1
- Also includes raw nodeId for debugging
- servers.js: Simplified grouping logic
- Removed fallback checks for 'Node 2', 'Node 3', name patterns
- Now uses clean s.node === 'TX1' / 'NC1' checks
THE BUG:
getMinecraftServers() was only returning identifier and name,
but the matrix filter was checking s.node which was undefined.
Servers were being grouped by name pattern fallback only.
Files changed:
- services/arbiter-3.0/src/panel/discovery.js (+8 lines)
- services/arbiter-3.0/src/routes/admin/servers.js (simplified)
Signed-off-by: Claude (Chronicler #60) <claude@firefrostgaming.com>
ISSUE:
We accidentally built a flat admin.js that replaced the working modular system
All modules (Players, Servers, Grace, Audit, Roles, Financials) exist in /routes/admin/
FIX:
1. Backed up flat admin.js to admin-backup-chronicler57.js
2. Changed index.js to require('./routes/admin/index')
3. Restored proper modular structure
MODULES RESTORED:
- /admin/players - Full CRUD with detail view, tier changes, staff toggle
- /admin/servers - Server matrix with Pterodactyl integration
- /admin/grace - Grace period management
- /admin/audit - Audit log feed
- /admin/roles - Role diagnostics
- /admin/financials - Revenue analytics
ALL ORIGINAL FUNCTIONALITY RESTORED
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
FIXES:
1. Players: mrr_value is decimal type, needs parseFloat() before toFixed()
2. Audit Log: Use SELECT * to handle any column name variations
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
COMPLETED ALL 5 ENDPOINTS:
1. Grace Period ✅ (already done)
- Shows users in grace period with countdown
2. Audit Log ✅ NEW
- Queries webhook_events_processed table
- Shows last 50 webhook events
- Color-coded by event type
3. Players ✅ NEW
- Queries subscriptions table
- Shows all subscribers with tier, status, MRR
- Sortable table with 100 most recent
4. Servers Matrix ✅ NEW
- Static server list (7 servers)
- Shows machine, status, player count
- Note about Pterodactyl API integration coming
5. Role Diagnostics ✅ NEW
- Shows subscription counts by tier
- Summary of active vs lifetime
- Note about Discord API integration coming
ALL ADMIN PAGES NOW FUNCTIONAL FOR SOFT LAUNCH
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
ISSUE (per Gemini consultation):
express-ejs-layouts was wrapping HTMX AJAX responses in full layout
Caused crashes because HTMX endpoints don't pass layout variables
FIX:
Added middleware to detect HX-Request header
Sets res.locals.layout = false for HTMX requests
HTMX endpoints now return raw HTML fragments
This fixes all 5 admin modules:
- Servers Matrix
- Player Management
- Grace Period (with real DB query)
- Audit Log
- Role Diagnostics
Credit: Gemini consultation - HTMX middleware pattern
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
Templates use HTMX to load data dynamically via AJAX
Added 5 endpoint stubs (will implement with real data):
1. /admin/servers/matrix - Server status grid
2. /admin/players/table - Player list
3. /admin/grace/list - Grace period users (WORKING with real DB query)
4. /admin/audit/feed - Recent webhook events
5. /admin/roles/mismatches - Discord role diagnostics
Grace endpoint COMPLETE - queries PostgreSQL and shows users in grace period
Others return 'Coming Soon' placeholders
TESTING:
- Grace Period page should show real data
- Other pages show 'Coming Soon' instead of loading spinner
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
ISSUE:
All module templates still had old EJS v2 include() wrapper
Caused 'include is not a function' errors
FIX:
Removed first and last line (include wrapper) from:
- servers/index.ejs
- players/index.ejs
- roles/index.ejs
- grace/index.ejs
- audit/index.ejs
- financials/index.ejs
Same fix as dashboard.ejs - express-ejs-layouts handles layout injection
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
ISSUE:
Layout.ejs expects 'title' parameter but admin route wasn't passing it
FIX:
Added title: 'Dashboard' to render context
Added error.message to error output for debugging
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>
ISSUE:
Admin panel at /admin returned JSON instead of EJS template
User authenticated successfully but saw raw data
FIX:
Changed res.json() to res.render('admin/dashboard')
Added user, mappings, csrfToken to template context
FILES MODIFIED:
- services/arbiter-3.0/src/routes/admin.js (5 lines changed)
TESTING:
Visit /admin after Discord OAuth, should show HTML UI
Signed-off-by: Claude (Chronicler #57) <claude@firefrostgaming.com>