Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/admin/admin.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Admin } from './entities/admin.entity';
import { Incident } from './entities/incident.entity';
import { ModerationReport } from './entities/moderation-report.entity';
import { AuditLog } from '../audit/entities/audit-log.entity';
import { Claim } from '../claims/entities/claim.entity';
import { AdminService } from './admin.service';
import { AdminController } from './admin.controller';
import { ModerationService } from './moderation/moderation.service';
Expand All @@ -14,9 +15,18 @@ import { DashboardService } from './dashboard/dashboard.service';
import { DashboardController } from './dashboard/dashboard.controller';
import { RolesGuard } from './guards/roles.guard';
import { AdminGuard } from './guards/admin.guard';
import { JobsModule } from '../jobs/jobs.module';
import { NotificationModule } from '../notifications/notification.module';
import { RedisModule } from '../redis/redis.module';
import { MetricsModule } from '../metrics/metrics.module';

@Module({
imports: [
TypeOrmModule.forFeature([Admin, Incident, ModerationReport, AuditLog]),
TypeOrmModule.forFeature([Admin, Incident, ModerationReport, AuditLog, Claim]),
JobsModule,
NotificationModule,
RedisModule,
MetricsModule,
],
controllers: [
AdminController,
Expand Down
106 changes: 105 additions & 1 deletion src/admin/dashboard/dashboard.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ import { AdminGuard } from '../guards/admin.guard';
import { RolesGuard } from '../guards/roles.guard';
import { Roles } from '../decorators/roles.decorator';
import { AdminRole } from '../entities/admin.entity';
import {
DashboardOverviewDto,
HealthStatusDto,
OperationalDashboardDto,
InfrastructureHealthDto,
QueueMetricsDto,
WorkerStatusDto,
ApiMetricsDto,
CacheStatisticsDto,
DatabaseMetricsDto,
NotificationMetricsDto,
WebhookMetricsDto,
BackgroundJobsDto,
ProtocolActivityDto,
} from '../dto/dashboard.dto';

@ApiTags('admin / dashboard')
@ApiBearerAuth()
Expand All @@ -16,18 +31,107 @@ export class DashboardController {
@Get('overview')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.AUDITOR)
@ApiOperation({ summary: 'Get main dashboard overview' })
@ApiResponse({ status: 200, description: 'Dashboard overview data' })
@ApiResponse({ status: 200, description: 'Dashboard overview data', type: DashboardOverviewDto })
async getOverview() {
return this.dashboardService.getOverview();
}

@Get('health')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.SECURITY_ANALYST, AdminRole.AUDITOR)
@ApiOperation({ summary: 'Get protocol health status' })
@ApiResponse({ status: 200, description: 'System health summary', type: HealthStatusDto })
async getHealth() {
return this.dashboardService.getHealth();
}

@Get('operational-summary')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.AUDITOR, AdminRole.SECURITY_ANALYST)
@ApiOperation({ summary: 'Get aggregated operational dashboard summary' })
@ApiResponse({ status: 200, description: 'Aggregated operational dashboard data', type: OperationalDashboardDto })
async getOperationalSummary() {
return this.dashboardService.getOperationalSummary();
}

@Get('infrastructure')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.AUDITOR, AdminRole.SECURITY_ANALYST)
@ApiOperation({ summary: 'Get infrastructure health and queue status' })
@ApiResponse({ status: 200, description: 'Infrastructure health summary', type: InfrastructureHealthDto })
async getInfrastructureHealth() {
return this.dashboardService.getInfrastructureHealth();
}

@Get('queues')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.AUDITOR, AdminRole.SECURITY_ANALYST)
@ApiOperation({ summary: 'Get queue statistics across background workers' })
@ApiResponse({ status: 200, description: 'Queue metrics', type: QueueMetricsDto })
async getQueueStatistics() {
return this.dashboardService.getQueueStatistics();
}

@Get('workers')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.AUDITOR, AdminRole.SECURITY_ANALYST)
@ApiOperation({ summary: 'Get worker health and utilisation' })
@ApiResponse({ status: 200, description: 'Worker status summary', type: WorkerStatusDto })
async getWorkerStatus() {
return this.dashboardService.getWorkerStatus();
}

@Get('api-metrics')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.AUDITOR, AdminRole.SECURITY_ANALYST)
@ApiOperation({ summary: 'Get API traffic and latency metrics' })
@ApiResponse({ status: 200, description: 'API metrics summary', type: ApiMetricsDto })
async getApiMetrics() {
return this.dashboardService.getApiMetrics();
}

@Get('cache')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.AUDITOR, AdminRole.SECURITY_ANALYST)
@ApiOperation({ summary: 'Get cache and Redis status' })
@ApiResponse({ status: 200, description: 'Cache summary', type: CacheStatisticsDto })
async getCacheStatistics() {
return this.dashboardService.getCacheStatistics();
}

@Get('database')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.AUDITOR, AdminRole.SECURITY_ANALYST)
@ApiOperation({ summary: 'Get database health and table statistics' })
@ApiResponse({ status: 200, description: 'Database metrics', type: DatabaseMetricsDto })
async getDatabaseMetrics() {
return this.dashboardService.getDatabaseMetrics();
}

@Get('notifications')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.AUDITOR, AdminRole.SECURITY_ANALYST)
@ApiOperation({ summary: 'Get notification queue and delivery metrics' })
@ApiResponse({ status: 200, description: 'Notification metrics', type: NotificationMetricsDto })
async getNotificationMetrics() {
return this.dashboardService.getNotificationMetrics();
}

@Get('webhooks')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.AUDITOR, AdminRole.SECURITY_ANALYST)
@ApiOperation({ summary: 'Get webhook delivery metrics' })
@ApiResponse({ status: 200, description: 'Webhook metrics', type: WebhookMetricsDto })
async getWebhookMetrics() {
return this.dashboardService.getWebhookMetrics();
}

@Get('jobs')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.AUDITOR, AdminRole.SECURITY_ANALYST)
@ApiOperation({ summary: 'Get background job queue overview' })
@ApiResponse({ status: 200, description: 'Background job overview', type: BackgroundJobsDto })
async getBackgroundJobs() {
return this.dashboardService.getBackgroundJobs();
}

@Get('protocol')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.AUDITOR, AdminRole.SECURITY_ANALYST)
@ApiOperation({ summary: 'Get protocol activity summary' })
@ApiResponse({ status: 200, description: 'Protocol activity summary', type: ProtocolActivityDto })
async getProtocolActivity() {
return this.dashboardService.getProtocolActivity();
}

@Get('moderation')
@Roles(AdminRole.SUPER_ADMIN, AdminRole.ADMINISTRATOR, AdminRole.MODERATOR, AdminRole.AUDITOR)
@ApiOperation({ summary: 'Get moderation workload statistics' })
Expand Down
78 changes: 78 additions & 0 deletions src/admin/dashboard/dashboard.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { DashboardService } from './dashboard.service';

describe('DashboardService', () => {
let service: DashboardService;
let adminRepo: any;
let incidentRepo: any;
let reportRepo: any;
let auditLogRepo: any;
let jobsService: any;
let notificationService: any;
let redisService: any;
let metricsService: any;

beforeEach(() => {
adminRepo = {
count: jest.fn(),
query: jest.fn(),
};
incidentRepo = {
count: jest.fn(),
find: jest.fn(),
};
reportRepo = {
count: jest.fn(),
find: jest.fn(),
};
auditLogRepo = {
createQueryBuilder: jest.fn(),
};
jobsService = {
getAllQueueMetrics: jest.fn(),
};
notificationService = {
getMetrics: jest.fn(),
};
redisService = {
isHealthy: jest.fn(),
getStatus: jest.fn(),
};
metricsService = {
getSummary: jest.fn(),
};

service = new DashboardService(
adminRepo,
incidentRepo,
reportRepo,
auditLogRepo,
jobsService,
notificationService,
redisService,
metricsService,
);
});

it('should aggregate operational summary data from backend services', async () => {
adminRepo.count.mockResolvedValueOnce(4).mockResolvedValueOnce(2);
incidentRepo.count.mockResolvedValueOnce(3).mockResolvedValueOnce(1);
reportRepo.count.mockResolvedValueOnce(2).mockResolvedValueOnce(1).mockResolvedValueOnce(3);
reportRepo.find.mockResolvedValue([{ createdAt: new Date(), resolvedAt: new Date() }]);
jobsService.getAllQueueMetrics.mockResolvedValue([
{ name: 'default', waiting: 2, active: 1, completed: 10, failed: 0, delayed: 1, paused: false },
]);
notificationService.getMetrics.mockResolvedValue({ queued: 5, delivered: 4, failed: 1, queueDepth: 2 });
redisService.isHealthy.mockResolvedValue(true);
redisService.getStatus.mockReturnValue({ connected: true, enabled: true });
metricsService.getSummary.mockResolvedValue({ totalRequests: 120, errorCount: 3, averageLatencyMs: 40 });

const summary = await service.getOperationalSummary();

expect(summary.system.status).toBe('healthy');
expect(summary.infrastructure.database.status).toBe('healthy');
expect(summary.queues.totalWaiting).toBe(2);
expect(summary.notifications.delivered).toBe(4);
expect(summary.api.totalRequests).toBe(120);
expect(summary.protocol.activeClaims).toBe(3);
});
});
Loading