Обновил интерфейс, добавил вкладки
parent
46a8bbb35d
commit
637b559fe8
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
27
src/App.jsx
27
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 (
|
||||
<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;
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -26,4 +26,11 @@
|
|||
|
||||
.warning span {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
.indicator-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue