// Read-only Ops Console API. Every handler returns the standard ok() envelope. No writes, // no auth (loopback-bound, "Đọc ≠ Ghi"); management/RBAC is Plan-13 Track 2/3 (future). import { Controller, Get, Inject, Param, Query, Req, Res } from '@nestjs/common'; import type { Request, Response } from 'express'; import { ok } from '../common/api-response.js'; import { TelemetryService } from './telemetry.service.js'; @Controller('api/v1') export class TelemetryController { // Explicit @Inject token so DI works under BOTH tsc (emits decorator metadata) and the // tsx/esbuild dev runner (which does not emit design:paramtypes). constructor(@Inject(TelemetryService) private readonly svc: TelemetryService) {} @Get('overview') overview() { return ok(this.svc.overview()); } @Get('runs') runs(@Query('limit') limit?: string) { const n = Math.min(Math.max(Number(limit) || 50, 1), 500); const r = this.svc.runs(n); return ok(r, { total: r.count }); } @Get('runs/:traceId') run(@Param('traceId') traceId: string) { return ok(this.svc.run(traceId)); } @Get('runs/:traceId/graph') traceGraph(@Param('traceId') traceId: string) { return ok(this.svc.traceGraph(traceId)); } @Get('runs/:traceId/events') traceEvents(@Param('traceId') traceId: string, @Req() req: Request, @Res() res: Response) { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache, no-transform'); res.setHeader('Connection', 'keep-alive'); res.flushHeaders(); let previous = ''; const publish = () => { const graph = this.svc.traceGraph(traceId); const serialized = JSON.stringify(graph); if (serialized !== previous) { res.write(`event: trace\ndata: ${serialized}\n\n`); previous = serialized; } else { res.write(': heartbeat\n\n'); } }; const timer = setInterval(publish, 750); const close = () => { clearInterval(timer); if (!res.writableEnded) res.end(); }; req.on('close', close); publish(); } @Get('governance') governance() { return ok(this.svc.governance()); } @Get('security') security() { return ok(this.svc.security()); } @Get('incidents') incidents() { return ok(this.svc.incidents()); } @Get('tools') tools() { return ok(this.svc.tools()); } @Get('traceability') traceability() { return ok(this.svc.traceability()); } @Get('drift') drift() { return ok(this.svc.drift()); } @Get('cost') cost() { return ok(this.svc.cost()); } @Get('command') command() { return ok(this.svc.commandCenter()); } }