Частично отрефакторил код, переделал sidebar menu и tabs с сипользованием MUI, обновил авторизацию
test-org/trust-module-frontend/pipeline/pr-rc This commit looks good
Details
test-org/trust-module-frontend/pipeline/pr-rc This commit looks good
Details
parent
cfaa31acee
commit
a54b1d0c39
|
|
@ -24,4 +24,11 @@ dist-ssr
|
|||
*.sw?
|
||||
|
||||
*.les*
|
||||
node_modules
|
||||
node_modules
|
||||
|
||||
# Игнорировать .env файлы
|
||||
.env
|
||||
.env.local
|
||||
.env.development
|
||||
.env.production
|
||||
.env.test
|
||||
|
|
@ -22,7 +22,8 @@
|
|||
"react-datepicker": "^8.1.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/material": "^6.4.7"
|
||||
"@mui/material": "^6.4.7",
|
||||
"@mui/icons-material": "^6.4.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
|
|
|
|||
39
src/App.jsx
39
src/App.jsx
|
|
@ -1,26 +1,39 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useState, useMemo } from "react";
|
||||
import { ThemeProvider, CssBaseline, Switch, Box } from "@mui/material";
|
||||
import Dashboard from "./Components/Layout/Dashboard";
|
||||
import LoginModal from "./Components/UI/LoginModal"; // Импортируем компонент авторизации
|
||||
import "./Style/LoginModal.css"; // Импортируем стили
|
||||
import LoginModal from "./Components/UI/LoginModal";
|
||||
import { lightTheme, darkTheme } from "./Style/theme";
|
||||
import "./Style/LoginModal.css";
|
||||
|
||||
function App() {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false); // Состояние авторизации
|
||||
const [showLoginModal, setShowLoginModal] = useState(true); // Показывать ли модальное окно
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [showLoginModal, setShowLoginModal] = useState(true);
|
||||
const [isDarkMode, setIsDarkMode] = useState(
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
);
|
||||
|
||||
const theme = useMemo(() => (isDarkMode ? darkTheme : lightTheme), [isDarkMode]);
|
||||
|
||||
const handleLogin = () => {
|
||||
setIsAuthenticated(true); // Устанавливаем авторизацию
|
||||
setShowLoginModal(false); // Скрываем модальное окно
|
||||
setIsAuthenticated(true);
|
||||
setShowLoginModal(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", height: "100vh", overflow: "hidden" }}>
|
||||
{!isAuthenticated && showLoginModal && (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
{!isAuthenticated && showLoginModal ? (
|
||||
<LoginModal onLogin={handleLogin} onClose={() => setShowLoginModal(false)} />
|
||||
) : (
|
||||
<Box sx={{ display: "flex", height: "100vh", overflow: "hidden", bgcolor: "background.default", color: "text.primary" }}>
|
||||
<Dashboard />
|
||||
<Box sx={{ position: "absolute", top: 10, right: 10 }}>
|
||||
<Switch checked={isDarkMode} onChange={() => setIsDarkMode((prev) => !prev)} />
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{isAuthenticated && <Dashboard />}
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
|
|
|
|||
|
|
@ -96,6 +96,11 @@ const PrometheusChart = ({ metricName }) => {
|
|||
params: { metric: metricName, start, end, step },
|
||||
});
|
||||
|
||||
/*
|
||||
const response = await axios.get(`${process.env.REACT_APP_BACK_URL}/metrics`, {
|
||||
params: { metric: metricName, start, end, step },
|
||||
}); */
|
||||
|
||||
const result = response.data;
|
||||
let metrics = Array.isArray(result) ? result : result.data || [];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +1,32 @@
|
|||
import React, { useState, useEffect, useRef, useCallback } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import SidebarMenu from "./SidebarMenu";
|
||||
import TreeChart from "../TreeChart/TreeChart";
|
||||
import "../../Style/Dashboard.css";
|
||||
import SystemStatusChart from "../../Charts/SystemStatusChart";
|
||||
import Tabs from "../UI/Tabs";
|
||||
import menuData from "../TreeChart/menuData.json";
|
||||
import TreeTable from "../UI/TreeTable";
|
||||
import { statusManager1, statusManager2 } from "../TreeChart/dataUtils";
|
||||
import generateTabContent from "../TreeChart/tabContent";
|
||||
import CustomTabs from "../UI/MUItabs";
|
||||
import useTabs from "../hooks/useTabs";
|
||||
import useSidebarResize from "../hooks/useSidebarResize";
|
||||
import TabContent from "../hooks/TabContent";
|
||||
import menuData from "../TreeChart/menuData.json";
|
||||
|
||||
const Dashboard = () => {
|
||||
const [tabs, setTabs] = useState([]);
|
||||
const [activeTab, setActiveTab] = useState("Главная");
|
||||
const { tabs, activeTab, handleOpenTab, handleCloseTab, setActiveTab } = useTabs("Главная");
|
||||
const { sidebarWidth, startResizing } = useSidebarResize(250);
|
||||
const [tabContent, setTabContent] = useState({});
|
||||
const [treeData1, setTreeData1] = useState(menuData);
|
||||
const [treeData2, setTreeData2] = useState(menuData);
|
||||
const [sidebarWidth, setSidebarWidth] = useState(250);
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const [statusHistories, setStatusHistories] = useState({
|
||||
history1: [],
|
||||
history2: [],
|
||||
});
|
||||
const sidebarRef = useRef(null);
|
||||
|
||||
// Генерация контента для вкладок
|
||||
useEffect(() => {
|
||||
const generatedTabContent = generateTabContent(menuData);
|
||||
setTabContent(generatedTabContent);
|
||||
}, []);
|
||||
|
||||
// Обновление статусов каждые 30 секунд
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
const updatedData1 = JSON.parse(JSON.stringify(treeData1));
|
||||
|
|
@ -56,106 +55,33 @@ const Dashboard = () => {
|
|||
return () => clearInterval(interval);
|
||||
}, [treeData1, treeData2]);
|
||||
|
||||
const startResizing = useCallback((e) => {
|
||||
e.preventDefault();
|
||||
setIsResizing(true);
|
||||
}, []);
|
||||
|
||||
const resize = useCallback((e) => {
|
||||
if (isResizing) {
|
||||
const newWidth = e.clientX;
|
||||
if (newWidth > 100 && newWidth < 400) {
|
||||
setSidebarWidth(newWidth);
|
||||
}
|
||||
}
|
||||
}, [isResizing]);
|
||||
|
||||
const stopResizing = useCallback(() => {
|
||||
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, resize, stopResizing]);
|
||||
|
||||
const handleOpenTab = (id, title) => {
|
||||
if (!tabs.some((tab) => tab.id === id)) {
|
||||
setTabs([...tabs, { id, title }]);
|
||||
}
|
||||
setActiveTab(id);
|
||||
};
|
||||
|
||||
const handleCloseTab = (id) => {
|
||||
const newTabs = tabs.filter((tab) => tab.id !== id);
|
||||
setTabs(newTabs);
|
||||
if (activeTab === id) {
|
||||
setActiveTab(newTabs.length > 0 ? newTabs[newTabs.length - 1].id : "Главная");
|
||||
}
|
||||
};
|
||||
|
||||
const renderTabContent = () => {
|
||||
if (activeTab === "Главная") {
|
||||
return (
|
||||
<div>
|
||||
<h2>Общий мониторинг состояния системы</h2>
|
||||
|
||||
<div>
|
||||
<div style={{ display: 'inline-block', width: '48%', marginRight: '2%' }}>
|
||||
<label>Надежность системы</label>
|
||||
<SystemStatusChart data={statusHistories.history1} />
|
||||
</div>
|
||||
<div style={{ display: 'inline-block', width: '48%' }}>
|
||||
<label>Функциональность системы</label>
|
||||
<SystemStatusChart data={statusHistories.history2} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label>Статус компонентов системы</label>
|
||||
<TreeTable data={treeData1} />
|
||||
</div>
|
||||
);
|
||||
} else if (activeTab === "Визуализация") {
|
||||
return <TreeChart data={treeData1} onNodeClick={(id, title) => handleOpenTab(id, title)} />;
|
||||
} else {
|
||||
const tabData = tabContent[activeTab];
|
||||
return tabData ? tabData.content : <p>Нет данных</p>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="dashboard-container">
|
||||
<div
|
||||
className="sidebar"
|
||||
ref={sidebarRef}
|
||||
style={{ width: sidebarWidth }}
|
||||
>
|
||||
<SidebarMenu data={treeData1} onOpenTab={handleOpenTab} sidebarWidth={sidebarWidth} />
|
||||
<div
|
||||
className="sidebar-resizer"
|
||||
onMouseDown={startResizing}
|
||||
/>
|
||||
{/* Сайдбар */}
|
||||
<div className="sidebar" style={{ width: sidebarWidth }}>
|
||||
<SidebarMenu data={treeData1} onOpenTab={handleOpenTab} sidebarWidth={sidebarWidth} startResizing={startResizing} />
|
||||
<div className="sidebar-resizer" onMouseDown={startResizing} />
|
||||
</div>
|
||||
|
||||
<div className="main-content" style={{ marginLeft: sidebarWidth }}>
|
||||
<Tabs
|
||||
{/* Основной контент */}
|
||||
<div className="main-content">
|
||||
{/* Вкладки */}
|
||||
<CustomTabs
|
||||
tabs={tabs}
|
||||
activeTab={activeTab}
|
||||
onTabClick={(id) => setActiveTab(id)}
|
||||
onTabClick={setActiveTab}
|
||||
onCloseTab={handleCloseTab}
|
||||
/>
|
||||
|
||||
{/* Контент вкладки */}
|
||||
<div className="content">
|
||||
{renderTabContent()}
|
||||
<TabContent
|
||||
activeTab={activeTab}
|
||||
statusHistories={statusHistories}
|
||||
treeData1={treeData1}
|
||||
tabContent={tabContent}
|
||||
handleOpenTab={handleOpenTab}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,86 +1,49 @@
|
|||
import React, { useState } from "react";
|
||||
import "../../Style/SidebarMenu.css";
|
||||
import { ThemeProvider, createTheme, CssBaseline, Button } from "@mui/material";
|
||||
import { getStatusColor } from "../TreeChart/dataUtils"; // Импортируем только нужную функцию
|
||||
import React from "react";
|
||||
import { Drawer, List } from "@mui/material";
|
||||
import MenuItem from "./SidebarMenuComponents/MenuItem";
|
||||
import SidebarFooter from "./SidebarMenuComponents/SidebarFooter";
|
||||
|
||||
const MenuItem = ({ item, onSelectItem, sidebarWidth }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const hasChildren = Array.isArray(item.items) && item.items.length > 0;
|
||||
const statusColor = getStatusColor(item.status);
|
||||
|
||||
const handleSingleClick = () => {
|
||||
if (hasChildren) {
|
||||
setIsOpen(!isOpen);
|
||||
} else {
|
||||
onSelectItem(item);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenParent = (e) => {
|
||||
e.stopPropagation();
|
||||
onSelectItem(item);
|
||||
const SidebarMenu = ({ data, onOpenTab, sidebarWidth, startResizing }) => {
|
||||
const handleSelectItem = (id, title, children) => {
|
||||
onOpenTab(id, title, children);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`menu-item ${hasChildren ? "has-children" : ""}`} style={{ width: sidebarWidth - 20 }}>
|
||||
<div
|
||||
onClick={handleSingleClick}
|
||||
className="menu-item-header"
|
||||
>
|
||||
{/* Круглый индикатор статуса */}
|
||||
<div
|
||||
className={`status-indicator ${statusColor === "red" ? "blinking" : ""}`}
|
||||
style={{ backgroundColor: statusColor }}
|
||||
/>
|
||||
{/* Текст элемента меню */}
|
||||
<span>{item.title}</span>
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
width: sidebarWidth,
|
||||
flexShrink: 0,
|
||||
"& .MuiDrawer-paper": {
|
||||
width: sidebarWidth,
|
||||
boxSizing: "border-box",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<List>
|
||||
<h2 style={{ padding: "16px", fontWeight: "bold" }}>Меню</h2>
|
||||
<MenuItem item={data} onSelectItem={handleSelectItem} />
|
||||
</List>
|
||||
|
||||
{/* Иконки */}
|
||||
{hasChildren && (
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
{/* Иконка для открытия родителя */}
|
||||
<span
|
||||
onClick={handleOpenParent}
|
||||
className="open-parent-icon"
|
||||
title="Открыть родителя"
|
||||
>
|
||||
📂
|
||||
</span>
|
||||
{/* Иконка для разворачивания/сворачивания */}
|
||||
<span className="toggle-icon">
|
||||
{isOpen ? "▲" : "▼"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isOpen && hasChildren && (
|
||||
<div className="submenu">
|
||||
{item.items.map((child, index) => (
|
||||
<MenuItem key={index} item={child} onSelectItem={onSelectItem} sidebarWidth={sidebarWidth} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Ресайзер */}
|
||||
<div
|
||||
onMouseDown={startResizing}
|
||||
style={{
|
||||
width: "5px",
|
||||
cursor: "ew-resize",
|
||||
backgroundColor: "#ccc",
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
}}
|
||||
/>
|
||||
|
||||
<SidebarFooter sidebarWidth={sidebarWidth} />
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
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} sidebarWidth={sidebarWidth} />
|
||||
</div>
|
||||
<div className="sidebar-footer" style={{ width: sidebarWidth }}> {/* Динамическая ширина */}
|
||||
<h2 className="help">Помощь</h2>
|
||||
<h2 className="settings">Настройка</h2>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SidebarMenu;
|
||||
export default SidebarMenu;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
import React from "react";
|
||||
import { Drawer, List, ListItem, ListItemIcon, ListItemText, Collapse } from "@mui/material";
|
||||
import { ExpandLess, ExpandMore, Folder, FolderOpen } from "@mui/icons-material";
|
||||
|
||||
// Функция для сбора всех потомков
|
||||
const getAllChildren = (node) => {
|
||||
let children = [];
|
||||
if (node.items && node.items.length > 0) {
|
||||
node.items.forEach((child) => {
|
||||
children.push(child); // Добавляем текущий элемент
|
||||
children = children.concat(getAllChildren(child)); // Рекурсивно добавляем потомков
|
||||
});
|
||||
}
|
||||
return children;
|
||||
};
|
||||
|
||||
const MenuItem = ({ item, onSelectItem }) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const hasChildren = Array.isArray(item.items) && item.items.length > 0;
|
||||
|
||||
const handleToggle = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
const handleOpenTab = (e) => {
|
||||
e.stopPropagation(); // Останавливаем всплытие события
|
||||
const allChildren = getAllChildren(item); // Собираем всех потомков
|
||||
onSelectItem(item.id, item.title, allChildren); // Передаем данные в родительский компонент
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItem component="div" onClick={handleToggle}>
|
||||
<ListItemIcon>
|
||||
<div onClick={handleOpenTab} style={{ cursor: "pointer" }}>
|
||||
{hasChildren ? (isOpen ? <FolderOpen /> : <Folder />) : <Folder />}
|
||||
</div>
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={item.title} />
|
||||
{hasChildren && (isOpen ? <ExpandLess /> : <ExpandMore />)}
|
||||
</ListItem>
|
||||
{hasChildren && (
|
||||
<Collapse in={isOpen} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding>
|
||||
{item.items.map((child, index) => (
|
||||
<MenuItem key={index} item={child} onSelectItem={onSelectItem} />
|
||||
))}
|
||||
</List>
|
||||
</Collapse>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuItem;
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import React from "react";
|
||||
import { List, ListItem, ListItemText } from "@mui/material";
|
||||
|
||||
const SidebarFooter = ({ sidebarWidth }) => {
|
||||
return (
|
||||
<List sx={{ marginTop: "auto", backgroundColor: "#ffffff", padding: "10px 0" }}>
|
||||
<ListItem button={true}>
|
||||
<ListItemText primary="Помощь" sx={{ color: "#000000" }} />
|
||||
</ListItem>
|
||||
<ListItem button={true}>
|
||||
<ListItemText primary="Настройка" sx={{ color: "#000000" }} />
|
||||
</ListItem>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarFooter;
|
||||
|
|
@ -1,18 +1,8 @@
|
|||
import React, { useState } from "react";
|
||||
import Modal from "./Modal";
|
||||
import "../../Style/LoginModal.css";
|
||||
import Box from '@mui/material/Box';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Input from '@mui/material/Input';
|
||||
import FilledInput from '@mui/material/FilledInput';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import InputAdornment from '@mui/material/InputAdornment';
|
||||
import FormHelperText from '@mui/material/FormHelperText';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import TextField from '@mui/material/TextField';
|
||||
|
||||
|
||||
const LoginModal = ({ onLogin, onClose }) => {
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
|
@ -21,13 +11,31 @@ const LoginModal = ({ onLogin, onClose }) => {
|
|||
|
||||
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (username === "admin" && password === "admin") {
|
||||
onLogin(); // Успешная авторизация
|
||||
onClose(); // Закрыть модальное окно
|
||||
} else {
|
||||
setError("Неверный логин или пароль");
|
||||
|
||||
try {
|
||||
// Отправляем данные на бэкенд
|
||||
console.log("Отправляем данные:", { username, password });
|
||||
const response = await fetch('http://192.168.2.39:3000/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ login: username, password }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
onLogin(); // Успешная авторизация
|
||||
onClose(); // Закрыть модальное окно
|
||||
} else {
|
||||
setError(data.message || "Неверный логин или пароль");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Ошибка при отправке запроса:', err);
|
||||
setError("Ошибка при подключении к серверу");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -35,77 +43,28 @@ const LoginModal = ({ onLogin, onClose }) => {
|
|||
<Modal onClose={onClose}>
|
||||
<h2>Авторизация</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{/* <div>
|
||||
<label>Логин:</label>
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Пароль:</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div> */}
|
||||
|
||||
{/* <Box
|
||||
component="form"
|
||||
sx={{ '& > :not(style)': { m: 1, width: '25ch' } }}
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
> */}
|
||||
<TextField
|
||||
fullWidth
|
||||
id="user-login"
|
||||
label="Логин"
|
||||
variant="filled"
|
||||
margin="normal"
|
||||
required
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
size="normal"
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
id="user-login"
|
||||
label="Логин"
|
||||
variant="filled"
|
||||
margin="normal"
|
||||
required
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
size="normal"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
id="user-password"
|
||||
label="Пароль"
|
||||
variant="filled"
|
||||
margin="normal"
|
||||
required
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
size="normal"
|
||||
/>
|
||||
{/* <FormControl sx={{ m: 1, width: '25ch' }} variant="outlined">
|
||||
<InputLabel htmlFor="outlined-adornment-password">Password</InputLabel>
|
||||
<OutlinedInput
|
||||
id="outlined-adornment-password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label={
|
||||
showPassword ? 'hide the password' : 'display the password'
|
||||
}
|
||||
onClick={handleClickShowPassword}
|
||||
onMouseDown={handleMouseDownPassword}
|
||||
onMouseUp={handleMouseUpPassword}
|
||||
edge="end"
|
||||
>
|
||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
label="Password"
|
||||
/>
|
||||
</FormControl> */}
|
||||
{/* </Box> */}
|
||||
<TextField
|
||||
fullWidth
|
||||
id="user-password"
|
||||
label="Пароль"
|
||||
variant="filled"
|
||||
margin="normal"
|
||||
required
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
size="normal"
|
||||
/>
|
||||
|
||||
{error && <p className="error">{error}</p>}
|
||||
<button type="submit">Войти</button>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import React from "react";
|
||||
import { Tabs, Tab, Box } from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
|
||||
const CustomTabs = ({ tabs, activeTab, onTabClick, onCloseTab }) => {
|
||||
const handleMouseDown = (e, id) => {
|
||||
if (e.button === 1) {
|
||||
e.preventDefault();
|
||||
onCloseTab(id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
onTabClick(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={handleChange}
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
aria-label="tabs"
|
||||
>
|
||||
{/* Всегда отображаемые вкладки */}
|
||||
<Tab
|
||||
label="Главная"
|
||||
value="Главная"
|
||||
onMouseDown={(e) => handleMouseDown(e, "Главная")}
|
||||
/>
|
||||
<Tab
|
||||
label="Визуализация"
|
||||
value="Визуализация"
|
||||
onMouseDown={(e) => handleMouseDown(e, "Визуализация")}
|
||||
/>
|
||||
|
||||
{/* Динамически добавляемые вкладки */}
|
||||
{tabs.map((tab) => (
|
||||
<Tab
|
||||
key={tab.id}
|
||||
label={
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<span>{tab.title}</span>
|
||||
<CloseIcon
|
||||
fontSize="small"
|
||||
sx={{ ml: 1, cursor: "pointer" }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onCloseTab(tab.id);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
value={tab.id}
|
||||
onMouseDown={(e) => handleMouseDown(e, tab.id)}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomTabs;
|
||||
|
|
@ -27,12 +27,6 @@ const TreeTable = ({ data }) => {
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
adjustFontSize();
|
||||
window.addEventListener("resize", adjustFontSize);
|
||||
return () => window.removeEventListener("resize", adjustFontSize);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
const newLog = [];
|
||||
const traverse = (items) => {
|
||||
|
|
@ -41,7 +35,7 @@ const TreeTable = ({ data }) => {
|
|||
newLog.push({
|
||||
title: item.title,
|
||||
status: item.status,
|
||||
time: new Date().toLocaleTimeString() // Добавляем время
|
||||
time: new Date().toLocaleTimeString(), // Добавляем время
|
||||
});
|
||||
}
|
||||
if (item.items) {
|
||||
|
|
@ -50,19 +44,31 @@ const TreeTable = ({ data }) => {
|
|||
});
|
||||
};
|
||||
traverse(data.items);
|
||||
setLog(newLog);
|
||||
|
||||
// Ограничиваем количество сообщений до 50
|
||||
setLog((prevLog) => [...newLog, ...prevLog].slice(0, 50));
|
||||
}, [data]);
|
||||
|
||||
const filteredData = data.items.filter((item) => item.title !== "Функциональные задачи");
|
||||
|
||||
// Функция для отображения заголовков
|
||||
const renderHeaders = (items) => {
|
||||
return items.map((item) => {
|
||||
const colSpan = item.items ? item.items.length : 1;
|
||||
return (
|
||||
<th key={item.id} colSpan={colSpan} className="tree-table-header" title={item.title}>
|
||||
<div className="header-content">
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager1.getStatusColor(item.status) }} />
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager2.getStatusColor(item.status), marginLeft: "5px" }} />
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(item.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(item.status),
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
{item.title}
|
||||
</div>
|
||||
</th>
|
||||
|
|
@ -70,41 +76,117 @@ const TreeTable = ({ data }) => {
|
|||
});
|
||||
};
|
||||
|
||||
const renderRows = (items) => {
|
||||
if (!items || items.length === 0) return null;
|
||||
const hasChildren = items.some((item) => item.items && item.items.length > 0);
|
||||
if (!hasChildren) return null;
|
||||
return (
|
||||
<tr className="tree-table-row">
|
||||
{items.map((item) => {
|
||||
if (item.items && item.items.length > 0) {
|
||||
return (
|
||||
<React.Fragment key={item.id}>
|
||||
{item.items.map((child) => (
|
||||
<td key={child.id} className="tree-table-cell" title={child.title}>
|
||||
<div className="cell-content">
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager1.getStatusColor(child.status) }} />
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager2.getStatusColor(child.status), marginLeft: "5px" }} />
|
||||
{child.title}
|
||||
</div>
|
||||
</td>
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
// Функция для отображения подзаголовков
|
||||
const renderSubHeaders = (items) => {
|
||||
return items.map((item) => {
|
||||
if (item.items) {
|
||||
return item.items.map((child) => (
|
||||
<th key={child.id} className="tree-table-header" title={child.title}>
|
||||
<div className="header-content">
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(child.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(child.status),
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
{child.title}
|
||||
</div>
|
||||
</th>
|
||||
));
|
||||
} else {
|
||||
return (
|
||||
<th key={item.id} className="tree-table-header" title={item.title}>
|
||||
<div className="header-content">
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(item.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(item.status),
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
{item.title}
|
||||
</div>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Функция для отображения данных
|
||||
const renderData = (items) => {
|
||||
return items.map((item) => {
|
||||
if (item.items) {
|
||||
return item.items.map((child) => {
|
||||
if (child.items) {
|
||||
return child.items.map((subChild) => (
|
||||
<td key={subChild.id} className="tree-table-cell" title={subChild.title}>
|
||||
<div className="cell-content">
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(subChild.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(subChild.status),
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
<span className="cell-text">{subChild.title}</span>
|
||||
</div>
|
||||
</td>
|
||||
));
|
||||
} else {
|
||||
return (
|
||||
<td key={item.id} className="tree-table-cell" title={item.title}>
|
||||
<td key={child.id} className="tree-table-cell" title={child.title}>
|
||||
<div className="cell-content">
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager1.getStatusColor(item.status) }} />
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager2.getStatusColor(item.status), marginLeft: "5px" }} />
|
||||
{item.title}
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(child.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(child.status),
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
<span className="cell-text">{child.title}</span>
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return (
|
||||
<td key={item.id} className="tree-table-cell" title={item.title}>
|
||||
<div className="cell-content">
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(item.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(item.status),
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
<span className="cell-text">{item.title}</span>
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -118,15 +200,27 @@ const TreeTable = ({ data }) => {
|
|||
title={data.title}
|
||||
>
|
||||
<div className="header-content">
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager1.getStatusColor(data.status) }} />
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager2.getStatusColor(data.status), marginLeft: "5px" }} />
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(data.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(data.status),
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
{data.title}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>{renderHeaders(filteredData)}</tr>
|
||||
<tr>{renderSubHeaders(filteredData)}</tr>
|
||||
</thead>
|
||||
<tbody>{renderRows(filteredData)}</tbody>
|
||||
<tbody>
|
||||
<tr className="tree-table-row">{renderData(filteredData)}</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button onClick={() => setIsLogVisible(!isLogVisible)} className="toggle-log-button">
|
||||
{isLogVisible ? "Скрыть лог" : "Показать лог"}
|
||||
|
|
|
|||
|
|
@ -1,151 +0,0 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import "../../Style/TreeTable.css";
|
||||
import { statusManager1, statusManager2 } from "../TreeChart/dataUtils";
|
||||
|
||||
const TreeTable = ({ data }) => {
|
||||
const tableRef = useRef(null);
|
||||
const [fontSize, setFontSize] = useState(16);
|
||||
const [log, setLog] = useState([]);
|
||||
const [isLogVisible, setIsLogVisible] = useState(true);
|
||||
|
||||
const adjustFontSize = () => {
|
||||
if (tableRef.current) {
|
||||
let newSize = 16;
|
||||
const maxWidth = window.innerWidth;
|
||||
|
||||
while (tableRef.current.scrollWidth > maxWidth && newSize > 10) {
|
||||
newSize -= 1;
|
||||
tableRef.current.style.fontSize = `${newSize}px`;
|
||||
}
|
||||
|
||||
while (tableRef.current.scrollWidth < maxWidth && newSize < 16) {
|
||||
newSize += 1;
|
||||
tableRef.current.style.fontSize = `${newSize}px`;
|
||||
}
|
||||
|
||||
setFontSize(newSize);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
adjustFontSize();
|
||||
window.addEventListener("resize", adjustFontSize);
|
||||
return () => window.removeEventListener("resize", adjustFontSize);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
const newLog = [];
|
||||
const traverse = (items) => {
|
||||
items.forEach((item) => {
|
||||
if (["yellow", "orange", "red"].includes(item.status)) {
|
||||
newLog.push({
|
||||
title: item.title,
|
||||
status: item.status,
|
||||
time: new Date().toLocaleTimeString() // Добавляем время
|
||||
});
|
||||
}
|
||||
if (item.items) {
|
||||
traverse(item.items);
|
||||
}
|
||||
});
|
||||
};
|
||||
traverse(data.items);
|
||||
setLog(newLog);
|
||||
}, [data]);
|
||||
|
||||
const filteredData = data.items.filter((item) => item.title !== "Функциональные задачи");
|
||||
|
||||
const renderHeaders = (items) => {
|
||||
return items.map((item) => {
|
||||
// Если это последний уровень, не отображаем заголовок
|
||||
if (!item.items || item.items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const colSpan = item.items ? item.items.length : 1;
|
||||
return (
|
||||
<th key={item.id} colSpan={colSpan} className="tree-table-header" title={item.title}>
|
||||
<div className="header-content">
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager1.getStatusColor(item.status) }} />
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager2.getStatusColor(item.status), marginLeft: "5px" }} />
|
||||
{item.title}
|
||||
</div>
|
||||
</th>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const renderRows = (items) => {
|
||||
if (!items || items.length === 0) return null;
|
||||
|
||||
// Если это последний уровень, не отображаем строки
|
||||
if (items.every((item) => !item.items || item.items.length === 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr className="tree-table-row">
|
||||
{items.map((item) => {
|
||||
if (item.items && item.items.length > 0) {
|
||||
return (
|
||||
<React.Fragment key={item.id}>
|
||||
{item.items.map((child) => (
|
||||
<td key={child.id} className="tree-table-cell" title={child.title}>
|
||||
<div className="cell-content">
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager1.getStatusColor(child.status) }} />
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager2.getStatusColor(child.status), marginLeft: "5px" }} />
|
||||
{child.title}
|
||||
</div>
|
||||
</td>
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
return null; // Не отображаем элементы последнего уровня
|
||||
}
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="tree-table-container">
|
||||
<table ref={tableRef} className="tree-table" style={{ fontSize: `${fontSize}px` }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
colSpan={filteredData.reduce((acc, item) => acc + (item.items ? item.items.length : 1), 0)}
|
||||
className="tree-table-header"
|
||||
title={data.title}
|
||||
>
|
||||
<div className="header-content">
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager1.getStatusColor(data.status) }} />
|
||||
<div className="status-indicator-bar" style={{ backgroundColor: statusManager2.getStatusColor(data.status), marginLeft: "5px" }} />
|
||||
{data.title}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>{renderHeaders(filteredData)}</tr>
|
||||
</thead>
|
||||
<tbody>{renderRows(filteredData)}</tbody>
|
||||
</table>
|
||||
<button onClick={() => setIsLogVisible(!isLogVisible)} className="toggle-log-button">
|
||||
{isLogVisible ? "Скрыть лог" : "Показать лог"}
|
||||
</button>
|
||||
{isLogVisible && (
|
||||
<div className="status-log">
|
||||
<h3>Лог статусов</h3>
|
||||
<ul>
|
||||
{log.map((entry, index) => (
|
||||
<li key={index} style={{ color: statusManager1.getStatusColor(entry.status) }}>
|
||||
[{entry.time}] {entry.status}: {entry.title}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TreeTable;
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import SystemStatusChart from "../../Charts/SystemStatusChart";
|
||||
import TreeTable from "../UI/TreeTable";
|
||||
import TreeChart from "../TreeChart/TreeChart";
|
||||
|
||||
const TabContent = ({ activeTab, statusHistories, treeData1, tabContent, handleOpenTab }) => {
|
||||
if (activeTab === "Главная") {
|
||||
return (
|
||||
<div>
|
||||
<h2>Общий мониторинг состояния системы</h2>
|
||||
<div>
|
||||
<div style={{ display: 'inline-block', width: '48%', marginRight: '2%' }}>
|
||||
<label>Надежность системы</label>
|
||||
<SystemStatusChart data={statusHistories.history1} />
|
||||
</div>
|
||||
<div style={{ display: 'inline-block', width: '48%' }}>
|
||||
<label>Функциональность системы</label>
|
||||
<SystemStatusChart data={statusHistories.history2} />
|
||||
</div>
|
||||
</div>
|
||||
<label>Статус компонентов системы</label>
|
||||
<TreeTable data={treeData1} />
|
||||
</div>
|
||||
);
|
||||
} else if (activeTab === "Визуализация") {
|
||||
return <TreeChart data={treeData1} onNodeClick={(id, title) => handleOpenTab(id, title)} />;
|
||||
} else {
|
||||
const tabData = tabContent[activeTab];
|
||||
return tabData ? tabData.content : <p>Нет данных</p>;
|
||||
}
|
||||
};
|
||||
|
||||
export default TabContent;
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { useState, useCallback, useEffect } from "react";
|
||||
|
||||
const useSidebarResize = (initialWidth = 250) => {
|
||||
const [sidebarWidth, setSidebarWidth] = useState(initialWidth);
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
|
||||
const startResizing = useCallback((e) => {
|
||||
e.preventDefault();
|
||||
setIsResizing(true);
|
||||
}, []);
|
||||
|
||||
const resize = useCallback((e) => {
|
||||
if (isResizing) {
|
||||
const newWidth = e.clientX;
|
||||
if (newWidth > 100 && newWidth < 400) {
|
||||
setSidebarWidth(newWidth);
|
||||
}
|
||||
}
|
||||
}, [isResizing]);
|
||||
|
||||
const stopResizing = useCallback(() => {
|
||||
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, resize, stopResizing]);
|
||||
|
||||
return { sidebarWidth, startResizing };
|
||||
};
|
||||
|
||||
export default useSidebarResize;
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { useState, useCallback } from "react";
|
||||
|
||||
const useTabs = (initialTab) => {
|
||||
const [tabs, setTabs] = useState([]);
|
||||
const [activeTab, setActiveTab] = useState(initialTab);
|
||||
|
||||
const handleOpenTab = useCallback((id, title) => {
|
||||
setTabs((prevTabs) =>
|
||||
prevTabs.some((tab) => tab.id === id)
|
||||
? prevTabs
|
||||
: [...prevTabs, { id, title }]
|
||||
);
|
||||
setActiveTab(id);
|
||||
}, []);
|
||||
|
||||
const handleCloseTab = useCallback((id) => {
|
||||
setTabs((prevTabs) => prevTabs.filter((tab) => tab.id !== id));
|
||||
if (activeTab === id) {
|
||||
setActiveTab(tabs.length > 1 ? tabs[tabs.length - 2].id : initialTab);
|
||||
}
|
||||
}, [activeTab, tabs, initialTab]);
|
||||
|
||||
return { tabs, activeTab, handleOpenTab, handleCloseTab, setActiveTab };
|
||||
};
|
||||
|
||||
export default useTabs;
|
||||
|
|
@ -2,24 +2,34 @@
|
|||
.dashboard-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: calc(100vw - 20px);
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
margin-left: 20px;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
/* Сайдбар */
|
||||
.sidebar {
|
||||
flex-shrink: 0;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
background-color: var(--sidebar-color);
|
||||
color: var(--sidebar-text-color);
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
|
||||
/* Основной контент */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
margin-left: 50px;
|
||||
transition: margin-left 0.2s ease;
|
||||
overflow: auto;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
|
||||
/* Контент */
|
||||
.content {
|
||||
background-color: var(--modal-background);
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ button {
|
|||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
caption {
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
import { createTheme, ThemeProvider } from "@mui/material/styles";
|
||||
|
||||
// Светлая тема
|
||||
const lightTheme = createTheme({
|
||||
palette: {
|
||||
mode: "light",
|
||||
background: {
|
||||
default: "#FFFFFF",
|
||||
paper: "#f4f4f4",
|
||||
},
|
||||
text: {
|
||||
primary: "#000000",
|
||||
secondary: "#333333",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Темная тема
|
||||
const darkTheme = createTheme({
|
||||
palette: {
|
||||
mode: "dark",
|
||||
background: {
|
||||
default: "#1E1E1E",
|
||||
paper: "#2d2d2d",
|
||||
},
|
||||
text: {
|
||||
primary: "#E0E0E0",
|
||||
secondary: "#CCCCCC",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
.tree-table-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-x: hidden;
|
||||
/* Убираем горизонтальный скролл */
|
||||
}
|
||||
|
||||
.tree-table {
|
||||
|
|
@ -8,26 +9,33 @@
|
|||
border-collapse: collapse;
|
||||
text-align: center;
|
||||
table-layout: fixed;
|
||||
/* Фиксированная ширина колонок */
|
||||
background-color: var(--table-cell-background);
|
||||
color: var(--table-text-color);
|
||||
/* Используем переменную для цвета текста */
|
||||
}
|
||||
|
||||
.tree-table-header {
|
||||
padding: 10px;
|
||||
border: 1px solid var(--table-border);
|
||||
border: 1px solid black;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
/* Текст не переносится */
|
||||
overflow: hidden;
|
||||
/* Скрываем текст, который не помещается */
|
||||
text-overflow: ellipsis;
|
||||
/* Добавляем многоточие */
|
||||
background-color: var(--table-header-background);
|
||||
}
|
||||
|
||||
.tree-table-cell {
|
||||
padding: 8px;
|
||||
border: 1px solid var(--table-border);
|
||||
border: 1px solid black;
|
||||
white-space: nowrap;
|
||||
/* Текст не переносится */
|
||||
overflow: hidden;
|
||||
/* Скрываем текст, который не помещается */
|
||||
text-overflow: ellipsis;
|
||||
/* Добавляем многоточие */
|
||||
}
|
||||
|
||||
.cell-content,
|
||||
|
|
@ -40,6 +48,12 @@
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.cell-text {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.status-indicator-bar {
|
||||
width: 6px;
|
||||
height: 20px;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* Светлая тема по умолчанию */
|
||||
:root {
|
||||
--background-color: #FFFFFF;
|
||||
--text-color: #FFFFFF;
|
||||
--text-color: #000000;
|
||||
--header-color: #333333;
|
||||
/* Основной цвет текста (черный) */
|
||||
--sidebar-color: #3d74c7;
|
||||
|
|
@ -16,8 +16,8 @@
|
|||
--table-cell-background: #FFFFFF;
|
||||
--table-text-color: #000000;
|
||||
/* Черный текст в таблице */
|
||||
|
||||
|
||||
/* hover for buttons */
|
||||
--hover-button: #2d62b1;
|
||||
--hover-text-color : #FFFFFF
|
||||
--hover-text-color: #FFFFFF
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import { createTheme } from "@mui/material/styles";
|
||||
|
||||
export const lightTheme = createTheme({
|
||||
palette: {
|
||||
mode: "light",
|
||||
background: {
|
||||
default: "#FFFFFF",
|
||||
paper: "#FFFFFF",
|
||||
},
|
||||
text: {
|
||||
primary: "#000000",
|
||||
},
|
||||
primary: {
|
||||
main: "#3d74c7",
|
||||
},
|
||||
secondary: {
|
||||
main: "#0f55bec2",
|
||||
},
|
||||
custom: {
|
||||
background: "#FFFFFF",
|
||||
text: "#000000",
|
||||
sidebar: "#3d74c7",
|
||||
sidebarText: "#FFFFFF",
|
||||
modalBackground: "#FFFFFF",
|
||||
modalBtnBackground: "#0f55bec2",
|
||||
modalText: "#333333",
|
||||
tableBorder: "#ddd",
|
||||
tableHeaderBackground: "#f9f9f9",
|
||||
tableCellBackground: "#FFFFFF",
|
||||
tableText: "#000000",
|
||||
treeChartText: "#000000",
|
||||
scrollbarTrack: "#f1f1f1",
|
||||
hoverButton: "#2d62b1",
|
||||
hoverText: "#FFFFFF",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const darkTheme = createTheme({
|
||||
palette: {
|
||||
mode: "dark",
|
||||
background: {
|
||||
default: "#1E1E1E",
|
||||
paper: "#2d2d2d",
|
||||
},
|
||||
text: {
|
||||
primary: "#E0E0E0",
|
||||
},
|
||||
primary: {
|
||||
main: "#2d2d2d",
|
||||
},
|
||||
secondary: {
|
||||
main: "#333333",
|
||||
},
|
||||
custom: {
|
||||
background: "#1E1E1E",
|
||||
text: "#E0E0E0",
|
||||
sidebar: "#2d2d2d",
|
||||
sidebarText: "#E0E0E0",
|
||||
modalBackground: "#2d2d2d",
|
||||
modalBtnBackground: "#333333",
|
||||
modalText: "#FFFFFF",
|
||||
tableBorder: "#444444",
|
||||
tableHeaderBackground: "#2d2d2d",
|
||||
tableCellBackground: "#333333",
|
||||
tableText: "#E0E0E0",
|
||||
treeChartText: "#FFFFFF",
|
||||
scrollbarTrack: "#333",
|
||||
hoverButton: "#333d4d",
|
||||
hoverText: "#E0E0E0",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -2,8 +2,8 @@ import { StrictMode } from 'react'
|
|||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.jsx'
|
||||
import './Style/light-theme.css'; // Подключаем светлую тему по умолчанию
|
||||
import './Style/dark-theme.css'; // Подключаем темную тему
|
||||
//import './Style/light-theme.css'; // Подключаем светлую тему по умолчанию
|
||||
//import './Style/dark-theme.css'; // Подключаем темную тему
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
|
|
|
|||
Loading…
Reference in New Issue