From 12e1ff08f51040f15a1e6377ddb2f5bc11e0eec0 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 4 Jun 2025 08:37:45 -0400 Subject: [PATCH 1/3] status color fix --- src/Charts2/PrometheusChart.jsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Charts2/PrometheusChart.jsx b/src/Charts2/PrometheusChart.jsx index a0be805..8bbce1f 100644 --- a/src/Charts2/PrometheusChart.jsx +++ b/src/Charts2/PrometheusChart.jsx @@ -39,18 +39,18 @@ const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => { const formatMetricData = (dataArray) => { return dataArray - .map(item => ({ - ...item, - timestamp: item.timestamp, - value: parseFloat(item.value), - name: item.__name__ || metricName, - status: item.status?.toString() || '0', - device: item.device?.trim() || null, - source_id: item.source_id || null, - description: item.description || description - })) - .sort((a, b) => a.timestamp - b.timestamp); - }; + .map(item => ({ + ...item, + timestamp: item.timestamp, + value: parseFloat(item.value), + name: item.__name__ || metricName, + status: parseInt(item.status) || 0, + device: item.device?.trim() || null, + source_id: item.source_id || null, + description: item.description || description + })) + .sort((a, b) => a.timestamp - b.timestamp); + }; // Обновляем логи при изменении данных useEffect(() => { From b5b758ffa05fa709ca36412c2c67911c82a15635 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Thu, 5 Jun 2025 08:08:13 -0400 Subject: [PATCH 2/3] added ranges for charts --- src/Charts2/Components/LineChartComponent.jsx | 176 +++++++++++++++--- src/Charts2/PrometheusChart.jsx | 38 ++-- src/Components/Layout/Dashboard.jsx | 7 +- 3 files changed, 181 insertions(+), 40 deletions(-) diff --git a/src/Charts2/Components/LineChartComponent.jsx b/src/Charts2/Components/LineChartComponent.jsx index d7df0ef..40b9aa7 100644 --- a/src/Charts2/Components/LineChartComponent.jsx +++ b/src/Charts2/Components/LineChartComponent.jsx @@ -8,19 +8,29 @@ import { Tooltip, Legend, ResponsiveContainer, - ReferenceArea + ReferenceArea, + ReferenceLine } from 'recharts'; +import { Tag } from 'antd'; + +// Цвета для граничных значений +const rangeColors = { + 1: '#4CAF50', // зеленый (норма) + 2: '#FFC107', // желтый (предупреждение) + 3: '#FF9800', // оранжевый (опасно) + 4: '#F44336' // красный (критично) +}; -// ====== Вспомогательные функции ====== const getStatusColor = (status) => { - switch(status) { - case 0: return '#757575'; // тёмно-серый - case 1: return '#00C853'; // ярко-зелёный - case 2: return '#FF6D00'; // ярко-оранжевый - case 3: return '#D50000'; // ярко-красный - default: return '#BDBDBD'; // fallback-серый - } - }; + switch (status) { + case 0: return '#757575'; // серый (нет связи) + case 1: return rangeColors[1]; // зеленый + case 2: return rangeColors[2]; // желтый + case 3: return rangeColors[3]; // оранжевый + case 4: return rangeColors[4]; // красный + default: return '#BDBDBD'; // серый по умолчанию + } +}; const getStatusText = (status) => { return { @@ -43,9 +53,9 @@ const getStatusDescription = (status) => { const StatusIndicator = ({ cx, cy, payload }) => { const status = payload?.status ?? 0; return ( - { ); }; -// ====== Основной компонент ====== const LineChartComponent = ({ data, title, description, metaInfo, dataKey = 'value', - height = 400 + height = 400, + ranges = [], + statusBoundaries = [] }) => { const getStatusAreas = () => { @@ -137,6 +148,79 @@ const LineChartComponent = ({ )); }; + const renderRangeAreas = () => { + if (!ranges || ranges.length === 0) return null; + + return ranges.map((range, index) => { + const hasData = data && data.length > 0; + const minX = hasData ? data[0].timestamp : 0; + const maxX = hasData ? data[data.length - 1].timestamp : 0; + + return ( + + ); + }); + }; + + const renderRangeLines = () => { + if (!ranges || ranges.length === 0) return null; + + const uniqueValues = new Set(); + ranges.forEach(range => { + uniqueValues.add(range.min); + uniqueValues.add(range.max); + }); + + return Array.from(uniqueValues).map((value, index) => ( + + )); + }; + + const renderStatusBoundaries = () => { + if (!statusBoundaries || statusBoundaries.length === 0) return null; + + return statusBoundaries.map((boundary, index) => ( + + )); + }; + return (
{title &&

{title}

} @@ -149,7 +233,52 @@ const LineChartComponent = ({
)} - + {/* Легенда граничных значений */} + {ranges.length > 0 && ( +
+ Диапазоны: + {ranges + .sort((a, b) => a.min - b.min) + .map((range, index) => ( + + {range.min.toFixed(0)}-{range.max.toFixed(0)} (Ур. {range.status}) + + ))} +
+ )} + + {/* Легенда границ статусов */} + {statusBoundaries.length > 0 && ( +
+ Границы статусов: + {statusBoundaries.map((boundary, index) => ( + + {boundary.label || `Граница ${index + 1}`} ({new Date(boundary.timestamp).toLocaleString()}) + + ))} +
+ )} + + new Date(ts).toLocaleTimeString()} /> + {renderRangeLines()} + {renderRangeAreas()} + {renderStatusBoundaries()} {/* Добавляем отображение границ */} {getStatusAreas()} } /> @@ -185,16 +317,16 @@ const LineChartComponent = ({ flexWrap: 'wrap' }}> {[ - { status: 1, label: '1 - Норма', color: '#4CAF50' }, - { status: 2, label: '2 - Отклонение', color: '#FF9800' }, - { status: 3, label: '3 - Критично', color: '#F44336' }, - { status: 0, label: '0 - Нет связи', color: '#888' } + { status: 1, label: '1 - Норма' }, + { status: 2, label: '2 - Отклонение' }, + { status: 3, label: '3 - Критично' }, + { status: 0, label: '0 - Нет связи' } ].map(item => (
@@ -206,4 +338,4 @@ const LineChartComponent = ({ ); }; -export default LineChartComponent; +export default LineChartComponent; \ No newline at end of file diff --git a/src/Charts2/PrometheusChart.jsx b/src/Charts2/PrometheusChart.jsx index 8bbce1f..bca8ac6 100644 --- a/src/Charts2/PrometheusChart.jsx +++ b/src/Charts2/PrometheusChart.jsx @@ -8,13 +8,14 @@ import StatusLogTable from './Components/StatusLogTable'; import { Box, IconButton, Tooltip as MuiTooltip } from '@mui/material'; import { ListAlt } from '@mui/icons-material'; -const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => { +const PrometheusChart = ({ metricInfo, chartHeight = 580 }) => { const { name: metricName, filters = {}, title = metricName, description, - context = {} + context = {}, + ranges = [] } = metricInfo || {}; const { device, source_id } = context; @@ -39,18 +40,18 @@ const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => { const formatMetricData = (dataArray) => { return dataArray - .map(item => ({ - ...item, - timestamp: item.timestamp, - value: parseFloat(item.value), - name: item.__name__ || metricName, - status: parseInt(item.status) || 0, - device: item.device?.trim() || null, - source_id: item.source_id || null, - description: item.description || description - })) - .sort((a, b) => a.timestamp - b.timestamp); - }; + .map(item => ({ + ...item, + timestamp: item.timestamp, + value: parseFloat(item.value), + name: item.__name__ || metricName, + status: parseInt(item.status) || 0, + device: item.device?.trim() || null, + source_id: item.source_id || null, + description: item.description || description + })) + .sort((a, b) => a.timestamp - b.timestamp); + }; // Обновляем логи при изменении данных useEffect(() => { @@ -64,7 +65,7 @@ const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => { setStatusLogs(newLogs); } }, [chartData]); - + const fetchHistoricalData = async (start, end) => { setIsLoading(true); @@ -239,6 +240,13 @@ const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => { device, source_id }} + ranges={ranges} + /*ranges={ranges.length > 0 ? ranges : [ + { min: 0, max: 60, status: 1 }, + { min: 60, max: 80, status: 2 }, + { min: 80, max: 90, status: 3 }, + { min: 90, max: 100, status: 4 } + ]}*/ /> {showLogs && ( diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx index b5b3768..e0c2c42 100755 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -84,7 +84,6 @@ const Dashboard = ({ isDarkMode, setIsDarkMode }) => { const tabId = `tab_${item.id}`; const tabTitle = item.title || 'Новая вкладка'; - // Если это метрика, создаём специальный контент с графиком const tabContent = item.metric ? { filters: item.filters, title: item.title, description: item.description, + ranges: item.ranges, context: { device: item.filters?.device, source_id: item.filters?.source_id, @@ -110,13 +110,14 @@ const Dashboard = ({ isDarkMode, setIsDarkMode }) => { content: tabContent, type: item.metric ? 'metric' : 'menuItem', metric: item.metric, - filters: item.filters + filters: item.filters, + ranges: item.ranges }; handleOpenTab(newTab); } else { setActiveTab(tabId); } - }; +}; // Вспомогательная функция для получения всех дочерних элементов const getAllChildren = (node) => { From f87274d41a08f512b44c8fd8c2e3b5361c6c794c Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Fri, 6 Jun 2025 07:25:32 -0400 Subject: [PATCH 3/3] improving charts --- src/Charts2/Components/LineChartComponent.jsx | 177 ++++++++++-------- src/Charts2/Components/StatusLogTable.jsx | 28 ++- src/Charts2/PrometheusChart.jsx | 31 ++- 3 files changed, 135 insertions(+), 101 deletions(-) diff --git a/src/Charts2/Components/LineChartComponent.jsx b/src/Charts2/Components/LineChartComponent.jsx index 40b9aa7..e09476f 100644 --- a/src/Charts2/Components/LineChartComponent.jsx +++ b/src/Charts2/Components/LineChartComponent.jsx @@ -37,7 +37,8 @@ const getStatusText = (status) => { 0: 'Нет соединения', 1: 'Норма', 2: 'Отклонение', - 3: 'Критично' + 3: 'Критично', + 4: 'Авария' }[status] || 'Неизвестно'; }; @@ -46,7 +47,8 @@ const getStatusDescription = (status) => { 0: 'Устройство не отвечает', 1: 'Параметры в норме', 2: 'Обнаружены отклонения от нормы', - 3: 'Критическое состояние системы' + 3: 'Критическое состояние системы', + 4: 'Авария' }[status] || 'Статус неизвестен'; }; @@ -139,66 +141,73 @@ const LineChartComponent = ({ return areas.map((area, i) => ( + key={`area-${i}`} + x1={area.start} + x2={area.end} + fill={getStatusColor(area.status)} + fillOpacity={0.12} + stroke={getStatusColor(area.status)} + strokeWidth={1} + strokeOpacity={0.5} +/> + )); }; - const renderRangeAreas = () => { - if (!ranges || ranges.length === 0) return null; - - return ranges.map((range, index) => { - const hasData = data && data.length > 0; - const minX = hasData ? data[0].timestamp : 0; - const maxX = hasData ? data[data.length - 1].timestamp : 0; - - return ( - - ); - }); - }; - const renderRangeLines = () => { if (!ranges || ranges.length === 0) return null; - - const uniqueValues = new Set(); - ranges.forEach(range => { - uniqueValues.add(range.min); - uniqueValues.add(range.max); + + // Собираем только уникальные граничные значения, исключая дубликаты на стыках диапазонов + const boundaryValues = []; + ranges.forEach((range, index) => { + // Для первого диапазона добавляем и min и max + if (index === 0) { + boundaryValues.push(range.min); + boundaryValues.push(range.max); + } + // Для остальных добавляем только max (min будет совпадать с max предыдущего) + else { + boundaryValues.push(range.max); + } + }); + + return boundaryValues.map((value, index) => { + // Находим диапазон, к которому принадлежит эта граница + const range = ranges.find(r => r.min === value || r.max === value); + const status = range ? range.status : 1; + + const lineStyle = { + 1: { strokeWidth: 1, strokeDasharray: "none", opacity: 0.7 }, + 2: { strokeWidth: 2, strokeDasharray: "none", opacity: 0.9 }, + 3: { strokeWidth: 2, strokeDasharray: "none", opacity: 1 }, + 4: { strokeWidth: 2, strokeDasharray: "none", opacity: 1 } + }[status] || { strokeWidth: 1, strokeDasharray: "3 3", opacity: 0.7 }; + + return ( + + ); }); - - return Array.from(uniqueValues).map((value, index) => ( - - )); }; const renderStatusBoundaries = () => { @@ -279,33 +288,34 @@ const LineChartComponent = ({ )} - - - new Date(ts).toLocaleTimeString()} - /> - - {renderRangeLines()} - {renderRangeAreas()} - {renderStatusBoundaries()} {/* Добавляем отображение границ */} - {getStatusAreas()} - } /> - - } - activeDot={{ r: 8 }} - isAnimationActive={false} - name={title} - /> - + + + new Date(ts).toLocaleTimeString()} + /> + + {renderRangeLines()} + {renderStatusBoundaries()} + {getStatusAreas()} + } /> + + } + activeDot={{ r: 8 }} + isAnimationActive={false} + name={title} + /> + + + {/* Легенда статусов */} @@ -320,6 +330,7 @@ const LineChartComponent = ({ { status: 1, label: '1 - Норма' }, { status: 2, label: '2 - Отклонение' }, { status: 3, label: '3 - Критично' }, + { status: 4, label: '4 - Авария' }, { status: 0, label: '0 - Нет связи' } ].map(item => (
diff --git a/src/Charts2/Components/StatusLogTable.jsx b/src/Charts2/Components/StatusLogTable.jsx index 33dbc06..ccffd23 100644 --- a/src/Charts2/Components/StatusLogTable.jsx +++ b/src/Charts2/Components/StatusLogTable.jsx @@ -1,4 +1,3 @@ -// src/Components/StatusLogTable.jsx import React from 'react'; import { Table, @@ -12,11 +11,13 @@ import { Typography } from '@mui/material'; +// Используем те же цвета, что и в LineChartComponent const statusColors = { - '0': 'default', - '1': 'success', - '2': 'warning', - '3': 'error' + '0': '#757575', // серый (нет связи) + '1': '#4CAF50', // зеленый (норма) + '2': '#FFC107', // желтый (отклонение) + '3': '#FF9800', // оранжевый (критично) + '4': '#F44336' // красный (авария) }; const StatusLogTable = ({ logs }) => { @@ -42,9 +43,14 @@ const StatusLogTable = ({ logs }) => { {log.device} {log.source_id?.split('$')[1]} - @@ -62,13 +68,14 @@ const StatusLogTable = ({ logs }) => { ); }; -// Вспомогательные функции +// Вспомогательные функции (оставляем без изменений) const getStatusText = (status) => { const statusMap = { '0': 'Нет соединения', '1': 'Норма', '2': 'Отклонение', - '3': 'Критично' + '3': 'Критично', + '4': 'Авария' }; return statusMap[status] || 'Неизвестно'; }; @@ -78,7 +85,8 @@ const getStatusDescription = (status) => { '0': 'Устройство не отвечает', '1': 'Параметры в норме', '2': 'Обнаружены отклонения от нормы', - '3': 'Критическое состояние системы' + '3': 'Критическое состояние системы', + '4': 'Аварийное состояние системы' }; return descriptions[status] || 'Статус неизвестен'; }; diff --git a/src/Charts2/PrometheusChart.jsx b/src/Charts2/PrometheusChart.jsx index bca8ac6..65f0fb2 100644 --- a/src/Charts2/PrometheusChart.jsx +++ b/src/Charts2/PrometheusChart.jsx @@ -53,6 +53,19 @@ const PrometheusChart = ({ metricInfo, chartHeight = 580 }) => { .sort((a, b) => a.timestamp - b.timestamp); }; + const downsampleData = (data, maxPoints = 500) => { + if (data.length <= maxPoints) return data; + + const ratio = Math.ceil(data.length / maxPoints); + return data.filter((_, index) => index % ratio === 0); + }; + + const calculateStep = (startTime, endTime, maxPoints = 10000) => { + const seconds = (endTime.getTime() - startTime.getTime()) / 1000; + return Math.max(Math.ceil(seconds / maxPoints), 1); // в секундах + }; + + // Обновляем логи при изменении данных useEffect(() => { if (chartData.length > 0) { @@ -78,15 +91,17 @@ const PrometheusChart = ({ metricInfo, chartHeight = 580 }) => { ...(source_id && { source_id: source_id.toString() }) }; - const data = await metricsService.fetchMetricsRange( - metricName, - Math.floor(start.getTime() / 1000), - Math.floor(end.getTime() / 1000), - 15, - extendedFilters - ); + const step = calculateStep(start, end); +const data = await metricsService.fetchMetricsRange( + metricName, + Math.floor(start.getTime() / 1000), + Math.floor(end.getTime() / 1000), + step, + extendedFilters +); - const formattedData = formatMetricData(data); + + const formattedData = downsampleData(formatMetricData(data), 100); //КОЛИЧЕСТВО ТОЧЕК НА ГРАФИКЕ if (formattedData.length > 0) { setMetricMeta({ type: data[0]?.type,