feat: add HTMX endpoint stubs for all admin modules

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>
This commit is contained in:
Claude (Chronicler #57)
2026-04-03 18:07:39 +00:00
parent f7fec6fb84
commit 4c7a7a0832

View File

@@ -136,3 +136,104 @@ router.post('/mappings', isAdmin, express.json(), (req, res) => {
});
module.exports = router;
// ==========================================
// HTMX API ENDPOINTS (Return HTML fragments)
// ==========================================
// Servers Matrix Endpoint
router.get('/servers/matrix', isAdmin, async (req, res) => {
// TODO: Query server status from Pterodactyl API
res.send(`
<div class="p-6 text-center text-gray-500 dark:text-gray-400">
<p class="text-lg font-medium mb-2">Server Matrix Coming Soon</p>
<p class="text-sm">Will display real-time status of all 13 Minecraft servers</p>
</div>
`);
});
// Players Table Endpoint
router.get('/players/table', isAdmin, async (req, res) => {
// TODO: Query subscriptions with Discord user data
res.send(`
<div class="p-6 text-center text-gray-500 dark:text-gray-400">
<p class="text-lg font-medium mb-2">Player Database Coming Soon</p>
<p class="text-sm">Will display all subscribers with search/filter</p>
</div>
`);
});
// Grace Period List Endpoint
router.get('/grace/list', isAdmin, async (req, res) => {
const { Pool } = require('pg');
const pool = new Pool({
host: '127.0.0.1',
user: 'arbiter',
password: 'FireFrost2026!Arbiter',
database: 'arbiter_db'
});
try {
const result = await pool.query(`
SELECT discord_id, tier_level, grace_period_started_at, grace_period_ends_at
FROM subscriptions
WHERE status = 'grace_period'
ORDER BY grace_period_ends_at ASC
`);
if (result.rows.length === 0) {
res.send(`
<div class="p-8 text-center text-gray-500 dark:text-gray-400">
<p class="text-lg">✅ No users in grace period!</p>
<p class="text-sm mt-2">All subscribers are current on payments</p>
</div>
`);
} else {
let html = '<div class="divide-y divide-gray-200 dark:divide-gray-700">';
result.rows.forEach(row => {
const endsAt = new Date(row.grace_period_ends_at);
const hoursLeft = Math.round((endsAt - new Date()) / (1000 * 60 * 60));
html += `
<div class="p-4 hover:bg-gray-50 dark:hover:bg-gray-800">
<div class="flex justify-between items-center">
<div>
<div class="font-medium dark:text-white">Discord ID: ${row.discord_id}</div>
<div class="text-sm text-gray-500">Tier ${row.tier_level}</div>
</div>
<div class="text-right">
<div class="text-yellow-600 dark:text-yellow-400 font-medium">${hoursLeft}h remaining</div>
<div class="text-xs text-gray-500">${endsAt.toLocaleString()}</div>
</div>
</div>
</div>
`;
});
html += '</div>';
res.send(html);
}
} catch (error) {
res.send(`<div class="p-6 text-red-600">Error: ${error.message}</div>`);
}
});
// Audit Log Feed Endpoint
router.get('/audit/feed', isAdmin, async (req, res) => {
// TODO: Query webhook_events_processed table
res.send(`
<div class="p-6 text-center text-gray-500 dark:text-gray-400">
<p class="text-lg font-medium mb-2">Audit Log Coming Soon</p>
<p class="text-sm">Will display recent webhook events and role changes</p>
</div>
`);
});
// Role Mismatches Diagnostic Endpoint
router.get('/roles/mismatches', isAdmin, async (req, res) => {
// TODO: Compare Discord roles vs database subscriptions
res.send(`
<div class="p-6 text-center text-gray-500 dark:text-gray-400">
<p class="text-lg font-medium mb-2">Role Diagnostics Coming Soon</p>
<p class="text-sm">Will scan Discord server and compare with database</p>
</div>
`);
});