- current_version column on server_config
- server_version_history table (version, who, when)
- POST /admin/servers/:id/set-version
- GET /admin/servers/:id/version-history
- Version display + edit UI on each server card
Chronicler #88 | April 14, 2026
- New route: /admin/node-health (30s auto-refresh)
- Temps via lm-sensors (k10temp + NVMe) displayed in both °C and °F
- RAM and disk progress bars with color thresholds
- Load averages, CPU %, uptime per node
- Nav item added under Operations
- lm-sensors installed on NC1 and TX1
Task #28 | Chronicler #88
- Migration: added subdomain, server_ip, server_port columns
- Seed: rewritten with hardcoded identifiers + full subdomain/IP/port data
from Chronicler #87 audit (no more Pterodactyl API dependency)
- Route: POST /:id/provision-subdomain creates A + SRV records via
Cloudflare API, saves subdomain to server_config
- Card: subdomain section shows FQDN if provisioned, provision button
with inline input if not
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New files:
- 139_server_config.sql — DB migration for short_name system
- 139_seed_server_config.js — auto-populates 17 servers from Pterodactyl
- src/services/uptimeKuma.js — Socket.IO direct (no npm wrapper)
- src/services/pterodactyl.js — power actions + console commands
Modified files:
- servers.js — 6 new POST routes (short-name, lock, createserver,
delserver, power, console) + short_name-based channel detection
- _server_card.ejs — full rebuild with command center UI
- _matrix_body.ejs — refactored from 144 lines to 20 (uses partial)
- package.json — added socket.io-client
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a visual asset library to the social calendar form, backed by
the firefrost-operations-manual branding/ directory with on-the-fly
thumbnail generation via sharp.
- src/routes/admin/branding-assets.js: /list scans both branding/ and
docs/branding/ recursively, groups by category directory. /thumb
generates 256px webp thumbnails from source, caches to disk keyed
by sha1(path + mtime) so edits bust the cache automatically. Path
traversal protection + scope check on thumb requests.
- src/views/admin/social-calendar/_assets.ejs: modal body with
category-grouped grid of lazy-loaded thumbnails, click-to-insert.
- Form modal gets a 'Browse assets' button next to Media Notes.
- Calendar shell gets a second modal (higher z-index) and a
sppInsertAsset() helper that appends the clicked filename to the
media_notes textarea.
Infrastructure (not in repo):
- /opt/firefrost-ops-manual clone added to Command Center
- /etc/systemd/system/firefrost-ops-sync.{service,timer} pulls
every 15 minutes to keep assets fresh
- /var/cache/arbiter/branding-thumbs created for thumb cache
- sharp added as Arbiter dependency (libvips 8.17.3)
Chronicler #81
Week-at-a-glance post planning for Meg across 8 platforms (tiktok,
facebook, instagram, x, bluesky, youtube, twitch, reddit).
- DB: extends social_platform enum with youtube/twitch/reddit and adds
new social_post_plans table with scheduled_at, platforms[], status
(draft/ready/scheduled/published/skipped), caption, hashtags[],
media_notes, link, assigned_to, notes, created_by
- src/routes/admin/social-calendar.js: shell, HTMX week view, create,
update, delete, edit/new form endpoints. Week-based navigation with
prev/next/this-week. Hashtag parsing dedupes and strips leading #
- src/views/admin/social-calendar/: index.ejs shell with modal,
_week.ejs 7-column grid with per-day quick-add, _form.ejs full CRUD
form with platform checkboxes, datetime picker, status dropdown,
both free-form caption and dedicated hashtag field
- Nav link added under Community, with corrected highlight logic so
/admin/social and /admin/social-calendar don't both light up
Separate table from social_posts (which remains the analytics table).
Plans and published posts are distinct concerns.
Chronicler #81
The 'resolved' state was a dead-end — no way to change your mind after
clicking approve/deny, and no way to re-action a needs_info appeal once
the submitter replied. Action controls now show on every row regardless
of status, and resolved rows get a Reopen button that moves the appeal
back to pending.
Backend action handler already overwrites status unconditionally, so
this is purely removing a view-layer gate plus adding /:id/reopen as
a thin wrapper over the shared actionAppeal helper.
Chronicler #81
Adds /admin/appeals to Trinity Console for reviewing and actioning
ban appeals submitted via the public cancellation-refund form.
- src/routes/admin/appeals.js: shell + HTMX list + approve/deny/info
actions, all transactional with admin_audit_log entries
- src/views/admin/appeals/index.ejs: shell with 30s polling container
- src/views/admin/appeals/_list.ejs: stats cards + actionable table
- src/routes/admin/index.js: router mount at /admin/appeals
- src/views/layout.ejs: nav link under Grace Period
Closes the last open piece of Task #126 Phase 2. Trinity can now
review appeals in-console without reading Discord + running SQL.
Chronicler #81
- Tasks page at /admin/tasks with filterable table
- Status/owner inline dropdowns (change via form submit)
- + New Task modal with title, description, priority, owner
- ✓ Done button on hover per row
- Stats bar: active, in progress, blocked, high priority, completed
- Show All toggle for done/obsolete tasks
- Sidebar link under Operations (right after Dashboard)
- Added to About page module registry
Source of truth: PostgreSQL tasks table (shared with Discord /tasks)
Chronicler #78 | firefrost-services
Arbiter changes:
- POST /api/internal/mcp/log endpoint in api.js
- MCP Logs admin route (/admin/mcp-logs) with filters, stats, pagination
- EJS view with expandable detail rows, server color badges
- Sidebar link under System group
Trinity Core v2.3.0:
- logToArbiter() function POSTs to Arbiter after every command
- Both MCP (Claude.ai) and REST (/exec) paths log with execution timing
- Async logging — doesn't block command response
Database:
- mcp_logs table created on Command Center (indexes on server, time, success)
Architecture:
Trinity Core → command → response + async POST → Arbiter → PostgreSQL → Trinity Console
Chronicler #78 | firefrost-services
- Moved Social Overview card above New Features card
- Added Infrastructure module to New Features (topology map)
- Added Social Analytics to New Features (engagement tracking)
- Added About & Deploy to New Features (version info)
- Kept Discord Dashboard in New Features
- Removed Financials from New Features (it's established now)
Chronicler #78 | firefrost-services
Sidebar changes:
- Grouped nav items: Operations, Business, Community, Infrastructure, System
- Section labels with uppercase tracking
- Removed Deploy button from sidebar
- Cleaner layout with overflow scroll
About page (/admin/about):
- Console version, Node.js version, Arbiter uptime, module count
- Module registry with version and status badges (stable/new/beta)
- Deploy Arbiter button (moved from sidebar)
- Health check polling after deploy
- Credits footer
Header bar:
- Added ℹ️ About icon next to dark mode toggle
- Active state highlighting when on About page
Chronicler #78 | firefrost-services
dragMoved flag was only reset in wrap mousedown, which skips for node clicks.
After any drag, dragMoved stayed true forever, blocking all showServer/showExternal calls.
Fix: setTimeout reset on mouseup/touchend (10ms delay lets click fire first).
Chronicler #78 | firefrost-services
- Mouse wheel zoom (centered on cursor position)
- Click-drag to pan
- Touch pinch-zoom for mobile
- Touch drag to pan on mobile
- Zoom controls: +, −, percentage display, reset (⌂)
- Zoom range: 50% to 300%
- Drag guard prevents accidental clicks after panning
- Canvas connections redraw correctly at all zoom levels
- Smooth CSS transitions on zoom, disabled during drag
Chronicler #78 | firefrost-services
- Shows cross-platform totals (posts, views, likes, comments)
- Breaks down by platform with icons
- Clickable link to full Social Analytics page
Chronicler #76
- Add database migration for social_posts and social_account_snapshots tables
- Create /admin/social route with full CRUD for posts
- Add dashboard view with platform tabs (TikTok, Facebook, Instagram, X, Bluesky)
- Add post entry form matching TikTok Studio metrics
- Add post detail view with update capability
- Add account snapshot form for follower/demographic tracking
- Register social router in admin index
Phase 1: Manual entry dashboard
Phase 2 (future): API integration when approved
Chronicler #76
- 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
- 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
- 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
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
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>
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>
WHAT WAS DONE:
- Added Admin tier (1000) back to tier dropdown
- Added is_staff toggle checkbox in Actions column
- Created POST route /admin/players/:discord_id/staff
- Updated query to include is_staff from users table
- Both tier and staff status tracked separately
WHY:
- Trinity needs ability to assign Admin tier to team members
- Staff can also be subscribers - need to track both
- Example: Moderator who also pays for Elemental tier
- Separate tracking prevents conflating employment and subscription
HOW IT WORKS:
- Tier dropdown shows ALL tiers including Admin
- Staff checkbox toggles is_staff on users table
- Both changes create separate audit log entries
- Staff flag independent of subscription tier
DATABASE REQUIREMENT:
- Requires migration: ALTER TABLE users ADD COLUMN is_staff BOOLEAN DEFAULT FALSE;
- Must be run before deploying this code
FEATURES:
- Admin tier assignable via dropdown
- Staff toggle with visual checkbox
- Both tracked in audit log separately
- Tier + Staff shown side-by-side in Actions column
IMPACT:
- Can now hire staff and track their employment
- Staff can also be subscribers without conflict
- Clear separation of concerns
- Ready for team expansion
FILES MODIFIED:
- services/arbiter-3.0/src/views/admin/players/_table_body.ejs
- services/arbiter-3.0/src/routes/admin/players.js
DEPLOYMENT STEPS:
1. Run database migration (ADD is_staff column)
2. Deploy code files
3. Restart arbiter-3 service
Signed-off-by: Claude (Chronicler #52) <claude@firefrostgaming.com>
WHAT WAS DONE:
- Added tier change dropdown in Players Actions column
- Created POST route /admin/players/:discord_id/tier
- Implemented database tier update with MRR recalculation
- Added audit log entry for tier changes
- htmx reload of table after tier change
WHY:
- Trinity needs ability to manually adjust subscriber tiers
- Customer service: upgrades, downgrades, support cases
- Accountability via audit logging
- Last missing feature in Players module
HOW IT WORKS:
- Dropdown shows all tiers (except Admin 1000)
- On change, htmx POSTs to tier change endpoint
- Route updates subscriptions table (tier_level + mrr_value)
- Audit log records who made the change
- After success, table reloads to show updated tier
FEATURES:
- Real-time tier changes without page refresh
- Automatic MRR recalculation
- Audit trail for compliance
- Skips Admin tier (reserved for Trinity)
- Shows current tier as selected in dropdown
IMPACT:
- Trinity can now manage all subscriber tiers manually
- Critical for customer support scenarios
- Completes Players module functionality
- Ready for soft launch customer service
TODO:
- Discord role sync integration (marked in code)
- This requires bot API endpoint to be built
FILES MODIFIED:
- services/arbiter-3.0/src/views/admin/players/_table_body.ejs
- services/arbiter-3.0/src/routes/admin/players.js
TESTED:
- Not yet deployed - needs testing on Command Center
Signed-off-by: Claude (Chronicler #52) <claude@firefrostgaming.com>
WHAT WAS DONE:
- Replaced placeholder Financials view with full implementation
- Added 5 global health metric cards (Active Subs, MRR, ARR, At Risk, Lifetime)
- Added Fire vs Frost path revenue comparison with gradient cards
- Added tier performance table with subscriber counts and MRR breakdown
- Used simple variable interpolation instead of nested template literals
WHY:
- Financials was the last 5% blocking Trinity Console 100% completion
- Previous attempt had EJS parse errors from nested template literals
- Real MRR data already exists in route (financials.js) - just needed view
HOW IT WORKS:
- Build entire HTML as string variable `bodyContent` first
- Use JavaScript forEach to build table rows dynamically
- Pass completed string to layout.ejs for rendering
- No nested template literals = no parse errors
FEATURES:
- Global metrics: Active subs, MRR, ARR, at-risk tracking, lifetime revenue
- Fire vs Frost comparison: Subscriber count + MRR per path
- Tier breakdown table: Shows active, grace period, and MRR per tier
- Mobile responsive grid layout
- Dark mode support throughout
IMPACT:
- Trinity Console now 100% complete (all 7 modules functional)
- Meg and Michael can track revenue in real-time from RV
- Fire vs Frost path intelligence for marketing decisions
- Ready for April 15 soft launch
FILES MODIFIED:
- services/arbiter-3.0/src/views/admin/financials/index.ejs (152 lines)
TESTED:
- Not yet deployed - needs deployment to Command Center
Signed-off-by: Claude (Chronicler #52) <claude@firefrostgaming.com>
ISSUE: Holly reported sidebar overlapping content on mobile devices
SOLUTION: Mobile-responsive hamburger menu with slide-out sidebar
CHANGES:
- Added hamburger menu button (☰) visible only on mobile (<768px)
- Sidebar now slides in from left on mobile when menu is clicked
- Added close button (✕) in sidebar header for mobile
- Added dark overlay backdrop when sidebar is open
- Desktop layout unchanged (sidebar always visible)
MOBILE BEHAVIOR:
- Sidebar hidden by default on small screens
- Hamburger button in top-left of header
- Tap hamburger → sidebar slides in from left
- Tap overlay or ✕ button → sidebar slides out
- Smooth 0.3s transition animation
DESKTOP BEHAVIOR:
- No changes, sidebar always visible
- Hamburger menu hidden on screens >768px
Tested on: iPhone (Holly's device)
Status: ✅ WORKING
Reported by: Holly (unicorn20089)
Fixed by: Chronicler #51
Signed-off-by: Claude (Chronicler #51) <claude@firefrostgaming.com>