Compare commits

..

9 Commits

Author SHA1 Message Date
deployer3000 ce363d8b6b Merge pull request 'rc' (#15) from rc into main 2025-04-10 15:12:17 +03:00
YurijO c5e5d62f07 Merge pull request 'feature' (#13) from feature into rc
test-org/trust-module-backend/pipeline/pr-main Build succeeded
Reviewed-on: http://192.168.2.61/deployer3000/trust-module-backend/pulls/13
Reviewed-by: Vladislav Drozdov <ya2@ya.ru>
Reviewed-by: YurijO <ya@ya.ru>
2025-04-10 14:58:26 +03:00
DmitriyA 7d8a207728 commented on the section with CORS
test-org/trust-module-backend/pipeline/pr-rc This commit looks good Details
2025-04-10 05:41:41 -04:00
yuobrezkov 296c6dddc8 auto versioning added 2025-04-10 12:05:27 +03:00
DmitriyA 243a45756a added the api prefix
test-org/trust-module-backend/pipeline/pr-rc This commit looks good Details
2025-04-09 10:02:55 -04:00
DmitriyA 23d2fd7eff Added the start, end, and step parameters to solve the update issue on the charts. 2025-04-08 01:58:15 -04:00
DmitriyA 553b9141d4 fixed bugs 2025-04-04 15:32:21 -04:00
DmitriyA 23438a0e7f fixed bugs with the web socket 2025-04-02 19:49:13 -04:00
DmitriyA 49a5471c01 Added the ability to establish a connection using a web socket 2025-04-01 11:52:04 -04:00
5 changed files with 185 additions and 9 deletions

38
Jenkinsfile vendored
View File

@ -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")
} }
} }
} }

View File

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

View File

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

View File

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

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}`);
};
}
}