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