103 lines
2.6 KiB
TypeScript
103 lines
2.6 KiB
TypeScript
// 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());
|
|
}
|
|
}
|