From b5b758ffa05fa709ca36412c2c67911c82a15635 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Thu, 5 Jun 2025 08:08:13 -0400 Subject: [PATCH] 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) => {