feature #13

Merged
Ghost merged 6 commits from feature into rc 2025-04-10 14:58:27 +03:00
4 changed files with 152 additions and 4 deletions

View File

@ -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",

View File

@ -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 { }

View File

@ -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();

133
src/metrics.gateway.ts Normal file
View File

@ -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<string, Socket> = 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}`);
};
}
}