Обновил дизайн статусов и добавил адаптивный сайдбар

pull/8/head
DmitriyA 2025-02-28 08:42:48 -05:00
parent eaf706ddfe
commit 750f06a4e5
7 changed files with 249 additions and 80 deletions

View File

@ -14,7 +14,7 @@ const PrometheusChart = ({ metricName }) => {
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
try { try {
const response = await axios.get(`http://192.168.2.39:3000/metrics?metric=zvks_apiforsnmp_ifOutUnicastPacket1`); const response = await axios.get(`http://192.168.2.39:3000/metrics?metric=zvks_apiforsnmp_cpurawsystem`);
const result = response.data; const result = response.data;
// Проверяем структуру данных // Проверяем структуру данных

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useRef } 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";
@ -14,8 +14,11 @@ const Dashboard = () => {
const [activeTab, setActiveTab] = useState("Главная"); const [activeTab, setActiveTab] = useState("Главная");
const [tabContent, setTabContent] = useState({}); const [tabContent, setTabContent] = useState({});
const [treeData, setTreeData] = useState(menuData); // Загружаем меню в state const [treeData, setTreeData] = useState(menuData); // Загружаем меню в state
const [sidebarWidth, setSidebarWidth] = useState(250); // Начальная ширина сайдбара
const [isResizing, setIsResizing] = useState(false); // Состояние перетаскивания
const sidebarRef = useRef(null); // Референс на сайдбар
// Обновление treeData каждые 10 секунд // Обновление treeData каждые 30 секунд
useEffect(() => { useEffect(() => {
setTabContent(tabContentData); setTabContent(tabContentData);
@ -25,11 +28,48 @@ const Dashboard = () => {
updateStatuses(updatedData); // Обновляем статусы updateStatuses(updatedData); // Обновляем статусы
return updatedData; return updatedData;
}); });
}, 10000); }, 30000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, []);
// Обработчик начала перетаскивания
const startResizing = (e) => {
e.preventDefault();
setIsResizing(true);
};
// Обработчик движения мыши
const resize = (e) => {
if (isResizing) {
const newWidth = e.clientX; // Новая ширина сайдбара
if (newWidth > 100 && newWidth < 400) { // Ограничиваем минимальную и максимальную ширину
setSidebarWidth(newWidth);
}
}
};
// Обработчик окончания перетаскивания
const stopResizing = () => {
setIsResizing(false);
};
// Добавляем обработчики событий
useEffect(() => {
const handleMouseMove = (e) => resize(e);
const handleMouseUp = () => stopResizing();
if (isResizing) {
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp);
}
return () => {
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
};
}, [isResizing]);
const handleOpenTab = (id, title) => { const handleOpenTab = (id, title) => {
if (!tabs.some((tab) => tab.id === id)) { if (!tabs.some((tab) => tab.id === id)) {
setTabs([...tabs, { id, title }]); setTabs([...tabs, { id, title }]);
@ -51,7 +91,8 @@ const Dashboard = () => {
<div> <div>
<h2>Общий мониторинг</h2> <h2>Общий мониторинг</h2>
<ErrorIndicator /> <ErrorIndicator />
<TreeTable data={treeData.items} /> {/* Теперь используем актуальные данные */} <TreeTable data={treeData.items} /> {/* Используем актуальные данные */}
</div> </div>
); );
} else if (activeTab === "Визуализация") { } else if (activeTab === "Визуализация") {
@ -64,8 +105,20 @@ const Dashboard = () => {
return ( return (
<div className="dashboard-container"> <div className="dashboard-container">
<SidebarMenu data={treeData} onOpenTab={handleOpenTab} /> {/* Передаём обновлённые данные */} <div
<div className="main-content"> className="sidebar"
ref={sidebarRef}
style={{ width: sidebarWidth }} // Динамическая ширина сайдбара
>
<SidebarMenu data={treeData} onOpenTab={handleOpenTab} sidebarWidth={sidebarWidth} />
{/* Элемент для перетаскивания */}
<div
className="sidebar-resizer"
onMouseDown={startResizing}
/>
</div>
<div className="main-content" style={{ marginLeft: sidebarWidth }}>
<Tabs <Tabs
tabs={tabs} tabs={tabs}
activeTab={activeTab} activeTab={activeTab}

View File

@ -2,10 +2,10 @@ import React, { useState } from "react";
import "../../Style/SidebarMenu.css"; import "../../Style/SidebarMenu.css";
import { getStatusColor } from "../TreeChart/dataUtils"; // Импортируем только нужную функцию import { getStatusColor } from "../TreeChart/dataUtils"; // Импортируем только нужную функцию
const MenuItem = ({ item, onSelectItem }) => { const MenuItem = ({ item, onSelectItem, sidebarWidth }) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const hasChildren = Array.isArray(item.items) && item.items.length > 0; const hasChildren = Array.isArray(item.items) && item.items.length > 0;
const backgroundColor = getStatusColor(item.status); const statusColor = getStatusColor(item.status);
const handleClick = () => { const handleClick = () => {
if (hasChildren) { if (hasChildren) {
@ -16,15 +16,20 @@ const MenuItem = ({ item, onSelectItem }) => {
}; };
return ( return (
<div className="menu-item"> <div className="menu-item" style={{ width: sidebarWidth - 20 }}> {/* Динамическая ширина */}
<div onClick={handleClick} className="menu-item-header" style={{ backgroundColor }}> <div onClick={handleClick} className="menu-item-header">
{/* Круглый индикатор статуса */}
<div
className={`status-indicator ${statusColor === "red" ? "blinking" : ""}`}
style={{ backgroundColor: statusColor }}
/>
<span>{item.title}</span> <span>{item.title}</span>
{hasChildren && <span>{isOpen ? "▲" : "▼"}</span>} {hasChildren && <span>{isOpen ? "▲" : "▼"}</span>}
</div> </div>
{isOpen && hasChildren && ( {isOpen && hasChildren && (
<div className="submenu"> <div className="submenu">
{item.items.map((child, index) => ( {item.items.map((child, index) => (
<MenuItem key={index} item={child} onSelectItem={onSelectItem} /> <MenuItem key={index} item={child} onSelectItem={onSelectItem} sidebarWidth={sidebarWidth} />
))} ))}
</div> </div>
)} )}
@ -32,15 +37,21 @@ const MenuItem = ({ item, onSelectItem }) => {
); );
}; };
function SidebarMenu({ data, onOpenTab }) { // Теперь получаем `data` из пропсов function SidebarMenu({ data, onOpenTab, sidebarWidth }) {
const handleSelectItem = (item) => { const handleSelectItem = (item) => {
onOpenTab(item.id, item.title); onOpenTab(item.id, item.title);
}; };
return ( return (
<div className="sidebar"> <div className="sidebar">
<div className="sidebar-content" style={{ width: sidebarWidth }}> {/* Динамическая ширина */}
<h2 className="sidebar-title">Меню</h2> <h2 className="sidebar-title">Меню</h2>
<MenuItem item={data} onSelectItem={handleSelectItem} /> <MenuItem item={data} onSelectItem={handleSelectItem} sidebarWidth={sidebarWidth} />
</div>
<div className="sidebar-footer" style={{ width: sidebarWidth }}> {/* Динамическая ширина */}
<h2 className="help">Помощь</h2>
<h2 className="settings">Настройка</h2>
</div>
</div> </div>
); );
} }

View File

@ -1,10 +1,10 @@
// Функция для генерации случайных статусов // Функция для генерации случайных статусов
const getRandomStatus = () => { const getRandomStatus = () => {
const statuses = [ const statuses = [
"green", "green", "green", "green", "green", "green", "green", // 7/10 chance ...Array(90).fill("green"), // 63/70 chance (примерно 90%)
"yellow", // 1/10 chance ...Array(6).fill("yellow"), // 1/70 chance (примерно 1.43%)
"orange", // 1/10 chance ...Array(3).fill("orange"), // 1/70 chance (примерно 1.43%)
"red", // 1/10 chance ...Array(1).fill("red"), // 1/70 chance (примерно 1.43%)
]; ];
return statuses[Math.floor(Math.random() * statuses.length)]; return statuses[Math.floor(Math.random() * statuses.length)];
}; };

View File

@ -1,21 +1,56 @@
/* Основной контейнер */
.dashboard-container { .dashboard-container {
display: flex; display: flex;
height: 100vh; height: 100vh;
width: 100vw; width: 98vw;
overflow: hidden; 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);
/* Эффект при наведении */
}
/* Основной контент */
.main-content { .main-content {
flex: 1; flex: 1;
min-width: 400px;
max-width: calc(100vw - 250px);
padding: 20px; padding: 20px;
box-sizing: border-box; margin-left: 50px;
overflow-y: auto; transition: margin-left 0.2s ease;
/* Добавляем вертикальную прокрутку */ /* Плавное изменение отступа */
height: 100vh;
/* Ограничиваем высоту */
} }
/* Контент */ /* Контент */
@ -26,19 +61,7 @@
box-shadow: 0 2px 5px rgba(29, 1, 1, 0.521); box-shadow: 0 2px 5px rgba(29, 1, 1, 0.521);
} }
.default-content { /* Заголовки */
display: flex;
flex-direction: column;
gap: 50px;
}
.tab-content {
background-color: #fff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
h2 { h2 {
color: #444; color: #444;
} }

View File

@ -1,71 +1,124 @@
/* Боковое меню */ /* Боковое меню */
.sidebar { .sidebar {
width: 270px;
background-color: #3d74c7;
padding: 20px;
box-sizing: border-box;
border-right: 1px solid white;
height: 100vh; height: 100vh;
/* Занимает всю высоту экрана */ background-color: #3d74c7;
overflow-y: auto; color: white;
/* Прокрутка внутри меню, если контент не помещается */ position: fixed;
position: sticky; left: 0;
/* Фиксируем меню */
top: 0; top: 0;
/* Прилипаем к верху */ z-index: 999;
overflow: hidden;
transition: width 0.2s ease;
/* Плавное изменение ширины */
display: flex;
flex-direction: column;
} }
/* Контейнер для основного контента меню */
.sidebar-content {
flex: 1;
overflow-y: auto;
/* Вертикальная прокрутка */
overflow-x: hidden;
/* Убираем горизонтальную прокрутку */
padding-bottom: 20px;
/* Отступ для "Помощи" и "Настроек" */
}
/* Заголовок меню */
.sidebar-title { .sidebar-title {
margin-bottom: 20px; margin-bottom: 20px;
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
color: white; color: white;
padding: 10px;
/* Добавляем отступы */
} }
/* Элементы меню */
.menu-item { .menu-item {
margin-bottom: 10px; margin-bottom: 10px;
color: white; color: white;
} width: 100%;
/* Ширина на всю ширину сайдбара */
h2 {
color: white
} }
.menu-item-header { .menu-item-header {
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
/* Выравниваем элементы по центру */
padding: 10px; padding: 10px;
background-color: #3d74c7;
border-radius: 5px; border-radius: 5px;
border: 1px solid white;
cursor: pointer; cursor: pointer;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
.menu-item-header:hover { .menu-item-header:hover {
background-color: #195fc9; background-color: rgba(255, 255, 255, 0.1);
/* Легкий эффект при наведении */
} }
/* Круглый индикатор статуса */
.status-indicator {
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 секунда */
}
/* Подменю */
.submenu { .submenu {
margin-left: 20px; margin-left: 20px;
margin-top: 10px; margin-top: 10px;
} }
.tabs-container { /* Футер сайдбара */
margin-top: 20px; .sidebar-footer {
}
.tab {
padding: 10px; padding: 10px;
background-color: #3d74c7; background-color: #3d74c7;
border: 1px solid white; text-align: center;
border-radius: 5px; border-top: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 5px; /* Разделительная линия */
cursor: pointer; flex-shrink: 0;
transition: background-color 0.3s ease; /* Запрещаем сжатие */
width: 100%;
/* Ширина на всю ширину сайдбара */
} }
.tab:hover { .help,
background-color: #3d74c7; .settings {
color: white;
margin: 5px 0;
/* Отступы между элементами */
overflow-x: hidden;
/* Убираем горизонтальную прокрутку */
text-align: left;
} }

View File

@ -1,43 +1,72 @@
/* src/Style/common.css */ /* src/Style/common.css */
/* Вкладки */
/* Контейнер для вкладок */
.tabs { .tabs {
display: flex; display: flex;
gap: 5px; gap: 5px;
/* Расстояние между вкладками */
padding: 5px; padding: 5px;
background-color: #3d74c7; background-color: #3d74c7;
/* Цвет фона */
border-bottom: 2px solid #195fc9; 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: #3d74c7;
/* Цвет фона вкладки */
color: white; color: white;
padding: 5px 10px; /* Цвет текста */
padding: 5px 15px;
/* Отступы внутри вкладки */
border-radius: 5px 5px 0 0; border-radius: 5px 5px 0 0;
/* Скругление углов */
cursor: pointer; cursor: pointer;
max-width: 250px; /* Курсор при наведении */
min-width: 100px;
flex-shrink: 0; flex-shrink: 0;
position: relative; /* Запрет сжатия */
transition: background-color 0.3s ease;
/* Плавное изменение цвета */
} }
/* Активная вкладка */
.tab.active { .tab.active {
background-color: #195fc9; background-color: #195fc9;
/* Цвет фона активной вкладки */
} }
/* Кнопка закрытия вкладки */
.close-tab { .close-tab {
background: none; background: none;
border: none; border: none;
color: white;
/* Цвет крестика */
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 16px;
margin-left: 10px;
/* Отступ от текста */
padding: 0; padding: 0;
transition: color 0.3s ease;
/* Плавное изменение цвета */
} }
.error { /* Эффект при наведении на кнопку закрытия */
color: red; .close-tab:hover {
margin-bottom: 10px; color: #ff6b6b;
/* Цвет крестика при наведении */
}
/* Эффект при наведении на вкладку */
.tab:hover {
background-color: #195fc9;
/* Цвет фона при наведении */
} }