Admin Panel — System Stats, User Management, Queue Monitoring, and Audit Log

The admin panel is a separate Angular feature module — accessible only to users with the admin role — that provides system-wide oversight of all workspaces, users, tasks, and background jobs. It surfaces the operational metrics from Chapter 22 in a management interface: queue health, system stats, user management, and audit logs. The admin panel demonstrates role-based UI at the feature module level (not just individual components) and shows how to build operator-facing tooling on top of the production application infrastructure.

Admin Panel Features

Section Data Source Actions
System Overview GET /admin/stats View — total users, workspaces, tasks, queue depth
User Management GET /admin/users Search, view profile, suspend/reactivate, change role
Workspace Management GET /admin/workspaces List all, view members, force-delete
Queue Monitor GET /admin/queues View waiting/active/completed/failed job counts, retry failed
Audit Log GET /admin/audit-log Search by user/workspace/action, paginated
Health Dashboard GET /health + Prometheus metrics Live system health, dependency status
Note: The admin feature module uses a dedicated adminGuard that checks both authentication AND the admin role: if (!authStore.isAuthenticated()) → /auth/login, if (!authStore.isAdmin()) → /403. The guard applies at the module level in the routing config — every route within /admin/** is protected by the same guard without repeating it on individual routes. This is the correct pattern for feature-level access control: one guard at the module boundary rather than per-component checks.
Tip: Bull Board (@bull-board/express) provides a pre-built queue management UI that can be mounted in your Express app at /admin/queues. It shows job counts, job details, and allows retrying failed jobs without any custom UI code. Mount it with admin authentication middleware: router.use('/admin/queues', verifyAccessToken, requireRole('admin'), bullBoardRouter). This is faster to ship than a custom queue UI and covers all the operational needs for queue management.
Warning: Admin endpoints must be thoroughly protected — they expose sensitive user data and allow destructive operations. Apply three layers: JWT authentication (verifyAccessToken), role check (requireRole(‘admin’)), and rate limiting (adminLimiter — stricter than the public API). Log every admin action to the audit log with the admin’s user ID, the action, the target resource, and a timestamp. Admin actions are the most sensitive operations in the system and must be fully auditable.

Complete Admin Panel Implementation

// ── Admin routes — Express ────────────────────────────────────────────────
// apps/api/src/modules/admin/admin.routes.js
const { Router }        = require('express');
const { verifyAccessToken }  = require('../../middleware/auth.middleware');
const { requireRole }        = require('../../middleware/rbac.middleware');
const { adminRateLimiter }   = require('../../middleware/rate-limiter');
const adminController        = require('./admin.controller');
const AuditLog               = require('../audit/audit-log.model');

const router = Router();

// All admin routes require admin role
router.use(verifyAccessToken, requireRole('admin'), adminRateLimiter);

router.get('/stats',        adminController.getSystemStats);
router.get('/users',        adminController.getUsers);
router.patch('/users/:id/role',    adminController.changeUserRole);
router.patch('/users/:id/status',  adminController.toggleUserStatus);
router.get('/workspaces',   adminController.getWorkspaces);
router.delete('/workspaces/:id',   adminController.forceDeleteWorkspace);
router.get('/queues',       adminController.getQueueStats);
router.post('/queues/:name/retry-failed', adminController.retryFailedJobs);
router.get('/audit-log',    adminController.getAuditLog);

module.exports = router;

// ── Admin controller (stats + queue monitoring) ───────────────────────────
const asyncHandler = require('express-async-handler');
const mongoose     = require('mongoose');
const User         = require('../users/user.model');
const Task         = require('../tasks/task.model');
const Workspace    = require('../workspaces/workspace.model');
const { emailQueue, reportQueue, notifyQueue } = require('../../queues');
const AuditLog     = require('../audit/audit-log.model');

exports.getSystemStats = asyncHandler(async (req, res) => {
    const [userCount, taskCount, workspaceCount, activeTaskCount, queueStats] = await Promise.all([
        User.countDocuments({ isActive: true }),
        Task.countDocuments({ deletedAt: { $exists: false } }),
        Workspace.countDocuments(),
        Task.countDocuments({ status: { $in: ['todo','in-progress','in-review'] }, deletedAt: { $exists: false } }),
        getQueueStats(),
    ]);

    res.json({
        success: true,
        data: {
            users:       { total: userCount },
            tasks:       { total: taskCount, active: activeTaskCount },
            workspaces:  { total: workspaceCount },
            queues:      queueStats,
            uptime:      process.uptime(),
            memory:      process.memoryUsage(),
            nodeVersion: process.version,
        },
    });
});

async function getQueueStats() {
    const [email, report, notify] = await Promise.all([
        emailQueue.getJobCounts(),
        reportQueue.getJobCounts(),
        notifyQueue.getJobCounts(),
    ]);
    return {
        email:  { ...email,  name: 'email'  },
        report: { ...report, name: 'report' },
        notify: { ...notify, name: 'notify' },
    };
}

exports.retryFailedJobs = asyncHandler(async (req, res) => {
    const queueMap = { email: emailQueue, report: reportQueue, notify: notifyQueue };
    const queue    = queueMap[req.params.name];
    if (!queue) return res.status(404).json({ message: 'Queue not found' });

    const failed  = await queue.getFailed();
    await Promise.all(failed.map(job => job.retry()));

    // Log admin action
    await AuditLog.create({
        actor:    req.user.sub,
        action:   'ADMIN_RETRY_FAILED_JOBS',
        resource: { type: 'queue', id: req.params.name },
        metadata: { count: failed.length },
    });

    res.json({ success: true, data: { retried: failed.length } });
});

exports.toggleUserStatus = asyncHandler(async (req, res) => {
    const user = await User.findByIdAndUpdate(
        req.params.id,
        [{ $set: { isActive: { $not: '$isActive' } } }],  // toggle
        { new: true }
    );
    if (!user) return res.status(404).json({ message: 'User not found' });

    await AuditLog.create({
        actor:    req.user.sub,
        action:   user.isActive ? 'ADMIN_USER_ACTIVATED' : 'ADMIN_USER_SUSPENDED',
        resource: { type: 'user', id: user._id },
        metadata: { targetEmail: user.email },
    });

    res.json({ success: true, data: { id: user._id, isActive: user.isActive } });
});
// ── Angular admin guard ───────────────────────────────────────────────────
// core/guards/admin.guard.ts
import { inject }   from '@angular/core';
import { Router }   from '@angular/router';
import { AuthStore }from '../stores/auth.store';

export const adminGuard = () => {
    const authStore = inject(AuthStore);
    const router    = inject(Router);

    if (!authStore.isAuthenticated()) {
        return router.createUrlTree(['/auth/login']);
    }
    if (!authStore.isAdmin()) {
        return router.createUrlTree(['/403']);
    }
    return true;
};

// Admin routes
// { path: 'admin', canActivate: [adminGuard], loadChildren: () => import('./features/admin/admin.routes') }

How It Works

Step 1 — Three-Layer Admin Route Protection

Admin routes apply verifyAccessToken (authentication), requireRole('admin') (authorisation), and adminRateLimiter (abuse prevention) as a middleware chain. This defence in depth means: an unauthenticated request gets 401, an authenticated non-admin gets 403, and an authenticated admin who makes too many requests gets 429. No admin operation can bypass any of these checks because they are applied to the entire router rather than individual endpoints.

Step 2 — Toggle Pattern with Aggregation Pipeline Update

The toggle expression [{ $set: { isActive: { $not: '$isActive' } } }] is a MongoDB aggregation pipeline update — it reads the current value and inverts it atomically. Without this, the code would need to: read the current value, compute the inverse, write the new value. This read-modify-write pattern has a race condition window. The aggregation pipeline update is atomic — no race condition is possible.

Step 3 — Audit Log Records Every Admin Action

Every state-changing admin action writes an audit log entry with the actor (admin’s user ID), the action (a string constant), the affected resource (type + ID), and relevant metadata. This creates a complete audit trail: who did what to which resource and when. The audit log is queryable via the admin panel — if a user is suspended, an admin can search for ADMIN_USER_SUSPENDED actions to see which admin did it and when.

Step 4 — Bull Board Provides Zero-Code Queue Management UI

Installing @bull-board/express and mounting it at /admin/queues-ui provides a full-featured queue management interface: job counts, job details, job payloads, retry failed jobs, clear completed jobs, and real-time job progress. This is production-ready operational tooling that would take days to build from scratch, provided free as an open-source library. The only requirement is protecting it with admin authentication middleware.

Step 5 — Feature-Level Guard Eliminates Per-Route Repetition

Applying canActivate: [adminGuard] to the /admin route entry (the one that lazy-loads the admin feature module) protects every route within that feature. New admin routes added to the admin module are automatically protected — the developer cannot forget to add the guard. This is superior to adding the guard to every individual admin route, where missing it on one route creates a security hole.

Quick Reference

Task Code
Admin route group router.use(verifyAccessToken, requireRole('admin'), adminRateLimiter)
Atomic toggle { $set: { isActive: { $not: '$isActive' } } } (aggregation pipeline update)
Queue stats queue.getJobCounts(){ waiting, active, completed, failed }
Retry failed jobs const failed = await queue.getFailed(); failed.map(j => j.retry())
Audit log entry AuditLog.create({ actor, action, resource: { type, id }, metadata })
Angular admin guard canActivate: [adminGuard] on the admin module route
Bull Board UI npm install @bull-board/express + mount with auth middleware

🧠 Test Yourself

A new admin endpoint GET /admin/reports is added to the admin router (which has router.use(verifyAccessToken, requireRole('admin'))). Does the new endpoint automatically require admin authentication? Why?