Compare commits

..

5 Commits

22 changed files with 591 additions and 984 deletions

View File

@ -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 (
<ResponsiveContainer width="100%" height={300}>
<LineChart
data={trimmedData} // Используем обрезанный массив
margin={{
top: 5, right: 30, left: 20, bottom: 5,
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="time" />
<YAxis domain={[0, 100]} />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="status" stroke="#8884d8" activeDot={{ r: 8 }} />
</LineChart>
</ResponsiveContainer>
);
};
export default SystemStatusChart;

View File

@ -34,6 +34,45 @@ const PrometheusChart = ({ metricName }) => {
const [filteredData, setFilteredData] = useState(null); // Отфильтрованные данные const [filteredData, setFilteredData] = useState(null); // Отфильтрованные данные
const intervalRef = useRef(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 () => { const fetchData = async () => {
try { try {
let start, end; let start, end;
@ -147,7 +186,10 @@ const PrometheusChart = ({ metricName }) => {
}); });
const filtered = data.slice(startIndex, endIndex + 1); const filtered = data.slice(startIndex, endIndex + 1);
setFilteredData(filtered); // Сохраняем отфильтрованные данные
// Интерполируем данные, если точек меньше 15
const interpolated = interpolateData(filtered, 15);
setFilteredData(interpolated); // Сохраняем интерполированные данные
} else { } else {
setFilteredData(null); // Сбрасываем фильтрацию, если диапазон не выбран setFilteredData(null); // Сбрасываем фильтрацию, если диапазон не выбран
} }

View File

@ -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 (
<ResponsiveContainer width="100%" height={300}>
<LineChart
data={trimmedData} // Используем обрезанный массив
margin={{
top: 5, right: 30, left: 20, bottom: 5,
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="time" />
<YAxis domain={[0, 100]} />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="status" stroke="#8884d8" activeDot={{ r: 8 }} />
</LineChart>
</ResponsiveContainer>
);
};
export default SystemStatusChart;

View File

@ -1,64 +1,79 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef, useCallback } from "react";
import SidebarMenu from "./SidebarMenu"; import SidebarMenu from "./SidebarMenu";
import TreeChart from "../TreeChart/TreeChart"; import TreeChart from "../TreeChart/TreeChart";
import "../../Style/Dashboard.css"; import "../../Style/Dashboard.css";
import ErrorIndicator from "../UI/ErrorIndicator"; import SystemStatusChart from "../../Charts/SystemStatusChart";
import Tabs from "../UI/Tabs"; import Tabs from "../UI/Tabs";
import menuData from "../TreeChart/menuData.json"; // Импортируем JSON-данные import menuData from "../TreeChart/menuData.json";
import TreeTable from "../UI/TreeTable"; import TreeTable from "../UI/TreeTable";
import { updateStatuses } from "../TreeChart/dataUtils"; // Функция обновления статусов import { statusManager1, statusManager2 } from "../TreeChart/dataUtils";
import generateTabContent from "../TreeChart/tabContent"; // Импортируем функцию generateTabContent import generateTabContent from "../TreeChart/tabContent";
const Dashboard = () => { const Dashboard = () => {
const [tabs, setTabs] = useState([]); const [tabs, setTabs] = useState([]);
const [activeTab, setActiveTab] = useState("Главная"); const [activeTab, setActiveTab] = useState("Главная");
const [tabContent, setTabContent] = useState({}); // Состояние для контента вкладок const [tabContent, setTabContent] = useState({});
const [treeData, setTreeData] = useState(menuData); // Загружаем меню в state const [treeData1, setTreeData1] = useState(menuData);
const [sidebarWidth, setSidebarWidth] = useState(250); // Начальная ширина сайдбара const [treeData2, setTreeData2] = useState(menuData);
const [isResizing, setIsResizing] = useState(false); // Состояние перетаскивания const [sidebarWidth, setSidebarWidth] = useState(250);
const sidebarRef = useRef(null); // Референс на сайдбар const [isResizing, setIsResizing] = useState(false);
const [statusHistories, setStatusHistories] = useState({
history1: [],
history2: [],
});
const sidebarRef = useRef(null);
// Генерация контента для вкладок на основе menuData
useEffect(() => { useEffect(() => {
const generatedTabContent = generateTabContent(menuData); const generatedTabContent = generateTabContent(menuData);
setTabContent(generatedTabContent); setTabContent(generatedTabContent);
}, []); }, []);
// Обновление treeData каждые 30 секунд
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
setTreeData((prevData) => { const updatedData1 = JSON.parse(JSON.stringify(treeData1));
const updatedData = JSON.parse(JSON.stringify(prevData)); // Клонируем данные const averageStatusValue1 = statusManager1.updateStatuses(updatedData1);
updateStatuses(updatedData); // Обновляем статусы const statusPercentage1 = Math.max(0, Math.min(100, averageStatusValue1 * 100));
return updatedData;
}); 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); }, 30000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, [treeData1, treeData2]);
// Обработчик начала перетаскивания const startResizing = useCallback((e) => {
const startResizing = (e) => {
e.preventDefault(); e.preventDefault();
setIsResizing(true); setIsResizing(true);
}; }, []);
// Обработчик движения мыши const resize = useCallback((e) => {
const resize = (e) => {
if (isResizing) { if (isResizing) {
const newWidth = e.clientX; // Новая ширина сайдбара const newWidth = e.clientX;
if (newWidth > 100 && newWidth < 400) { // Ограничиваем минимальную и максимальную ширину if (newWidth > 100 && newWidth < 400) {
setSidebarWidth(newWidth); setSidebarWidth(newWidth);
} }
} }
}; }, [isResizing]);
// Обработчик окончания перетаскивания const stopResizing = useCallback(() => {
const stopResizing = () => {
setIsResizing(false); setIsResizing(false);
}; }, []);
// Добавляем обработчики событий
useEffect(() => { useEffect(() => {
const handleMouseMove = (e) => resize(e); const handleMouseMove = (e) => resize(e);
const handleMouseUp = () => stopResizing(); const handleMouseUp = () => stopResizing();
@ -72,7 +87,7 @@ const Dashboard = () => {
window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp); window.removeEventListener("mouseup", handleMouseUp);
}; };
}, [isResizing]); }, [isResizing, resize, stopResizing]);
const handleOpenTab = (id, title) => { const handleOpenTab = (id, title) => {
if (!tabs.some((tab) => tab.id === id)) { if (!tabs.some((tab) => tab.id === id)) {
@ -93,12 +108,17 @@ const Dashboard = () => {
if (activeTab === "Главная") { if (activeTab === "Главная") {
return ( return (
<div> <div>
<h2>Общий мониторинг</h2> <h2>Общий мониторинг состояния системы</h2>
<TreeTable data={treeData.items} /> {/* Используем актуальные данные */} <label>Процент доверия системы</label>
<SystemStatusChart data={statusHistories.history1} />
<label>Функциональность системы</label>
<SystemStatusChart data={statusHistories.history2} />
<label>Статус компонентов системы</label>
<TreeTable data={treeData1} />
</div> </div>
); );
} else if (activeTab === "Визуализация") { } else if (activeTab === "Визуализация") {
return <TreeChart data={treeData} onNodeClick={(id, title) => handleOpenTab(id, title)} />; return <TreeChart data={treeData1} onNodeClick={(id, title) => handleOpenTab(id, title)} />;
} else { } else {
const tabData = tabContent[activeTab]; const tabData = tabContent[activeTab];
return tabData ? tabData.content : <p>Нет данных</p>; return tabData ? tabData.content : <p>Нет данных</p>;
@ -110,10 +130,9 @@ const Dashboard = () => {
<div <div
className="sidebar" className="sidebar"
ref={sidebarRef} ref={sidebarRef}
style={{ width: sidebarWidth }} // Динамическая ширина сайдбара style={{ width: sidebarWidth }}
> >
<SidebarMenu data={treeData} onOpenTab={handleOpenTab} sidebarWidth={sidebarWidth} /> <SidebarMenu data={treeData1} onOpenTab={handleOpenTab} sidebarWidth={sidebarWidth} />
{/* Элемент для перетаскивания */}
<div <div
className="sidebar-resizer" className="sidebar-resizer"
onMouseDown={startResizing} onMouseDown={startResizing}

View File

@ -1,5 +1,6 @@
import React, { useRef, useEffect, useMemo } from "react"; import React, { useRef, useEffect, useMemo } from "react";
import * as d3 from "d3"; import * as d3 from "d3";
import "../../Style/TreeChart.css";
import { getStatusColor } from "./dataUtils"; import { getStatusColor } from "./dataUtils";
const TreeChart = ({ data, onNodeClick }) => { const TreeChart = ({ data, onNodeClick }) => {
@ -11,8 +12,16 @@ const TreeChart = ({ data, onNodeClick }) => {
if (!data || !data.items) return { root: null, nodes: [], links: [] }; if (!data || !data.items) return { root: null, nodes: [], links: [] };
const root = d3.hierarchy(data, (d) => d.items); const root = d3.hierarchy(data, (d) => d.items);
const nodes = root.descendants(); const maxDepth = d3.max(root.descendants(), (d) => d.depth);
const links = root.links();
// Фильтруем узлы, исключая последний уровень
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) => { nodes.forEach((node) => {
@ -20,10 +29,9 @@ const TreeChart = ({ data, onNodeClick }) => {
if (prev) { if (prev) {
node.x = prev.x; node.x = prev.x;
node.y = prev.y; node.y = prev.y;
node.fx = prev.fx ?? null; // Если фиксированные координаты были, сохраняем node.fx = prev.fx ?? null;
node.fy = prev.fy ?? null; node.fy = prev.fy ?? null;
} else { } else {
// Если узел новый, задаем ему позицию рядом с родителем
const parent = node.parent; const parent = node.parent;
node.x = parent ? parent.x + Math.random() * 50 - 25 : Math.random() * 1000; node.x = parent ? parent.x + Math.random() * 50 - 25 : Math.random() * 1000;
node.y = parent ? parent.y + 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) const svg = d3.select(chartRef.current)
.attr("width", 2000) .attr("width", 2000)
.attr("height", 1000) .attr("height", 2000)
.attr("viewBox", [-500, -500, 1000, 1000]) .attr("viewBox", [-500, -500, 1000, 1000])
.attr("style", "max-width: 100%; height: auto;"); .attr("style", "max-width: 100%; height: auto;");
@ -53,25 +61,27 @@ const TreeChart = ({ data, onNodeClick }) => {
.force("charge", d3.forceManyBody().strength(-200)) .force("charge", d3.forceManyBody().strength(-200))
.force("center", d3.forceCenter(0, 0)) .force("center", d3.forceCenter(0, 0))
.force("collision", d3.forceCollide().radius(20)) .force("collision", d3.forceCollide().radius(20))
.force("x", d3.forceX(0).strength(0.05)) // Ограничиваем разлет по X .force("x", d3.forceX(0).strength(0.05))
.force("y", d3.forceY(0).strength(0.05)) // Ограничиваем разлет по Y .force("y", d3.forceY(0).strength(0.05))
.force("radial", d3.forceRadial(200, 0, 0).strength(0.02)) // Держим узлы ближе к центру .force("radial", d3.forceRadial(200, 0, 0).strength(0.02))
.alphaDecay(0.02) // Замедляем затухание .alphaDecay(0.02)
.alphaTarget(0.1); .alphaTarget(0.1);
// Запускаем симуляцию на 15 секунд, затем отключаем // Запускаем симуляцию на 15 секунд, затем отключаем
setTimeout(() => { setTimeout(() => {
simulationRef.current.stop(); // Останавливаем симуляцию if (simulationRef.current) {
nodes.forEach((node) => { simulationRef.current.stop(); // Останавливаем симуляцию
node.fx = node.x; // Фиксируем текущие позиции узлов nodes.forEach((node) => {
node.fy = node.y; node.fx = node.x; // Фиксируем текущие позиции узлов
}); node.fy = node.y;
});
}
}, 15000); // 15 секунд }, 15000); // 15 секунд
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!root || !chartRef.current) return; if (!root || !chartRef.current || !simulationRef.current) return; // Проверяем, что симуляция инициализирована
const svg = d3.select(chartRef.current); const svg = d3.select(chartRef.current);
const linkGroup = svg.select(".links"); const linkGroup = svg.select(".links");
@ -107,9 +117,11 @@ const TreeChart = ({ data, onNodeClick }) => {
.selectAll("text") .selectAll("text")
.data(nodes, (d) => d.data.id) .data(nodes, (d) => d.data.id)
.join("text") .join("text")
.text((d) => d.data.title) .text((d) => (nodes.length > 50 ? "" : d.data.title)) // Скрываем текст, если узлов много
.attr("dx", 12) .attr("dx", 12)
.attr("dy", 4); .attr("dy", 4)
.style("user-select", "none") // Запрет выделения текста
.style("pointer-events", "none"); // Запрет взаимодействия с текстом
// Обновляем симуляцию // Обновляем симуляцию
simulationRef.current.nodes(nodes); simulationRef.current.nodes(nodes);
@ -136,7 +148,7 @@ const TreeChart = ({ data, onNodeClick }) => {
const drag = () => { const drag = () => {
function dragstarted(event, d) { 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.fx = d.x;
d.fy = d.y; d.fy = d.y;
} }
@ -147,7 +159,7 @@ const TreeChart = ({ data, onNodeClick }) => {
} }
function dragended(event, d) { 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 }); nodePositions.current.set(d.data.id, { x: d.x, y: d.y, fx: d.fx, fy: d.fy });
} }

View File

@ -1,53 +1,95 @@
// Функция для генерации случайных статусов const StatusManager = () => {
const getRandomStatus = () => { const getRandomStatus = () => {
const statuses = [ const statuses = [
...Array(90).fill("green"), // 63/70 chance (примерно 90%) ...Array(90).fill("green"), // 90% шанс
...Array(6).fill("yellow"), // 1/70 chance (примерно 1.43%) ...Array(6).fill("yellow"), // 6% шанс
...Array(3).fill("orange"), // 1/70 chance (примерно 1.43%) ...Array(3).fill("orange"), // 3% шанс
...Array(1).fill("red"), // 1/70 chance (примерно 1.43%) ...Array(1).fill("red"), // 1% шанс
]; ];
return statuses[Math.floor(Math.random() * statuses.length)]; 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) => { export const statusManager1 = StatusManager();
if (!data.items || data.items.length === 0) { export const statusManager2 = StatusManager();
// Если это элемент нижнего уровня, генерируем случайный статус
data.status = getRandomStatus();
return data.status;
}
// Рекурсивно обновляем статусы для всех дочерних элементов // Функция для расчета процентов здоровья системы
let childStatuses = data.items.map((child) => updateStatuses(child)); export const calculateStatusPercentage = (averageStatusValue) => {
return Math.max(0, Math.min(100, averageStatusValue * 100));
// Определяем статус текущего элемента на основе статусов дочерних элементов
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;
}; };
// Функция для получения цвета по статусу // Экспортируем getStatusColor отдельно
const getStatusColor = (status) => { export const getStatusColor = (status) => {
switch (status) { switch (status) {
case "green": case "green":
return "#4CAF50"; // Зеленый return "#4CAF50"; // Зеленый
case "yellow": case "yellow":
return "#FFEB3B"; // Желтый return "#cebd21"; // Желтый
case "orange": case "orange":
return "#FF9800"; // Оранжевый return "#FF9800"; // Оранжевый
case "red": case "red":
return "#F44336"; // Красный return "#F44336"; // Красный
default: default:
return "#4CAF50"; // Синий (или любой другой стандартный цвет) return "#4CAF50"; // По умолчанию зеленый
} }
}; };
export { getRandomStatus, updateStatuses, getStatusColor };

View File

@ -1,9 +1,10 @@
{ {
"title": "Сервер ЗВКС", "title": "Сервис ЗВКС",
"id": "1", "id": "1",
"items": [ "items": [
{ {
"title": "Функциональные задачи", "title": "Функциональные задачи",
"id": "functional_tasks",
"items": [ "items": [
{ {
"id": "system_control", "id": "system_control",
@ -451,9 +452,11 @@
}, },
{ {
"title": "Медиа сервер", "title": "Медиа сервер",
"id": "media_server_1",
"items": [ "items": [
{ {
"title": "Аппаратное обеспечение", "title": "Аппаратное обеспечение",
"id": "system_software_1",
"items": [ "items": [
{ {
"id": "media_system_software_1_2", "id": "media_system_software_1_2",
@ -475,6 +478,7 @@
}, },
{ {
"title": "Программное обеспечение", "title": "Программное обеспечение",
"id": "software_1",
"items": [ "items": [
{ {
"id": "media_software_1_2", "id": "media_software_1_2",
@ -498,9 +502,11 @@
}, },
{ {
"title": "Медиа сервер", "title": "Медиа сервер",
"id": "media_server_2",
"items": [ "items": [
{ {
"title": "Аппаратное обеспечение", "title": "Аппаратное обеспечение",
"id": "system_software_2",
"items": [ "items": [
{ {
"id": "media_system_software_1_3", "id": "media_system_software_1_3",
@ -522,6 +528,7 @@
}, },
{ {
"title": "Программное обеспечение", "title": "Программное обеспечение",
"id": "software_2",
"items": [ "items": [
{ {
"id": "media_software_1_3", "id": "media_software_1_3",
@ -545,9 +552,11 @@
}, },
{ {
"title": "Медиа сервер", "title": "Медиа сервер",
"id": "media_server_3",
"items": [ "items": [
{ {
"title": "Аппаратное обеспечение", "title": "Аппаратное обеспечение",
"id": "system_software_3",
"items": [ "items": [
{ {
"id": "media_system_software_1_4", "id": "media_system_software_1_4",
@ -569,6 +578,7 @@
}, },
{ {
"title": "Программное обеспечение", "title": "Программное обеспечение",
"id": "software_3",
"items": [ "items": [
{ {
"id": "media_software_1_4", "id": "media_software_1_4",
@ -592,9 +602,11 @@
}, },
{ {
"title": "Медиа сервер", "title": "Медиа сервер",
"id": "media_server_4",
"items": [ "items": [
{ {
"title": "Аппаратное обеспечение", "title": "Аппаратное обеспечение",
"id": "system_software_4",
"items": [ "items": [
{ {
"id": "media_system_software_1_5", "id": "media_system_software_1_5",
@ -616,6 +628,7 @@
}, },
{ {
"title": "Программное обеспечение", "title": "Программное обеспечение",
"id": "software_4",
"items": [ "items": [
{ {
"id": "media_software_1_5", "id": "media_software_1_5",
@ -639,9 +652,11 @@
}, },
{ {
"title": "Сервер систем", "title": "Сервер систем",
"id": "system_server_1",
"items": [ "items": [
{ {
"title": "Аппаратное обеспечение", "title": "Аппаратное обеспечение",
"id": "system_software_5",
"items": [ "items": [
{ {
"id": "copy_system_software_1", "id": "copy_system_software_1",
@ -663,6 +678,7 @@
}, },
{ {
"title": "Программное обеспечение", "title": "Программное обеспечение",
"id": "software_5",
"items": [ "items": [
{ {
"id": "copy_software_1", "id": "copy_software_1",
@ -686,9 +702,11 @@
}, },
{ {
"title": "Сервер систем", "title": "Сервер систем",
"id": "system_server_2",
"items": [ "items": [
{ {
"title": "Аппаратное обеспечение", "title": "Аппаратное обеспечение",
"id": "system_software_6",
"items": [ "items": [
{ {
"id": "control_system_software_1", "id": "control_system_software_1",
@ -710,6 +728,7 @@
}, },
{ {
"title": "Программное обеспечение", "title": "Программное обеспечение",
"id": "software_6",
"items": [ "items": [
{ {
"id": "control_software_1", "id": "control_software_1",

View File

@ -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": "ПО"
}
]
}
]
}
]
}

View File

@ -26,64 +26,38 @@ const getAllChildIds = (node) => {
const tabContent = (data) => { const tabContent = (data) => {
const tabContent = {}; const tabContent = {};
// Функция для рекурсивного обхода и сбора данных
const generateContent = (nodes) => { const generateContent = (nodes) => {
nodes.forEach((node) => { nodes.forEach((node) => {
// Если у узла есть вложенные элементы, рекурсивно обрабатываем их // Если у узла есть вложенные элементы, рекурсивно обрабатываем их
if (node.items && node.items.length > 0) { if (node.items && node.items.length > 0) {
// Создаем контент для родителя
const childrenContent = generateContent(node.items);
generateContent(node.items); const content = (
}
// Если у узла есть id, добавляем его в tabContent
if (node.id) {
let content = (
<div> <div>
<h2>{node.title}</h2> <h2>{node.title}</h2>
<p>Контент для {node.title}.</p> <p>Контент для {node.title}.</p>
{childrenContent}
</div> </div>
); );
// Если у узла есть потомки, добавляем графики для всех потомков // Сохраняем контент для текущего id
if (node.items && node.items.length > 0) { tabContent[node.id] = {
const childIds = getAllChildIds(node); // Получаем все id потомков title: node.title,
const charts = childIds.map((id) => { content: content,
const metricName = getMetricName(id); };
return ( } else {
<div key={id}> // Если у узла нет вложенных элементов, это самый нижний уровень
<h3>{node.title} - {id}</h3> const metricName = getMetricName(node.id);
<Suspense fallback={<div>Загрузка графика...</div>}> const content = (
<PrometheusChart metricName={metricName} /> <div key={node.id}>
</Suspense> <h3>{node.title}</h3> {/* Используем title узла */}
</div> <Suspense fallback={<div>Загрузка графика...</div>}>
); <PrometheusChart metricName={metricName} />
}); </Suspense>
</div>
content = ( );
<div>
<h2>{node.title}</h2>
<p>Контент для {node.title}.</p>
{charts}
</div>
);
} else {
// Если у узла нет потомков, добавляем график для него
const metricName = getMetricName(node.id);
content = (
<div>
<h2>{node.title}</h2>
<p>Контент для {node.title}.</p>
<Suspense fallback={<div>Загрузка графика...</div>}>
<PrometheusChart metricName={metricName} />
</Suspense>
</div>
);
}
// Сохраняем контент для текущего id // Сохраняем контент для текущего id
tabContent[node.id] = { tabContent[node.id] = {
@ -92,6 +66,15 @@ const tabContent = (data) => {
}; };
} }
}); });
// Возвращаем контент для всех потомков
return (
<div>
{nodes.map((node) => (
<div key={node.id}>{tabContent[node.id].content}</div>
))}
</div>
);
}; };
// Начинаем обработку с корневого уровня // Начинаем обработку с корневого уровня
@ -104,4 +87,4 @@ const tabContent = (data) => {
return tabContent; return tabContent;
}; };
export default tabContent; // Экспортируем только функцию export default tabContent;

View File

@ -1,96 +0,0 @@
import React from "react";
import PrometheusChart from '../../Charts/PrometheusChart';
const tabContent = {
// Сервис ВКС
service1: { title: "Сервис ВКС", content: <div><h2>Сервис ВКС</h2></div> },
// Функциональные задачи
system_control: { title: "Контроль системы", content: <div><h2>Контроль системы</h2><p>Описание контроля.</p></div> },
system_management: { title: "Система управления", content: <div><h2>Система управления</h2><p>Описание системы управления.</p></div> },
conference: { title: "Проведение ВКС", content: <div><h2>Проведение ВКС</h2><p>Информация о проведении ВКС.</p></div> },
backup: { title: "Резервное копирование", content: <div><h2>Резервное копирование</h2><p>Процесс резервного копирования.</p></div> },
relay_info: { title: "Ретрансляция информации", content: <div><h2>Ретрансляция информации</h2><p>Детали ретрансляции.</p></div> },
// Медиа сервер 1
media_system_software_1: { title: "Центральный процессор", content: <div><h2>Центральный процессор</h2><p>Описание центрального процессора медиа сервера.</p></div> },
media_system_software_2: { title: "Оперативная память", content: <div><h2>Оперативная память</h2><p>Описание оперативной памяти медиа сервера.</p></div> },
media_system_software_3: { title: "Жесткий диск", content: <div><h2>Жесткий диск</h2><p>Описание жесткого диска медиа сервера.</p></div> },
media_system_software_4: { title: "Сетевые адаптеры", content: <div><h2>Сетевые адаптеры</h2><p>Описание сетевых адаптеров медиа сервера.</p></div> },
media_software_1: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><PrometheusChart /></div> },
media_software_2: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
media_software_3: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
media_software_4: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
// Медиа сервер 2
media_system_software_1_2: { title: "Центральный процессор", content: <div><h2>Центральный процессор</h2><p>Описание центрального процессора медиа сервера.</p></div> },
media_system_software_2_2: { title: "Оперативная память", content: <div><h2>Оперативная память</h2><p>Описание оперативной памяти медиа сервера.</p></div> },
media_system_software_3_2: { title: "Жесткий диск", content: <div><h2>Жесткий диск</h2><p>Описание жесткого диска медиа сервера.</p></div> },
media_system_software_4_2: { title: "Сетевые адаптеры", content: <div><h2>Сетевые адаптеры</h2><p>Описание сетевых адаптеров медиа сервера.</p></div> },
media_software_1_2: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><PrometheusChart /></div> },
media_software_2_2: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
media_software_3_2: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
media_software_4_2: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
// Медиа сервер 3
media_system_software_1_3: { title: "Центральный процессор", content: <div><h2>Центральный процессор</h2><p>Описание центрального процессора медиа сервера.</p></div> },
media_system_software_2_3: { title: "Оперативная память", content: <div><h2>Оперативная память</h2><p>Описание оперативной памяти медиа сервера.</p></div> },
media_system_software_3_3: { title: "Жесткий диск", content: <div><h2>Жесткий диск</h2><p>Описание жесткого диска медиа сервера.</p></div> },
media_system_software_4_3: { title: "Сетевые адаптеры", content: <div><h2>Сетевые адаптеры</h2><p>Описание сетевых адаптеров медиа сервера.</p></div> },
media_software_1_3: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><PrometheusChart /></div> },
media_software_2_3: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
media_software_3_3: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
media_software_4_3: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
// Медиа сервер 4
media_system_software_1_4: { title: "Центральный процессор", content: <div><h2>Центральный процессор</h2><p>Описание центрального процессора медиа сервера.</p></div> },
media_system_software_2_4: { title: "Оперативная память", content: <div><h2>Оперативная память</h2><p>Описание оперативной памяти медиа сервера.</p></div> },
media_system_software_3_4: { title: "Жесткий диск", content: <div><h2>Жесткий диск</h2><p>Описание жесткого диска медиа сервера.</p></div> },
media_system_software_4_4: { title: "Сетевые адаптеры", content: <div><h2>Сетевые адаптеры</h2><p>Описание сетевых адаптеров медиа сервера.</p></div> },
media_software_1_4: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><PrometheusChart /></div> },
media_software_2_4: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
media_software_3_4: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
media_software_4_4: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
// Медиа сервер 5
media_system_software_1_5: { title: "Центральный процессор", content: <div><h2>Центральный процессор</h2><p>Описание центрального процессора медиа сервера.</p></div> },
media_system_software_2_5: { title: "Оперативная память", content: <div><h2>Оперативная память</h2><p>Описание оперативной памяти медиа сервера.</p></div> },
media_system_software_3_5: { title: "Жесткий диск", content: <div><h2>Жесткий диск</h2><p>Описание жесткого диска медиа сервера.</p></div> },
media_system_software_4_5: { title: "Сетевые адаптеры", content: <div><h2>Сетевые адаптеры</h2><p>Описание сетевых адаптеров медиа сервера.</p></div> },
media_software_1_5: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><PrometheusChart /></div> },
media_software_2_5: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
media_software_3_5: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
media_software_4_5: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
// Сервер резервного копирования
copy_system_software_1: { title: "Центральный процессор", content: <div><h2>Центральный процессор</h2><p>Описание центрального процессора сервера резервного копирования.</p></div> },
copy_system_software_2: { title: "Оперативная память", content: <div><h2>Оперативная память</h2><p>Описание оперативной памяти сервера резервного копирования.</p></div> },
copy_system_software_3: { title: "Жесткий диск", content: <div><h2>Жесткий диск</h2><p>Описание жесткого диска сервера резервного копирования.</p></div> },
copy_system_software_4: { title: "Сетевые адаптеры", content: <div><h2>Сетевые адаптеры</h2><p>Описание сетевых адаптеров сервера резервного копирования.</p></div> },
copy_software_1: { title: "ПО", content: <div><h2>Программное обеспечение сервера резервного копирования</h2><p>Описание ПО сервера резервного копирования.</p></div> },
copy_software_2: { title: "ПО", content: <div><h2>Программное обеспечение сервера резервного копирования</h2><p>Описание ПО сервера резервного копирования.</p></div> },
copy_software_3: { title: "ПО", content: <div><h2>Программное обеспечение сервера резервного копирования</h2><p>Описание ПО сервера резервного копирования.</p></div> },
copy_software_4: { title: "ПО", content: <div><h2>Программное обеспечение сервера резервного копирования</h2><p>Описание ПО сервера резервного копирования.</p></div> },
// Сервер системы управления
control_system_software_1: { title: "Центральный процессор", content: <div><h2>Центральный процессор</h2><p>Описание центрального процессора сервера системы управления.</p></div> },
control_system_software_2: { title: "Оперативная память", content: <div><h2>Оперативная память</h2><p>Описание оперативной памяти сервера системы управления.</p></div> },
control_system_software_3: { title: "Жесткий диск", content: <div><h2>Жесткий диск</h2><p>Описание жесткого диска сервера системы управления.</p></div> },
control_system_software_4: { title: "Сетевые адаптеры", content: <div><h2>Сетевые адаптеры</h2><p>Описание сетевых адаптеров сервера системы управления.</p></div> },
control_software_1: { title: "ПО", content: <div><h2>Программное обеспечение сервера системы управления</h2><p>Описание ПО сервера системы управления.</p></div> },
control_software_2: { title: "ПО", content: <div><h2>Программное обеспечение сервера системы управления</h2><p>Описание ПО сервера системы управления.</p></div> },
control_software_3: { title: "ПО", content: <div><h2>Программное обеспечение сервера системы управления</h2><p>Описание ПО сервера системы управления.</p></div> },
control_software_4: { title: "ПО", content: <div><h2>Программное обеспечение сервера системы управления</h2><p>Описание ПО сервера системы управления.</p></div> },
// Сервер сбора и ретрансляции информации
system_software_1: { title: "Центральный процессор", content: <div><h2>Центральный процессор</h2><p>Описание центрального процессора сервера сбора и ретрансляции информации.</p></div> },
system_software_2: { title: "Оперативная память", content: <div><h2>Оперативная память</h2><p>Описание оперативной памяти сервера сбора и ретрансляции информации.</p></div> },
system_software_3: { title: "Жесткий диск", content: <div><h2>Жесткий диск</h2><p>Описание жесткого диска сервера сбора и ретрансляции информации.</p></div> },
system_software_4: { title: "Сетевые адаптеры", content: <div><h2>Сетевые адаптеры</h2><p>Описание сетевых адаптеров сервера сбора и ретрансляции информации.</p></div> },
software_1: { title: "ПО", content: <div><h2>Программное обеспечение сервера сбора и ретрансляции информации</h2><p>Описание ПО сервера сбора и ретрансляции информации.</p></div> },
software_2: { title: "ПО", content: <div><h2>Программное обеспечение сервера сбора и ретрансляции информации</h2><p>Описание ПО сервера сбора и ретрансляции информации.</p></div> },
software_3: { title: "ПО", content: <div><h2>Программное обеспечение сервера сбора и ретрансляции информации</h2><p>Описание ПО сервера сбора и ретрансляции информации.</p></div> },
software_4: { title: "ПО", content: <div><h2>Программное обеспечение сервера сбора и ретрансляции информации</h2><p>Описание ПО сервера сбора и ретрансляции информации.</p></div> },
};
export default tabContent;

View File

@ -5,7 +5,6 @@ const Modal = ({ children, onClose }) => {
<div className="modal-overlay"> <div className="modal-overlay">
<div className="modal"> <div className="modal">
{children} {children}
<button onClick={onClose}>Закрыть</button>
</div> </div>
</div> </div>
); );

View File

@ -1,76 +1,150 @@
import React from "react"; import React, { useEffect, useRef, useState } from "react";
import "../../Style/TreeTable.css"; import "../../Style/TreeTable.css";
import { getStatusColor } from "../TreeChart/dataUtils"; // Импортируем функцию import { statusManager1, statusManager2 } from "../TreeChart/dataUtils";
const TreeTable = ({ data }) => { const TreeTable = ({ data }) => {
// Фильтруем данные, чтобы убрать "Функциональные задачи" const tableRef = useRef(null);
const filteredData = data.filter((item) => item.title !== "Функциональные задачи"); 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 (
<th key={item.id} colSpan={colSpan} className="tree-table-header" title={item.title}>
<div className="header-content">
<div className="status-indicator-bar" style={{ backgroundColor: statusManager1.getStatusColor(item.status) }} />
<div className="status-indicator-bar" style={{ backgroundColor: statusManager2.getStatusColor(item.status), marginLeft: "5px" }} />
{item.title}
</div>
</th>
);
});
};
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 (
<tr className="tree-table-row">
{items.map((item) => {
if (item.items && item.items.length > 0) {
return (
<React.Fragment key={item.id}>
{item.items.map((child) => (
<td key={child.id} className="tree-table-cell" title={child.title}>
<div className="cell-content">
<div className="status-indicator-bar" style={{ backgroundColor: statusManager1.getStatusColor(child.status) }} />
<div className="status-indicator-bar" style={{ backgroundColor: statusManager2.getStatusColor(child.status), marginLeft: "5px" }} />
{child.title}
</div>
</td>
))}
</React.Fragment>
);
} else {
return (
<td key={item.id} className="tree-table-cell" title={item.title}>
<div className="cell-content">
<div className="status-indicator-bar" style={{ backgroundColor: statusManager1.getStatusColor(item.status) }} />
<div className="status-indicator-bar" style={{ backgroundColor: statusManager2.getStatusColor(item.status), marginLeft: "5px" }} />
{item.title}
</div>
</td>
);
}
})}
</tr>
);
};
return ( return (
<div className="table-container"> <div className="tree-table-container">
<table className="tree-table"> <table ref={tableRef} className="tree-table" style={{ fontSize: `${fontSize}px` }}>
<thead> <thead>
{/* Первый уровень: Заголовки "Медиа сервер" */}
<tr> <tr>
{filteredData.map((item, index) => ( <th
<th key={index} colSpan="2" className="tree-table-header" style={{ backgroundColor: getStatusColor(item.status) }}> colSpan={filteredData.reduce((acc, item) => acc + (item.items ? item.items.length : 1), 0)}
{item.title} className="tree-table-header"
</th> title={data.title}
))} >
</tr> <div className="header-content">
{/* Второй уровень: "АО" и "ПО" */} <div className="status-indicator-bar" style={{ backgroundColor: statusManager1.getStatusColor(data.status) }} />
<tr> <div className="status-indicator-bar" style={{ backgroundColor: statusManager2.getStatusColor(data.status), marginLeft: "5px" }} />
{filteredData.map((item, index) => ( {data.title}
<React.Fragment key={index}> </div>
<td className="tree-table-subheader" style={{ backgroundColor: getStatusColor(item.items[0]?.status) }}> </th>
АО
</td>
<td className="tree-table-subheader" style={{ backgroundColor: getStatusColor(item.items[1]?.status) }}>
ПО
</td>
</React.Fragment>
))}
</tr> </tr>
<tr>{renderHeaders(filteredData)}</tr>
</thead> </thead>
<tbody> <tbody>{renderRows(filteredData)}</tbody>
{/* Третий уровень: Вложенные элементы "АО" и "ПО" */}
{renderRows(filteredData)}
</tbody>
</table> </table>
<button onClick={() => setIsLogVisible(!isLogVisible)} className="toggle-log-button">
{isLogVisible ? "Скрыть лог" : "Показать лог"}
</button>
{isLogVisible && (
<div className="status-log">
<h3>Лог статусов</h3>
<ul>
{log.map((entry, index) => (
<li key={index} style={{ color: statusManager1.getStatusColor(entry.status) }}>
[{entry.time}] {entry.status}: {entry.title}
</li>
))}
</ul>
</div>
)}
</div> </div>
); );
}; };
// Функция для отображения строк с вложенными элементами
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(
<tr key={i} className="tree-table-row">
{data.map((item, index) => (
<React.Fragment key={index}>
<td className="tree-table-cell" style={{ backgroundColor: getStatusColor(item.items[0]?.items[i]?.status) }}>
{item.items[0]?.items[i]?.title || ""}
</td>
<td className="tree-table-cell" style={{ backgroundColor: getStatusColor(item.items[1]?.items[i]?.status) }}>
{item.items[1]?.items[i]?.title || ""}
</td>
</React.Fragment>
))}
</tr>
);
}
return rows;
};
export default TreeTable; export default TreeTable;

View File

@ -2,46 +2,11 @@
.dashboard-container { .dashboard-container {
display: flex; display: flex;
height: 100vh; height: 100vh;
width: calc(100vw - 20px); /* Учитываем отступ */ width: calc(100vw - 20px);
overflow: hidden; overflow: hidden;
margin-left: 20px; margin-left: 20px;
} background-color: var(--background-color);
color: var(--text-color);
/* Сайдбар */
.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);
/* Эффект при наведении */
} }
/* Основной контент */ /* Основной контент */
@ -50,24 +15,27 @@
padding: 20px; padding: 20px;
margin-left: 50px; margin-left: 50px;
transition: margin-left 0.2s ease; transition: margin-left 0.2s ease;
overflow: auto; /* Позволяет прокручивать контент, если он не влезает */ overflow: auto;
background-color: var(--background-color);
color: var(--text-color);
} }
/* Контент */ /* Контент */
.content { .content {
background-color: #f9f9f9; background-color: var(--modal-background);
padding: 20px; padding: 20px;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 2px 5px rgba(29, 1, 1, 0.521); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.521);
max-width: 100%; /* Гарантируем, что контент не выйдет за границы */ max-width: 100%;
overflow: auto; /* Включаем скролл, если нужно */ overflow: auto;
color: var(--text-color);
} }
/* Заголовки */ /* Заголовки */
h2 { h2 {
color: #444; color: var(--text-color);
} }
p { p {
color: #333; color: var(--text-color);
} }

View File

@ -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;
}

View File

@ -11,12 +11,13 @@
} }
.modal { .modal {
background: rgb(255, 255, 255); background: var(--modal-background);
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
max-width: 400px; max-width: 400px;
width: 100%; width: 100%;
color: var(--modal-text);
} }
.modal h2 { .modal h2 {
@ -26,7 +27,7 @@
.modal label { .modal label {
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;
color: black; color: var(--modal-text);
} }
.modal input { .modal input {
@ -35,20 +36,22 @@
margin-bottom: 10px; margin-bottom: 10px;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 4px; border-radius: 4px;
background-color: var(--modal-background);
color: var(--modal-text);
} }
.modal button { .modal button {
padding: 10px 20px; padding: 10px 20px;
margin-bottom: 5px; margin-bottom: 5px;
background: #08294b; background: var(--accent-color);
color: white; color: var(--text-color);
border: none; border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
} }
.modal button:hover { .modal button:hover {
background: #0056b3; background: var(--accent-hover-color);
} }
.error { .error {

View File

@ -1,15 +1,15 @@
/* Боковое меню */ /* Сайдбар */
.sidebar { .sidebar {
height: 100vh; height: 100vh;
background-color: #3d74c7; background-color: var(--sidebar-color);
color: white; color: var(--sidebar-text-color);
/* Используем переменную для цвета текста */
position: fixed; position: fixed;
left: 0; left: 0;
top: 0; top: 0;
z-index: 999; z-index: 999;
overflow: hidden; overflow: hidden;
transition: width 0.2s ease; transition: width 0.2s ease;
/* Плавное изменение ширины */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@ -18,11 +18,8 @@
.sidebar-content { .sidebar-content {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
/* Вертикальная прокрутка */
overflow-x: hidden; overflow-x: hidden;
/* Убираем горизонтальную прокрутку */
padding-bottom: 20px; padding-bottom: 20px;
/* Отступ для "Помощи" и "Настроек" */
} }
/* Заголовок меню */ /* Заголовок меню */
@ -30,23 +27,39 @@
margin-bottom: 20px; margin-bottom: 20px;
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
color: white; color: var(--sidebar-text-color);
/* Используем переменную для цвета текста */
padding: 10px; padding: 10px;
/* Добавляем отступы */
} }
/* Элементы меню */ /* Элементы меню */
.menu-item { .menu-item {
margin-bottom: 10px; margin-bottom: 10px;
color: white; color: var(--sidebar-text-color);
/* Используем переменную для цвета текста */
width: 100%; 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 { .menu-item-header {
display: flex; display: flex;
align-items: center; align-items: center;
/* Выравниваем элементы по центру */
padding: 10px; padding: 10px;
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: pointer;
@ -55,7 +68,6 @@
.menu-item-header:hover { .menu-item-header:hover {
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
/* Легкий эффект при наведении */
} }
/* Круглый индикатор статуса */ /* Круглый индикатор статуса */
@ -63,35 +75,8 @@
width: 10px; width: 10px;
height: 10px; height: 10px;
border-radius: 50%; border-radius: 50%;
/* Делаем круглым */
margin-right: 10px; margin-right: 10px;
/* Отступ от текста */
flex-shrink: 0; 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 { .sidebar-footer {
padding: 10px; padding: 10px;
background-color: #3d74c7; background-color: var(--sidebar-color);
text-align: center; text-align: center;
border-top: 1px solid rgba(255, 255, 255, 0.1); border-top: 1px solid rgba(255, 255, 255, 0.1);
/* Разделительная линия */
flex-shrink: 0; flex-shrink: 0;
/* Запрещаем сжатие */
width: 100%; width: 100%;
/* Ширина на всю ширину сайдбара */
} }
.help, .help,
.settings { .settings {
color: white; color: var(--sidebar-text-color);
/* Используем переменную для цвета текста */
margin: 5px 0; margin: 5px 0;
/* Отступы между элементами */
overflow-x: hidden; overflow-x: hidden;
/* Убираем горизонтальную прокрутку */
text-align: left; text-align: left;
} }

9
src/Style/TreeChart.css Normal file
View File

@ -0,0 +1,9 @@
svg {
user-select: none;
/* Запрет выделения текста */
}
text {
pointer-events: none;
/* Запрет взаимодействия с текстом */
}

View File

@ -1,58 +1,48 @@
/* Контейнер для таблицы с прокруткой */ .tree-table-container {
.table-container {
width: 100%; width: 100%;
/* Занимает всю доступную ширину */
overflow-x: auto; overflow-x: auto;
/* Горизонтальная прокрутка при необходимости */
margin: 0 auto;
/* Центрирование контейнера */
} }
/* Стили для таблицы */
.tree-table { .tree-table {
width: auto; width: 100%;
/* Автоматическая ширина, чтобы таблица могла расширяться */
min-width: 95%;
/* Минимальная ширина таблицы */
border-collapse: collapse; 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 { .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-cell {
.tree-table-row:nth-child(even) { padding: 8px;
background-color: #f9f9f9; 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;
} }

View File

@ -1,72 +1,53 @@
/* src/Style/common.css */
/* Контейнер для вкладок */ /* Контейнер для вкладок */
.tabs { .tabs {
display: flex; display: flex;
gap: 5px; gap: 5px;
/* Расстояние между вкладками */
padding: 5px; padding: 5px;
background-color: #3d74c7; background-color: var(--sidebar-color);
/* Цвет фона */ border-bottom: 2px solid var(--accent-color);
border-bottom: 2px solid #195fc9;
/* Линия под вкладками */
overflow-x: auto; overflow-x: auto;
/* Прокрутка, если вкладок много */
border-radius: 5px; border-radius: 5px;
/* Скругление углов */
white-space: nowrap; white-space: nowrap;
/* Запрет переноса текста */
} }
/* Стили для отдельной вкладки */ /* Стили для отдельной вкладки */
.tab { .tab {
display: flex; display: flex;
align-items: center; align-items: center;
background-color: #3d74c7; background-color: var(--sidebar-color);
/* Цвет фона вкладки */ color: var(--sidebar-text-color);
color: white; /* Используем переменную для цвета текста */
/* Цвет текста */
padding: 5px 15px; padding: 5px 15px;
/* Отступы внутри вкладки */
border-radius: 5px 5px 0 0; border-radius: 5px 5px 0 0;
/* Скругление углов */
cursor: pointer; cursor: pointer;
/* Курсор при наведении */
flex-shrink: 0; flex-shrink: 0;
/* Запрет сжатия */
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
/* Плавное изменение цвета */
} }
/* Активная вкладка */ /* Активная вкладка */
.tab.active { .tab.active {
background-color: #195fc9; background-color: var(--accent-color);
/* Цвет фона активной вкладки */
} }
/* Кнопка закрытия вкладки */ /* Кнопка закрытия вкладки */
.close-tab { .close-tab {
background: none; background: none;
border: none; border: none;
color: white; color: var(--sidebar-text-color);
/* Цвет крестика */ /* Используем переменную для цвета текста */
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 16px;
margin-left: 10px; margin-left: 10px;
/* Отступ от текста */
padding: 0; padding: 0;
transition: color 0.3s ease; transition: color 0.3s ease;
/* Плавное изменение цвета */
} }
/* Эффект при наведении на кнопку закрытия */ /* Эффект при наведении на кнопку закрытия */
.close-tab:hover { .close-tab:hover {
color: #ff6b6b; color: #ff6b6b;
/* Цвет крестика при наведении */
} }
/* Эффект при наведении на вкладку */ /* Эффект при наведении на вкладку */
.tab:hover { .tab:hover {
background-color: #195fc9; background-color: var(--accent-hover-color);
/* Цвет фона при наведении */
} }

19
src/Style/dark-theme.css Normal file
View File

@ -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;
/* Светлый текст в таблице */
}
}

17
src/Style/light-theme.css Normal file
View File

@ -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;
/* Черный текст в таблице */
}

View File

@ -2,6 +2,8 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import './index.css' import './index.css'
import App from './App.jsx' import App from './App.jsx'
import './Style/light-theme.css'; // Подключаем светлую тему по умолчанию
import './Style/dark-theme.css'; // Подключаем темную тему
createRoot(document.getElementById('root')).render( createRoot(document.getElementById('root')).render(
<StrictMode> <StrictMode>