- 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
130 lines
4.4 KiB
JavaScript
130 lines
4.4 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { requireTrinityAccess } = require('./middleware');
|
|
const { getMinecraftServers } = require('../../panel/discovery');
|
|
const db = require('../../database');
|
|
|
|
// Sub-routers
|
|
const playersRouter = require('./players');
|
|
const serversRouter = require('./servers');
|
|
const financialsRouter = require('./financials');
|
|
const graceRouter = require('./grace');
|
|
const auditRouter = require('./audit');
|
|
const rolesRouter = require('./roles');
|
|
const schedulerRouter = require('./scheduler');
|
|
const discordAuditRouter = require('./discord-audit');
|
|
const systemRouter = require('./system');
|
|
const socialRouter = require('./social');
|
|
const infrastructureRouter = require('./infrastructure');
|
|
const aboutRouter = require('./about');
|
|
const mcpLogsRouter = require('./mcp-logs');
|
|
const tasksRouter = require('./tasks');
|
|
|
|
router.use(requireTrinityAccess);
|
|
|
|
// Make CSRF token available to all admin views
|
|
router.use((req, res, next) => {
|
|
res.locals.csrfToken = req.csrfToken();
|
|
next();
|
|
});
|
|
|
|
router.get('/', (req, res) => {
|
|
res.redirect('/admin/dashboard');
|
|
});
|
|
|
|
router.get('/dashboard', async (req, res) => {
|
|
try {
|
|
// Fetch server count from Pterodactyl
|
|
const servers = await getMinecraftServers();
|
|
const serversOnline = servers.length;
|
|
|
|
// Fetch subscriber stats from database
|
|
const { rows: subStats } = await db.query(`
|
|
SELECT
|
|
COUNT(*) FILTER (WHERE status IN ('active', 'grace_period') OR is_lifetime = true) as active_count,
|
|
COALESCE(SUM(mrr_value) FILTER (WHERE status = 'active'), 0) as mrr
|
|
FROM subscriptions
|
|
`);
|
|
|
|
const activeSubscribers = parseInt(subStats[0]?.active_count || 0);
|
|
const totalMRR = parseFloat(subStats[0]?.mrr || 0);
|
|
|
|
// Fetch most recent successful sync time
|
|
const { rows: syncRows } = await db.query(`
|
|
SELECT MAX(last_successful_sync) as last_sync
|
|
FROM server_sync_log
|
|
WHERE is_online = true
|
|
`);
|
|
const lastSyncTime = syncRows[0]?.last_sync || null;
|
|
|
|
// Fetch social stats across all platforms
|
|
const { rows: socialStats } = await db.query(`
|
|
SELECT
|
|
platform,
|
|
COUNT(*) as post_count,
|
|
COALESCE(SUM(views), 0) as total_views,
|
|
COALESCE(SUM(likes), 0) as total_likes,
|
|
COALESCE(SUM(comments), 0) as total_comments
|
|
FROM social_posts
|
|
GROUP BY platform
|
|
`);
|
|
|
|
const socialTotals = {
|
|
posts: 0,
|
|
views: 0,
|
|
likes: 0,
|
|
comments: 0,
|
|
platforms: {}
|
|
};
|
|
|
|
for (const row of socialStats) {
|
|
socialTotals.posts += parseInt(row.post_count);
|
|
socialTotals.views += parseInt(row.total_views);
|
|
socialTotals.likes += parseInt(row.total_likes);
|
|
socialTotals.comments += parseInt(row.total_comments);
|
|
socialTotals.platforms[row.platform] = {
|
|
posts: parseInt(row.post_count),
|
|
views: parseInt(row.total_views),
|
|
likes: parseInt(row.total_likes)
|
|
};
|
|
}
|
|
|
|
res.render('admin/dashboard', {
|
|
title: 'Command Bridge',
|
|
serversOnline,
|
|
activeSubscribers,
|
|
totalMRR,
|
|
lastSyncTime,
|
|
socialTotals
|
|
});
|
|
} catch (error) {
|
|
console.error('Dashboard data fetch error:', error);
|
|
// Fallback to zeros on error
|
|
res.render('admin/dashboard', {
|
|
title: 'Command Bridge',
|
|
serversOnline: 0,
|
|
activeSubscribers: 0,
|
|
totalMRR: 0,
|
|
lastSyncTime: null,
|
|
socialTotals: { posts: 0, views: 0, likes: 0, comments: 0, platforms: {} }
|
|
});
|
|
}
|
|
});
|
|
|
|
router.use('/players', playersRouter);
|
|
router.use('/servers', serversRouter);
|
|
router.use('/financials', financialsRouter);
|
|
router.use('/grace', graceRouter);
|
|
router.use('/audit', auditRouter);
|
|
router.use('/roles', rolesRouter);
|
|
router.use('/scheduler', schedulerRouter);
|
|
router.use('/discord', discordAuditRouter);
|
|
router.use('/system', systemRouter);
|
|
router.use('/social', socialRouter);
|
|
router.use('/infrastructure', infrastructureRouter);
|
|
router.use('/about', aboutRouter);
|
|
router.use('/mcp-logs', mcpLogsRouter);
|
|
router.use('/tasks', tasksRouter);
|
|
|
|
module.exports = router;
|