Добавил окно авторизации и визуализацию меню в виде древа
parent
f8eab83bb4
commit
517d3893be
|
|
@ -1,9 +1,22 @@
|
||||||
{
|
[
|
||||||
"labels": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
{
|
||||||
"datasets": [
|
"timestamp": "2025-02-18 12:00",
|
||||||
{
|
"value": 10
|
||||||
"data": [50, 52, 55, 53, 60, 58, 65, 62, 66, 72],
|
},
|
||||||
"data2": [20,56,74,45,21,20,56,74,45,21]
|
{
|
||||||
}
|
"timestamp": "2025-02-18 12:05",
|
||||||
]
|
"value": 12
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2025-02-18 12:10",
|
||||||
|
"value": 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2025-02-18 12:15",
|
||||||
|
"value": 13
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"timestamp": "2025-02-18 12:20",
|
||||||
|
"value": 17
|
||||||
|
}
|
||||||
|
]
|
||||||
35
src/App.jsx
35
src/App.jsx
|
|
@ -1,29 +1,26 @@
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import Dashboard from "./Components/Dashboard";
|
import Dashboard from "./Components/Dashboard";
|
||||||
import NetworkSpeedChart from './Charts/TestCharts';
|
import LoginModal from "./Components/LoginModal"; // Импортируем компонент авторизации
|
||||||
import NetworkSpeedChart2 from './Charts/TestCharts2'
|
import "./Style/LoginModal.css"; // Импортируем стили
|
||||||
import NetworkSpeedChart3 from './Charts/TestCharts3'
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const [isAuthenticated, setIsAuthenticated] = useState(false); // Состояние авторизации
|
||||||
|
const [showLoginModal, setShowLoginModal] = useState(true); // Показывать ли модальное окно
|
||||||
|
|
||||||
|
const handleLogin = () => {
|
||||||
|
setIsAuthenticated(true); // Устанавливаем авторизацию
|
||||||
|
setShowLoginModal(false); // Скрываем модальное окно
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: "flex", height: "100vh", overflow: "hidden" }}>
|
<div style={{ display: "flex", height: "100vh", overflow: "hidden" }}>
|
||||||
<Dashboard />
|
{!isAuthenticated && showLoginModal && (
|
||||||
|
<LoginModal onLogin={handleLogin} onClose={() => setShowLoginModal(false)} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isAuthenticated && <Dashboard />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
/*
|
|
||||||
return (
|
|
||||||
<div style={{ padding: "20px" }}>
|
|
||||||
<h1>Dashboard</h1>
|
|
||||||
<Dashboard />
|
|
||||||
<div style={{ marginBottom: "40px" }}>
|
|
||||||
<h2>Примеры импорта данных</h2>
|
|
||||||
<NetworkSpeedChart />
|
|
||||||
<NetworkSpeedChart2 />
|
|
||||||
<NetworkSpeedChart3 />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
@ -12,9 +12,8 @@ import {
|
||||||
Legend,
|
Legend,
|
||||||
TimeScale,
|
TimeScale,
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import 'chartjs-adapter-date-fns'; // Импортируем адаптер дат
|
import 'chartjs-adapter-date-fns';
|
||||||
|
|
||||||
// Регистрируем компоненты Chart.js
|
|
||||||
ChartJS.register(
|
ChartJS.register(
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
LinearScale,
|
LinearScale,
|
||||||
|
|
@ -23,141 +22,72 @@ ChartJS.register(
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
TimeScale // Регистрируем временную шкалу
|
TimeScale
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const MAX_DATA_POINTS = 50;
|
||||||
|
|
||||||
const NetworkSpeedChart2 = () => {
|
const NetworkSpeedChart2 = () => {
|
||||||
const [chartData, setChartData] = useState({
|
const [chartData, setChartData] = useState({ labels: [], datasets: [] });
|
||||||
labels: [],
|
|
||||||
datasets: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const chartRef = useRef(null); // Референс на график
|
|
||||||
|
|
||||||
// Функция для загрузки данных
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('http://192.168.2.33:3000/metrics?metric=node_time_seconds');
|
const response = await axios.get('http://192.168.2.33:3000/metrics?metric=node_time_seconds');
|
||||||
const newData = response.data;
|
const newData = response.data;
|
||||||
|
|
||||||
console.log('New data from backend:', newData); // Проверяем новые данные
|
|
||||||
|
|
||||||
// Обновляем состояние, добавляя новые данные к существующим
|
|
||||||
setChartData((prevChartData) => {
|
setChartData((prevChartData) => {
|
||||||
// Группируем новые данные по устройству (device)
|
|
||||||
const newGroupedData = newData.reduce((acc, entry) => {
|
const newGroupedData = newData.reduce((acc, entry) => {
|
||||||
const device = entry.device;
|
if (!acc[entry.device]) acc[entry.device] = [];
|
||||||
if (!acc[device]) {
|
acc[entry.device].push({ x: new Date(entry.timestamp), y: entry.value });
|
||||||
acc[device] = [];
|
|
||||||
}
|
|
||||||
acc[device].push(entry);
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
// Создаем новый набор данных
|
|
||||||
const newDatasets = Object.keys(newGroupedData).map((device, index) => {
|
const newDatasets = Object.keys(newGroupedData).map((device, index) => {
|
||||||
// Находим существующий dataset для этого устройства
|
const existingDataset = prevChartData.datasets.find((d) => d.label === `Device: ${device}`);
|
||||||
const existingDataset = prevChartData.datasets.find((dataset) => dataset.label === `Device: ${device}`);
|
const updatedData = existingDataset ? [...existingDataset.data, ...newGroupedData[device]] : newGroupedData[device];
|
||||||
|
|
||||||
// Если dataset уже существует, добавляем новые данные к нему
|
|
||||||
if (existingDataset) {
|
|
||||||
return {
|
|
||||||
...existingDataset,
|
|
||||||
data: [
|
|
||||||
...existingDataset.data,
|
|
||||||
...newGroupedData[device].map((entry) => ({
|
|
||||||
x: new Date(entry.timestamp), // Временная метка
|
|
||||||
y: entry.value, // Значение
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если dataset не существует, создаем новый
|
|
||||||
return {
|
return {
|
||||||
label: `Device: ${device}`,
|
label: `Device: ${device}`,
|
||||||
data: newGroupedData[device].map((entry) => ({
|
data: updatedData.slice(-MAX_DATA_POINTS),
|
||||||
x: new Date(entry.timestamp),
|
|
||||||
y: entry.value,
|
|
||||||
})),
|
|
||||||
borderColor: `hsl(${(index * 360) / Object.keys(newGroupedData).length}, 70%, 50%)`,
|
borderColor: `hsl(${(index * 360) / Object.keys(newGroupedData).length}, 70%, 50%)`,
|
||||||
backgroundColor: `hsla(${(index * 360) / Object.keys(newGroupedData).length}, 70%, 50%, 0.2)`,
|
backgroundColor: `hsla(${(index * 360) / Object.keys(newGroupedData).length}, 70%, 50%, 0.2)`,
|
||||||
tension: 0.2,
|
tension: 0.2,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обновляем labels (метки времени)
|
return { labels: newDatasets[0]?.data.map((d) => d.x) || [], datasets: newDatasets };
|
||||||
const newLabels = [
|
|
||||||
...prevChartData.labels,
|
|
||||||
...newData.map((entry) => new Date(entry.timestamp)),
|
|
||||||
];
|
|
||||||
|
|
||||||
return {
|
|
||||||
labels: newLabels,
|
|
||||||
datasets: newDatasets,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка при загрузке метрик:', error);
|
console.error('Ошибка при загрузке метрик:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Загружаем данные при монтировании компонента и обновляем каждые 5 секунд
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
const interval = setInterval(fetchData, 5000);
|
const interval = setInterval(fetchData, 5000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
// Очищаем интервал и уничтожаем график при размонтировании компонента
|
|
||||||
return () => {
|
|
||||||
clearInterval(interval);
|
|
||||||
if (chartRef.current) {
|
|
||||||
chartRef.current.destroy();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Опции графика
|
|
||||||
const options = {
|
const options = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: { position: 'top' },
|
||||||
position: 'top',
|
title: { display: true, text: 'node_time_seconds' },
|
||||||
},
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'node_time_seconds',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
type: 'time', // Используем временную шкалу
|
type: 'time',
|
||||||
time: {
|
time: { unit: 'second', displayFormats: { second: 'HH:mm:ss' } },
|
||||||
unit: 'second', // Единица времени
|
title: { display: true, text: 'Time' },
|
||||||
displayFormats: {
|
|
||||||
second: 'HH:mm:ss', // Формат отображения времени
|
|
||||||
},
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Time',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Данные',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
y: { title: { display: true, text: 'Value' } },
|
||||||
},
|
},
|
||||||
animation: {
|
animation: { duration: 1000, easing: 'linear' },
|
||||||
duration: 1000, // Длительность анимации
|
|
||||||
easing: 'linear', // Тип анимации
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '800px', height: '400px' }}>
|
<div style={{ width: '800px', height: '400px' }}>
|
||||||
<Line ref={chartRef} data={chartData} options={options} />
|
<Line data={chartData} options={options} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,165 +1,68 @@
|
||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import axios from 'axios';
|
|
||||||
import { Line } from 'react-chartjs-2';
|
import { Line } from 'react-chartjs-2';
|
||||||
import {
|
import axios from 'axios';
|
||||||
Chart as ChartJS,
|
import { Chart as ChartJS, Title, Tooltip, Legend, LineElement, CategoryScale, LinearScale } from 'chart.js';
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
PointElement,
|
|
||||||
LineElement,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
TimeScale,
|
|
||||||
} from 'chart.js';
|
|
||||||
import 'chartjs-adapter-date-fns'; // Импортируем адаптер дат
|
|
||||||
|
|
||||||
// Регистрируем компоненты Chart.js
|
// Регистрация компонентов Chart.js
|
||||||
ChartJS.register(
|
ChartJS.register(Title, Tooltip, Legend, LineElement, CategoryScale, LinearScale);
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
PointElement,
|
|
||||||
LineElement,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
TimeScale // Регистрируем временную шкалу
|
|
||||||
);
|
|
||||||
|
|
||||||
const NetworkSpeedChart3 = () => {
|
const SimpleGraph = () => {
|
||||||
const [chartData, setChartData] = useState({
|
const [data, setData] = useState([]);
|
||||||
labels: [],
|
|
||||||
datasets: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const chartRef = useRef(null); // Референс на график
|
|
||||||
|
|
||||||
// Функция для загрузки данных
|
|
||||||
const fetchData = async () => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get('http://192.168.2.33:3000/metrics?metric=node_memory_MemAvailable_bytes');
|
|
||||||
const newData = response.data;
|
|
||||||
|
|
||||||
console.log('New data from backend:', newData); // Проверяем новые данные
|
|
||||||
|
|
||||||
// Обновляем состояние, добавляя новые данные к существующим
|
|
||||||
setChartData((prevChartData) => {
|
|
||||||
// Группируем новые данные по устройству (device)
|
|
||||||
const newGroupedData = newData.reduce((acc, entry) => {
|
|
||||||
const device = entry.device;
|
|
||||||
if (!acc[device]) {
|
|
||||||
acc[device] = [];
|
|
||||||
}
|
|
||||||
acc[device].push(entry);
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
// Создаем новый набор данных
|
|
||||||
const newDatasets = Object.keys(newGroupedData).map((device, index) => {
|
|
||||||
// Находим существующий dataset для этого устройства
|
|
||||||
const existingDataset = prevChartData.datasets.find((dataset) => dataset.label === `Device: ${device}`);
|
|
||||||
|
|
||||||
// Если dataset уже существует, добавляем новые данные к нему
|
|
||||||
if (existingDataset) {
|
|
||||||
return {
|
|
||||||
...existingDataset,
|
|
||||||
data: [
|
|
||||||
...existingDataset.data,
|
|
||||||
...newGroupedData[device].map((entry) => ({
|
|
||||||
x: new Date(entry.timestamp), // Временная метка
|
|
||||||
y: entry.value, // Значение
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если dataset не существует, создаем новый
|
|
||||||
return {
|
|
||||||
label: `Device: ${device}`,
|
|
||||||
data: newGroupedData[device].map((entry) => ({
|
|
||||||
x: new Date(entry.timestamp),
|
|
||||||
y: entry.value,
|
|
||||||
})),
|
|
||||||
borderColor: `hsl(${(index * 360) / Object.keys(newGroupedData).length}, 70%, 50%)`,
|
|
||||||
backgroundColor: `hsla(${(index * 360) / Object.keys(newGroupedData).length}, 70%, 50%, 0.2)`,
|
|
||||||
tension: 0.2,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обновляем labels (метки времени)
|
|
||||||
const newLabels = [
|
|
||||||
...prevChartData.labels,
|
|
||||||
...newData.map((entry) => new Date(entry.timestamp)),
|
|
||||||
];
|
|
||||||
|
|
||||||
return {
|
|
||||||
labels: newLabels,
|
|
||||||
datasets: newDatasets,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка при загрузке метрик:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Загружаем данные при монтировании компонента и обновляем каждые 5 секунд
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
const fetchData = async () => {
|
||||||
const interval = setInterval(fetchData, 5000);
|
try {
|
||||||
|
// Загружаем данные из файла с использованием axios
|
||||||
|
const response = await axios.get('/data.json'); // Путь должен быть относительно папки public
|
||||||
|
const rawData = response.data;
|
||||||
|
|
||||||
// Очищаем интервал и уничтожаем график при размонтировании компонента
|
// Проверяем, что данные действительно массив
|
||||||
return () => {
|
if (Array.isArray(rawData)) {
|
||||||
clearInterval(interval);
|
const chartData = rawData.map(item => ({
|
||||||
if (chartRef.current) {
|
timestamp: item.timestamp,
|
||||||
chartRef.current.destroy();
|
value: item.value,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setData(chartData);
|
||||||
|
} else {
|
||||||
|
throw new Error('Ошибка: Данные не являются массивом.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching data:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Опции графика
|
if (data.length === 0) return <div>Loading...</div>;
|
||||||
const options = {
|
|
||||||
|
// Настройки графика
|
||||||
|
const chartOptions = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
|
||||||
position: 'top',
|
|
||||||
},
|
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: 'node_memory_MemAvailable_bytes',
|
text: 'Simple Data Graph',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: 'time', // Используем временную шкалу
|
|
||||||
time: {
|
|
||||||
unit: 'second', // Единица времени
|
|
||||||
displayFormats: {
|
|
||||||
second: 'HH:mm:ss', // Формат отображения времени
|
|
||||||
},
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Time',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Данные',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
duration: 1000, // Длительность анимации
|
|
||||||
easing: 'linear', // Тип анимации
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const chartData = {
|
||||||
<div style={{ width: '800px', height: '400px' }}>
|
labels: data.map(item => item.timestamp), // Массив меток для оси X
|
||||||
<Line ref={chartRef} data={chartData} options={options} />
|
datasets: [
|
||||||
</div>
|
{
|
||||||
);
|
label: 'Value',
|
||||||
|
data: data.map(item => item.value), // Массив значений для оси Y
|
||||||
|
borderColor: 'rgb(75, 192, 192)',
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||||
|
fill: false,
|
||||||
|
tension: 0.1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Line data={chartData} options={chartOptions} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NetworkSpeedChart3;
|
export default SimpleGraph;
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ import React, { useState, useEffect } from "react";
|
||||||
import SidebarMenu from "./SidebarMenu";
|
import SidebarMenu from "./SidebarMenu";
|
||||||
import SystemStatusTable from "../Charts/SystemStatusTable";
|
import SystemStatusTable from "../Charts/SystemStatusTable";
|
||||||
import SystemStatusTableSoftware from "../Charts/SystemStatusTableSoftware";
|
import SystemStatusTableSoftware from "../Charts/SystemStatusTableSoftware";
|
||||||
import TreeChart from "./TreeChart"; // Подключаем граф
|
import TreeChart from "./TreeChart";
|
||||||
import "../Style/Dashboard.css";
|
import "../Style/Dashboard.css";
|
||||||
import ErrorIndicator from "./ErrorIndicator";
|
import ErrorIndicator from "./ErrorIndicator";
|
||||||
import tabContentData from "./tabContent";
|
import tabContentData from "./tabContent";
|
||||||
import menuData from "./menuData.json"; // Загружаем меню
|
import menuData from "./menuData.json"; // Загружаем новое меню
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const [tabs, setTabs] = useState([]);
|
const [tabs, setTabs] = useState([]);
|
||||||
|
|
@ -16,24 +16,42 @@ const Dashboard = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTabContent(tabContentData);
|
setTabContent(tabContentData);
|
||||||
setTreeData({ title: "Меню", items: menuData }); // Передаём данные в граф
|
setTreeData(menuData); // Теперь menuData - объект, а не массив
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleOpenTab = (tabName) => {
|
const handleOpenTab = (id, title) => {
|
||||||
if (!tabs.includes(tabName)) {
|
if (!tabs.includes(id)) {
|
||||||
setTabs([...tabs, tabName]);
|
setTabs([...tabs, id]);
|
||||||
}
|
}
|
||||||
setActiveTab(tabName);
|
setActiveTab(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseTab = (tabName) => {
|
const handleCloseTab = (id) => {
|
||||||
const newTabs = tabs.filter((tab) => tab !== tabName);
|
const newTabs = tabs.filter((tab) => tab !== id);
|
||||||
setTabs(newTabs);
|
setTabs(newTabs);
|
||||||
if (activeTab === tabName) {
|
if (activeTab === id) {
|
||||||
setActiveTab(newTabs.length > 0 ? newTabs[newTabs.length - 1] : "Главная");
|
setActiveTab(newTabs.length > 0 ? newTabs[newTabs.length - 1] : "Главная");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderTabContent = () => {
|
||||||
|
if (activeTab === "Главная") {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>Общий мониторинг</h2>
|
||||||
|
<ErrorIndicator />
|
||||||
|
<SystemStatusTable />
|
||||||
|
<SystemStatusTableSoftware />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (activeTab === "Визуализация") {
|
||||||
|
return <TreeChart data={treeData} onNodeClick={(id, title) => handleOpenTab(id, title)} />;
|
||||||
|
} else {
|
||||||
|
const tabData = tabContent[activeTab];
|
||||||
|
return tabData ? tabData.content : <p>Нет данных</p>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-container">
|
<div className="dashboard-container">
|
||||||
<SidebarMenu onOpenTab={handleOpenTab} />
|
<SidebarMenu onOpenTab={handleOpenTab} />
|
||||||
|
|
@ -73,18 +91,7 @@ const Dashboard = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="content">
|
<div className="content">
|
||||||
{activeTab === "Главная" ? (
|
{renderTabContent()}
|
||||||
<div>
|
|
||||||
<h2>Общий мониторинг</h2>
|
|
||||||
<ErrorIndicator />
|
|
||||||
<SystemStatusTable />
|
|
||||||
<SystemStatusTableSoftware />
|
|
||||||
</div>
|
|
||||||
) : activeTab === "Визуализация" ? (
|
|
||||||
<TreeChart data={treeData} onNodeClick={(node) => handleOpenTab(node.title)} />
|
|
||||||
) : (
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: tabContent[activeTab] || "<p>Нет данных</p>" }} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
const Login = ({ onLogin, onClose }) => {
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (username === "admin" && password === "admin") {
|
||||||
|
onLogin(); // Успешная авторизация
|
||||||
|
onClose(); // Закрыть модальное окно
|
||||||
|
} else {
|
||||||
|
setError("Неверный логин или пароль");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal-overlay">
|
||||||
|
<div className="modal">
|
||||||
|
<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>
|
||||||
|
{error && <p className="error">{error}</p>}
|
||||||
|
<button type="submit">Войти</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Login;
|
||||||
|
|
@ -2,19 +2,15 @@ import React, { useState } from "react";
|
||||||
import "../Style/SidebarMenu.css";
|
import "../Style/SidebarMenu.css";
|
||||||
import menuData from "./menuData.json";
|
import menuData from "./menuData.json";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Рекурсивный компонент для отображения меню
|
|
||||||
const MenuItem = ({ item, onSelectItem }) => {
|
const MenuItem = ({ item, onSelectItem }) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const hasChildren = Array.isArray(item.items) && item.items.length > 0;
|
const hasChildren = Array.isArray(item.items) && item.items.length > 0;
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
setIsOpen(!isOpen); // Раскрываем/сворачиваем подменю
|
setIsOpen(!isOpen);
|
||||||
} else {
|
} else {
|
||||||
onSelectItem(item); // Выбираем конечный элемент
|
onSelectItem(item);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -27,32 +23,23 @@ const MenuItem = ({ item, onSelectItem }) => {
|
||||||
{isOpen && hasChildren && (
|
{isOpen && hasChildren && (
|
||||||
<div className="submenu">
|
<div className="submenu">
|
||||||
{item.items.map((child, index) => (
|
{item.items.map((child, index) => (
|
||||||
<MenuItem
|
<MenuItem key={index} item={child} onSelectItem={onSelectItem} />
|
||||||
key={index}
|
|
||||||
item={typeof child === "string" ? { title: child, id: child } : child}
|
|
||||||
onSelectItem={onSelectItem}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Основной компонент SidebarMenu
|
|
||||||
function SidebarMenu({ onOpenTab }) {
|
function SidebarMenu({ onOpenTab }) {
|
||||||
const handleSelectItem = (item) => {
|
const handleSelectItem = (item) => {
|
||||||
onOpenTab(item.id, item.title); // Передаем и ID, и название
|
onOpenTab(item.id, item.title); // Передаем id и title
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sidebar">
|
<div className="sidebar">
|
||||||
<h2 className="sidebar-title">Уровень доверия:</h2>
|
|
||||||
<h2 className="sidebar-title">Меню</h2>
|
<h2 className="sidebar-title">Меню</h2>
|
||||||
{menuData.map((section, index) => ( // Используем menuData вместо menuItems
|
<MenuItem item={menuData} onSelectItem={handleSelectItem} />
|
||||||
<MenuItem key={index} item={section} onSelectItem={handleSelectItem} />
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ const TreeChart = ({ data, onNodeClick }) => {
|
||||||
|
|
||||||
node.on("click", (event, d) => {
|
node.on("click", (event, d) => {
|
||||||
if (onNodeClick) {
|
if (onNodeClick) {
|
||||||
onNodeClick(d.data);
|
onNodeClick(d.data.id, d.data.title); // Передаем id и title
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,55 @@
|
||||||
[
|
{
|
||||||
{
|
"title": "Сервис ВКС",
|
||||||
"title": "Выбор сервиса",
|
"items": [
|
||||||
"items": [
|
{
|
||||||
{
|
"title": "Функциональные задачи",
|
||||||
"id": "service1",
|
"items": [
|
||||||
"title": "Сервис ВКС"
|
{
|
||||||
},
|
"id": "system_control",
|
||||||
{
|
"title": "Контроль системы"
|
||||||
"id": "service2",
|
},
|
||||||
"title": "Сервис 2"
|
{
|
||||||
},
|
"id": "system_management",
|
||||||
{
|
"title": "Система управления"
|
||||||
"id": "service3",
|
},
|
||||||
"title": "Сервис 3"
|
{
|
||||||
}
|
"id": "conference",
|
||||||
]
|
"title": "Проведение ВКС"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Функциональные задачи",
|
"id": "backup",
|
||||||
"items": [
|
"title": "Резервное копирование"
|
||||||
{
|
},
|
||||||
"id": "system_control",
|
{
|
||||||
"title": "Контроль системы"
|
"id": "relay_info",
|
||||||
},
|
"title": "Ретрансляция информации"
|
||||||
{
|
}
|
||||||
"id": "system_management",
|
]
|
||||||
"title": "Система управления"
|
},
|
||||||
},
|
{
|
||||||
{
|
"title": "Аппаратное ПО",
|
||||||
"id": "conference",
|
"items": [
|
||||||
"title": "Проведение ВКС"
|
{
|
||||||
},
|
"id": "hardware_software_1",
|
||||||
{
|
"title": "ПО1"
|
||||||
"id": "backup",
|
},
|
||||||
"title": "Резервное копирование"
|
{
|
||||||
},
|
"id": "hardware_software_2",
|
||||||
{
|
"title": "ПО2"
|
||||||
"id": "relay_info",
|
},
|
||||||
"title": "Ретрансляция информации"
|
{
|
||||||
}
|
"id": "hardware_software_3",
|
||||||
]
|
"title": "ПО3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Аппаратное ПО",
|
"id": "hardware_software_4",
|
||||||
"items": [
|
"title": "ПО4"
|
||||||
{
|
},
|
||||||
"id": "hardware_software_1",
|
{
|
||||||
"title": "ПО1"
|
"id": "hardware_software_5",
|
||||||
},
|
"title": "ПО5"
|
||||||
{
|
}
|
||||||
"id": "hardware_software_2",
|
]
|
||||||
"title": "ПО2"
|
}
|
||||||
},
|
]
|
||||||
{
|
}
|
||||||
"id": "hardware_software_3",
|
|
||||||
"title": "ПО3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "hardware_software_4",
|
|
||||||
"title": "ПО4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "hardware_software_5",
|
|
||||||
"title": "ПО5"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import NetworkSpeedChart2 from '../Charts/TestCharts2';
|
||||||
|
|
||||||
const tabContent = {
|
const tabContent = {
|
||||||
service1: <div><h2>Сервис ВКС</h2></div>,
|
service1: { title: "Сервис ВКС", content: <div><h2>Сервис ВКС</h2></div> },
|
||||||
service2: <div><h2>Сервис 2</h2></div>,
|
system_control: { title: "Контроль системы", content: <div><h2>Контроль системы</h2><p>Описание контроля.</p></div> },
|
||||||
service3: <div><h2>Сервис 3</h2></div>,
|
system_management: { title: "Система управления", content: <div><h2>Система управления</h2><p>Описание системы управления.</p></div> },
|
||||||
system_control: <div><h2>Контроль системы</h2><p>Описание контроля.</p></div>,
|
conference: { title: "Проведение ВКС", content: <div><h2>Проведение ВКС</h2><p>Информация о проведении ВКС.</p></div> },
|
||||||
system_management: <div><h2>Система управления</h2><p>Описание системы управления.</p></div>,
|
backup: { title: "Резервное копирование", content: <div><h2>Резервное копирование</h2><p>Процесс резервного копирования.</p></div> },
|
||||||
conference: <div><h2>Проведение ВКС</h2><p>Информация о проведении ВКС.</p></div>,
|
relay_info: { title: "Ретрансляция информации", content: <div><h2>Ретрансляция информации</h2><p>Детали ретрансляции.</p></div> },
|
||||||
backup: <div><h2>Резервное копирование</h2><p>Процесс резервного копирования.</p></div>,
|
hardware_software_1: { title: "График скорости сети", content: <div><h2>График скорости сети</h2><NetworkSpeedChart2 /></div> },
|
||||||
relay_info: <div><h2>Ретрансляция информации</h2><p>Детали ретрансляции.</p></div>,
|
hardware_software_2: { title: "ПО2", content: <div><h2>ПО2</h2></div> },
|
||||||
|
hardware_software_3: { title: "ПО3", content: <div><h2>ПО3</h2></div> },
|
||||||
|
hardware_software_4: { title: "ПО4", content: <div><h2>ПО4</h2></div> },
|
||||||
|
hardware_software_5: { title: "ПО5", content: <div><h2>ПО5</h2></div> },
|
||||||
};
|
};
|
||||||
|
|
||||||
export default tabContent;
|
export default tabContent;
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
max-width: 400px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal h2 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal button:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
@ -1,53 +1,57 @@
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
table-layout: fixed; /* Фиксированная ширина столбцов */
|
table-layout: fixed;
|
||||||
}
|
/* Фиксированная ширина столбцов */
|
||||||
|
}
|
||||||
|
|
||||||
th, td {
|
th,
|
||||||
width: 25%; /* Равномерное распределение ширины для 4 столбцов */
|
td {
|
||||||
padding: 10px;
|
width: 25%;
|
||||||
text-align: left;
|
/* Равномерное распределение ширины для 4 столбцов */
|
||||||
border-bottom: 1px solid #ddd;
|
padding: 10px;
|
||||||
}
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.normal {
|
.status.normal {
|
||||||
background-color: green;
|
background-color: green;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.warning {
|
.status.warning {
|
||||||
background-color: orange;
|
background-color: orange;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status.critical {
|
.status.critical {
|
||||||
background-color: red;
|
background-color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: #0056b3;
|
background-color: #0056b3;
|
||||||
}
|
}
|
||||||
|
|
||||||
caption {
|
caption {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-right: 100% ;
|
margin-right: 100%;
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue