From c077449b2c7d102070957ee10c2c7c3dc7727d78 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 26 Mar 2025 05:16:43 -0400 Subject: [PATCH] redesign of graphs and visualizations --- src/Charts/PrometheusChart.jsx | 166 ++++++++++++++---- src/Components/TreeChart/FlowChart.jsx | 17 +- .../FlowChartComponents/DataParser.jsx | 62 ++++--- .../FlowChartComponents/NodeWrapper.jsx | 92 +++++----- src/Components/UI/LoginModal.jsx | 1 - src/Style/DatePicker.css | 0 src/Style/range-selector.css | 54 ++++++ 7 files changed, 287 insertions(+), 105 deletions(-) delete mode 100755 src/Style/DatePicker.css create mode 100644 src/Style/range-selector.css diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index d3a151b..ddb1b65 100755 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -217,41 +217,143 @@ const PrometheusChart = ({ metricName }) => { }); return ( -
-
- - -
-
- -
- - setStartDate(date)} - showTimeSelect - timeFormat="HH:mm" - timeIntervals={15} - dateFormat="yyyy-MM-dd HH:mm" - /> +
+ {/* Заголовок графика */} +

+

+ + {/* Группа элементов управления */} +
+ {/* Стандартные диапазоны */} +
+ +
-
- - setEndDate(date)} - showTimeSelect - timeFormat="HH:mm" - timeIntervals={15} - dateFormat="yyyy-MM-dd HH:mm" - /> + + {/* Кастомный диапазон */} +
+
+ Или укажите свой диапазон: +
+
+
+ setStartDate(date)} + showTimeSelect + timeFormat="HH:mm" + timeIntervals={15} + dateFormat="yyyy-MM-dd HH:mm" + placeholderText="Начальная дата" + customInput={ + + } + /> +
+
+ setEndDate(date)} + showTimeSelect + timeFormat="HH:mm" + timeIntervals={15} + dateFormat="yyyy-MM-dd HH:mm" + placeholderText="Конечная дата" + customInput={ + + } + /> +
+ +
-
+ + {/* Индикатор текущего диапазона */} +
+ Текущий диапазон: {useCustomRange + ? `${startDate.toLocaleString()} - ${endDate.toLocaleString()}` + : selectedRange.label} +
+ + {/* График */} { @@ -40,11 +40,17 @@ const FlowChart = ({ data }) => { setNodes(initialNodes); setEdges(initialEdges); + // Автоматически сворачиваем узлы, которые являются родителями последнего уровня if (!initialized.current && data) { const findAndCollapseLastLevelParents = (items) => { items.forEach(item => { - if (item.items?.length > 0) { - const hasGrandchildren = item.items.some(child => child.items?.length > 0); + if (item.items && item.items.length > 0) { + // Проверяем, есть ли у детей свои дети + const hasGrandchildren = item.items.some(child => + child.items && child.items.length > 0 + ); + + // Если у детей нет своих детей - это родители последнего уровня if (!hasGrandchildren) { toggleNodeCollapse(item.id); } else { @@ -53,6 +59,7 @@ const FlowChart = ({ data }) => { } }); }; + findAndCollapseLastLevelParents(data.items || []); initialized.current = true; } @@ -65,7 +72,9 @@ const FlowChart = ({ data }) => { }; useEffect(() => { - return () => debouncedSetNodePositions.cancel(); + return () => { + debouncedSetNodePositions.cancel(); + }; }, [debouncedSetNodePositions]); return ( diff --git a/src/Components/TreeChart/FlowChartComponents/DataParser.jsx b/src/Components/TreeChart/FlowChartComponents/DataParser.jsx index 658f1ea..916bc97 100644 --- a/src/Components/TreeChart/FlowChartComponents/DataParser.jsx +++ b/src/Components/TreeChart/FlowChartComponents/DataParser.jsx @@ -1,7 +1,34 @@ import { useCallback } from 'react'; import { isLeafNode } from './nodeUtils'; +import { getStatusColor } from '../dataUtils'; export const useDataParser = (nodePositions, collapsedNodes) => { + const getNodeStyle = useCallback((item, isLeaf) => ({ + width: isLeaf ? 60 : 70, + height: isLeaf ? 60 : 70, + borderRadius: '50%', + backgroundColor: getStatusColor(item.status), + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + color: 'black', + border: '2px solid #fff', + fontSize: isLeaf ? '0.8rem' : '1rem' + }), []); + + const getCenterNodeStyle = useCallback((item) => ({ + width: 80, + height: 80, + borderRadius: '50%', + backgroundColor: getStatusColor(item.status), + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + color: 'black', + border: '2px solid #fff', + fontSize: '1.2rem' + }), []); + const parseData = useCallback((data) => { if (!data) return { nodes: [], edges: [] }; @@ -12,7 +39,7 @@ export const useDataParser = (nodePositions, collapsedNodes) => { const baseLevelRadius = 150; const traverse = (item, parentId = null, level = 0, angleStart = 0, angleEnd = 2 * Math.PI, parentRadius = 0) => { - if (!item || collapsedNodes[parentId]) return; + if (!item || collapsedNodes[parentId]) return; // Пропускаем свёрнутые узлы const nodeId = item.id; const items = item.items || []; @@ -26,15 +53,14 @@ export const useDataParser = (nodePositions, collapsedNodes) => { const node = { id: nodeId, + type: 'customNode', position, - type: 'customNode', // Важно для кастомного рендеринга data: { + ...item, label: item.title, - status: item.status, + style: getNodeStyle(item, isLeaf), // Переносим стили в data hasChildren: items.length > 0, - collapsed: collapsedNodes[nodeId], - isLeaf, - isCenterNode: parentId === null // Центральный узел + collapsed: collapsedNodes[nodeId] } }; @@ -45,16 +71,14 @@ export const useDataParser = (nodePositions, collapsedNodes) => { id: `${parentId}-${nodeId}`, source: parentId, target: nodeId, - style: { - stroke: isLeaf ? '#aaa' : '#666', - strokeWidth: isLeaf ? 1 : 2 - } + style: { stroke: isLeaf ? '#aaa' : '#666', strokeWidth: isLeaf ? 1 : 2 } }); } if (!collapsedNodes[nodeId] && items.length > 0) { const spreadAngle = angleEnd - angleStart; items.forEach((child, index) => { + if (!child) return; const itemAngleStart = angleStart + (index / items.length) * spreadAngle; const itemAngleEnd = angleStart + ((index + 1) / items.length) * spreadAngle; traverse(child, nodeId, level + 1, itemAngleStart, itemAngleEnd, parentRadius + baseLevelRadius); @@ -62,33 +86,27 @@ export const useDataParser = (nodePositions, collapsedNodes) => { } }; - // Центральный узел const centerNode = { id: data.id, + type: 'customNode', // Добавляем тип узла position: nodePositions[data.id] || { x: centerX, y: centerY }, - type: 'customNode', - data: { - label: data.title, - status: data.status, - hasChildren: data.items.length > 0, - collapsed: collapsedNodes[data.id], - isLeaf: false, - isCenterNode: true - } + style: getCenterNodeStyle(data), + data: { label: data.title, hasChildren: data.items.length > 0, collapsed: collapsedNodes[data.id] } }; + nodes.push(centerNode); - // Обработка дочерних узлов if (!collapsedNodes[data.id] && data.items.length > 0) { const angleStep = (2 * Math.PI) / data.items.length; data.items.forEach((child, index) => { + if (!child) return; traverse(child, data.id, 1, index * angleStep, (index + 1) * angleStep, 0); }); } return { nodes, edges }; - }, [nodePositions, collapsedNodes]); + }, [nodePositions, collapsedNodes, getNodeStyle, getCenterNodeStyle]); return { parseData }; }; \ No newline at end of file diff --git a/src/Components/TreeChart/FlowChartComponents/NodeWrapper.jsx b/src/Components/TreeChart/FlowChartComponents/NodeWrapper.jsx index 7ee61a0..d64d391 100644 --- a/src/Components/TreeChart/FlowChartComponents/NodeWrapper.jsx +++ b/src/Components/TreeChart/FlowChartComponents/NodeWrapper.jsx @@ -1,63 +1,63 @@ -import React from 'react'; -import { getStatusColor } from '../dataUtils'; - -const NodeWrapper = ({ id, data, selected }) => { - // Параметры стиля - const size = data.isCenterNode ? 80 : (data.isLeaf ? 60 : 70); - const fontSize = data.isCenterNode ? '1.2rem' : (data.isLeaf ? '0.8rem' : '1rem'); - const backgroundColor = getStatusColor(data.status); - - // Базовый стиль узла - const nodeStyle = { - width: size, - height: size, - borderRadius: '50%', - backgroundColor, - border: `2px solid ${selected ? '#1890ff' : '#fff'}`, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - fontSize, - color: '#000', - position: 'relative', - boxShadow: selected ? '0 0 8px rgba(24, 144, 255, 0.5)' : 'none', - transition: 'all 0.2s ease' - }; +import React, { memo } from 'react'; +import { Handle } from 'reactflow'; +const NodeWrapper = memo(({ id, data, selected }) => { return ( -
+
+ {/* Хендл для входящих соединений */} + + + {/* Обёртка для текста с ограничением ширины */}
{data.label}
{data.hasChildren && ( -
{data.collapsed ? '+' : '-'} -
+ )} + + {/* Хендл для исходящих соединений */} +
); -}; +}); -export default React.memo(NodeWrapper); \ No newline at end of file +export default NodeWrapper; \ No newline at end of file diff --git a/src/Components/UI/LoginModal.jsx b/src/Components/UI/LoginModal.jsx index 61c154d..1068d55 100755 --- a/src/Components/UI/LoginModal.jsx +++ b/src/Components/UI/LoginModal.jsx @@ -17,7 +17,6 @@ const LoginModal = ({ onLogin, onClose }) => { try { // Отправляем данные на бэкенд - console.log("Отправляем данные:", { username, password }); const response = await fetch('http://192.168.2.39:3000/auth/login', { method: 'POST', headers: { diff --git a/src/Style/DatePicker.css b/src/Style/DatePicker.css deleted file mode 100755 index e69de29..0000000 diff --git a/src/Style/range-selector.css b/src/Style/range-selector.css new file mode 100644 index 0000000..9a8fc73 --- /dev/null +++ b/src/Style/range-selector.css @@ -0,0 +1,54 @@ +.range-selector { + display: flex; + flex-direction: column; + gap: 10px; + padding: 15px; + border: 1px solid #ccc; + border-radius: 8px; + background-color: #f9f9f9; + max-width: 500px; +} + +.range-selector label { + font-weight: bold; +} + +.range-selector select, +.range-selector button { + padding: 8px; + border: 1px solid #aaa; + border-radius: 5px; + background-color: white; + cursor: pointer; +} + +.custom-range { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: space-between; + align-items: center; +} + +.custom-range div { + display: flex; + flex-direction: column; +} + +.date-picker { + width: 180px; +} + +.apply-button { + background-color: #007bff; + color: white; + padding: 8px 12px; + border: none; + border-radius: 5px; + cursor: pointer; + margin-top: 5px; +} + +.apply-button:hover { + background-color: #0056b3; +}