rework ws
parent
fd5a202d74
commit
1bcb15f655
|
|
@ -43,7 +43,14 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
return `${metricName}${filterParts.length ? `?${filterParts.join('&')}` : ''}`;
|
return `${metricName}${filterParts.length ? `?${filterParts.join('&')}` : ''}`;
|
||||||
}, [metricName, device, source_id]);
|
}, [metricName, device, source_id]);
|
||||||
|
|
||||||
const formatMetricData = (dataArray) => {
|
const formatMetricData = (responseData) => {
|
||||||
|
const dataArray = Array.isArray(responseData) ? responseData : responseData.data;
|
||||||
|
|
||||||
|
if (!Array.isArray(dataArray)) {
|
||||||
|
console.error('Expected array but got:', responseData);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return dataArray.map(item => ({
|
return dataArray.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
timestamp: item.timestamp,
|
timestamp: item.timestamp,
|
||||||
|
|
@ -74,6 +81,7 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const step = calculateStep(start, end);
|
const step = calculateStep(start, end);
|
||||||
|
|
||||||
const data = await metricsService.fetchMetricsRange(
|
const data = await metricsService.fetchMetricsRange(
|
||||||
metricName,
|
metricName,
|
||||||
Math.floor(start.getTime() / 1000),
|
Math.floor(start.getTime() / 1000),
|
||||||
|
|
@ -82,7 +90,8 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
extendedFilters
|
extendedFilters
|
||||||
);
|
);
|
||||||
|
|
||||||
const formattedData = formatMetricData(data);
|
const responseData = Array.isArray(data) ? data : data.data;
|
||||||
|
const formattedData = formatMetricData(responseData);
|
||||||
setRawData(formattedData);
|
setRawData(formattedData);
|
||||||
|
|
||||||
if (formattedData.length > 0) {
|
if (formattedData.length > 0) {
|
||||||
|
|
@ -108,21 +117,19 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
|
|
||||||
const end = new Date();
|
const end = new Date();
|
||||||
const start = new Date(end.getTime() - TIME_WINDOW_MS);
|
const start = new Date(end.getTime() - TIME_WINDOW_MS);
|
||||||
|
const cutoffTime = Date.now() - TIME_WINDOW_MS;
|
||||||
fetchHistoricalData(start, end).finally(() => setIsLoading(false));
|
fetchHistoricalData(start, end).finally(() => setIsLoading(false));
|
||||||
|
|
||||||
return metricsService.subscribeToMetric(
|
return metricsService.subscribeToMetric(
|
||||||
subscriptionKey,
|
subscriptionKey,
|
||||||
(newData) => {
|
(newData) => {
|
||||||
setRawData(prev => {
|
setRawData(prev => {
|
||||||
const now = Date.now();
|
const actualData = Array.isArray(newData) ? newData : newData.data;
|
||||||
const cutoffTime = now - TIME_WINDOW_MS;
|
const formattedNewData = formatMetricData(actualData)
|
||||||
|
|
||||||
const formattedNewData = formatMetricData(newData)
|
|
||||||
.filter(point => point.timestamp >= cutoffTime);
|
.filter(point => point.timestamp >= cutoffTime);
|
||||||
|
|
||||||
const filteredPrev = prev.filter(point => point.timestamp >= cutoffTime);
|
const filteredPrev = prev.filter(point => point.timestamp >= cutoffTime);
|
||||||
|
|
||||||
// Объединяем данные, удаляем дубликаты
|
|
||||||
const merged = [...filteredPrev, ...formattedNewData]
|
const merged = [...filteredPrev, ...formattedNewData]
|
||||||
.filter((v, i, a) =>
|
.filter((v, i, a) =>
|
||||||
a.findIndex(t =>
|
a.findIndex(t =>
|
||||||
|
|
@ -134,7 +141,7 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
return merged;
|
return merged;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
5000, // Интервал обновления 5 секунд
|
5000,
|
||||||
{
|
{
|
||||||
...filters,
|
...filters,
|
||||||
...(device && { device }),
|
...(device && { device }),
|
||||||
|
|
@ -154,7 +161,6 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Обновляем логи статусов
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (rawData.length > 0) {
|
if (rawData.length > 0) {
|
||||||
const logs = [];
|
const logs = [];
|
||||||
|
|
|
||||||
|
|
@ -1,90 +1,157 @@
|
||||||
import { io } from 'socket.io-client';
|
|
||||||
|
|
||||||
class MetricsService {
|
class MetricsService {
|
||||||
constructor(baseUrl) {
|
constructor() {
|
||||||
this.baseUrl = baseUrl || window.location.origin;
|
this.baseUrl = 'ws://localhost:3001/api/metrics-ws';
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
this.subscriptions = new Map();
|
this.subscriptions = new Map();
|
||||||
this.pendingRequests = new Map();
|
this.pendingRequests = new Map();
|
||||||
window.addEventListener('beforeunload', this.cleanupAll.bind(this));
|
this.reconnectAttempts = 0;
|
||||||
window.addEventListener('pagehide', this.cleanupAll.bind(this));
|
this.maxReconnectAttempts = 5;
|
||||||
|
this.reconnectDelay = 5000;
|
||||||
|
|
||||||
window.addEventListener('beforeunload', () => {
|
window.addEventListener('beforeunload', () => this.cleanupAll());
|
||||||
this.cleanupAll();
|
window.addEventListener('pagehide', () => this.cleanupAll());
|
||||||
});
|
}
|
||||||
|
|
||||||
|
handleServerMessage(msg) {
|
||||||
|
try {
|
||||||
|
if (!msg || typeof msg !== 'object') {
|
||||||
|
console.error('Invalid message format', msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { event, data, requestId } = msg;
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
case 'metrics-data':
|
||||||
|
if (requestId && this.pendingRequests.has(requestId)) {
|
||||||
|
const { resolve } = this.pendingRequests.get(requestId);
|
||||||
|
resolve(data);
|
||||||
|
this.pendingRequests.delete(requestId);
|
||||||
|
} else {
|
||||||
|
const metricKey = data.metric;
|
||||||
|
const callbacks = this.subscriptions.get(metricKey) || [];
|
||||||
|
callbacks.forEach(cb => cb(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'metrics-error':
|
||||||
|
if (requestId && this.pendingRequests.has(requestId)) {
|
||||||
|
const { reject } = this.pendingRequests.get(requestId);
|
||||||
|
reject(new Error(data.error));
|
||||||
|
this.pendingRequests.delete(requestId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.warn('Unknown message type:', event);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing message:', error, msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connectWebSocket() {
|
connectWebSocket() {
|
||||||
if (this.socket) {
|
if (this.socket && (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING)) {
|
||||||
console.log('WebSocket already exists');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Connecting WebSocket...');
|
console.log('Connecting WebSocket...');
|
||||||
this.socket = io(`${this.baseUrl.replace('http', 'ws')}/api/metrics-ws`, {
|
this.socket = new WebSocket(this.baseUrl);
|
||||||
transports: ['websocket'],
|
|
||||||
withCredentials: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('connect', () => {
|
this.socket.addEventListener('open', () => {
|
||||||
console.log('WebSocket connected');
|
console.log('WebSocket connected');
|
||||||
// Восстанавливаем подписки при переподключении
|
this.reconnectAttempts = 0;
|
||||||
this.subscriptions.forEach((_, metricKey) => {
|
this.subscriptions.forEach((_, metricKey) => {
|
||||||
const [metric, query] = metricKey.split('?');
|
|
||||||
const filters = this.parseFiltersFromKey(metricKey);
|
const filters = this.parseFiltersFromKey(metricKey);
|
||||||
this.socket.emit('subscribe-metric', { metric, filters });
|
const [metric] = metricKey.split('?');
|
||||||
|
this.sendMessage('subscribe-metric', { metric, filters });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('disconnect', () => {
|
this.socket.addEventListener('close', () => {
|
||||||
console.log('WebSocket disconnected');
|
console.log('WebSocket disconnected');
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
|
this.scheduleReconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('metrics-data', ({ metric, data, requestId }) => {
|
this.socket.addEventListener('error', (err) => {
|
||||||
if (requestId && this.pendingRequests.has(requestId)) {
|
console.error('WebSocket error:', err);
|
||||||
const { resolve } = this.pendingRequests.get(requestId);
|
|
||||||
resolve(data);
|
|
||||||
this.pendingRequests.delete(requestId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const callbacks = this.subscriptions.get(metric) || [];
|
|
||||||
callbacks.forEach(cb => cb(data));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('metrics-error', ({ error, requestId }) => {
|
this.socket.addEventListener('message', (event) => {
|
||||||
if (requestId && this.pendingRequests.has(requestId)) {
|
try {
|
||||||
const { reject } = this.pendingRequests.get(requestId);
|
const msg = JSON.parse(event.data);
|
||||||
reject(new Error(error));
|
this.handleServerMessage(msg);
|
||||||
this.pendingRequests.delete(requestId);
|
} catch (e) {
|
||||||
|
console.error('Error parsing WS message:', e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheduleReconnect() {
|
||||||
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||||
|
console.warn('Max reconnect attempts reached');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reconnectAttempts++;
|
||||||
|
const delay = this.reconnectDelay * this.reconnectAttempts;
|
||||||
|
console.log(`Scheduling reconnect attempt ${this.reconnectAttempts} in ${delay}ms`);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.connectWebSocket();
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(event, data) {
|
||||||
|
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
||||||
|
if (this.socket && this.socket.readyState === WebSocket.CONNECTING) {
|
||||||
|
const waitForOpen = () => {
|
||||||
|
if (this.socket.readyState === WebSocket.OPEN) {
|
||||||
|
this.socket.send(JSON.stringify({ event, data }));
|
||||||
|
} else if (this.socket.readyState === WebSocket.CONNECTING) {
|
||||||
|
setTimeout(waitForOpen, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
waitForOpen();
|
||||||
|
} else {
|
||||||
|
console.warn('WebSocket not connected, cannot send:', event);
|
||||||
|
this.connectWebSocket();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket.send(JSON.stringify({ event, data }));
|
||||||
|
}
|
||||||
|
|
||||||
async fetchMetricsRange(metric, start, end, step = 15, filters = {}) {
|
async fetchMetricsRange(metric, start, end, step = 15, filters = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.connectWebSocket();
|
this.connectWebSocket();
|
||||||
|
|
||||||
const requestId = `range-${Date.now()}`;
|
const requestId = `range-${Date.now()}`;
|
||||||
this.pendingRequests.set(requestId, { resolve, reject });
|
|
||||||
|
|
||||||
this.socket.emit('get-metrics', {
|
// Таймаут для очистки
|
||||||
metric,
|
const timeout = setTimeout(() => {
|
||||||
start,
|
reject(new Error('Request timeout'));
|
||||||
end,
|
this.pendingRequests.delete(requestId);
|
||||||
step,
|
}, 12000);
|
||||||
filters,
|
|
||||||
isRangeQuery: true,
|
this.pendingRequests.set(requestId, {
|
||||||
requestId
|
resolve: (responseData) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
const data = Array.isArray(responseData) ? responseData :
|
||||||
|
(responseData?.data || []);
|
||||||
|
resolve(data);
|
||||||
|
},
|
||||||
|
reject: (err) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
this.sendMessage('get-metrics', {
|
||||||
if (this.pendingRequests.has(requestId)) {
|
metric, start, end, step, filters, isRangeQuery: true, requestId
|
||||||
reject(new Error('Request timeout'));
|
});
|
||||||
this.pendingRequests.delete(requestId);
|
|
||||||
}
|
|
||||||
}, 30000);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,19 +161,13 @@ class MetricsService {
|
||||||
if (!this.subscriptions.has(metricKey)) {
|
if (!this.subscriptions.has(metricKey)) {
|
||||||
this.subscriptions.set(metricKey, []);
|
this.subscriptions.set(metricKey, []);
|
||||||
const [metric] = metricKey.split('?');
|
const [metric] = metricKey.split('?');
|
||||||
this.socket.emit('subscribe-metric', {
|
this.sendMessage('subscribe-metric', { metric, interval, filters });
|
||||||
metric,
|
|
||||||
interval,
|
|
||||||
filters
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const callbacks = this.subscriptions.get(metricKey);
|
const callbacks = this.subscriptions.get(metricKey);
|
||||||
callbacks.push(callback);
|
callbacks.push(callback);
|
||||||
|
|
||||||
return () => {
|
return () => this.unsubscribeFromMetric(metricKey, callback);
|
||||||
this.unsubscribeFromMetric(metricKey, callback);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribeFromMetric(metricKey, callback) {
|
unsubscribeFromMetric(metricKey, callback) {
|
||||||
|
|
@ -115,10 +176,8 @@ class MetricsService {
|
||||||
|
|
||||||
if (filtered.length === 0) {
|
if (filtered.length === 0) {
|
||||||
this.subscriptions.delete(metricKey);
|
this.subscriptions.delete(metricKey);
|
||||||
if (this.socket && this.socket.connected) {
|
const [metric] = metricKey.split('?');
|
||||||
const [metric] = metricKey.split('?');
|
this.sendMessage('unsubscribe-metric', { metric });
|
||||||
this.socket.emit('unsubscribe-metric', { metric });
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.subscriptions.set(metricKey, filtered);
|
this.subscriptions.set(metricKey, filtered);
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +186,6 @@ class MetricsService {
|
||||||
parseFiltersFromKey(metricKey) {
|
parseFiltersFromKey(metricKey) {
|
||||||
const parts = metricKey.split('?');
|
const parts = metricKey.split('?');
|
||||||
if (parts.length < 2) return {};
|
if (parts.length < 2) return {};
|
||||||
|
|
||||||
return parts[1].split('&').reduce((acc, pair) => {
|
return parts[1].split('&').reduce((acc, pair) => {
|
||||||
const [key, value] = pair.split('=');
|
const [key, value] = pair.split('=');
|
||||||
if (key && value) acc[key] = value;
|
if (key && value) acc[key] = value;
|
||||||
|
|
@ -136,22 +194,19 @@ class MetricsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanupAll() {
|
cleanupAll() {
|
||||||
if (this.socket && this.socket.connected) {
|
this.sendMessage('unsubscribe-all', {});
|
||||||
this.socket.emit('unsubscribe-all');
|
|
||||||
}
|
|
||||||
this.subscriptions.clear();
|
this.subscriptions.clear();
|
||||||
this.disconnectWebSocket();
|
this.disconnectWebSocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectWebSocket() {
|
disconnectWebSocket() {
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.disconnect();
|
this.socket.close();
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Создаем экземпляр сервиса
|
const metricsService = new MetricsService();
|
||||||
const metricsService = new MetricsService(import.meta.env.VITE_BACK_URL);
|
|
||||||
|
|
||||||
export default metricsService;
|
export default metricsService;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ const PrometheusChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
const [showLogs, setShowLogs] = useState(false);
|
const [showLogs, setShowLogs] = useState(false);
|
||||||
const [statusLogs, setStatusLogs] = useState([]);
|
const [statusLogs, setStatusLogs] = useState([]);
|
||||||
const MAX_POINTS = 50;
|
const MAX_POINTS = 50;
|
||||||
const TIME_WINDOW_MS = 3600 * 1000; // 1 час в миллисекундах
|
const TIME_WINDOW_MS = 3600 * 1000;
|
||||||
|
|
||||||
|
|
||||||
const getSubscriptionKey = () => {
|
const getSubscriptionKey = () => {
|
||||||
|
|
@ -42,17 +42,28 @@ const PrometheusChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatMetricData = (dataArray) => {
|
const formatMetricData = (dataArray) => {
|
||||||
return dataArray
|
if (!Array.isArray(dataArray)) {
|
||||||
.map(item => ({
|
console.error('Expected array in formatMetricData, got:', typeof dataArray);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataArray.map(item => {
|
||||||
|
if (item.timestamp === undefined || item.value === undefined) {
|
||||||
|
console.warn('Invalid metric item:', item);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
...item,
|
...item,
|
||||||
timestamp: item.timestamp,
|
timestamp: Number(item.timestamp),
|
||||||
value: parseFloat(item.value),
|
value: parseFloat(item.value),
|
||||||
|
status: parseInt(item.status || '0'),
|
||||||
name: item.__name__ || metricName,
|
name: item.__name__ || metricName,
|
||||||
status: parseInt(item.status) || 0,
|
|
||||||
device: item.device?.trim() || null,
|
device: item.device?.trim() || null,
|
||||||
source_id: item.source_id || null,
|
source_id: item.source_id || null,
|
||||||
description: item.description || description
|
description: item.description || description
|
||||||
}))
|
};
|
||||||
|
}).filter(Boolean)
|
||||||
.sort((a, b) => a.timestamp - b.timestamp);
|
.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -73,7 +84,6 @@ const PrometheusChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
result.push(sortedData[i]);
|
result.push(sortedData[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Всегда включаем последнюю точку
|
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
const lastOriginalPoint = sortedData[sortedData.length - 1];
|
const lastOriginalPoint = sortedData[sortedData.length - 1];
|
||||||
if (result[result.length - 1].timestamp !== lastOriginalPoint.timestamp) {
|
if (result[result.length - 1].timestamp !== lastOriginalPoint.timestamp) {
|
||||||
|
|
@ -85,7 +95,6 @@ const PrometheusChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Обновляем логи при изменении данных
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chartData.length > 0) {
|
if (chartData.length > 0) {
|
||||||
const newLogs = chartData.reduce((acc, point, index) => {
|
const newLogs = chartData.reduce((acc, point, index) => {
|
||||||
|
|
@ -122,7 +131,6 @@ const PrometheusChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
const formattedData = formatMetricData(data)
|
const formattedData = formatMetricData(data)
|
||||||
.sort((a, b) => a.timestamp - b.timestamp);
|
.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
|
||||||
// Применяем ограничение по количеству точек только для исторических данных
|
|
||||||
const limitedData = formattedData.length > MAX_POINTS
|
const limitedData = formattedData.length > MAX_POINTS
|
||||||
? formattedData.slice(-MAX_POINTS)
|
? formattedData.slice(-MAX_POINTS)
|
||||||
: formattedData;
|
: formattedData;
|
||||||
|
|
@ -152,42 +160,42 @@ const PrometheusChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
|
|
||||||
const end = new Date();
|
const end = new Date();
|
||||||
const start = new Date(end.getTime() - TIME_WINDOW_MS);
|
const start = new Date(end.getTime() - TIME_WINDOW_MS);
|
||||||
|
|
||||||
fetchHistoricalData(start, end).finally(() => setIsLoading(false));
|
fetchHistoricalData(start, end).finally(() => setIsLoading(false));
|
||||||
|
|
||||||
return metricsService.subscribeToMetric(
|
return metricsService.subscribeToMetric(
|
||||||
getSubscriptionKey(),
|
getSubscriptionKey(),
|
||||||
(newData) => {
|
(newData) => {
|
||||||
|
console.log('Received WS update:', newData);
|
||||||
|
if (!Array.isArray(newData)) {
|
||||||
|
console.error('Expected array in WS update, got:', typeof newData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setChartData(prev => {
|
setChartData(prev => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const cutoffTime = now - TIME_WINDOW_MS;
|
const cutoffTime = now - TIME_WINDOW_MS;
|
||||||
|
|
||||||
// Фильтруем старые точки (старше TIME_WINDOW_MS)
|
const formattedNew = formatMetricData(newData)
|
||||||
const filteredPrev = prev.filter(point => point.timestamp >= cutoffTime);
|
|
||||||
|
|
||||||
// Добавляем новые точки
|
|
||||||
const newPoints = formatMetricData(newData)
|
|
||||||
.filter(point => point.timestamp >= cutoffTime);
|
.filter(point => point.timestamp >= cutoffTime);
|
||||||
|
|
||||||
// Объединяем и удаляем дубликаты
|
const filteredPrev = prev.filter(point =>
|
||||||
const mergedData = [...filteredPrev, ...newPoints]
|
point.timestamp >= cutoffTime
|
||||||
.filter((v, i, a) => a.findIndex(t => t.timestamp === v.timestamp) === i)
|
);
|
||||||
|
|
||||||
|
const merged = [...filteredPrev, ...formattedNew]
|
||||||
|
.filter((v, i, a) =>
|
||||||
|
a.findIndex(t => t.timestamp === v.timestamp) === i
|
||||||
|
)
|
||||||
.sort((a, b) => a.timestamp - b.timestamp);
|
.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
|
||||||
// Если точек слишком много, равномерно прореживаем
|
return merged.length > MAX_POINTS
|
||||||
if (mergedData.length > MAX_POINTS) {
|
? merged.slice(-MAX_POINTS)
|
||||||
const step = Math.ceil(mergedData.length / MAX_POINTS);
|
: merged;
|
||||||
return mergedData.filter((_, index) => index % step === 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mergedData;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
1000, // Уменьшаем интервал обновления до 1 секунды
|
1000,
|
||||||
{
|
{ ...filters, device, source_id }
|
||||||
...filters,
|
|
||||||
...(device && { device }),
|
|
||||||
...(source_id && { source_id })
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -204,20 +212,24 @@ const PrometheusChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('Current metric context:', { device, source_id, metricName });
|
console.log('Metric changed:', { metricName, device, source_id, filters });
|
||||||
|
|
||||||
let unsubscribe;
|
let unsubscribe;
|
||||||
if (mode === 'realtime') {
|
const init = async () => {
|
||||||
unsubscribe = startRealtimeUpdates();
|
if (mode === 'realtime') {
|
||||||
} else {
|
unsubscribe = startRealtimeUpdates();
|
||||||
stopRealtimeUpdates();
|
} else {
|
||||||
fetchHistoricalData(startDate, endDate);
|
await fetchHistoricalData(startDate, endDate);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (unsubscribe) unsubscribe();
|
if (unsubscribe) unsubscribe();
|
||||||
stopRealtimeUpdates();
|
stopRealtimeUpdates();
|
||||||
};
|
};
|
||||||
}, [mode, metricName, device, source_id]);
|
}, [mode, metricName, device, source_id, filters]);
|
||||||
|
|
||||||
const metaInfo = [
|
const metaInfo = [
|
||||||
metricMeta.instance && `Instance: ${metricMeta.instance}`,
|
metricMeta.instance && `Instance: ${metricMeta.instance}`,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:3000',
|
target: 'http://localhost:3000',
|
||||||
|
ws: true,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
bypass(req, res, options) {
|
bypass(req, res, options) {
|
||||||
console.log('Proxying request:', req.url);
|
console.log('Proxying request:', req.url);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue