Compare commits
2 Commits
5f1708424f
...
5fc70cd610
| Author | SHA1 | Date |
|---|---|---|
|
|
5fc70cd610 | |
|
|
4c0a272df2 |
|
|
@ -149,24 +149,39 @@ export class MenuService {
|
|||
});
|
||||
return Array.from(entities);
|
||||
}
|
||||
|
||||
private normalizeIdPart(part: string): string {
|
||||
return part
|
||||
.replace(/\$/g, '_') // Заменяем $ на _
|
||||
.replace(/[^a-zA-Z0-9-_]/g, ''); // Удаляем другие спецсимволы
|
||||
}
|
||||
|
||||
private generateModuleItems(
|
||||
device: string,
|
||||
seriesData: { metric: string; labels: Record<string, string> }[],
|
||||
metadataMap: Map<string, string>
|
||||
): MenuItem[] {
|
||||
const modules = new Set<string>();
|
||||
const modules = new Map<string, string>(); // source_id -> display name
|
||||
|
||||
seriesData.forEach(({ labels }) => {
|
||||
if (labels.device === device && labels.source_id) {
|
||||
modules.add(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);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(modules).map(module => ({
|
||||
id: `module_${device}_${module}`,
|
||||
title: `Module ${module.replace('module$', '')}`,
|
||||
items: this.generateMetricItems(device, module, seriesData, metadataMap),
|
||||
return Array.from(modules.entries()).map(([sourceId, displayName]) => ({
|
||||
id: `module_${device}_${sourceId}`,
|
||||
title: displayName,
|
||||
items: this.generateMetricItems(device, sourceId, seriesData, metadataMap),
|
||||
isDynamic: true
|
||||
}));
|
||||
}
|
||||
|
|
@ -177,24 +192,45 @@ export class MenuService {
|
|||
seriesData: { metric: string; labels: Record<string, string> }[],
|
||||
metadataMap: Map<string, string>
|
||||
): MenuItem[] {
|
||||
// Фильтруем метрики для текущего устройства и модуля
|
||||
const filtered = seriesData.filter(
|
||||
({ labels }) => labels.device === device && labels.source_id === module
|
||||
);
|
||||
|
||||
|
||||
// Получаем уникальные имена метрик
|
||||
const uniqueMetrics = new Set(filtered.map(entry => entry.metric));
|
||||
|
||||
|
||||
// Нормализуем идентификаторы
|
||||
const normalizeIdPart = (part: string): string => {
|
||||
return part
|
||||
.replace(/\$/g, '_') // Заменяем $ на _
|
||||
.replace(/[^a-zA-Z0-9-_]/g, '') // Удаляем другие спецсимволы
|
||||
.replace(/_+/g, '_') // Заменяем множественные _ на один
|
||||
.replace(/^_|_$/g, ''); // Удаляем _ в начале и конце
|
||||
};
|
||||
|
||||
const safeDevice = normalizeIdPart(device);
|
||||
const safeModule = normalizeIdPart(module);
|
||||
|
||||
// Формируем пункты меню
|
||||
return Array.from(uniqueMetrics).map(metric => {
|
||||
const description = metadataMap.get(metric) || metric;
|
||||
|
||||
const safeMetric = normalizeIdPart(metric);
|
||||
|
||||
return {
|
||||
id: `metric_${device}_${module}_${metric}`,
|
||||
id: `metric_${safeDevice}_${safeModule}_${safeMetric}`, // Безопасный ID
|
||||
title: description,
|
||||
metric,
|
||||
filters: {
|
||||
device,
|
||||
source_id: module
|
||||
device, // Оригинальное значение device
|
||||
source_id: module // Оригинальное значение source_id
|
||||
},
|
||||
isDynamic: true
|
||||
isDynamic: true,
|
||||
// Сохраняем оригинальные значения для отладки
|
||||
meta: {
|
||||
originalDevice: device,
|
||||
originalModule: module
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,11 +117,10 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat
|
|||
}
|
||||
|
||||
private getSubscriptionKey(metric: string, filters: Record<string, string>): string {
|
||||
// Создаём уникальный ключ на основе метрики и фильтров
|
||||
const filterKeys = Object.keys(filters).sort();
|
||||
const filterString = filterKeys.map(k => `${k}=${filters[k]}`).join('&');
|
||||
const filterString = filterKeys.map(k => `${k}=${encodeURIComponent(filters[k])}`).join('&');
|
||||
return `${metric}${filterString ? `?${filterString}` : ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeMessage('subscribe-metric')
|
||||
async handleSubscribeMetric(
|
||||
|
|
@ -140,7 +139,6 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat
|
|||
metric,
|
||||
interval,
|
||||
(data) => {
|
||||
// Отправляем только подписчикам этой конкретной метрики с фильтрами
|
||||
this.server.emit('metrics-data', {
|
||||
metric: subscriptionKey,
|
||||
data
|
||||
|
|
|
|||
|
|
@ -109,19 +109,17 @@ export class PrometheusService {
|
|||
}
|
||||
|
||||
private buildFilteredQuery(metric: string, filters: Record<string, string>): string {
|
||||
const filterParts = Object.entries(filters)
|
||||
.filter(([_, value]) => value !== undefined && value !== null && value !== "")
|
||||
.map(([key, value]) => {
|
||||
if (key === 'source_id' && !value.startsWith('module$')) {
|
||||
return `${key}="module$${value}"`;
|
||||
}
|
||||
return `${key}="${value}"`;
|
||||
});
|
||||
const filterParts = Object.entries(filters)
|
||||
.filter(([_, value]) => value !== undefined && value !== null && value !== "")
|
||||
.map(([key, value]) => {
|
||||
// Убираем автоматическое добавление "module$" для source_id
|
||||
return `${key}="${value}"`;
|
||||
});
|
||||
|
||||
return filterParts.length > 0
|
||||
? `${metric}{${filterParts.join(',')}}`
|
||||
: metric;
|
||||
}
|
||||
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 query = this.buildFilteredQuery(metric, {
|
||||
|
|
|
|||
Loading…
Reference in New Issue