added ranges for charts

pull/44/head
DmitriyA 2025-06-05 08:08:13 -04:00
parent 12e1ff08f5
commit b5b758ffa0
3 changed files with 181 additions and 40 deletions

View File

@ -8,19 +8,29 @@ import {
Tooltip, Tooltip,
Legend, Legend,
ResponsiveContainer, ResponsiveContainer,
ReferenceArea ReferenceArea,
ReferenceLine
} from 'recharts'; } from 'recharts';
import { Tag } from 'antd';
// Цвета для граничных значений
const rangeColors = {
1: '#4CAF50', // зеленый (норма)
2: '#FFC107', // желтый (предупреждение)
3: '#FF9800', // оранжевый (опасно)
4: '#F44336' // красный (критично)
};
// ====== Вспомогательные функции ======
const getStatusColor = (status) => { const getStatusColor = (status) => {
switch(status) { switch (status) {
case 0: return '#757575'; // тёмно-серый case 0: return '#757575'; // серый (нет связи)
case 1: return '#00C853'; // ярко-зелёный case 1: return rangeColors[1]; // зеленый
case 2: return '#FF6D00'; // ярко-оранжевый case 2: return rangeColors[2]; // желтый
case 3: return '#D50000'; // ярко-красный case 3: return rangeColors[3]; // оранжевый
default: return '#BDBDBD'; // fallback-серый case 4: return rangeColors[4]; // красный
default: return '#BDBDBD'; // серый по умолчанию
} }
}; };
const getStatusText = (status) => { const getStatusText = (status) => {
return { return {
@ -98,14 +108,15 @@ const CustomTooltip = ({ active, payload, label }) => {
); );
}; };
// ====== Основной компонент ======
const LineChartComponent = ({ const LineChartComponent = ({
data, data,
title, title,
description, description,
metaInfo, metaInfo,
dataKey = 'value', dataKey = 'value',
height = 400 height = 400,
ranges = [],
statusBoundaries = []
}) => { }) => {
const getStatusAreas = () => { 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 (
<ReferenceArea
key={`range-${index}`}
y1={range.min}
y2={range.max}
x1={minX}
x2={maxX}
fill={rangeColors[range.status] || '#e8e8e8'}
fillOpacity={0.15}
stroke={rangeColors[range.status] || '#e8e8e8'}
strokeWidth={1}
strokeDasharray="3 3"
ifOverflow="extendDomain"
/>
);
});
};
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) => (
<ReferenceLine
key={`line-${index}`}
y={value}
stroke="#888"
strokeDasharray="3 3"
strokeOpacity={0.7}
ifOverflow="extendDomain"
label={{
value: value.toFixed(1),
position: 'insideRight',
fill: '#888',
fontSize: 12
}}
/>
));
};
const renderStatusBoundaries = () => {
if (!statusBoundaries || statusBoundaries.length === 0) return null;
return statusBoundaries.map((boundary, index) => (
<ReferenceLine
key={`boundary-${index}`}
x={boundary.timestamp}
stroke={getStatusColor(boundary.status)}
strokeWidth={2}
strokeDasharray="5 3"
label={{
value: boundary.label || `Граница ${index + 1}`,
position: 'top',
fill: getStatusColor(boundary.status),
fontSize: 12
}}
/>
));
};
return ( return (
<div style={{ width: '100%', height: `${height}px` }}> <div style={{ width: '100%', height: `${height}px` }}>
{title && <h3>{title}</h3>} {title && <h3>{title}</h3>}
@ -149,7 +233,52 @@ const LineChartComponent = ({
</div> </div>
)} )}
<ResponsiveContainer width="100%" height="80%"> {/* Легенда граничных значений */}
{ranges.length > 0 && (
<div style={{ marginBottom: 10 }}>
<span style={{ marginRight: 8, fontWeight: 'bold' }}>Диапазоны:</span>
{ranges
.sort((a, b) => a.min - b.min)
.map((range, index) => (
<Tag
key={`range-tag-${index}`}
color={rangeColors[range.status] || 'default'}
style={{
marginRight: 5,
marginBottom: 5,
border: `1px solid ${rangeColors[range.status]}`,
background: `${rangeColors[range.status]}20`,
color: '#000000'
}}
>
{range.min.toFixed(0)}-{range.max.toFixed(0)} (Ур. {range.status})
</Tag>
))}
</div>
)}
{/* Легенда границ статусов */}
{statusBoundaries.length > 0 && (
<div style={{ marginBottom: 10 }}>
<span style={{ marginRight: 8, fontWeight: 'bold' }}>Границы статусов:</span>
{statusBoundaries.map((boundary, index) => (
<Tag
key={`boundary-tag-${index}`}
color={getStatusColor(boundary.status)}
style={{
marginRight: 5,
marginBottom: 5,
border: `1px solid ${getStatusColor(boundary.status)}`,
background: `${getStatusColor(boundary.status)}20`
}}
>
{boundary.label || `Граница ${index + 1}`} ({new Date(boundary.timestamp).toLocaleString()})
</Tag>
))}
</div>
)}
<ResponsiveContainer width="100%" height="75%">
<LineChart <LineChart
data={data} data={data}
margin={{ top: 5, right: 30, left: 20, bottom: 5 }} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
@ -160,6 +289,9 @@ const LineChartComponent = ({
tickFormatter={(ts) => new Date(ts).toLocaleTimeString()} tickFormatter={(ts) => new Date(ts).toLocaleTimeString()}
/> />
<YAxis /> <YAxis />
{renderRangeLines()}
{renderRangeAreas()}
{renderStatusBoundaries()} {/* Добавляем отображение границ */}
{getStatusAreas()} {getStatusAreas()}
<Tooltip content={<CustomTooltip />} /> <Tooltip content={<CustomTooltip />} />
<Legend /> <Legend />
@ -185,16 +317,16 @@ const LineChartComponent = ({
flexWrap: 'wrap' flexWrap: 'wrap'
}}> }}>
{[ {[
{ status: 1, label: '1 - Норма', color: '#4CAF50' }, { status: 1, label: '1 - Норма' },
{ status: 2, label: '2 - Отклонение', color: '#FF9800' }, { status: 2, label: '2 - Отклонение' },
{ status: 3, label: '3 - Критично', color: '#F44336' }, { status: 3, label: '3 - Критично' },
{ status: 0, label: '0 - Нет связи', color: '#888' } { status: 0, label: '0 - Нет связи' }
].map(item => ( ].map(item => (
<div key={item.status} style={{ display: 'flex', alignItems: 'center' }}> <div key={item.status} style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ <div style={{
width: 16, width: 16,
height: 16, height: 16,
backgroundColor: item.color, backgroundColor: getStatusColor(item.status),
marginRight: 8, marginRight: 8,
borderRadius: '50%' borderRadius: '50%'
}}></div> }}></div>

View File

@ -8,13 +8,14 @@ import StatusLogTable from './Components/StatusLogTable';
import { Box, IconButton, Tooltip as MuiTooltip } from '@mui/material'; import { Box, IconButton, Tooltip as MuiTooltip } from '@mui/material';
import { ListAlt } from '@mui/icons-material'; import { ListAlt } from '@mui/icons-material';
const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => { const PrometheusChart = ({ metricInfo, chartHeight = 580 }) => {
const { const {
name: metricName, name: metricName,
filters = {}, filters = {},
title = metricName, title = metricName,
description, description,
context = {} context = {},
ranges = []
} = metricInfo || {}; } = metricInfo || {};
const { device, source_id } = context; const { device, source_id } = context;
@ -239,6 +240,13 @@ const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => {
device, device,
source_id 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 && ( {showLogs && (
<StatusLogTable logs={statusLogs} /> <StatusLogTable logs={statusLogs} />

View File

@ -84,7 +84,6 @@ const Dashboard = ({ isDarkMode, setIsDarkMode }) => {
const tabId = `tab_${item.id}`; const tabId = `tab_${item.id}`;
const tabTitle = item.title || 'Новая вкладка'; const tabTitle = item.title || 'Новая вкладка';
// Если это метрика, создаём специальный контент с графиком
const tabContent = item.metric const tabContent = item.metric
? <MetricTabContent ? <MetricTabContent
metricInfo={{ metricInfo={{
@ -92,6 +91,7 @@ const Dashboard = ({ isDarkMode, setIsDarkMode }) => {
filters: item.filters, filters: item.filters,
title: item.title, title: item.title,
description: item.description, description: item.description,
ranges: item.ranges,
context: { context: {
device: item.filters?.device, device: item.filters?.device,
source_id: item.filters?.source_id, source_id: item.filters?.source_id,
@ -110,13 +110,14 @@ const Dashboard = ({ isDarkMode, setIsDarkMode }) => {
content: tabContent, content: tabContent,
type: item.metric ? 'metric' : 'menuItem', type: item.metric ? 'metric' : 'menuItem',
metric: item.metric, metric: item.metric,
filters: item.filters filters: item.filters,
ranges: item.ranges
}; };
handleOpenTab(newTab); handleOpenTab(newTab);
} else { } else {
setActiveTab(tabId); setActiveTab(tabId);
} }
}; };
// Вспомогательная функция для получения всех дочерних элементов // Вспомогательная функция для получения всех дочерних элементов
const getAllChildren = (node) => { const getAllChildren = (node) => {