diff --git a/package.json b/package.json index 0fe1fec..6469933 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,10 @@ "pg": "^8.14.1", "typeorm": "^0.3.21", "bcrypt": "^5.1.1", - "@types/bcrypt": "^5.0.2" + "@types/bcrypt": "^5.0.2", + "socket.io": "^4.8.1", + "@nestjs/websockets": "11.0.12", + "@nestjs/platform-socket.io": "11.0.12" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", diff --git a/src/app.module.ts b/src/app.module.ts index 33b9558..e8ecfad 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,6 +5,7 @@ import { PrometheusService } from './prometheus.service'; import { MetricsController } from './metrics.controller'; import { ConfigModule } from '@nestjs/config'; import { AuthModule } from './auth/auth.module'; +import { MetricsGateway } from './metrics.gateway'; @Module({ imports: [ @@ -27,6 +28,10 @@ import { AuthModule } from './auth/auth.module'; AuthModule, ], controllers: [MetricsController], - providers: [PrometheusService], + providers: [ + PrometheusService, + MetricsGateway, + ], + exports: [MetricsGateway], }) export class AppModule { } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index c76cb40..85934af 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,11 +3,18 @@ import { AppModule } from './app.module'; async function bootstrap() { - const app = await NestFactory.create(AppModule, { cors: false }); + const app = await NestFactory.create(AppModule); + + // Установка глобального префикса для всех маршрутов + app.setGlobalPrefix('api'); //настройка CORS - + app.enableCors({ + origin: '*', + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', + allowedHeaders: 'Content-Type, Authorization', + }); await app.listen(process.env.PORT ?? 3000); } bootstrap(); \ No newline at end of file diff --git a/src/metrics.gateway.ts b/src/metrics.gateway.ts new file mode 100644 index 0000000..1d1f669 --- /dev/null +++ b/src/metrics.gateway.ts @@ -0,0 +1,133 @@ +import { WebSocketGateway, WebSocketServer, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect, SubscribeMessage, } from '@nestjs/websockets'; +import { Server, Socket } from 'socket.io'; +import { PrometheusService } from './prometheus.service'; +import { Logger } from '@nestjs/common'; + +@WebSocketGateway({ + /* + cors: { + origin: '*', // В production укажите конкретные домены + methods: ['GET', 'POST'], + credentials: true + }, */ + namespace: '/api/metrics-ws' +}) +export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { + @WebSocketServer() server: Server; + private readonly logger = new Logger(MetricsGateway.name); + private activeSockets: Map = new Map(); + + constructor(private readonly prometheusService: PrometheusService) { } + + afterInit(server: Server) { + this.logger.log('WebSocket Gateway initialized'); + this.logger.log('WebSocket server initialized successfully'); + } + + handleConnection(client: Socket) { + this.logger.log(`Client connected: ${client.id}`); + this.logger.log(`New client connected: ${client.id} from ${client.handshake.address}`); + this.activeSockets.set(client.id, client); + } + + handleDisconnect(client: Socket) { + this.logger.log(`Client disconnected: ${client.id}`); + this.activeSockets.delete(client.id); + } + + @SubscribeMessage('get-metrics') + async handleGetMetrics(client: Socket, payload: any) { + const { metric, start, end, step, _t } = payload; + this.logger.log(`Received metrics request: ${metric}, start: ${start}, end: ${end}, step: ${step}`); + + try { + // Для запросов с диапазоном - просто возвращаем данные без подписки + if (start && end) { + const data = await this.prometheusService.fetchMetricsRange(metric, start, end, step); + client.emit('metrics-data', { metric, data }); + return; + } + + // Для запросов без диапазона (realtime) - запускаем подписку + const stopUpdates = await this.sendPeriodicUpdates( + metric, + step || 5000, // Используем переданный шаг или дефолтный + client + ); + + client.on('disconnect', () => stopUpdates()); + client.on('unsubscribe-metric', () => stopUpdates()); + + } catch (error) { + this.logger.error(`Error fetching metrics: ${error.message}`); + client.emit('metrics-error', { + metric, + error: error.message + }); + } + } + + @SubscribeMessage('get-metric-types') + async handleGetMetricTypes(client: Socket, payload: { metric: string }) { + try { + const type = await this.prometheusService.fetchMetricType(payload.metric); + const description = await this.prometheusService.fetchMetricDescription(payload.metric); + client.emit('metric-types', { + metric: payload.metric, + type, + description + }); + } catch (error) { + this.logger.log(`Error fetching metric types: ${error.message}`); + client.emit('metrics-error', { + metric: payload.metric, + error: error.message + }); + } + } + + @SubscribeMessage('get-all-metrics') + async handleGetAllMetrics(client: Socket) { + try { + const metrics = await this.prometheusService.fetchAllMetrics(); + client.emit('all-metrics', metrics); + } catch (error) { + this.logger.log(`Error fetching all metrics: ${error.message}`); + client.emit('metrics-error', { + error: error.message + }); + } + } + + + + @SubscribeMessage('subscribe-metric') + async handleSubscribeMetric(client: Socket, payload: { metric: string, interval?: number }) { + const stopUpdates = await this.sendPeriodicUpdates( + payload.metric, + payload.interval || 5000, // Добавляем значение по умолчанию + client // Передаем клиента + ); + + // Сохраняем функцию остановки для этого клиента + client.on('disconnect', () => stopUpdates()); + client.on('unsubscribe-metric', () => stopUpdates()); + } + + // Метод для периодической отправки обновлений + async sendPeriodicUpdates(metric: string, interval: number, client: Socket) { + const timer = setInterval(async () => { + try { + const data = await this.prometheusService.fetchMetrics(metric); + client.emit('metrics-data', { metric, data }); + } catch (error) { + this.logger.error(`Error in periodic update for ${metric}: ${error.message}`); + } + }, interval); + + return () => { + clearInterval(timer); + this.logger.log(`Stopped updates for ${metric}`); + }; + } +} \ No newline at end of file