diff --git a/src/Charts/NegativeStatusChart.jsx b/src/Charts/NegativeStatusChart.jsx new file mode 100644 index 0000000..0ee7531 --- /dev/null +++ b/src/Charts/NegativeStatusChart.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/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..d6f7b0b 100644 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -1,64 +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 ErrorIndicator from "../UI/ErrorIndicator"; +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 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)); // Клонируем данные - updateStatuses(updatedData); // Обновляем статусы - return updatedData; - }); + const updatedData1 = JSON.parse(JSON.stringify(treeData1)); + const averageStatusValue1 = statusManager1.updateStatuses(updatedData1); + const statusPercentage1 = Math.max(0, Math.min(100, averageStatusValue1 * 100)); + + const updatedData2 = JSON.parse(JSON.stringify(treeData2)); + const averageStatusValue2 = statusManager2.updateStatuses(updatedData2); + const statusPercentage2 = Math.max(0, Math.min(100, averageStatusValue2 * 100)); + + setStatusHistories((prevHistories) => ({ + history1: [ + ...prevHistories.history1.slice(-49), + { time: new Date().toLocaleTimeString(), status: statusPercentage1 }, + ], + history2: [ + ...prevHistories.history2.slice(-49), + { time: new Date().toLocaleTimeString(), status: statusPercentage2 }, + ], + })); + + 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(); @@ -72,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)) { @@ -93,12 +108,17 @@ const Dashboard = () => { if (activeTab === "Главная") { return (
-

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

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

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

+ + + + + +
); } else if (activeTab === "Визуализация") { - return handleOpenTab(id, title)} />; + return handleOpenTab(id, title)} />; } else { const tabData = tabContent[activeTab]; return tabData ? tabData.content :

Нет данных

; @@ -110,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 fa67130..1a1c70d 100644 --- a/src/Components/TreeChart/dataUtils.jsx +++ b/src/Components/TreeChart/dataUtils.jsx @@ -1,53 +1,95 @@ -// Функция для генерации случайных статусов -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%) - ]; - 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 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; + } + + // Вычисляем среднее арифметическое значение весов статусов + 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 updateStatuses = (data) => { - if (!data.items || data.items.length === 0) { - // Если это элемент нижнего уровня, генерируем случайный статус - data.status = getRandomStatus(); - return data.status; - } +// Создаем два независимых менеджера статусов +export const statusManager1 = StatusManager(); +export const statusManager2 = StatusManager(); - // Рекурсивно обновляем статусы для всех дочерних элементов - let childStatuses = 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"; - } - - return data.status; +// Функция для расчета процентов здоровья системы +export const calculateStatusPercentage = (averageStatusValue) => { + return Math.max(0, Math.min(100, averageStatusValue * 100)); }; -// Функция для получения цвета по статусу -const getStatusColor = (status) => { +// Экспортируем getStatusColor отдельно +export const getStatusColor = (status) => { switch (status) { case "green": return "#4CAF50"; // Зеленый case "yellow": - return "#FFEB3B"; // Желтый + return "#cebd21"; // Желтый case "orange": return "#FF9800"; // Оранжевый case "red": return "#F44336"; // Красный default: - return "#4CAF50"; // Синий (или любой другой стандартный цвет) + return "#4CAF50"; // По умолчанию зеленый } }; - -export { getRandomStatus, updateStatuses, getStatusColor }; \ No newline at end of file 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/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/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/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/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..51287aa 100644 --- a/src/Components/UI/TreeTable.jsx +++ b/src/Components/UI/TreeTable.jsx @@ -1,76 +1,150 @@ -import React from "react"; +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 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 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/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 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(