diff --git a/package.json b/package.json index 8a3d352..72c68e5 100755 --- a/package.json +++ b/package.json @@ -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", diff --git a/public/data.json b/public/data.json new file mode 100644 index 0000000..65e363b --- /dev/null +++ b/public/data.json @@ -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] + } + ] +} \ No newline at end of file diff --git a/src/App.css b/src/App.css index b9d355d..9c7629a 100755 --- a/src/App.css +++ b/src/App.css @@ -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; -} +} \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index 8c0ccf5..e2b16f2 100755 --- a/src/App.jsx +++ b/src/App.jsx @@ -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 ( -
- -
-

Мониторинг состояния системы

-
- {/* Индикатор ошибок */} -

Индикатор ошибок:

- -
- {/* График температуры CPU -
- - - -
*/} - -
+
+
); } -export default App; - +export default App; \ No newline at end of file diff --git a/src/Charts/CpuTemperatureChart.jsx b/src/Charts/CpuTemperatureChart.jsx index 97ff723..80ff592 100644 --- a/src/Charts/CpuTemperatureChart.jsx +++ b/src/Charts/CpuTemperatureChart.jsx @@ -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 (
-

График температуры ЦП

+

График температуры ЦП

{ 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 (
-

График температуры ГП

+

График температуры ГП

{ { label: "Доступно", value: " 9,5 ГБ" }, { label: "Выделено", value: " 6,8/18,2 ГБ" }, { label: "Скорость", value: " 3200 МГц" }, - + ]; return (
-

График загруженности ОЗУ

+

График загруженности ОЗУ

Сервис 1

, + "Сервис 2":

Сервис 2

, + "Сервис 3":

Сервис 3

, + "Контроль системы":

Контроль системы

Описание контроля.

, + "Система управления":

Система управления

Описание системы управления.

, + "Проведение ВКС":

Проведение ВКС

Информация о проведении ВКС.

, + "Резервное копирование":

Резервное копирование

Процесс резервного копирования.

, + "Ретрансляция информации":

Ретрансляция информации

Детали ретрансляции.

, +}; 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 (
- {/* Левая колонка (Графики) */} -
- - {/* Можно заменить на другие графики */} - -
+ - {/* Правая колонка (Информационный блок) */} -
-

Информационный блок

-

Здесь можно выводить любые данные о системе.

- -
- Температура CPU: - 65°C +
+ {/* Вкладки */} +
+
setActiveTab("Главная")} + > + Главная +
+ {tabs.map(tab => ( +
setActiveTab(tab)} + > + {tab} + +
+ ))}
-
- Загрузка процессора: - 45% -
- -
- Ошибки аппаратного обеспечения: - 2 -
-
- Ошибки программного обеспечения: - 1 + {/* Контент */} +
+ {activeTab === "Главная" ? ( +
+

Общий мониторинг

+ + + + +
+ ) : ( + tabContent[activeTab] ||

Нет контента

+ )}
diff --git a/src/SidebarMenu/SidebarMenu.jsx b/src/SidebarMenu/SidebarMenu.jsx index 9c15a40..ba521bb 100644 --- a/src/SidebarMenu/SidebarMenu.jsx +++ b/src/SidebarMenu/SidebarMenu.jsx @@ -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 ( +
+
+ {item.title} + {hasChildren && {isOpen ? "▲" : "▼"}} +
+ {isOpen && hasChildren && ( +
+ {item.items.map((child, index) => ( + + ))} +
+ )} +
+ ); +}; + +// Основной компонент SidebarMenu +function SidebarMenu({ onOpenTab }) { + const handleSelectItem = (item) => { + onOpenTab(item.title); // Передаем название вкладки в родительский компонент }; return (

Меню

- {menuItems.map((section) => ( -
- - {openSections[section.title] && ( -
    - {section.items.map((item) => ( -
  • - {item} -
  • - ))} -
- )} -
+ {menuItems.map((section, index) => ( + ))}
); diff --git a/src/Style/Dashboard.css b/src/Style/Dashboard.css index 7b6a12b..8a83bd3 100644 --- a/src/Style/Dashboard.css +++ b/src/Style/Dashboard.css @@ -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; - } \ No newline at end of file + 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; +} \ No newline at end of file diff --git a/src/Style/ErrorIndicator.css b/src/Style/ErrorIndicator.css index 477ded5..efce90a 100644 --- a/src/Style/ErrorIndicator.css +++ b/src/Style/ErrorIndicator.css @@ -26,4 +26,11 @@ .warning span { color: orange; +} + +.indicator-container { + display: flex; + align-items: center; + gap: 15px; + justify-content: center; } \ No newline at end of file diff --git a/src/Style/Expandable.css b/src/Style/Expandable.css index a9daf79..6550184 100644 --- a/src/Style/Expandable.css +++ b/src/Style/Expandable.css @@ -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; - } \ No newline at end of file + 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; +} \ No newline at end of file diff --git a/src/Style/SidebarMenu.css b/src/Style/SidebarMenu.css index 8c476ef..2956424 100644 --- a/src/Style/SidebarMenu.css +++ b/src/Style/SidebarMenu.css @@ -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; } \ No newline at end of file diff --git a/src/index.css b/src/index.css index 6119ad9..5a6ad3e 100755 --- a/src/index.css +++ b/src/index.css @@ -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; } -} +} \ No newline at end of file