commit
ce363d8b6b
|
|
@ -30,7 +30,23 @@ pipeline {
|
||||||
stage ('Initialize variables') {
|
stage ('Initialize variables') {
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
env.IMAGE_TAG = sh(script: "git describe --tags --abbrev=0", returnStdout: true).trim()
|
def hasTags = sh(script: "git tag -l | wc -l", returnStdout: true).trim().toInteger() > 0
|
||||||
|
echo "${hasTags}"
|
||||||
|
|
||||||
|
def lastVersion = "0.0.0"
|
||||||
|
|
||||||
|
if (hasTags) {
|
||||||
|
lastVersion = sh(script: "git describe --tags --abbrev=0", returnStdout: true).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Last version: ${lastVersion}"
|
||||||
|
|
||||||
|
def (major, minor, patch) = lastVersion.tokenize('.')
|
||||||
|
def newVersion = "${major}.${minor}.${patch.toInteger() + 1}"
|
||||||
|
echo "New version: ${newVersion}"
|
||||||
|
|
||||||
|
env.IMAGE_TAG = newVersion
|
||||||
|
env.NEW_VERSION = newVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -71,17 +87,29 @@ pipeline {
|
||||||
echo "Attempting to merge PR ${env.CHANGE_ID} into master..."
|
echo "Attempting to merge PR ${env.CHANGE_ID} into master..."
|
||||||
withCredentials([usernamePassword(credentialsId: 'gitea_creds', usernameVariable: 'GITEA_USER', passwordVariable: 'GITEA_PASS')]) {
|
withCredentials([usernamePassword(credentialsId: 'gitea_creds', usernameVariable: 'GITEA_USER', passwordVariable: 'GITEA_PASS')]) {
|
||||||
def prId = env.CHANGE_ID
|
def prId = env.CHANGE_ID
|
||||||
|
|
||||||
sh """
|
sh """
|
||||||
curl -X POST \
|
curl -X POST \
|
||||||
-u "${GITEA_USER}:${GITEA_PASS}" \
|
-u "${GITEA_USER}:${GITEA_PASS}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"do":"merge"}' \
|
-d '{"do":"merge"}' \
|
||||||
http://git.entcor/api/v1/repos/deployer3000/trust-module-backend/pulls/${prId}/merge
|
http://git.entcor/api/v1/repos/deployer3000/${env.IMAGE_NAME}/pulls/${prId}/merge
|
||||||
"""
|
"""
|
||||||
|
def commitHash = sh(script: "git rev-parse HEAD~1", returnStdout: true).trim() // необходим для корректного отображения статусов
|
||||||
echo "PR ${prId} merged successfully into main!"
|
echo "PR ${prId} merged successfully into main!"
|
||||||
def context = "test-org/trust-module-backend/pipeline/pr-${env.CHANGE_TARGET}"
|
sleep(time: 15, unit: 'SECONDS')
|
||||||
def commitHash = sh(script: "git rev-parse HEAD~1", returnStdout: true).trim()
|
sh "git checkout main && git pull origin main"
|
||||||
notify(context, GITEA_USER, GITEA_PASS, env.GITEA_REPOSITORY_URL, "trust-module-backend", commitHash, "success")
|
|
||||||
|
sh """
|
||||||
|
curl -v -X POST -u "${GITEA_USER}:${GITEA_PASS}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"tag_name": "${env.NEW_VERSION}", "name": "Release ${env.NEW_VERSION}", "target_commitish": "main"}' \
|
||||||
|
"${env.GITEA_REPOSITORY_URL}deployer3000/${env.IMAGE_NAME}/releases"
|
||||||
|
"""
|
||||||
|
echo "New release succeeded!"
|
||||||
|
|
||||||
|
def context = "test-org/${env.IMAGE_NAME}/pipeline/pr-${env.CHANGE_TARGET}"
|
||||||
|
notify(context, GITEA_USER, GITEA_PASS, env.GITEA_REPOSITORY_URL, env.IMAGE_NAME, commitHash, "success")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,10 @@
|
||||||
"pg": "^8.14.1",
|
"pg": "^8.14.1",
|
||||||
"typeorm": "^0.3.21",
|
"typeorm": "^0.3.21",
|
||||||
"bcrypt": "^5.1.1",
|
"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": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { PrometheusService } from './prometheus.service';
|
||||||
import { MetricsController } from './metrics.controller';
|
import { MetricsController } from './metrics.controller';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { AuthModule } from './auth/auth.module';
|
import { AuthModule } from './auth/auth.module';
|
||||||
|
import { MetricsGateway } from './metrics.gateway';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -27,6 +28,10 @@ import { AuthModule } from './auth/auth.module';
|
||||||
AuthModule,
|
AuthModule,
|
||||||
],
|
],
|
||||||
controllers: [MetricsController],
|
controllers: [MetricsController],
|
||||||
providers: [PrometheusService],
|
providers: [
|
||||||
|
PrometheusService,
|
||||||
|
MetricsGateway,
|
||||||
|
],
|
||||||
|
exports: [MetricsGateway],
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
11
src/main.ts
11
src/main.ts
|
|
@ -3,11 +3,18 @@ import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule, { cors: false });
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
||||||
|
// Установка глобального префикса для всех маршрутов
|
||||||
|
app.setGlobalPrefix('api');
|
||||||
|
|
||||||
//настройка CORS
|
//настройка CORS
|
||||||
|
|
||||||
|
app.enableCors({
|
||||||
|
origin: '*',
|
||||||
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
|
||||||
|
allowedHeaders: 'Content-Type, Authorization',
|
||||||
|
});
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
await app.listen(process.env.PORT ?? 3000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|
@ -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}`);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue