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

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(() => {
const fetchData = async () => {
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;
// Проверяем структуру данных

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import SidebarMenu from "./SidebarMenu";
import TreeChart from "../TreeChart/TreeChart";
import "../../Style/Dashboard.css";
@ -14,8 +14,11 @@ const Dashboard = () => {
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); // Референс на сайдбар
// Обновление treeData каждые 10 секунд
// Обновление treeData каждые 30 секунд
useEffect(() => {
setTabContent(tabContentData);
@ -25,11 +28,48 @@ const Dashboard = () => {
updateStatuses(updatedData); // Обновляем статусы
return updatedData;
});
}, 10000);
}, 30000);
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) => {
if (!tabs.some((tab) => tab.id === id)) {
setTabs([...tabs, { id, title }]);
@ -51,7 +91,8 @@ const Dashboard = () => {
<div>
<h2>Общий мониторинг</h2>
<ErrorIndicator />
<TreeTable data={treeData.items} /> {/* Теперь используем актуальные данные */}
<TreeTable data={treeData.items} /> {/* Используем актуальные данные */}
</div>
);
} else if (activeTab === "Визуализация") {
@ -64,8 +105,20 @@ const Dashboard = () => {
return (
<div className="dashboard-container">
<SidebarMenu data={treeData} onOpenTab={handleOpenTab} /> {/* Передаём обновлённые данные */}
<div className="main-content">
<div
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}
activeTab={activeTab}

View File

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

View File

@ -1,10 +1,10 @@
// Функция для генерации случайных статусов
const getRandomStatus = () => {
const statuses = [
"green", "green", "green", "green", "green", "green", "green", // 7/10 chance
"yellow", // 1/10 chance
"orange", // 1/10 chance
"red", // 1/10 chance
...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)];
};

View File

@ -1,21 +1,56 @@
/* Основной контейнер */
.dashboard-container {
display: flex;
height: 100vh;
width: 100vw;
width: 98vw;
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 {
flex: 1;
min-width: 400px;
max-width: calc(100vw - 250px);
padding: 20px;
box-sizing: border-box;
overflow-y: auto;
/* Добавляем вертикальную прокрутку */
height: 100vh;
/* Ограничиваем высоту */
margin-left: 50px;
transition: margin-left 0.2s ease;
/* Плавное изменение отступа */
}
/* Контент */
@ -26,19 +61,7 @@
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 {
color: #444;
}

View File

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

View File

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