added complex variables

pull/38/head
SovietSpiderCat 2025-08-22 09:58:26 +03:00
parent c85b89b288
commit d8a8d6b8e5
3 changed files with 143 additions and 327 deletions

BIN
logs.txt

Binary file not shown.

View File

@ -187,41 +187,171 @@ export class MenuService {
private normalizeIdPart(part: string): string { private normalizeIdPart(part: string): string {
return part return part
.replace(/\$/g, '_') .replace(/\$/g, '_')
.replace(/[^a-zA-Z0-9-_]/g, ''); .replace(/,/g, '_') // Заменяем запятые
.replace(/\s+/g, '_') // Заменяем пробелы
.replace(/[^a-zA-Z0-9-_]/g, '')
.toLowerCase();
} }
// private async generateModuleItems(
// device: string,
// seriesData: { metric: string; labels: Record<string, string> }[],
// metadataMap: Map<string, string>
// ): Promise<MenuItem[]> {
// const modules = new Map<string, string>();
// seriesData.forEach(({ labels }) => {
// if (labels.device === device && labels.source_id) {
// const sourceId = labels.source_id;
// let displayName = sourceId;
// if (sourceId.startsWith('module$')) {
// displayName = `Module ${sourceId.split('$')[1]}`;
// } else if (sourceId.startsWith('port$')) {
// displayName = `Port ${sourceId.split('$')[1]}`;
// }
// modules.set(sourceId, displayName);
// }
// });
// const modulePromises = Array.from(modules.entries()).map(
// async ([sourceId, displayName]) => ({
// id: `module_${device}_${sourceId}`,
// title: displayName,
// items: await this.generateMetricItems(device, sourceId, seriesData, metadataMap),
// isDynamic: true
// })
// );
// return Promise.all(modulePromises);
// }
// private async generateMetricItems(
// device: string,
// module: string,
// seriesData: { metric: string; labels: Record<string, string> }[],
// metadataMap: Map<string, string>
// ): Promise<MenuItem[]> {
// const ranges = await this.rangeService.getRanges();
// const filtered = seriesData.filter(
// ({ labels }) => labels.device === device && labels.source_id === module
// );
// const uniqueMetrics = new Set(filtered.map(entry => entry.metric));
// const safeDevice = this.normalizeIdPart(device);
// const safeModule = this.normalizeIdPart(module);
// return Array.from(uniqueMetrics).map(metric => {
// const description = metadataMap.get(metric) || metric;
// const safeMetric = this.normalizeIdPart(metric);
// const metricRanges = ranges[description] || [];
// return {
// id: `metric_${safeDevice}_${safeModule}_${safeMetric}`,
// title: description,
// metric,
// filters: {
// device,
// source_id: module
// },
// ranges: metricRanges,
// isDynamic: true,
// meta: {
// originalDevice: device,
// originalModule: module
// }
// };
// });
// }
//ВРЕМЕННЫЙ КОСТЫЛЬ
private async generateModuleItems( private async generateModuleItems(
device: string, device: string,
seriesData: { metric: string; labels: Record<string, string> }[], seriesData: { metric: string; labels: Record<string, string> }[],
metadataMap: Map<string, string> metadataMap: Map<string, string>
): Promise<MenuItem[]> { ): Promise<MenuItem[]> {
const modules = new Map<string, string>(); const modules = new Map<string, string>();
const specialFolders = new Map<string, Map<string, string>>();
seriesData.forEach(({ labels }) => { seriesData.forEach(({ labels }) => {
if (labels.device === device && labels.source_id) { if (labels.device === device && labels.source_id) {
const sourceId = labels.source_id; const sourceId = labels.source_id;
let displayName = sourceId;
if (sourceId.startsWith('module$')) { // Проверяем наличие специальных тегов (complex, integration)
displayName = `Module ${sourceId.split('$')[1]}`; if (sourceId.includes(', complex') || sourceId.includes(', integration')) {
} else if (sourceId.startsWith('port$')) { const [modulePart, folderType] = sourceId.split(', ').map(s => s.trim());
displayName = `Port ${sourceId.split('$')[1]}`; let displayName = modulePart;
if (modulePart.startsWith('module$')) {
displayName = `Module ${modulePart.split('$')[1]}`;
} else if (modulePart.startsWith('port$')) {
displayName = `Port ${modulePart.split('$')[1]}`;
} else if (modulePart === 'undefined') {
displayName = 'Unknown Module';
}
// Сохраняем в специальные папки
if (!specialFolders.has(folderType)) {
specialFolders.set(folderType, new Map());
}
specialFolders.get(folderType)!.set(modulePart, displayName);
}
// Проверяем старые некорректные форматы (без запятой)
else if (sourceId.endsWith('complex') || sourceId.endsWith('integration')) {
// Игнорируем старые некорректные форматы
console.warn(`Ignoring legacy format: ${sourceId} for device ${device}`);
}
else {
// Обычная обработка
let displayName = sourceId;
if (sourceId.startsWith('module$')) {
displayName = `Module ${sourceId.split('$')[1]}`;
} else if (sourceId.startsWith('port$')) {
displayName = `Port ${sourceId.split('$')[1]}`;
}
modules.set(sourceId, displayName);
} }
modules.set(sourceId, displayName);
} }
}); });
const modulePromises = Array.from(modules.entries()).map( const moduleItems = Array.from(modules.entries()).map(
async ([sourceId, displayName]) => ({ async ([sourceId, displayName]) => ({
id: `module_${device}_${sourceId}`, id: `module_${device}_${this.normalizeIdPart(sourceId)}`,
title: displayName, title: displayName,
items: await this.generateMetricItems(device, sourceId, seriesData, metadataMap), items: await this.generateMetricItems(device, sourceId, seriesData, metadataMap),
isDynamic: true isDynamic: true
}) })
); );
return Promise.all(modulePromises); // Создаем специальные папки
const specialFolderItems = Array.from(specialFolders.entries()).map(
async ([folderType, folderModules]) => {
const folderItems = await Promise.all(
Array.from(folderModules.entries()).map(
async ([sourceId, displayName]) => ({
id: `module_${device}_${this.normalizeIdPart(sourceId)}_${this.normalizeIdPart(folderType)}`,
title: displayName,
items: await this.generateMetricItems(device, `${sourceId}, ${folderType}`, seriesData, metadataMap),
isDynamic: true
})
)
);
return {
id: `folder_${device}_${this.normalizeIdPart(folderType)}`,
title: folderType,
items: folderItems,
isDynamic: true
};
}
);
return [
...(await Promise.all(moduleItems)),
...(await Promise.all(specialFolderItems))
];
} }
private async generateMetricItems( private async generateMetricItems(
@ -231,6 +361,8 @@ export class MenuService {
metadataMap: Map<string, string> metadataMap: Map<string, string>
): Promise<MenuItem[]> { ): Promise<MenuItem[]> {
const ranges = await this.rangeService.getRanges(); const ranges = await this.rangeService.getRanges();
// Фильтруем по device и точному совпадению source_id
const filtered = seriesData.filter( const filtered = seriesData.filter(
({ labels }) => labels.device === device && labels.source_id === module ({ labels }) => labels.device === device && labels.source_id === module
); );

View File

@ -1,319 +1,3 @@
// import { Injectable } from '@nestjs/common';
// import { HttpService } from '@nestjs/axios';
// import { ConfigService } from '@nestjs/config';
// import { lastValueFrom } from 'rxjs';
// import { PrometheusMetric } from './prometheus-metric.interface';
// import { MenuItem } from '../menu/menu.interface';
// @Injectable()
// export class PrometheusService {
// private readonly prometheusUrl: string;
// private metricCache = new Map<string, { data: any; timestamp: number }>();
// private metadataCache = new Map<string, { type: string | null; description: string | undefined; timestamp: number }>();
// constructor(
// private readonly httpService: HttpService,
// private readonly configService: ConfigService
// ) {
// this.prometheusUrl = this.configService.get<string>('PROMETHEUS_API', 'http://localhost:9090');
// console.log('Prometheus API URL:', this.prometheusUrl);
// }
// async fetchMetricType(metric: string): Promise<string | null> {
// const cacheKey = `metadata-type-${metric}`;
// const cacheEntry = this.metadataCache.get(cacheKey);
// if (cacheEntry && Date.now() - cacheEntry.timestamp < 30000) {
// return cacheEntry.type;
// }
// try {
// const response = await lastValueFrom(
// this.httpService.get(`${this.prometheusUrl}/metadata`, {
// params: { metric },
// })
// );
// const metadata = response.data.data[metric];
// const result = metadata?.length ? metadata[0].type : null;
// this.metadataCache.set(cacheKey, {
// type: result,
// description: cacheEntry?.description,
// timestamp: Date.now()
// });
// return result;
// } catch (error) {
// console.error(`Ошибка при получении типа метрики ${metric}:`, error);
// return cacheEntry?.type || null;
// }
// }
// async fetchMetricDescription(metric: string): Promise<string | undefined> {
// const cacheKey = `metadata-description-${metric}`;
// const cacheEntry = this.metadataCache.get(cacheKey);
// if (cacheEntry && Date.now() - cacheEntry.timestamp < 30000) {
// return cacheEntry.description;
// }
// try {
// const response = await lastValueFrom(
// this.httpService.get(`${this.prometheusUrl}/metadata`, {
// params: { metric },
// })
// );
// const metadata = response.data.data[metric];
// const result = metadata?.length ? metadata[0].help : undefined;
// this.metadataCache.set(cacheKey, {
// type: cacheEntry?.type ?? null,
// description: result,
// timestamp: Date.now()
// });
// return result;
// } catch (error) {
// console.error(`Ошибка при получении описания метрики ${metric}:`, error);
// return cacheEntry?.description;
// }
// }
// async fetchMetrics(metric: string): Promise<PrometheusMetric[]> {
// const cacheKey = `${metric}:{}`;
// const cacheEntry = this.metricCache.get(cacheKey);
// if (cacheEntry && Date.now() - cacheEntry.timestamp < 5000) {
// return cacheEntry.data;
// }
// try {
// const response = await lastValueFrom(
// this.httpService.get(`${this.prometheusUrl}/query`, {
// params: { query: metric },
// })
// );
// const metricType = await this.fetchMetricType(metric);
// const metricDescription = await this.fetchMetricDescription(metric);
// const result = response.data.data.result.map((entry): PrometheusMetric => ({
// __name__: entry.metric.__name__ || metric,
// device: entry.metric.device,
// instance: entry.metric.instance,
// job: entry.metric.job,
// source_id: entry.metric.source_id,
// status: entry.metric.status || '0',
// timestamp: entry.value[0] * 1000,
// value: parseFloat(entry.value[1]),
// type: metricType || 'gauge',
// description: metricDescription,
// ...entry.metric
// }));
// this.metricCache.set(cacheKey, { data: result, timestamp: Date.now() });
// return result;
// } catch (error) {
// console.error(`Error fetching metrics for ${metric}:`, error);
// if (cacheEntry) return cacheEntry.data;
// throw error;
// }
// }
// async fetchMetricsWithFilters(metric: string, filters: Record<string, string>): Promise<PrometheusMetric[]> {
// const cacheKey = `${metric}:${JSON.stringify(filters)}`;
// const cacheEntry = this.metricCache.get(cacheKey);
// if (cacheEntry && Date.now() - cacheEntry.timestamp < 5000) {
// return cacheEntry.data;
// }
// try {
// const query = this.buildFilteredQuery(metric, filters);
// const response = await lastValueFrom(
// this.httpService.get(`${this.prometheusUrl}/query`, {
// params: { query }
// })
// );
// const metricType = await this.fetchMetricType(metric);
// const metricDescription = await this.fetchMetricDescription(metric);
// const result = response.data.data.result.map((entry): PrometheusMetric => ({
// __name__: entry.metric.__name__ || metric,
// device: entry.metric.device,
// instance: entry.metric.instance,
// job: entry.metric.job,
// source_id: entry.metric.source_id,
// status: entry.metric.status || '0',
// timestamp: entry.value[0] * 1000,
// value: parseFloat(entry.value[1]),
// type: metricType || 'gauge',
// description: metricDescription,
// ...entry.metric
// }));
// this.metricCache.set(cacheKey, { data: result, timestamp: Date.now() });
// return result;
// } catch (error) {
// console.error(`Error fetching metrics with filters for ${metric}:`, error);
// if (cacheEntry) return cacheEntry.data;
// throw error;
// }
// }
// private buildFilteredQuery(metric: string, filters: Record<string, string>): string {
// const filterParts = Object.entries(filters)
// .filter(([_, value]) => value !== undefined && value !== null && value !== "")
// .map(([key, value]) => {
// return `${key}="${value}"`;
// });
// return filterParts.length > 0
// ? `${metric}{${filterParts.join(',')}}`
// : metric;
// }
// async fetchMetricsRange(metric: string, start: number, end: number, step: number, filters: Record<string, string> = {}): Promise<PrometheusMetric[]> {
// // Рассчитываем оптимальный шаг, если не указан
// const duration = end - start;
// const optimalStep = Math.max(Math.floor(duration / 1000), 15); // Минимум 15 секунд
// const query = this.buildFilteredQuery(metric, {
// ...filters,
// instance: '192.168.2.34:9050'
// });
// try {
// const response = await lastValueFrom(
// this.httpService.get(`${this.prometheusUrl}/query_range`, {
// params: {
// query,
// start,
// end,
// step: optimalStep.toString()
// },
// })
// );
// const metricType = await this.fetchMetricType(metric);
// const metricDescription = await this.fetchMetricDescription(metric);
// return response.data.data.result.flatMap((entry) =>
// entry.values.map((value): PrometheusMetric => ({
// __name__: entry.metric.__name__ || metric,
// device: entry.metric.device,
// instance: entry.metric.instance,
// job: entry.metric.job,
// source_id: entry.metric.source_id,
// status: entry.metric.status || '0',
// timestamp: value[0] * 1000,
// value: parseFloat(value[1]),
// type: metricType || 'gauge',
// description: metricDescription,
// ...entry.metric
// }))
// );
// } catch (error) {
// console.error('Error in fetchMetricsRange:', {
// error: error.response?.data || error.message,
// query,
// filters
// });
// throw error;
// }
// }
// async getMetricsForMenuItem(menuItem: MenuItem): Promise<PrometheusMetric[]> {
// if (!menuItem.metric || !menuItem.filters) {
// throw new Error('MenuItem is not a metric item');
// }
// return this.fetchMetricsWithFilters(menuItem.metric, menuItem.filters);
// }
// async fetchMetricMetadata(metric: string): Promise<{
// name: string;
// help?: string;
// type?: string;
// }> {
// try {
// const response = await lastValueFrom(
// this.httpService.get(`${this.prometheusUrl}/metadata`, {
// params: { metric }
// })
// );
// const data = response.data?.data?.[metric]?.[0];
// return {
// name: metric,
// help: data?.help,
// type: data?.type
// };
// } catch (error) {
// console.error(`Error fetching metadata for ${metric}:`, error);
// return {
// name: metric
// };
// }
// }
// async fetchMetricSeries(metric: string): Promise<Record<string, string>[]> {
// try {
// const response = await lastValueFrom(
// this.httpService.get(`${this.prometheusUrl}/series`, {
// params: { 'match[]': metric }
// })
// );
// return response.data.data || [];
// } catch (error) {
// console.error(`Error fetching series for ${metric}:`, error);
// return [];
// }
// }
// async fetchAllMetrics(): Promise<string[]> {
// try {
// const response = await lastValueFrom(
// this.httpService.get(`${this.prometheusUrl}/label/__name__/values`)
// );
// return response.data.data;
// } catch (error) {
// console.error('Error fetching all metrics:', error);
// return [];
// }
// }
// async fetchAllMetricsWithValues(): Promise<any[]> {
// const metricNames = await this.fetchAllMetrics();
// const zvksMetrics = metricNames.filter(metric =>
// metric.startsWith('zvks') ||
// metric.includes('server_li') ||
// metric.includes('application_li')
// );
// const promises = zvksMetrics.map(async (metric) => {
// try {
// const data = await this.fetchMetrics(metric);
// return { metric, data };
// } catch (error) {
// console.error(`Error fetching data for metric ${metric}:`, error);
// return { metric, data: [] };
// }
// });
// return Promise.all(promises);
// }
// clearCache(): void {
// this.metricCache.clear();
// this.metadataCache.clear();
// }
// }
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios'; import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';