Обновил дизайн статусов и добавил адаптивный сайдбар
parent
eaf706ddfe
commit
750f06a4e5
|
|
@ -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;
|
||||
|
||||
// Проверяем структуру данных
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
<div className="sidebar-content" style={{ width: sidebarWidth }}> {/* Динамическая ширина */}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
/* Цвет фона при наведении */
|
||||
}
|
||||
Loading…
Reference in New Issue