From 5ebebdc00a24b147916b4bd01938a92beb733e7f Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 12 Mar 2025 10:07:28 -0400 Subject: [PATCH 1/3] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D1=83=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Charts/NegativeStatusChart.jsx | 30 ++++ src/Charts/PrometheusChart.jsx | 44 +++++- src/Charts/SystemStatusChart.jsx | 27 ++++ src/Components/Layout/Dashboard.jsx | 22 ++- src/Components/TreeChart/dataUtils.jsx | 57 +++++--- src/Components/TreeChart/menuData.json | 21 ++- src/Components/UI/Modal.jsx | 1 - src/Components/UI/TreeTable.jsx | 194 +++++++++++++++++-------- src/Components/UI/TreeTable3.jsx | 88 +++++++++++ src/Style/Dashboard.css | 58 ++------ src/Style/DatePicker.css | 129 ---------------- src/Style/LoginModal.css | 13 +- src/Style/SidebarMenu.css | 75 ++++------ src/Style/TreeTable.css | 82 +++++------ src/Style/common.css | 37 ++--- src/Style/dark-theme.css | 19 +++ src/Style/light-theme.css | 17 +++ src/main.jsx | 2 + 18 files changed, 528 insertions(+), 388 deletions(-) create mode 100644 src/Charts/NegativeStatusChart.jsx create mode 100644 src/Charts/SystemStatusChart.jsx create mode 100644 src/Components/UI/TreeTable3.jsx create mode 100644 src/Style/dark-theme.css create mode 100644 src/Style/light-theme.css diff --git a/src/Charts/NegativeStatusChart.jsx b/src/Charts/NegativeStatusChart.jsx new file mode 100644 index 0000000..d142ded --- /dev/null +++ b/src/Charts/NegativeStatusChart.jsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; + +const NegativeStatusChart = ({ data }) => { + // Подсчет количества негативных статусов + const processedData = data.map(entry => ({ + time: entry.time, + negativeCount: entry.statuses.filter(status => ['yellow', 'orange', 'red'].includes(status)).length + })); + + return ( + + + + + + + + + + + ); +}; + +export default NegativeStatusChart; diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index 1a0d741..81d42ab 100644 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -34,6 +34,45 @@ const PrometheusChart = ({ metricName }) => { const [filteredData, setFilteredData] = useState(null); // Отфильтрованные данные const intervalRef = useRef(null); + // Функция для интерполяции данных + const interpolateData = (data, minPoints = 15) => { + if (data.length >= minPoints) return data; + + const interpolatedData = []; + for (let i = 0; i < data.length - 1; i++) { + interpolatedData.push(data[i]); + + const currentPoint = data[i]; + const nextPoint = data[i + 1]; + + // Вычисляем разницу во времени между точками + const currentTime = new Date(currentPoint.time).getTime(); + const nextTime = new Date(nextPoint.time).getTime(); + const timeDiff = nextTime - currentTime; + + // Добавляем промежуточные точки + const steps = Math.ceil((minPoints - data.length) / (data.length - 1)); + for (let j = 1; j <= steps; j++) { + const interpolatedTime = new Date(currentTime + (timeDiff * j) / (steps + 1)).toLocaleString(); + const interpolatedPoint = { time: interpolatedTime }; + + // Интерполируем значения для каждой метрики + Object.keys(currentPoint).forEach(key => { + if (key !== 'time') { + const currentValue = currentPoint[key]; + const nextValue = nextPoint[key]; + interpolatedPoint[key] = currentValue + ((nextValue - currentValue) * j) / (steps + 1); + } + }); + + interpolatedData.push(interpolatedPoint); + } + } + + interpolatedData.push(data[data.length - 1]); // Добавляем последнюю точку + return interpolatedData.slice(0, minPoints); // Обрезаем до minPoints + }; + const fetchData = async () => { try { let start, end; @@ -147,7 +186,10 @@ const PrometheusChart = ({ metricName }) => { }); const filtered = data.slice(startIndex, endIndex + 1); - setFilteredData(filtered); // Сохраняем отфильтрованные данные + + // Интерполируем данные, если точек меньше 15 + const interpolated = interpolateData(filtered, 15); + setFilteredData(interpolated); // Сохраняем интерполированные данные } else { setFilteredData(null); // Сбрасываем фильтрацию, если диапазон не выбран } diff --git a/src/Charts/SystemStatusChart.jsx b/src/Charts/SystemStatusChart.jsx new file mode 100644 index 0000000..0ee7531 --- /dev/null +++ b/src/Charts/SystemStatusChart.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; + +const SystemStatusChart = ({ data }) => { + // Обрезаем массив, оставляя только последние 20 точек + const trimmedData = data.slice(-20); + + return ( + + + + + + + + + + + ); +}; + +export default SystemStatusChart; \ No newline at end of file diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx index 040dff0..415d68a 100644 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from "react"; import SidebarMenu from "./SidebarMenu"; import TreeChart from "../TreeChart/TreeChart"; import "../../Style/Dashboard.css"; -import ErrorIndicator from "../UI/ErrorIndicator"; +import SystemStatusChart from "../../Charts/SystemStatusChart"; import Tabs from "../UI/Tabs"; import menuData from "../TreeChart/menuData.json"; // Импортируем JSON-данные import TreeTable from "../UI/TreeTable"; @@ -16,6 +16,7 @@ const Dashboard = () => { const [treeData, setTreeData] = useState(menuData); // Загружаем меню в state const [sidebarWidth, setSidebarWidth] = useState(250); // Начальная ширина сайдбара const [isResizing, setIsResizing] = useState(false); // Состояние перетаскивания + const [statusHistory, setStatusHistory] = useState([]); // История статусов для графика const sidebarRef = useRef(null); // Референс на сайдбар // Генерация контента для вкладок на основе menuData @@ -29,7 +30,17 @@ const Dashboard = () => { const interval = setInterval(() => { setTreeData((prevData) => { const updatedData = JSON.parse(JSON.stringify(prevData)); // Клонируем данные - updateStatuses(updatedData); // Обновляем статусы + const averageStatusValue = updateStatuses(updatedData); // Обновляем статусы и получаем среднее значение + + // Преобразуем среднее значение в проценты (0% - 100%) + const statusPercentage = (1 - (averageStatusValue / 3)) * 100; + + // Добавляем новое состояние в историю + setStatusHistory((prevHistory) => [ + ...prevHistory, + { time: new Date().toLocaleTimeString(), status: statusPercentage } + ]); + return updatedData; }); }, 30000); @@ -93,8 +104,11 @@ const Dashboard = () => { if (activeTab === "Главная") { return (
-

Общий мониторинг

- {/* Используем актуальные данные */} +

Общий мониторинг состояния системы

+ + {/* График состояния системы */} + + {/* Используем актуальные данные */}
); } else if (activeTab === "Визуализация") { diff --git a/src/Components/TreeChart/dataUtils.jsx b/src/Components/TreeChart/dataUtils.jsx index fa67130..7ee90e0 100644 --- a/src/Components/TreeChart/dataUtils.jsx +++ b/src/Components/TreeChart/dataUtils.jsx @@ -1,37 +1,56 @@ // Функция для генерации случайных статусов const getRandomStatus = () => { const statuses = [ - ...Array(90).fill("green"), // 63/70 chance (примерно 90%) - ...Array(6).fill("yellow"), // 1/70 chance (примерно 1.43%) - ...Array(3).fill("orange"), // 1/70 chance (примерно 1.43%) - ...Array(1).fill("red"), // 1/70 chance (примерно 1.43%) + ...Array(90).fill("green"), // 90% chance + ...Array(6).fill("yellow"), // 6% chance + ...Array(3).fill("orange"), // 3% chance + ...Array(1).fill("red"), // 1% chance ]; return statuses[Math.floor(Math.random() * statuses.length)]; }; +// Функция для получения числового значения статуса +const getStatusValue = (status) => { + switch (status) { + case "green": + return 0; + case "yellow": + return 1; + case "orange": + return 2; + case "red": + return 3; + default: + return 0; // По умолчанию green + } +}; + +// Функция для получения статуса по числовому значению +const getStatusFromValue = (value) => { + if (value >= 3) return "red"; + if (value >= 2) return "orange"; + if (value >= 1) return "yellow"; + return "green"; +}; + // Функция для обновления статусов в дереве const updateStatuses = (data) => { if (!data.items || data.items.length === 0) { // Если это элемент нижнего уровня, генерируем случайный статус data.status = getRandomStatus(); - return data.status; + return getStatusValue(data.status); } // Рекурсивно обновляем статусы для всех дочерних элементов - let childStatuses = data.items.map((child) => updateStatuses(child)); + let childStatusValues = data.items.map((child) => updateStatuses(child)); - // Определяем статус текущего элемента на основе статусов дочерних элементов - if (childStatuses.includes("red")) { - data.status = "red"; - } else if (childStatuses.includes("orange")) { - data.status = "orange"; - } else if (childStatuses.includes("yellow")) { - data.status = "yellow"; - } else { - data.status = "green"; - } + // Вычисляем среднее арифметическое значение статусов + const averageStatusValue = childStatusValues.reduce((sum, value) => sum + value, 0) / childStatusValues.length; - return data.status; + // Определяем статус текущего элемента на основе среднего значения + data.status = getStatusFromValue(averageStatusValue); + + return getStatusValue(data.status); }; // Функция для получения цвета по статусу @@ -40,13 +59,13 @@ const getStatusColor = (status) => { case "green": return "#4CAF50"; // Зеленый case "yellow": - return "#FFEB3B"; // Желтый + return "#cebd21"; // Желтый case "orange": return "#FF9800"; // Оранжевый case "red": return "#F44336"; // Красный default: - return "#4CAF50"; // Синий (или любой другой стандартный цвет) + return "#4CAF50"; // По умолчанию зеленый } }; diff --git a/src/Components/TreeChart/menuData.json b/src/Components/TreeChart/menuData.json index 4cdcaff..35530f8 100644 --- a/src/Components/TreeChart/menuData.json +++ b/src/Components/TreeChart/menuData.json @@ -1,9 +1,10 @@ { - "title": "Сервер ЗВКС", + "title": "Сервис ЗВКС", "id": "1", "items": [ { "title": "Функциональные задачи", + "id": "functional_tasks", "items": [ { "id": "system_control", @@ -451,9 +452,11 @@ }, { "title": "Медиа сервер", + "id": "media_server_1", "items": [ { "title": "Аппаратное обеспечение", + "id": "system_software_1", "items": [ { "id": "media_system_software_1_2", @@ -475,6 +478,7 @@ }, { "title": "Программное обеспечение", + "id": "software_1", "items": [ { "id": "media_software_1_2", @@ -498,9 +502,11 @@ }, { "title": "Медиа сервер", + "id": "media_server_2", "items": [ { "title": "Аппаратное обеспечение", + "id": "system_software_2", "items": [ { "id": "media_system_software_1_3", @@ -522,6 +528,7 @@ }, { "title": "Программное обеспечение", + "id": "software_2", "items": [ { "id": "media_software_1_3", @@ -545,9 +552,11 @@ }, { "title": "Медиа сервер", + "id": "media_server_3", "items": [ { "title": "Аппаратное обеспечение", + "id": "system_software_3", "items": [ { "id": "media_system_software_1_4", @@ -569,6 +578,7 @@ }, { "title": "Программное обеспечение", + "id": "software_3", "items": [ { "id": "media_software_1_4", @@ -592,9 +602,11 @@ }, { "title": "Медиа сервер", + "id": "media_server_4", "items": [ { "title": "Аппаратное обеспечение", + "id": "system_software_4", "items": [ { "id": "media_system_software_1_5", @@ -616,6 +628,7 @@ }, { "title": "Программное обеспечение", + "id": "software_4", "items": [ { "id": "media_software_1_5", @@ -639,9 +652,11 @@ }, { "title": "Сервер систем", + "id": "system_server_1", "items": [ { "title": "Аппаратное обеспечение", + "id": "system_software_5", "items": [ { "id": "copy_system_software_1", @@ -663,6 +678,7 @@ }, { "title": "Программное обеспечение", + "id": "software_5", "items": [ { "id": "copy_software_1", @@ -686,9 +702,11 @@ }, { "title": "Сервер систем", + "id": "system_server_2", "items": [ { "title": "Аппаратное обеспечение", + "id": "system_software_6", "items": [ { "id": "control_system_software_1", @@ -710,6 +728,7 @@ }, { "title": "Программное обеспечение", + "id": "software_6", "items": [ { "id": "control_software_1", diff --git a/src/Components/UI/Modal.jsx b/src/Components/UI/Modal.jsx index 3718035..9ad4753 100644 --- a/src/Components/UI/Modal.jsx +++ b/src/Components/UI/Modal.jsx @@ -5,7 +5,6 @@ const Modal = ({ children, onClose }) => {
{children} -
); diff --git a/src/Components/UI/TreeTable.jsx b/src/Components/UI/TreeTable.jsx index 28b2105..235ac1d 100644 --- a/src/Components/UI/TreeTable.jsx +++ b/src/Components/UI/TreeTable.jsx @@ -1,76 +1,146 @@ -import React from "react"; +import React, { useEffect, useRef, useState } from "react"; import "../../Style/TreeTable.css"; -import { getStatusColor } from "../TreeChart/dataUtils"; // Импортируем функцию +import { getStatusColor } from "../TreeChart/dataUtils"; const TreeTable = ({ data }) => { - // Фильтруем данные, чтобы убрать "Функциональные задачи" - const filteredData = data.filter((item) => item.title !== "Функциональные задачи"); + const tableRef = useRef(null); + const [fontSize, setFontSize] = useState(16); + const [log, setLog] = useState([]); + const [isLogVisible, setIsLogVisible] = useState(true); + + const adjustFontSize = () => { + if (tableRef.current) { + let newSize = 16; + const maxWidth = window.innerWidth; + + while (tableRef.current.scrollWidth > maxWidth && newSize > 10) { + newSize -= 1; + tableRef.current.style.fontSize = `${newSize}px`; + } + + while (tableRef.current.scrollWidth < maxWidth && newSize < 16) { + newSize += 1; + tableRef.current.style.fontSize = `${newSize}px`; + } + + setFontSize(newSize); + } + }; + + useEffect(() => { + adjustFontSize(); + window.addEventListener("resize", adjustFontSize); + return () => window.removeEventListener("resize", adjustFontSize); + }, [data]); + + useEffect(() => { + const newLog = []; + const traverse = (items) => { + items.forEach((item) => { + if (["yellow", "orange", "red"].includes(item.status)) { + newLog.push({ + title: item.title, + status: item.status, + time: new Date().toLocaleTimeString() // Добавляем время + }); + } + if (item.items) { + traverse(item.items); + } + }); + }; + traverse(data.items); + setLog(newLog); + }, [data]); + + const filteredData = data.items.filter((item) => item.title !== "Функциональные задачи"); + + const renderHeaders = (items) => { + return items.map((item) => { + const colSpan = item.items ? item.items.length : 1; + return ( + +
+
+ {item.title} +
+ + ); + }); + }; + + const renderRows = (items) => { + if (!items || items.length === 0) return null; + const hasChildren = items.some((item) => item.items && item.items.length > 0); + if (!hasChildren) return null; + return ( + + {items.map((item) => { + if (item.items && item.items.length > 0) { + return ( + + {item.items.map((child) => ( + +
+
+ {child.title} +
+ + ))} + + ); + } else { + return ( + +
+
+ {item.title} +
+ + ); + } + })} + + ); + }; return ( -
- +
+
- {/* Первый уровень: Заголовки "Медиа сервер" */} - {filteredData.map((item, index) => ( - - ))} - - {/* Второй уровень: "АО" и "ПО" */} - - {filteredData.map((item, index) => ( - - - - - ))} + + {renderHeaders(filteredData)} - - {/* Третий уровень: Вложенные элементы "АО" и "ПО" */} - {renderRows(filteredData)} - + {renderRows(filteredData)}
- {item.title} -
- АО - - ПО - acc + (item.items ? item.items.length : 1), 0)} + className="tree-table-header" + title={data.title} + > +
+
+ {data.title} +
+
+ + {isLogVisible && ( +
+

Лог статусов

+
    + {log.map((entry, index) => ( +
  • + [{entry.time}] {entry.status}: {entry.title} +
  • + ))} +
+
+ )}
); }; -// Функция для отображения строк с вложенными элементами -const renderRows = (data) => { - const rows = []; - - // Находим максимальное количество элементов среди всех "АО" и "ПО" - const maxItems = Math.max( - ...data.flatMap((item) => [ - item.items[0]?.items?.length || 0, // АО - item.items[1]?.items?.length || 0 // ПО - ]) - ); - - // Генерируем строки - for (let i = 0; i < maxItems; i++) { - rows.push( - - {data.map((item, index) => ( - - - {item.items[0]?.items[i]?.title || ""} - - - {item.items[1]?.items[i]?.title || ""} - - - ))} - - ); - } - - return rows; -}; -export default TreeTable; \ No newline at end of file +export default TreeTable; diff --git a/src/Components/UI/TreeTable3.jsx b/src/Components/UI/TreeTable3.jsx new file mode 100644 index 0000000..ced08c1 --- /dev/null +++ b/src/Components/UI/TreeTable3.jsx @@ -0,0 +1,88 @@ +import React from "react"; +import "../../Style/TreeTable.css"; +import { getStatusColor } from "../TreeChart/dataUtils"; // Импортируем функцию + +const TreeTable = ({ data }) => { + // Проверяем, что data существует и имеет нужную структуру + if (!data || !data.items) { + return
Данные не загружены или имеют неверный формат
; + } + + // Фильтруем данные, чтобы убрать "Функциональные задачи" + const filteredData = data.items.filter((item) => item.title !== "Функциональные задачи"); + + return ( +
+ + + {/* Первый уровень: Название сервера */} + + + + {/* Второй уровень: Заголовки устройств */} + + {filteredData.map((item, index) => ( + + ))} + + {/* Третий уровень: Подзаголовки "АО" и "ПО" */} + + {filteredData.map((item, index) => ( + + + + + ))} + + + + {/* Четвертый уровень: Данные "АО" и "ПО" */} + {renderRows(filteredData)} + +
+ {data.title} +
+ {item.title} +
+ {item.items[0]?.title || "Нет данных"} + + {item.items[1]?.title || "Нет данных"} +
+
+ ); +}; + +// Функция для отображения строк с вложенными элементами +const renderRows = (data) => { + const rows = []; + + // Находим максимальное количество элементов среди всех "АО" и "ПО" + const maxItems = Math.max( + ...data.flatMap((item) => [ + item.items[0]?.items?.length || 0, // АО + item.items[1]?.items?.length || 0 // ПО + ]) + ); + + // Генерируем строки + for (let i = 0; i < maxItems; i++) { + rows.push( + + {data.map((item, index) => ( + + + {item.items[0]?.items[i]?.title || ""} + + + {item.items[1]?.items[i]?.title || ""} + + + ))} + + ); + } + + return rows; +}; + +export default TreeTable; \ No newline at end of file diff --git a/src/Style/Dashboard.css b/src/Style/Dashboard.css index 537a8ca..3e1be18 100644 --- a/src/Style/Dashboard.css +++ b/src/Style/Dashboard.css @@ -2,46 +2,11 @@ .dashboard-container { display: flex; height: 100vh; - width: calc(100vw - 20px); /* Учитываем отступ */ + width: calc(100vw - 20px); overflow: hidden; margin-left: 20px; -} - -/* Сайдбар */ -.sidebar { - height: 100vh; - background-color: #3d74c7; - color: white; - position: fixed; - left: 0; - top: 0; - z-index: 999; - overflow: hidden; - transition: width 0.2s ease; - /* Плавное изменение ширины */ - display: flex; - flex-direction: column; -} - -/* Элемент для перетаскивания */ -.sidebar-resizer { - width: 5px; - /* Ширина элемента перетаскивания */ - height: 100%; - background-color: rgba(255, 255, 255, 0.1); - position: absolute; - right: 0; - top: 0; - cursor: ew-resize; - /* Курсор "изменить размер" */ - transition: background-color 0.2s ease; - z-index: 1000; - /* Убедимся, что элемент поверх других */ -} - -.sidebar-resizer:hover { - background-color: rgba(255, 255, 255, 0.3); - /* Эффект при наведении */ + background-color: var(--background-color); + color: var(--text-color); } /* Основной контент */ @@ -50,24 +15,27 @@ padding: 20px; margin-left: 50px; transition: margin-left 0.2s ease; - overflow: auto; /* Позволяет прокручивать контент, если он не влезает */ + overflow: auto; + background-color: var(--background-color); + color: var(--text-color); } /* Контент */ .content { - background-color: #f9f9f9; + background-color: var(--modal-background); padding: 20px; border-radius: 10px; - box-shadow: 0 2px 5px rgba(29, 1, 1, 0.521); - max-width: 100%; /* Гарантируем, что контент не выйдет за границы */ - overflow: auto; /* Включаем скролл, если нужно */ + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.521); + max-width: 100%; + overflow: auto; + color: var(--text-color); } /* Заголовки */ h2 { - color: #444; + color: var(--text-color); } p { - color: #333; + color: var(--text-color); } \ No newline at end of file diff --git a/src/Style/DatePicker.css b/src/Style/DatePicker.css index 3b564d1..e69de29 100644 --- a/src/Style/DatePicker.css +++ b/src/Style/DatePicker.css @@ -1,129 +0,0 @@ -/* DatePicker.css */ - -.react-datepicker-wrapper { - width: auto; - display: inline-block; -} - -.react-datepicker__input-container input { - width: 200px; - padding: 8px 12px; - border: 1px solid #ddd; - border-radius: 6px; - font-size: 14px; - color: #333; - background-color: #fff; - transition: border-color 0.2s ease; -} - -.react-datepicker__input-container input:focus { - border-color: #0078d4; - outline: none; -} - -.react-datepicker { - font-family: 'Segoe UI', sans-serif; - border: 1px solid #e0e0e0; - border-radius: 8px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - background-color: #fff; - /* Непрозрачный фон */ - z-index: 1000; - /* Календарь поверх других элементов */ -} - -.react-datepicker-popper { - z-index: 1000; - /* Календарь поверх других элементов */ - pointer-events: auto; - /* Разрешить взаимодействие только с календарем */ -} - -.react-datepicker__header { - background-color: #f8f8f8; - /* Непрозрачный фон заголовка */ - border-bottom: 1px solid #e0e0e0; - border-radius: 8px 8px 0 0; - padding: 12px; -} - -.react-datepicker__current-month { - font-size: 14px; - font-weight: 600; - color: #333; -} - -.react-datepicker__navigation { - top: 12px; - border: 0.45rem solid transparent; -} - -.react-datepicker__navigation--previous { - left: 12px; - border-right-color: #666; -} - -.react-datepicker__navigation--next { - right: 12px; - border-left-color: #666; -} - -.react-datepicker__day-names { - display: flex; - justify-content: space-between; - padding: 0 8px; - margin-top: 8px; -} - -.react-datepicker__day-name { - width: 28px; - line-height: 28px; - text-align: center; - font-size: 12px; - color: #666; -} - -.react-datepicker__month { - background-color: #fff; - /* Непрозрачный фон месяца */ - margin: 0; - padding: 8px; -} - -.react-datepicker__week { - display: flex; - justify-content: space-between; -} - -.react-datepicker__day { - width: 28px; - line-height: 28px; - text-align: center; - font-size: 12px; - color: #333; - cursor: pointer; - border-radius: 50%; - transition: background-color 0.2s ease, color 0.2s ease; -} - -.react-datepicker__day:hover { - background-color: #f0f0f0; -} - -.react-datepicker__day--selected { - background-color: #0078d4; - color: #fff; -} - -.react-datepicker__day--selected:hover { - background-color: #005bb5; -} - -.react-datepicker__day--outside-month { - color: #ccc; -} - -.react-datepicker__day--disabled { - color: #ccc; - cursor: not-allowed; -} \ No newline at end of file diff --git a/src/Style/LoginModal.css b/src/Style/LoginModal.css index ae99fe0..efd544a 100644 --- a/src/Style/LoginModal.css +++ b/src/Style/LoginModal.css @@ -11,12 +11,13 @@ } .modal { - background: rgb(255, 255, 255); + background: var(--modal-background); padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); max-width: 400px; width: 100%; + color: var(--modal-text); } .modal h2 { @@ -26,7 +27,7 @@ .modal label { display: block; margin-bottom: 5px; - color: black; + color: var(--modal-text); } .modal input { @@ -35,20 +36,22 @@ margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px; + background-color: var(--modal-background); + color: var(--modal-text); } .modal button { padding: 10px 20px; margin-bottom: 5px; - background: #08294b; - color: white; + background: var(--accent-color); + color: var(--text-color); border: none; border-radius: 4px; cursor: pointer; } .modal button:hover { - background: #0056b3; + background: var(--accent-hover-color); } .error { diff --git a/src/Style/SidebarMenu.css b/src/Style/SidebarMenu.css index 9fcb8d6..bbfc484 100644 --- a/src/Style/SidebarMenu.css +++ b/src/Style/SidebarMenu.css @@ -1,15 +1,15 @@ -/* Боковое меню */ +/* Сайдбар */ .sidebar { height: 100vh; - background-color: #3d74c7; - color: white; + background-color: var(--sidebar-color); + color: var(--sidebar-text-color); + /* Используем переменную для цвета текста */ position: fixed; left: 0; top: 0; z-index: 999; overflow: hidden; transition: width 0.2s ease; - /* Плавное изменение ширины */ display: flex; flex-direction: column; } @@ -18,11 +18,8 @@ .sidebar-content { flex: 1; overflow-y: auto; - /* Вертикальная прокрутка */ overflow-x: hidden; - /* Убираем горизонтальную прокрутку */ padding-bottom: 20px; - /* Отступ для "Помощи" и "Настроек" */ } /* Заголовок меню */ @@ -30,23 +27,39 @@ margin-bottom: 20px; font-size: 18px; font-weight: bold; - color: white; + color: var(--sidebar-text-color); + /* Используем переменную для цвета текста */ padding: 10px; - /* Добавляем отступы */ } /* Элементы меню */ .menu-item { margin-bottom: 10px; - color: white; + color: var(--sidebar-text-color); + /* Используем переменную для цвета текста */ width: 100%; - /* Ширина на всю ширину сайдбара */ +} + +/* Элемент для перетаскивания */ +.sidebar-resizer { + width: 5px; + height: 100%; + background-color: rgba(255, 255, 255, 0.1); + position: absolute; + right: 0; + top: 0; + cursor: ew-resize; + transition: background-color 0.2s ease; + z-index: 1000; +} + +.sidebar-resizer:hover { + background-color: rgba(255, 255, 255, 0.3); } .menu-item-header { display: flex; align-items: center; - /* Выравниваем элементы по центру */ padding: 10px; border-radius: 5px; cursor: pointer; @@ -55,7 +68,6 @@ .menu-item-header:hover { background-color: rgba(255, 255, 255, 0.1); - /* Легкий эффект при наведении */ } /* Круглый индикатор статуса */ @@ -63,35 +75,8 @@ width: 10px; height: 10px; border-radius: 50%; - /* Делаем круглым */ margin-right: 10px; - /* Отступ от текста */ flex-shrink: 0; - /* Запрещаем сжатие */ -} - -/* Анимация мигания для красного индикатора */ -@keyframes blink { - 0% { - opacity: 1; - } - - /* Полная видимость */ - 50% { - opacity: 0.3; - } - - /* Полупрозрачность */ - 100% { - opacity: 1; - } - - /* Полная видимость */ -} - -.status-indicator.blinking { - animation: blink 1s infinite; - /* Бесконечная анимация с интервалом 1 секунда */ } /* Подменю */ @@ -103,22 +88,18 @@ /* Футер сайдбара */ .sidebar-footer { padding: 10px; - background-color: #3d74c7; + background-color: var(--sidebar-color); text-align: center; border-top: 1px solid rgba(255, 255, 255, 0.1); - /* Разделительная линия */ flex-shrink: 0; - /* Запрещаем сжатие */ width: 100%; - /* Ширина на всю ширину сайдбара */ } .help, .settings { - color: white; + color: var(--sidebar-text-color); + /* Используем переменную для цвета текста */ margin: 5px 0; - /* Отступы между элементами */ overflow-x: hidden; - /* Убираем горизонтальную прокрутку */ text-align: left; } \ No newline at end of file diff --git a/src/Style/TreeTable.css b/src/Style/TreeTable.css index d2093b1..10eb027 100644 --- a/src/Style/TreeTable.css +++ b/src/Style/TreeTable.css @@ -1,58 +1,48 @@ -/* Контейнер для таблицы с прокруткой */ -.table-container { +.tree-table-container { width: 100%; - /* Занимает всю доступную ширину */ overflow-x: auto; - /* Горизонтальная прокрутка при необходимости */ - margin: 0 auto; - /* Центрирование контейнера */ } -/* Стили для таблицы */ .tree-table { - width: auto; - /* Автоматическая ширина, чтобы таблица могла расширяться */ - min-width: 95%; - /* Минимальная ширина таблицы */ + width: 100%; border-collapse: collapse; - margin: 0 auto; - /* Центрирование таблицы */ + text-align: center; + table-layout: fixed; + background-color: var(--table-cell-background); + color: var(--table-text-color); + /* Используем переменную для цвета текста */ } -/* Заголовки таблицы (первый уровень) */ -.tree-table th { - border: 1px solid #ddd; - padding: 8px; - text-align: left; - white-space: nowrap; - /* Запрет на перенос текста */ - font-weight: bold; - /* Жирный шрифт для заголовков */ -} - -/* Подзаголовки (второй уровень: "АО" и "ПО") */ -.tree-table-subheader { - font-weight: 500; - /* Жирный шрифт для подзаголовков */ -} - -/* Ячейки таблицы */ -.tree-table td { - border: 1px solid #ddd; - padding: 8px; - text-align: left; - white-space: nowrap; - /* Запрет на перенос текста */ - font-weight: normal; - /* Обычный шрифт для ячеек */ -} - -/* Цвет фона для заголовков */ .tree-table-header { - background-color: #f4f4f4; + padding: 10px; + border: 1px solid var(--table-border); + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + background-color: var(--table-header-background); } -/* Чередование цвета строк */ -.tree-table-row:nth-child(even) { - background-color: #f9f9f9; +.tree-table-cell { + padding: 8px; + border: 1px solid var(--table-border); + white-space: nowrap; + overflow: hidden; +} + +.cell-content, +.header-content { + display: flex; + align-items: center; + gap: 2px; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} + +.status-indicator-bar { + width: 6px; + height: 20px; + border-radius: 3px; + flex-shrink: 0; } \ No newline at end of file diff --git a/src/Style/common.css b/src/Style/common.css index d170447..d0184b1 100644 --- a/src/Style/common.css +++ b/src/Style/common.css @@ -1,72 +1,53 @@ -/* src/Style/common.css */ - /* Контейнер для вкладок */ .tabs { display: flex; gap: 5px; - /* Расстояние между вкладками */ padding: 5px; - background-color: #3d74c7; - /* Цвет фона */ - border-bottom: 2px solid #195fc9; - /* Линия под вкладками */ + background-color: var(--sidebar-color); + border-bottom: 2px solid var(--accent-color); overflow-x: auto; - /* Прокрутка, если вкладок много */ border-radius: 5px; - /* Скругление углов */ white-space: nowrap; - /* Запрет переноса текста */ } /* Стили для отдельной вкладки */ .tab { display: flex; align-items: center; - background-color: #3d74c7; - /* Цвет фона вкладки */ - color: white; - /* Цвет текста */ + background-color: var(--sidebar-color); + color: var(--sidebar-text-color); + /* Используем переменную для цвета текста */ padding: 5px 15px; - /* Отступы внутри вкладки */ border-radius: 5px 5px 0 0; - /* Скругление углов */ cursor: pointer; - /* Курсор при наведении */ flex-shrink: 0; - /* Запрет сжатия */ transition: background-color 0.3s ease; - /* Плавное изменение цвета */ } /* Активная вкладка */ .tab.active { - background-color: #195fc9; - /* Цвет фона активной вкладки */ + background-color: var(--accent-color); } /* Кнопка закрытия вкладки */ .close-tab { background: none; border: none; - color: white; - /* Цвет крестика */ + color: var(--sidebar-text-color); + /* Используем переменную для цвета текста */ cursor: pointer; font-size: 16px; margin-left: 10px; - /* Отступ от текста */ padding: 0; transition: color 0.3s ease; - /* Плавное изменение цвета */ } /* Эффект при наведении на кнопку закрытия */ .close-tab:hover { color: #ff6b6b; - /* Цвет крестика при наведении */ } /* Эффект при наведении на вкладку */ .tab:hover { - background-color: #195fc9; - /* Цвет фона при наведении */ + background-color: var(--accent-hover-color); } \ No newline at end of file diff --git a/src/Style/dark-theme.css b/src/Style/dark-theme.css new file mode 100644 index 0000000..16709fb --- /dev/null +++ b/src/Style/dark-theme.css @@ -0,0 +1,19 @@ +/* Темная тема, если пользователь предпочитает ее */ +@media (prefers-color-scheme: dark) { + :root { + --background-color: #1E1E1E; + --text-color: #E0E0E0; + /* Основной цвет текста (светлый) */ + --sidebar-color: #2d2d2d; + /* Темный цвет сайдбара */ + --sidebar-text-color: #E0E0E0; + /* Светлый текст в сайдбаре */ + --modal-background: #333333; + --modal-text: #FFFFFF; + --table-border: #444444; + --table-header-background: #2d2d2d; + --table-cell-background: #333333; + --table-text-color: #E0E0E0; + /* Светлый текст в таблице */ + } +} \ No newline at end of file diff --git a/src/Style/light-theme.css b/src/Style/light-theme.css new file mode 100644 index 0000000..6a43ada --- /dev/null +++ b/src/Style/light-theme.css @@ -0,0 +1,17 @@ +/* Светлая тема по умолчанию */ +:root { + --background-color: #FFFFFF; + --text-color: #333333; + /* Основной цвет текста (черный) */ + --sidebar-color: #3d74c7; + /* Синий цвет сайдбара */ + --sidebar-text-color: #FFFFFF; + /* Белый текст в сайдбаре и вкладках */ + --modal-background: #FFFFFF; + --modal-text: #333333; + --table-border: #ddd; + --table-header-background: #f9f9f9; + --table-cell-background: #FFFFFF; + --table-text-color: #000000; + /* Черный текст в таблице */ +} \ No newline at end of file diff --git a/src/main.jsx b/src/main.jsx index b9a1a6d..f802dc0 100755 --- a/src/main.jsx +++ b/src/main.jsx @@ -2,6 +2,8 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import App from './App.jsx' +import './Style/light-theme.css'; // Подключаем светлую тему по умолчанию +import './Style/dark-theme.css'; // Подключаем темную тему createRoot(document.getElementById('root')).render( From 88b63959be53cc20e534fd25004b0df160975f5f Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 12 Mar 2025 10:09:35 -0400 Subject: [PATCH 2/3] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D1=83=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Components/TreeChart/menuData2.json | 382 ----------------------- src/Components/TreeChart/tabContent2.jsx | 96 ------ src/Components/UI/TreeTable3.jsx | 88 ------ 3 files changed, 566 deletions(-) delete mode 100644 src/Components/TreeChart/menuData2.json delete mode 100644 src/Components/TreeChart/tabContent2.jsx delete mode 100644 src/Components/UI/TreeTable3.jsx diff --git a/src/Components/TreeChart/menuData2.json b/src/Components/TreeChart/menuData2.json deleted file mode 100644 index 645a8ef..0000000 --- a/src/Components/TreeChart/menuData2.json +++ /dev/null @@ -1,382 +0,0 @@ -{ - "title": "Сервис ВКС", - "id":"service_VKS", - "items": [ - { - "title": "Функциональные задачи", - "id":"functions", - "items": [ - { - "id": "system_control", - "title": "Контроль системы" - }, - { - "id": "system_management", - "title": "Система управления" - }, - { - "id": "conference", - "title": "Проведение ВКС" - }, - { - "id": "backup", - "title": "Резервное копирование" - }, - { - "id": "relay_info", - "title": "Ретрансляция информации" - } - ] - }, - { - "title": "Медиа сервер", - "id":"media_server_1", - "items": [ - { - "title": "Аппаратное обеспечение", - "id":"system_software_1", - "items": [ - { - "id": "media_system_software_1", - "title": "Центральный процессор" - }, - { - "id": "media_system_software_2", - "title": "Оперативная память" - }, - { - "id": "media_system_software_3", - "title": "Жесткий диск" - }, - { - "id": "media_system_software_4", - "title": "Сетевые адаптеры" - } - ] - }, - { - "title": "Программное обеспечение", - "id":"software_1", - "items": [ - { - "id": "media_software_1", - "title": "ПО" - }, - { - "id": "media_software_2", - "title": "ПО" - }, - { - "id": "media_software_3", - "title": "ПО" - }, - { - "id": "media_software_4", - "title": "ПО" - } - ] - } - ] - }, - { - "title": "Медиа сервер", - "id":"media_server_2", - "items": [ - { - "title": "Аппаратное обеспечение", - "id":"system_software_2", - "items": [ - { - "id": "media_system_software_1_2", - "title": "Центральный процессор" - }, - { - "id": "media_system_software_2_2", - "title": "Оперативная память" - }, - { - "id": "media_system_software_3_2", - "title": "Жесткий диск" - }, - { - "id": "media_system_software_4_2", - "title": "Сетевые адаптеры" - } - ] - }, - { - "title": "Программное обеспечение", - "id":"software_2", - "items": [ - { - "id": "media_software_1_2", - "title": "ПО" - }, - { - "id": "media_software_2_2", - "title": "ПО" - }, - { - "id": "media_software_3_2", - "title": "ПО" - }, - { - "id": "media_software_4_2", - "title": "ПО" - } - ] - } - ] - }, - { - "title": "Медиа сервер", - "id":"media_server_3", - "items": [ - { - "title": "Аппаратное обеспечение", - "id":"system_software_3", - "items": [ - { - "id": "media_system_software_1_3", - "title": "Центральный процессор" - }, - { - "id": "media_system_software_2_3", - "title": "Оперативная память" - }, - { - "id": "media_system_software_3_3", - "title": "Жесткий диск" - }, - { - "id": "media_system_software_4_3", - "title": "Сетевые адаптеры" - } - ] - }, - { - "title": "Программное обеспечение", - "id":"software_3", - "items": [ - { - "id": "media_software_1_3", - "title": "ПО" - }, - { - "id": "media_software_2_3", - "title": "ПО" - }, - { - "id": "media_software_3_3", - "title": "ПО" - }, - { - "id": "media_software_4_3", - "title": "ПО" - } - ] - } - ] - }, - { - "title": "Медиа сервер", - "id":"media_server_4", - "items": [ - { - "title": "Аппаратное обеспечение", - "id":"system_software_4", - "items": [ - { - "id": "media_system_software_1_4", - "title": "Центральный процессор" - }, - { - "id": "media_system_software_2_4", - "title": "Оперативная память" - }, - { - "id": "media_system_software_3_4", - "title": "Жесткий диск" - }, - { - "id": "media_system_software_4_4", - "title": "Сетевые адаптеры" - } - ] - }, - { - "title": "Программное обеспечение", - "id":"software_4", - "items": [ - { - "id": "media_software_1_4", - "title": "ПО" - }, - { - "id": "media_software_2_4", - "title": "ПО" - }, - { - "id": "media_software_3_4", - "title": "ПО" - }, - { - "id": "media_software_4_4", - "title": "ПО" - } - ] - } - ] - }, - { - "title": "Медиа сервер", - "id":"media_server_5", - "items": [ - { - "title": "Аппаратное обеспечение", - "id":"system_software_5", - "items": [ - { - "id": "media_system_software_1_5", - "title": "Центральный процессор" - }, - { - "id": "media_system_software_2_5", - "title": "Оперативная память" - }, - { - "id": "media_system_software_3_5", - "title": "Жесткий диск" - }, - { - "id": "media_system_software_4_5", - "title": "Сетевые адаптеры" - } - ] - }, - { - "title": "Программное обеспечение", - "id":"software_5", - "items": [ - { - "id": "media_software_1_5", - "title": "ПО" - }, - { - "id": "media_software_2_5", - "title": "ПО" - }, - { - "id": "media_software_3_5", - "title": "ПО" - }, - { - "id": "media_software_4_5", - "title": "ПО" - } - ] - } - ] - }, - { - "title": "Сервер систем", - "id":"system_server_1", - "items": [ - { - "title": "Аппаратное обеспечение", - "id":"system_software_6", - "items": [ - { - "id": "copy_system_software_1", - "title": "Центральный процессор" - }, - { - "id": "copy_system_software_2", - "title": "Оперативная память" - }, - { - "id": "copy_system_software_3", - "title": "Жесткий диск" - }, - { - "id": "copy_system_software_4", - "title": "Сетевые адаптеры" - } - ] - }, - { - "title": "Программное обеспечение", - "id":"software_6", - "items": [ - { - "id": "copy_software_1", - "title": "ПО" - }, - { - "id": "copy_software_2", - "title": "ПО" - }, - { - "id": "copy_software_3", - "title": "ПО" - }, - { - "id": "copy_software_4", - "title": "ПО" - } - ] - } - ] - }, - { - "title": "Сервер систем", - "id":"system_server_2", - "items": [ - { - "title": "Аппаратное обеспечение", - "id":"system_software_7", - "items": [ - { - "id": "control_system_software_1", - "title": "Центральный процессор" - }, - { - "id": "control_system_software_2", - "title": "Оперативная память" - }, - { - "id": "control_system_software_3", - "title": "Жесткий диск" - }, - { - "id": "control_system_software_4", - "title": "Сетевые адаптеры" - } - ] - }, - { - "title": "Программное обеспечение", - "id":"software_7", - "items": [ - { - "id": "control_software_1", - "title": "ПО" - }, - { - "id": "control_software_2", - "title": "ПО" - }, - { - "id": "control_software_3", - "title": "ПО" - }, - { - "id": "control_software_4", - "title": "ПО" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/src/Components/TreeChart/tabContent2.jsx b/src/Components/TreeChart/tabContent2.jsx deleted file mode 100644 index 47dc890..0000000 --- a/src/Components/TreeChart/tabContent2.jsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from "react"; -import PrometheusChart from '../../Charts/PrometheusChart'; - -const tabContent = { - // Сервис ВКС - service1: { title: "Сервис ВКС", content:

Сервис ВКС

}, - - // Функциональные задачи - system_control: { title: "Контроль системы", content:

Контроль системы

Описание контроля.

}, - system_management: { title: "Система управления", content:

Система управления

Описание системы управления.

}, - conference: { title: "Проведение ВКС", content:

Проведение ВКС

Информация о проведении ВКС.

}, - backup: { title: "Резервное копирование", content:

Резервное копирование

Процесс резервного копирования.

}, - relay_info: { title: "Ретрансляция информации", content:

Ретрансляция информации

Детали ретрансляции.

}, - - // Медиа сервер 1 - media_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, - media_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, - media_system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, - media_system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, - media_software_1: { title: "ПО", content:

Программное обеспечение медиа сервера

}, - media_software_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - - // Медиа сервер 2 - media_system_software_1_2: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, - media_system_software_2_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, - media_system_software_3_2: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, - media_system_software_4_2: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, - media_software_1_2: { title: "ПО", content:

Программное обеспечение медиа сервера

}, - media_software_2_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_3_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_4_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - - // Медиа сервер 3 - media_system_software_1_3: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, - media_system_software_2_3: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, - media_system_software_3_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, - media_system_software_4_3: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, - media_software_1_3: { title: "ПО", content:

Программное обеспечение медиа сервера

}, - media_software_2_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_3_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_4_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - - // Медиа сервер 4 - media_system_software_1_4: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, - media_system_software_2_4: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, - media_system_software_3_4: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, - media_system_software_4_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, - media_software_1_4: { title: "ПО", content:

Программное обеспечение медиа сервера

}, - media_software_2_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_3_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_4_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - - // Медиа сервер 5 - media_system_software_1_5: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, - media_system_software_2_5: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, - media_system_software_3_5: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, - media_system_software_4_5: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, - media_software_1_5: { title: "ПО", content:

Программное обеспечение медиа сервера

}, - media_software_2_5: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_3_5: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_4_5: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - - // Сервер резервного копирования - copy_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора сервера резервного копирования.

}, - copy_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти сервера резервного копирования.

}, - copy_system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска сервера резервного копирования.

}, - copy_system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров сервера резервного копирования.

}, - copy_software_1: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, - copy_software_2: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, - copy_software_3: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, - copy_software_4: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, - - // Сервер системы управления - control_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора сервера системы управления.

}, - control_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти сервера системы управления.

}, - control_system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска сервера системы управления.

}, - control_system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров сервера системы управления.

}, - control_software_1: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, - control_software_2: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, - control_software_3: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, - control_software_4: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, - - // Сервер сбора и ретрансляции информации - system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора сервера сбора и ретрансляции информации.

}, - system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти сервера сбора и ретрансляции информации.

}, - system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска сервера сбора и ретрансляции информации.

}, - system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров сервера сбора и ретрансляции информации.

}, - software_1: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, - software_2: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, - software_3: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, - software_4: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, -}; - -export default tabContent; \ No newline at end of file diff --git a/src/Components/UI/TreeTable3.jsx b/src/Components/UI/TreeTable3.jsx deleted file mode 100644 index ced08c1..0000000 --- a/src/Components/UI/TreeTable3.jsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from "react"; -import "../../Style/TreeTable.css"; -import { getStatusColor } from "../TreeChart/dataUtils"; // Импортируем функцию - -const TreeTable = ({ data }) => { - // Проверяем, что data существует и имеет нужную структуру - if (!data || !data.items) { - return
Данные не загружены или имеют неверный формат
; - } - - // Фильтруем данные, чтобы убрать "Функциональные задачи" - const filteredData = data.items.filter((item) => item.title !== "Функциональные задачи"); - - return ( -
- - - {/* Первый уровень: Название сервера */} - - - - {/* Второй уровень: Заголовки устройств */} - - {filteredData.map((item, index) => ( - - ))} - - {/* Третий уровень: Подзаголовки "АО" и "ПО" */} - - {filteredData.map((item, index) => ( - - - - - ))} - - - - {/* Четвертый уровень: Данные "АО" и "ПО" */} - {renderRows(filteredData)} - -
- {data.title} -
- {item.title} -
- {item.items[0]?.title || "Нет данных"} - - {item.items[1]?.title || "Нет данных"} -
-
- ); -}; - -// Функция для отображения строк с вложенными элементами -const renderRows = (data) => { - const rows = []; - - // Находим максимальное количество элементов среди всех "АО" и "ПО" - const maxItems = Math.max( - ...data.flatMap((item) => [ - item.items[0]?.items?.length || 0, // АО - item.items[1]?.items?.length || 0 // ПО - ]) - ); - - // Генерируем строки - for (let i = 0; i < maxItems; i++) { - rows.push( - - {data.map((item, index) => ( - - - {item.items[0]?.items[i]?.title || ""} - - - {item.items[1]?.items[i]?.title || ""} - - - ))} - - ); - } - - return rows; -}; - -export default TreeTable; \ No newline at end of file From a53942b2649705d4547277a7866aa2638d72dd77 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 12 Mar 2025 12:19:43 -0400 Subject: [PATCH 3/3] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=B2=D1=82=D0=BE=D1=80=D0=BE=D0=B9=20=D0=B8=D0=BD=D0=B4?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=D1=82=D0=BE=D1=80,=20=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D0=BC=D0=BA=D0=BE=D0=B2=20=D0=B2=20=D1=83=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=BD=D1=8F=D1=85=20=D0=B2=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8,=20=D1=83=D0=BC=D0=B5=D0=BD=D1=8C?= =?UTF-8?q?=D1=88=D0=B8=D0=BB=20=D0=B3=D1=80=D0=B0=D1=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Charts/NegativeStatusChart.jsx | 17 ++-- src/Components/Layout/Dashboard.jsx | 97 +++++++++--------- src/Components/TreeChart/TreeChart.jsx | 52 ++++++---- src/Components/TreeChart/dataUtils.jsx | 129 ++++++++++++++---------- src/Components/TreeChart/tabContent.jsx | 79 ++++++--------- src/Components/UI/TreeTable.jsx | 18 ++-- src/Style/TreeChart.css | 9 ++ 7 files changed, 217 insertions(+), 184 deletions(-) create mode 100644 src/Style/TreeChart.css diff --git a/src/Charts/NegativeStatusChart.jsx b/src/Charts/NegativeStatusChart.jsx index d142ded..0ee7531 100644 --- a/src/Charts/NegativeStatusChart.jsx +++ b/src/Charts/NegativeStatusChart.jsx @@ -1,30 +1,27 @@ import React from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; -const NegativeStatusChart = ({ data }) => { - // Подсчет количества негативных статусов - const processedData = data.map(entry => ({ - time: entry.time, - negativeCount: entry.statuses.filter(status => ['yellow', 'orange', 'red'].includes(status)).length - })); +const SystemStatusChart = ({ data }) => { + // Обрезаем массив, оставляя только последние 20 точек + const trimmedData = data.slice(-20); return ( - + - + ); }; -export default NegativeStatusChart; +export default SystemStatusChart; \ No newline at end of file diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx index 415d68a..d6f7b0b 100644 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -1,75 +1,79 @@ -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef, useCallback } from "react"; import SidebarMenu from "./SidebarMenu"; import TreeChart from "../TreeChart/TreeChart"; import "../../Style/Dashboard.css"; import SystemStatusChart from "../../Charts/SystemStatusChart"; import Tabs from "../UI/Tabs"; -import menuData from "../TreeChart/menuData.json"; // Импортируем JSON-данные +import menuData from "../TreeChart/menuData.json"; import TreeTable from "../UI/TreeTable"; -import { updateStatuses } from "../TreeChart/dataUtils"; // Функция обновления статусов -import generateTabContent from "../TreeChart/tabContent"; // Импортируем функцию generateTabContent +import { statusManager1, statusManager2 } from "../TreeChart/dataUtils"; +import generateTabContent from "../TreeChart/tabContent"; const Dashboard = () => { const [tabs, setTabs] = useState([]); const [activeTab, setActiveTab] = useState("Главная"); - const [tabContent, setTabContent] = useState({}); // Состояние для контента вкладок - const [treeData, setTreeData] = useState(menuData); // Загружаем меню в state - const [sidebarWidth, setSidebarWidth] = useState(250); // Начальная ширина сайдбара - const [isResizing, setIsResizing] = useState(false); // Состояние перетаскивания - const [statusHistory, setStatusHistory] = useState([]); // История статусов для графика - const sidebarRef = useRef(null); // Референс на сайдбар + const [tabContent, setTabContent] = useState({}); + const [treeData1, setTreeData1] = useState(menuData); + const [treeData2, setTreeData2] = useState(menuData); + const [sidebarWidth, setSidebarWidth] = useState(250); + const [isResizing, setIsResizing] = useState(false); + const [statusHistories, setStatusHistories] = useState({ + history1: [], + history2: [], + }); + const sidebarRef = useRef(null); - // Генерация контента для вкладок на основе menuData useEffect(() => { const generatedTabContent = generateTabContent(menuData); setTabContent(generatedTabContent); }, []); - // Обновление treeData каждые 30 секунд useEffect(() => { const interval = setInterval(() => { - setTreeData((prevData) => { - const updatedData = JSON.parse(JSON.stringify(prevData)); // Клонируем данные - const averageStatusValue = updateStatuses(updatedData); // Обновляем статусы и получаем среднее значение + const updatedData1 = JSON.parse(JSON.stringify(treeData1)); + const averageStatusValue1 = statusManager1.updateStatuses(updatedData1); + const statusPercentage1 = Math.max(0, Math.min(100, averageStatusValue1 * 100)); - // Преобразуем среднее значение в проценты (0% - 100%) - const statusPercentage = (1 - (averageStatusValue / 3)) * 100; + const updatedData2 = JSON.parse(JSON.stringify(treeData2)); + const averageStatusValue2 = statusManager2.updateStatuses(updatedData2); + const statusPercentage2 = Math.max(0, Math.min(100, averageStatusValue2 * 100)); - // Добавляем новое состояние в историю - setStatusHistory((prevHistory) => [ - ...prevHistory, - { time: new Date().toLocaleTimeString(), status: statusPercentage } - ]); + setStatusHistories((prevHistories) => ({ + history1: [ + ...prevHistories.history1.slice(-49), + { time: new Date().toLocaleTimeString(), status: statusPercentage1 }, + ], + history2: [ + ...prevHistories.history2.slice(-49), + { time: new Date().toLocaleTimeString(), status: statusPercentage2 }, + ], + })); - return updatedData; - }); + setTreeData1(updatedData1); + setTreeData2(updatedData2); }, 30000); return () => clearInterval(interval); - }, []); + }, [treeData1, treeData2]); - // Обработчик начала перетаскивания - const startResizing = (e) => { + const startResizing = useCallback((e) => { e.preventDefault(); setIsResizing(true); - }; + }, []); - // Обработчик движения мыши - const resize = (e) => { + const resize = useCallback((e) => { if (isResizing) { - const newWidth = e.clientX; // Новая ширина сайдбара - if (newWidth > 100 && newWidth < 400) { // Ограничиваем минимальную и максимальную ширину + const newWidth = e.clientX; + if (newWidth > 100 && newWidth < 400) { setSidebarWidth(newWidth); } } - }; + }, [isResizing]); - // Обработчик окончания перетаскивания - const stopResizing = () => { + const stopResizing = useCallback(() => { setIsResizing(false); - }; + }, []); - // Добавляем обработчики событий useEffect(() => { const handleMouseMove = (e) => resize(e); const handleMouseUp = () => stopResizing(); @@ -83,7 +87,7 @@ const Dashboard = () => { window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mouseup", handleMouseUp); }; - }, [isResizing]); + }, [isResizing, resize, stopResizing]); const handleOpenTab = (id, title) => { if (!tabs.some((tab) => tab.id === id)) { @@ -105,14 +109,16 @@ const Dashboard = () => { return (

Общий мониторинг состояния системы

- - {/* График состояния системы */} - - {/* Используем актуальные данные */} + + + + + +
); } else if (activeTab === "Визуализация") { - return handleOpenTab(id, title)} />; + return handleOpenTab(id, title)} />; } else { const tabData = tabContent[activeTab]; return tabData ? tabData.content :

Нет данных

; @@ -124,10 +130,9 @@ const Dashboard = () => {
- - {/* Элемент для перетаскивания */} +
{ @@ -11,8 +12,16 @@ const TreeChart = ({ data, onNodeClick }) => { if (!data || !data.items) return { root: null, nodes: [], links: [] }; const root = d3.hierarchy(data, (d) => d.items); - const nodes = root.descendants(); - const links = root.links(); + const maxDepth = d3.max(root.descendants(), (d) => d.depth); + + // Фильтруем узлы, исключая последний уровень + const nodes = root.descendants().filter((d) => d.depth < maxDepth); + + // Фильтруем связи + const links = nodes.filter((d) => d.parent).map((d) => ({ + source: d.parent, + target: d, + })); // Применяем сохраненные позиции к узлам nodes.forEach((node) => { @@ -20,10 +29,9 @@ const TreeChart = ({ data, onNodeClick }) => { if (prev) { node.x = prev.x; node.y = prev.y; - node.fx = prev.fx ?? null; // Если фиксированные координаты были, сохраняем + node.fx = prev.fx ?? null; node.fy = prev.fy ?? null; } else { - // Если узел новый, задаем ему позицию рядом с родителем const parent = node.parent; node.x = parent ? parent.x + Math.random() * 50 - 25 : Math.random() * 1000; node.y = parent ? parent.y + Math.random() * 50 - 25 : Math.random() * 1000; @@ -39,7 +47,7 @@ const TreeChart = ({ data, onNodeClick }) => { const svg = d3.select(chartRef.current) .attr("width", 2000) - .attr("height", 1000) + .attr("height", 2000) .attr("viewBox", [-500, -500, 1000, 1000]) .attr("style", "max-width: 100%; height: auto;"); @@ -53,25 +61,27 @@ const TreeChart = ({ data, onNodeClick }) => { .force("charge", d3.forceManyBody().strength(-200)) .force("center", d3.forceCenter(0, 0)) .force("collision", d3.forceCollide().radius(20)) - .force("x", d3.forceX(0).strength(0.05)) // Ограничиваем разлет по X - .force("y", d3.forceY(0).strength(0.05)) // Ограничиваем разлет по Y - .force("radial", d3.forceRadial(200, 0, 0).strength(0.02)) // Держим узлы ближе к центру - .alphaDecay(0.02) // Замедляем затухание + .force("x", d3.forceX(0).strength(0.05)) + .force("y", d3.forceY(0).strength(0.05)) + .force("radial", d3.forceRadial(200, 0, 0).strength(0.02)) + .alphaDecay(0.02) .alphaTarget(0.1); // Запускаем симуляцию на 15 секунд, затем отключаем setTimeout(() => { - simulationRef.current.stop(); // Останавливаем симуляцию - nodes.forEach((node) => { - node.fx = node.x; // Фиксируем текущие позиции узлов - node.fy = node.y; - }); + if (simulationRef.current) { + simulationRef.current.stop(); // Останавливаем симуляцию + nodes.forEach((node) => { + node.fx = node.x; // Фиксируем текущие позиции узлов + node.fy = node.y; + }); + } }, 15000); // 15 секунд }, []); useEffect(() => { - if (!root || !chartRef.current) return; + if (!root || !chartRef.current || !simulationRef.current) return; // Проверяем, что симуляция инициализирована const svg = d3.select(chartRef.current); const linkGroup = svg.select(".links"); @@ -107,9 +117,11 @@ const TreeChart = ({ data, onNodeClick }) => { .selectAll("text") .data(nodes, (d) => d.data.id) .join("text") - .text((d) => d.data.title) + .text((d) => (nodes.length > 50 ? "" : d.data.title)) // Скрываем текст, если узлов много .attr("dx", 12) - .attr("dy", 4); + .attr("dy", 4) + .style("user-select", "none") // Запрет выделения текста + .style("pointer-events", "none"); // Запрет взаимодействия с текстом // Обновляем симуляцию simulationRef.current.nodes(nodes); @@ -136,7 +148,7 @@ const TreeChart = ({ data, onNodeClick }) => { const drag = () => { function dragstarted(event, d) { - if (!event.active) simulationRef.current.alphaTarget(0.3).restart(); + if (!event.active && simulationRef.current) simulationRef.current.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } @@ -147,7 +159,7 @@ const TreeChart = ({ data, onNodeClick }) => { } function dragended(event, d) { - if (!event.active) simulationRef.current.alphaTarget(0); + if (!event.active && simulationRef.current) simulationRef.current.alphaTarget(0); nodePositions.current.set(d.data.id, { x: d.x, y: d.y, fx: d.fx, fy: d.fy }); } @@ -157,4 +169,4 @@ const TreeChart = ({ data, onNodeClick }) => { return ; }; -export default TreeChart; \ No newline at end of file +export default TreeChart; diff --git a/src/Components/TreeChart/dataUtils.jsx b/src/Components/TreeChart/dataUtils.jsx index 7ee90e0..1a1c70d 100644 --- a/src/Components/TreeChart/dataUtils.jsx +++ b/src/Components/TreeChart/dataUtils.jsx @@ -1,60 +1,85 @@ -// Функция для генерации случайных статусов -const getRandomStatus = () => { - const statuses = [ - ...Array(90).fill("green"), // 90% chance - ...Array(6).fill("yellow"), // 6% chance - ...Array(3).fill("orange"), // 3% chance - ...Array(1).fill("red"), // 1% chance - ]; - return statuses[Math.floor(Math.random() * statuses.length)]; -}; +const StatusManager = () => { + const getRandomStatus = () => { + const statuses = [ + ...Array(90).fill("green"), // 90% шанс + ...Array(6).fill("yellow"), // 6% шанс + ...Array(3).fill("orange"), // 3% шанс + ...Array(1).fill("red"), // 1% шанс + ]; + return statuses[Math.floor(Math.random() * statuses.length)]; + }; -// Функция для получения числового значения статуса -const getStatusValue = (status) => { - switch (status) { - case "green": - return 0; - case "yellow": + const getStatusWeight = (status) => { + switch (status) { + case "green": return 1; // 100% здоровья + case "yellow": return 0.75; + case "orange": return 0.5; + case "red": return 0.25; // 25% здоровья + default: return 1; // По умолчанию "green" + } + }; + + const updateStatuses = (data) => { + if (!data.items || data.items.length === 0) { + // Если это элемент нижнего уровня, генерируем случайный статус + data.status = getRandomStatus(); + return getStatusWeight(data.status); + } + + // Рекурсивно обновляем статусы для всех дочерних элементов + let childStatusWeights = data.items.map((child) => updateStatuses(child)); + + // Проверяем, есть ли дочерние элементы (избегаем деления на 0) + if (childStatusWeights.length === 0) { + data.status = "green"; return 1; - case "orange": - return 2; - case "red": - return 3; - default: - return 0; // По умолчанию green - } + } + + // Вычисляем среднее арифметическое значение весов статусов + const averageStatusWeight = + childStatusWeights.reduce((sum, weight) => sum + weight, 0) / childStatusWeights.length; + + // Определяем статус текущего элемента + data.status = getStatusFromWeight(averageStatusWeight); + + return Math.max(0, averageStatusWeight); // Гарантия, что не будет отрицательных значений + }; + + const getStatusFromWeight = (weight) => { + if (weight >= 0.875) return "green"; + if (weight >= 0.625) return "yellow"; + if (weight >= 0.375) return "orange"; + return "red"; + }; + + const getStatusColor = (status) => { + switch (status) { + case "green": return "#4CAF50"; // Зеленый + case "yellow": return "#cebd21"; // Желтый + case "orange": return "#FF9800"; // Оранжевый + case "red": return "#F44336"; // Красный + default: return "#4CAF50"; // По умолчанию зеленый + } + }; + + return { + getRandomStatus, + updateStatuses, + getStatusColor, + }; }; -// Функция для получения статуса по числовому значению -const getStatusFromValue = (value) => { - if (value >= 3) return "red"; - if (value >= 2) return "orange"; - if (value >= 1) return "yellow"; - return "green"; +// Создаем два независимых менеджера статусов +export const statusManager1 = StatusManager(); +export const statusManager2 = StatusManager(); + +// Функция для расчета процентов здоровья системы +export const calculateStatusPercentage = (averageStatusValue) => { + return Math.max(0, Math.min(100, averageStatusValue * 100)); }; -// Функция для обновления статусов в дереве -const updateStatuses = (data) => { - if (!data.items || data.items.length === 0) { - // Если это элемент нижнего уровня, генерируем случайный статус - data.status = getRandomStatus(); - return getStatusValue(data.status); - } - - // Рекурсивно обновляем статусы для всех дочерних элементов - let childStatusValues = data.items.map((child) => updateStatuses(child)); - - // Вычисляем среднее арифметическое значение статусов - const averageStatusValue = childStatusValues.reduce((sum, value) => sum + value, 0) / childStatusValues.length; - - // Определяем статус текущего элемента на основе среднего значения - data.status = getStatusFromValue(averageStatusValue); - - return getStatusValue(data.status); -}; - -// Функция для получения цвета по статусу -const getStatusColor = (status) => { +// Экспортируем getStatusColor отдельно +export const getStatusColor = (status) => { switch (status) { case "green": return "#4CAF50"; // Зеленый @@ -68,5 +93,3 @@ const getStatusColor = (status) => { return "#4CAF50"; // По умолчанию зеленый } }; - -export { getRandomStatus, updateStatuses, getStatusColor }; \ No newline at end of file diff --git a/src/Components/TreeChart/tabContent.jsx b/src/Components/TreeChart/tabContent.jsx index 9c55909..58e6e49 100644 --- a/src/Components/TreeChart/tabContent.jsx +++ b/src/Components/TreeChart/tabContent.jsx @@ -26,64 +26,38 @@ const getAllChildIds = (node) => { const tabContent = (data) => { const tabContent = {}; - + // Функция для рекурсивного обхода и сбора данных const generateContent = (nodes) => { nodes.forEach((node) => { - - // Если у узла есть вложенные элементы, рекурсивно обрабатываем их if (node.items && node.items.length > 0) { + // Создаем контент для родителя + const childrenContent = generateContent(node.items); - generateContent(node.items); - } - - // Если у узла есть id, добавляем его в tabContent - if (node.id) { - - - let content = ( + const content = (

{node.title}

Контент для {node.title}.

+ {childrenContent}
); - // Если у узла есть потомки, добавляем графики для всех потомков - if (node.items && node.items.length > 0) { - const childIds = getAllChildIds(node); // Получаем все id потомков - const charts = childIds.map((id) => { - const metricName = getMetricName(id); - return ( -
-

{node.title} - {id}

- Загрузка графика...
}> - - -
- ); - }); - - content = ( -
-

{node.title}

-

Контент для {node.title}.

- {charts} -
- ); - } else { - // Если у узла нет потомков, добавляем график для него - - const metricName = getMetricName(node.id); - content = ( -
-

{node.title}

-

Контент для {node.title}.

- Загрузка графика...
}> - - -
- ); - } + // Сохраняем контент для текущего id + tabContent[node.id] = { + title: node.title, + content: content, + }; + } else { + // Если у узла нет вложенных элементов, это самый нижний уровень + const metricName = getMetricName(node.id); + const content = ( +
+

{node.title}

{/* Используем title узла */} + Загрузка графика...
}> + + +
+ ); // Сохраняем контент для текущего id tabContent[node.id] = { @@ -92,6 +66,15 @@ const tabContent = (data) => { }; } }); + + // Возвращаем контент для всех потомков + return ( +
+ {nodes.map((node) => ( +
{tabContent[node.id].content}
+ ))} +
+ ); }; // Начинаем обработку с корневого уровня @@ -104,4 +87,4 @@ const tabContent = (data) => { return tabContent; }; -export default tabContent; // Экспортируем только функцию \ No newline at end of file +export default tabContent; \ No newline at end of file diff --git a/src/Components/UI/TreeTable.jsx b/src/Components/UI/TreeTable.jsx index 235ac1d..51287aa 100644 --- a/src/Components/UI/TreeTable.jsx +++ b/src/Components/UI/TreeTable.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef, useState } from "react"; import "../../Style/TreeTable.css"; -import { getStatusColor } from "../TreeChart/dataUtils"; +import { statusManager1, statusManager2 } from "../TreeChart/dataUtils"; const TreeTable = ({ data }) => { const tableRef = useRef(null); @@ -61,7 +61,8 @@ const TreeTable = ({ data }) => { return (
-
+
+
{item.title}
@@ -82,7 +83,8 @@ const TreeTable = ({ data }) => { {item.items.map((child) => (
-
+
+
{child.title}
@@ -93,7 +95,8 @@ const TreeTable = ({ data }) => { return (
-
+
+
{item.title}
@@ -115,7 +118,8 @@ const TreeTable = ({ data }) => { title={data.title} >
-
+
+
{data.title}
@@ -132,7 +136,7 @@ const TreeTable = ({ data }) => {

Лог статусов

    {log.map((entry, index) => ( -
  • +
  • [{entry.time}] {entry.status}: {entry.title}
  • ))} @@ -143,4 +147,4 @@ const TreeTable = ({ data }) => { ); }; -export default TreeTable; +export default TreeTable; \ No newline at end of file diff --git a/src/Style/TreeChart.css b/src/Style/TreeChart.css new file mode 100644 index 0000000..5a86d0a --- /dev/null +++ b/src/Style/TreeChart.css @@ -0,0 +1,9 @@ +svg { + user-select: none; + /* Запрет выделения текста */ +} + +text { + pointer-events: none; + /* Запрет взаимодействия с текстом */ +} \ No newline at end of file