Обновил интерфейс, добавил вкладки

feature/#23
DmitriyA 2025-02-07 13:06:10 +00:00
parent 46a8bbb35d
commit 637b559fe8
14 changed files with 374 additions and 222 deletions

View File

@ -13,7 +13,8 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"chart.js": "^4.0.0",
"react-chartjs-2": "^5.0.0"
"react-chartjs-2": "^5.0.0",
"axios": "^1.7.9"
},
"devDependencies": {
"@eslint/js": "^9.17.0",

9
public/data.json Normal file
View File

@ -0,0 +1,9 @@
{
"labels": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
"datasets": [
{
"data": [50, 52, 55, 53, 60, 58, 65, 62, 66, 72],
"data2": [20,56,74,45,21,20,56,74,45,21]
}
]
}

View File

@ -11,9 +11,11 @@
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@ -22,6 +24,7 @@
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
@ -39,4 +42,4 @@
.read-the-docs {
color: #888;
}
}

View File

@ -1,32 +1,13 @@
import React from "react";
import SidebarMenu from "./SidebarMenu/SidebarMenu"; // Импорт компонента бокового меню
import CpuTemperatureChart from './Charts/CpuTemperatureChart';
import ErrorIndicator from "./SidebarMenu/ErrorIndicator"; // Индикатор ошибок
import GpuTemperatureChart from './Charts/GpuTemperatureChart';
import RamUsageChart from './Charts/RamUsageChart'
import Dashboard from "./SidebarMenu/Dashboard";
function App() {
return (
<div style={{ display: "flex" }}>
<SidebarMenu />
<div style={{ marginLeft: "250px" }}>
<h1 className="text-xl font-bold">Мониторинг состояния системы</h1>
<div className="indicator-container">
{/* Индикатор ошибок */}
<h2 className="sidebar-indicator">Индикатор ошибок: </h2>
<ErrorIndicator criticalCount={3} warningCount={7} />
</div>
{/* График температуры CPU
<div className="flex justify-center items-center h-96 bg-gray-100">
<CpuTemperatureChart />
<GpuTemperatureChart />
<RamUsageChart />
</div> */}
<Dashboard/>
</div>
<div style={{ display: "flex", height: "100vh", overflow: "hidden" }}>
<Dashboard />
</div>
);
}
export default App;
export default App;

View File

@ -1,5 +1,6 @@
import React, { useEffect, useRef, useState } from "react";
import { Line } from "react-chartjs-2";
import axios from "axios"
import {
Chart as ChartJS,
LineElement,
@ -29,18 +30,28 @@ const CpuTemperatureChart = () => {
});
useEffect(() => {
const interval = setInterval(() => {
setData((prevData) => {
const newTemp = Math.floor(Math.random() * 20) + 50; // Генерируем новую температуру (50-70°C)
const newLabels = [...prevData.labels.slice(1), prevData.labels[prevData.labels.length - 1] + 1]; // Сдвигаем ось X
const newDataset = [...prevData.datasets[0].data.slice(1), newTemp]; // Сдвигаем данные влево
const fetchData = async () => {
try {
//const response = await axios.get("/data.json"); // Укажите путь к JSON-файлу
const response = await axios.get(
'http://backend:9101/metrics',
{
params: {
metric: 'CPU'
}
}
);
setData({
labels: response.data.labels,
data: response.data,
});
} catch (error) {
console.error("Ошибка загрузки данных:", error);
}
};
return {
labels: newLabels,
datasets: [{ ...prevData.datasets[0], data: newDataset }],
};
});
}, 1000); // Обновление каждую секунду
fetchData();
const interval = setInterval(fetchData, 5000); // Обновляем данные каждые 5 секунд
return () => clearInterval(interval);
}, []);
@ -55,7 +66,7 @@ const CpuTemperatureChart = () => {
return (
<div className="w-full max-w-2xl mx-auto p-4 flex flex-col">
<h2 className="text-xl font-semibold mb-4">График температуры ЦП</h2>
<h2 className="text-xl font-semibold mb-4">График температуры ЦП</h2>
<Line
ref={chartRef}
data={data}

View File

@ -1,5 +1,6 @@
import React, { useEffect, useRef, useState } from "react";
import { Line } from "react-chartjs-2";
import axios from "axios";
import {
Chart as ChartJS,
LineElement,
@ -18,7 +19,7 @@ const GpuTemperatureChart = () => {
datasets: [
{
label: "Температура GPU (°C)",
data: Array(20).fill(50), // Начальные значения (например, 50°C)
data: [], // Начальные значения (например, 50°C)
borderColor: "blue",
borderWidth: 2,
fill: false,
@ -29,18 +30,20 @@ const GpuTemperatureChart = () => {
});
useEffect(() => {
const interval = setInterval(() => {
setData((prevData) => {
const newTemp = Math.floor(Math.random() * 20) + 40; // Генерируем новую температуру (50-600°C)
const newLabels = [...prevData.labels.slice(1), prevData.labels[prevData.labels.length - 1] + 1]; // Сдвигаем ось X
const newDataset = [...prevData.datasets[0].data.slice(1), newTemp]; // Сдвигаем данные влево
const fetchData = async () => {
try {
const response = await axios.get("/data.json"); // Укажите путь к JSON-файлу
setData({
labels: response.data.labels,
datasets: [{ ...data.datasets[0], data: response.data.datasets[0].data }],
});
} catch (error) {
console.error("Ошибка загрузки данных:", error);
}
};
return {
labels: newLabels,
datasets: [{ ...prevData.datasets[0], data: newDataset }],
};
});
}, 1000); // Обновление каждую секунду
fetchData();
const interval = setInterval(fetchData, 5000); // Обновляем данные каждые 5 секунд
return () => clearInterval(interval);
}, []);
@ -54,7 +57,7 @@ const GpuTemperatureChart = () => {
return (
<div className="w-full max-w-2xl mx-auto p-4 flex flex-col">
<h2 className="text-xl font-semibold mb-4">График температуры ГП</h2>
<h2 className="text-xl font-semibold mb-4">График температуры ГП</h2>
<Line
ref={chartRef}
data={data}

View File

@ -51,12 +51,12 @@ const RamUsageChart = () => {
{ label: "Доступно", value: " 9,5 ГБ" },
{ label: "Выделено", value: " 6,8/18,2 ГБ" },
{ label: "Скорость", value: " 3200 МГц" },
];
return (
<div className="w-full max-w-2xl mx-auto p-4 flex flex-col">
<h2 className="text-xl font-semibold mb-4">График загруженности ОЗУ</h2>
<h2 className="text-xl font-semibold mb-4">График загруженности ОЗУ</h2>
<Line
ref={chartRef}
data={data}

View File

@ -1,42 +1,84 @@
import React from "react";
import React, { useState } from "react";
import SidebarMenu from "./SidebarMenu";
import CpuTemperatureChart from "../Charts/CpuTemperatureChart";
import GpuTemperatureChart from "../Charts/GpuTemperatureChart";
import RamUsageChart from "../Charts/RamUsageChart";
import "../Style/Dashboard.css";
import ErrorIndicator from "./ErrorIndicator";
import "../Style/Dashboard.css"; // Подключаем стили
const tabContent = {
"Сервис ВКС": <div><h2>Сервис 1</h2></div>,
"Сервис 2": <div><h2>Сервис 2</h2></div>,
"Сервис 3": <div><h2>Сервис 3</h2></div>,
"Контроль системы": <div><h2>Контроль системы</h2><p>Описание контроля.</p></div>,
"Система управления": <div><h2>Система управления</h2><p>Описание системы управления.</p></div>,
"Проведение ВКС": <div><h2>Проведение ВКС</h2><p>Информация о проведении ВКС.</p></div>,
"Резервное копирование": <div><h2>Резервное копирование</h2><p>Процесс резервного копирования.</p></div>,
"Ретрансляция информации": <div><h2>Ретрансляция информации</h2><p>Детали ретрансляции.</p></div>,
};
const Dashboard = () => {
const [tabs, setTabs] = useState([]); // Открытые вкладки
const [activeTab, setActiveTab] = useState("Главная"); // Текущая активная вкладка
const handleOpenTab = (tabName) => {
if (!tabs.includes(tabName)) {
setTabs([...tabs, tabName]);
}
setActiveTab(tabName);
};
const handleCloseTab = (tabName) => {
const newTabs = tabs.filter(tab => tab !== tabName);
setTabs(newTabs);
if (activeTab === tabName) {
setActiveTab(newTabs.length > 0 ? newTabs[newTabs.length - 1] : "Главная");
}
};
return (
<div className="dashboard-container">
{/* Левая колонка (Графики) */}
<div className="left-column">
<CpuTemperatureChart />
<GpuTemperatureChart /> {/* Можно заменить на другие графики */}
<RamUsageChart />
</div>
<SidebarMenu onOpenTab={handleOpenTab} />
{/* Правая колонка (Информационный блок) */}
<div className="right-column">
<h2>Информационный блок</h2>
<p>Здесь можно выводить любые данные о системе.</p>
<div className="info-item">
<span className="label">Температура CPU:</span>
<span className="value">65°C</span>
<div className="main-content">
{/* Вкладки */}
<div className="tabs">
<div
className={`tab ${activeTab === "Главная" ? "active" : ""}`}
onClick={() => setActiveTab("Главная")}
>
Главная
</div>
{tabs.map(tab => (
<div
key={tab}
className={`tab ${activeTab === tab ? "active" : ""}`}
onClick={() => setActiveTab(tab)}
>
{tab}
<button
className="close-tab"
onClick={(e) => { e.stopPropagation(); handleCloseTab(tab); }}
>
×
</button>
</div>
))}
</div>
<div className="info-item">
<span className="label">Загрузка процессора:</span>
<span className="value">45%</span>
</div>
<div className="info-item">
<span className="label">Ошибки аппаратного обеспечения:</span>
<span className="value error">2</span>
</div>
<div className="info-item">
<span className="label">Ошибки программного обеспечения:</span>
<span className="value error">1</span>
{/* Контент */}
<div className="content">
{activeTab === "Главная" ? (
<div>
<h2>Общий мониторинг</h2>
<ErrorIndicator />
<CpuTemperatureChart />
<GpuTemperatureChart />
<RamUsageChart />
</div>
) : (
tabContent[activeTab] || <p>Нет контента</p>
)}
</div>
</div>
</div>

View File

@ -1,5 +1,6 @@
import React, { useState } from "react";
import "../Style/SidebarMenu.css"; // Импортируем стили для компонента
import "../Style/SidebarMenu.css";
const menuItems = [
{
title: "Выбор сервиса",
@ -11,42 +12,78 @@ const menuItems = [
},
{
title: "Программное обеспечение",
items: ["ПО 1", "ПО 2", "ПО 3"],
items: [
{
title: "ПО 1",
items: ["компонент ПО1", "компонент ПО2"],
},
{
title: "ПО 2",
items: ["компонент ПО3"],
},
],
},
{
title: "Аппаратное обеспечение",
items: ["Оборудование 1", "Оборудование 2", "Оборудование 3"],
items: [
{
title: "Оборудование 1",
items: ["компонент Оборудование 1"],
},
{
title: "Оборудование 2",
items: ["компонент Оборудование 2"],
},
],
},
];
function SidebarMenu() {
const [openSections, setOpenSections] = useState({});
// Рекурсивный компонент для отображения меню
const MenuItem = ({ item, onSelectItem }) => {
const [isOpen, setIsOpen] = useState(false);
const toggleSection = (title) => {
setOpenSections((prev) => ({ ...prev, [title]: !prev[title] }));
const hasChildren = Array.isArray(item.items) && item.items.length > 0;
const handleClick = () => {
if (hasChildren) {
setIsOpen(!isOpen); // Раскрываем/сворачиваем подменю
} else {
onSelectItem(item); // Выбираем конечный элемент
}
};
return (
<div className="menu-item">
<div onClick={handleClick} className="menu-item-header">
<span>{item.title}</span>
{hasChildren && <span>{isOpen ? "▲" : "▼"}</span>}
</div>
{isOpen && hasChildren && (
<div className="submenu">
{item.items.map((child, index) => (
<MenuItem
key={index}
item={typeof child === "string" ? { title: child } : child}
onSelectItem={onSelectItem}
/>
))}
</div>
)}
</div>
);
};
// Основной компонент SidebarMenu
function SidebarMenu({ onOpenTab }) {
const handleSelectItem = (item) => {
onOpenTab(item.title); // Передаем название вкладки в родительский компонент
};
return (
<div className="sidebar">
<h2 className="sidebar-title">Меню</h2>
{menuItems.map((section) => (
<div key={section.title} className="sidebar-section">
<button
onClick={() => toggleSection(section.title)}
className="sidebar-button"
>
{section.title}
</button>
{openSections[section.title] && (
<ul className="sidebar-items">
{section.items.map((item) => (
<li key={item} className="sidebar-item">
{item}
</li>
))}
</ul>
)}
</div>
{menuItems.map((section, index) => (
<MenuItem key={index} item={section} onSelectItem={handleSelectItem} />
))}
</div>
);

View File

@ -1,42 +1,89 @@
.dashboard-container {
display: flex;
gap: 20px;
padding: 20px;
}
.left-column {
flex: 2;
}
.right-column {
flex: 1;
background: #f3f3f3;
padding: 20px;
border-radius: 8px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
}
h2 {
font-size: 20px;
margin-bottom: 10px;
}
.info-item {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #ddd;
}
.label {
font-weight: bold;
}
.value {
color: #007bff;
font-weight: bold;
}
.value.error {
color: red;
}
display: flex;
height: 100vh;
width: 100vw;
overflow: hidden;
/* Запрещаем появление скролла */
}
.main-content {
flex: 1;
min-width: 400px;
max-width: calc(100vw - 250px);
padding: 20px;
box-sizing: border-box;
overflow-y: auto;
/* Добавляем вертикальную прокрутку */
height: 100vh;
/* Ограничиваем высоту */
}
/* Вкладки */
.tabs {
display: flex;
gap: 5px;
padding: 5px;
background-color: #222;
border-bottom: 2px solid #444;
overflow-x: auto;
white-space: nowrap;
}
.tab {
display: flex;
align-items: center;
background-color: #333;
color: white;
padding: 5px 10px;
border-radius: 5px 5px 0 0;
cursor: pointer;
max-width: 250px;
/* Ограничиваем максимальную ширину */
min-width: 100px;
/* Минимальная ширина */
flex-shrink: 0;
/* Не позволяет вкладкам сжиматься */
position: relative;
}
.tab.active {
background-color: #555;
}
.close-tab {
background: none;
border: none;
cursor: pointer;
font-size: 16px;
padding: 0;
}
/* Контент */
.content {
background-color: #f9f9f9;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.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;
}
p {
color: #333;
}

View File

@ -26,4 +26,11 @@
.warning span {
color: orange;
}
.indicator-container {
display: flex;
align-items: center;
gap: 15px;
justify-content: center;
}

View File

@ -1,38 +1,39 @@
.expandable-info {
margin-top: 10px;
}
.expand-button {
background-color: #444;
color: white;
border: none;
padding: 8px 16px;
cursor: pointer;
border-radius: 4px;
}
.expand-button:hover {
background-color: #333;
}
.details-menu {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #f9f9f9;
}
.detail-item {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
.label {
font-weight: bold;
}
.value {
color: #555;
}
margin-top: 10px;
}
.expand-button {
background-color: #444;
color: white;
border: none;
padding: 8px 16px;
cursor: pointer;
border-radius: 4px;
}
.expand-button:hover {
background-color: #333;
}
.details-menu {
margin-top: 10px;
padding: 10px;
border: 1px solid #333;
border-radius: 4px;
background-color: white;
}
.detail-item {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
.label {
font-weight: bold;
color: #333
}
.value {
color: #333;
}

View File

@ -1,66 +1,70 @@
/* SidebarMenu.css */
/* Боковое меню */
.sidebar {
position: fixed;
height: 100vh;
width: 250px;
/* height: 100vh; */
background-color: #333;
color: white;
padding: 20px;
box-sizing: border-box;
border-right: 1px solid #444;
height: 100vh;
/* Занимает всю высоту экрана */
overflow-y: auto;
/* Прокрутка внутри меню, если контент не помещается */
position: sticky;
/* Фиксируем меню */
top: 0;
/* Прилипаем к верху */
}
.sidebar-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
}
.sidebar-indicator {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
color: white;
}
.sidebar-section {
margin-bottom: 15px;
.menu-item {
margin-bottom: 10px;
color: white;
}
.sidebar-button {
width: 100%;
h2 {
color: white
}
.menu-item-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background-color: #444;
border: none;
color: white;
text-align: left;
cursor: pointer;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.sidebar-button:hover {
background-color: #555;
.menu-item-header:hover {
background-color: #222;
}
.sidebar-items {
list-style: none;
padding-left: 20px;
.submenu {
margin-left: 20px;
margin-top: 10px;
}
.sidebar-item {
padding: 5px 0;
.tabs-container {
margin-top: 20px;
}
.tab {
padding: 10px;
background-color: #444;
border: 1px solid #333;
border-radius: 5px;
margin-bottom: 5px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s ease;
}
.sidebar-item:hover {
color: #ccc;
}
.indicator-container {
display: flex;
align-items: center;
gap: 15px;
.tab:hover {
background-color: #222;
}

View File

@ -18,6 +18,7 @@ a {
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
@ -31,8 +32,9 @@ body {
}
h1 {
text-align: center;
font-size: 3.2em;
line-height: 1.1;
line-height: 1;
}
button {
@ -46,9 +48,11 @@ button {
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
@ -59,10 +63,12 @@ button:focus-visible {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
}