Отрефакторил код, добавил универсальный график, переработал древо, улучшил интерфейс

pull/8/head
DmitriyA 2025-02-26 08:42:46 +00:00
parent 517d3893be
commit afc3cec846
35 changed files with 1182 additions and 680 deletions

View File

@ -11,10 +11,12 @@
},
"dependencies": {
"chartjs-adapter-date-fns": "^3.0.0",
"recharts": "^2.15.1",
"d3": "^7.9.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"chart.js": "^4.0.0",
"chartjs-chart-box-and-violin-plot": "^4.0.0",
"react-chartjs-2": "^5.0.0",
"axios": "^1.7.9"
},

View File

@ -1,6 +1,6 @@
import React, { useState } from "react";
import Dashboard from "./Components/Dashboard";
import LoginModal from "./Components/LoginModal"; // Импортируем компонент авторизации
import Dashboard from "./Components/Layout/Dashboard";
import LoginModal from "./Components/UI/LoginModal"; // Импортируем компонент авторизации
import "./Style/LoginModal.css"; // Импортируем стили
function App() {

View File

@ -0,0 +1,45 @@
import React from 'react';
import { BarChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Bar, ResponsiveContainer } from 'recharts';
const BarChartComponent = ({ chartData, metricName, metricType, colors }) => {
// Преобразуем данные для отображения
const data = Object.keys(chartData).map(instance => {
const instanceData = chartData[instance].reduce((acc, point) => {
if (point.value !== null) {
acc[point.quantile] = point.value;
}
return acc;
}, {});
return { instance, ...instanceData };
});
// Получаем все уникальные квантили
const allQuantiles = [...new Set(
Object.values(chartData).flat().map(point => point.quantile)
)];
return (
<div>
<h2>{metricName} ({metricType})</h2>
<ResponsiveContainer width="100%" height={400}>
<BarChart data={data} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="instance" />
<YAxis />
<Tooltip />
<Legend />
{allQuantiles.map((quantile, index) => (
<Bar
key={quantile}
dataKey={quantile}
fill={colors[index % colors.length]}
name={`Quantile ${quantile}`}
/>
))}
</BarChart>
</ResponsiveContainer>
</div>
);
};
export default BarChartComponent;

View File

@ -0,0 +1,12 @@
import React from 'react';
const CounterComponent = ({ value, metricName }) => {
return (
<div style={{ textAlign: 'center', padding: '20px', border: '1px solid #ccc', borderRadius: '8px', margin: '10px' }}>
<h2>{metricName}</h2>
<p style={{ fontSize: '48px', fontWeight: 'bold', color: '#3e95cd' }}>{value}</p>
</div>
);
};
export default CounterComponent;

View File

@ -0,0 +1,48 @@
import React from 'react';
import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer } from 'recharts';
const LineChartComponent = ({ chartData, metricName, metricType, colors }) => {
// Создаем массив уникальных временных меток
const allTimes = Object.values(chartData)
.flat()
.map(point => point.time)
.filter((time, index, self) => self.indexOf(time) === index); // Убираем дубликаты
// Формируем данные для графика
const data = allTimes.map(time => {
const point = { time };
Object.keys(chartData).forEach(key => {
const instanceData = chartData[key].find(p => p.time === time);
point[key] = instanceData ? instanceData.value : null;
});
return point;
});
console.log('Processed Data:', data); // Логируем данные для графика
return (
<div>
<h2>{metricName} ({metricType})</h2>
<ResponsiveContainer width="100%" height={400}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="time" />
<YAxis />
<Tooltip />
<Legend />
{Object.keys(chartData).map((key, index) => (
<Line
key={key}
type="monotone"
dataKey={key} // Используем уникальный ключ как dataKey
stroke={colors[index % colors.length]}
name={key}
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
);
};
export default LineChartComponent;

View File

@ -0,0 +1,29 @@
import React from 'react';
import { ScatterChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Scatter, ResponsiveContainer } from 'recharts';
const ScatterChartComponent = ({ chartData, metricName, metricType, colors }) => {
return (
<div>
<h2>{metricName} ({metricType})</h2>
<ResponsiveContainer width="100%" height={400}>
<ScatterChart>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="time" />
<YAxis dataKey="value" />
<Tooltip />
<Legend />
{Object.keys(chartData).map((instance, index) => (
<Scatter
key={instance}
data={chartData[instance]}
name={instance}
fill={colors[index % colors.length]}
/>
))}
</ScatterChart>
</ResponsiveContainer>
</div>
);
};
export default ScatterChartComponent;

View File

@ -1,77 +0,0 @@
import React, { useEffect, useRef, useState } from "react";
import { Line } from "react-chartjs-2";
import axios from "axios";
import {
Chart as ChartJS,
LineElement,
PointElement,
LinearScale,
CategoryScale,
} from "chart.js";
import ExpandableInfo from "../Components/ExpandableInfo"
ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale);
const GpuTemperatureChart = () => {
const chartRef = useRef(null);
const [data, setData] = useState({
labels: Array(10).fill("").map((_, i) => i), // 20 точек по X
datasets: [
{
label: "Температура GPU (°C)",
data: [], // Начальные значения (например, 50°C)
borderColor: "blue",
borderWidth: 2,
fill: false,
cubicInterpolationMode: "monotone", // Сглаживание
tension: 0.4, // Делаем линию плавнее
},
],
});
useEffect(() => {
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);
}
};
fetchData();
const interval = setInterval(fetchData, 5000); // Обновляем данные каждые 5 секунд
return () => clearInterval(interval);
}, []);
// Пример данных для меню "Подробнее"
const details = [
{ label: "Использование", value: " 20%" },
{ label: "Оперативная память ГП", value: " 1,2/7,9 ГБ" },
{ label: "Общая память ГП", value: " 1,2/7,9 ГБ" },
];
return (
<div className="w-full max-w-2xl mx-auto p-4 flex flex-col">
<h2 className="text-xl font-semibold mb-4">График температуры ГП</h2>
<Line
ref={chartRef}
data={data}
options={{
animation: false, // Отключаем анимацию обновления (чтобы был плавный сдвиг)
scales: {
x: { display: true },
y: { min: 30, max: 80 }, // Ограничиваем Y (например, 30-80°C)
},
}}
/>
<ExpandableInfo details={details} />
</div>
);
};
export default GpuTemperatureChart;

View File

@ -0,0 +1,134 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import LineChartComponent from './Components/LineChartComponent';
import BarChartComponent from './Components/BarChartComponent';
import ScatterChartComponent from './Components/ScatterChartComponent';
const MAX_POINTS = 20; // Ограничение точек на графике
const COLORS = ['#3e95cd', '#8e5ea2', '#3cba9f', '#e8c3b9', '#c45850']; // Фиксированные цвета для линий
const PrometheusChart = ({ metricName }) => {
const [chartData, setChartData] = useState({});
const [metricType, setMetricType] = useState('');
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(`http://192.168.2.33:3000/metrics?metric=prometheus_target_metadata_cache_bytes`);
const result = response.data;
// Проверяем структуру данных
let metrics;
if (Array.isArray(result)) {
// Если данные пришли в виде массива
metrics = result;
} else if (result.data && Array.isArray(result.data)) {
// Если данные пришли в виде объекта с ключом data
metrics = result.data;
} else {
throw new Error('Invalid data format');
}
if (!Array.isArray(metrics) || metrics.length === 0) {
throw new Error('No metrics data available');
}
const type = metrics[0].type;
setMetricType(type);
if (type === 'summary') {
// Обработка данных для summary
const newData = metrics.map(m => ({
instance: m.instance,
quantile: m.quantile,
value: m.value
}));
// Группируем данные по instance
const groupedData = newData.reduce((acc, point) => {
if (!acc[point.instance]) {
acc[point.instance] = [];
}
acc[point.instance].push(point);
return acc;
}, {});
setChartData(groupedData);
} else {
// Обработка данных для counter, gauge, unknown
const newDataPoints = metrics.map(m => ({
time: new Date(m.timestamp).toLocaleTimeString(),
value: m.value,
instance: m.instance,
device: m.device || m.scrape_job, // Используем device или scrape_job
}));
// Группируем данные по instance и device/scrape_job
setChartData(prevData => {
const updatedData = { ...prevData };
newDataPoints.forEach(point => {
const key = `${point.instance}-${point.device}`; // Уникальный ключ
if (!updatedData[key]) {
updatedData[key] = [];
}
updatedData[key].push({
time: point.time,
value: point.value,
});
});
return updatedData;
});
}
} catch (error) {
console.error('Error fetching metrics:', error);
}
};
fetchData(); // Вызываем сразу при монтировании
const interval = setInterval(fetchData, 5000); // Обновляем каждые 5 секунд
return () => clearInterval(interval); // Очищаем интервал при размонтировании
}, [metricName]);
if (!Object.keys(chartData).length) return <p>Loading...</p>;
const renderChart = () => {
switch (metricType) {
case 'counter':
case 'gauge':
return (
<LineChartComponent
chartData={chartData}
metricName={metricName}
metricType={metricType}
colors={COLORS}
/>
);
case 'summary':
return (
<BarChartComponent
chartData={chartData}
metricName={metricName}
metricType={metricType}
colors={COLORS}
/>
);
case 'unknown':
return (
<ScatterChartComponent
chartData={chartData}
metricName={metricName}
metricType={metricType}
colors={COLORS}
/>
);
default:
return <p>Unsupported metric type</p>;
}
};
return renderChart();
};
export default PrometheusChart;

View File

@ -0,0 +1,83 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer } from 'recharts';
const MAX_POINTS = 20; // Ограничение точек на графике
const COLORS = ['#3e95cd', '#8e5ea2', '#3cba9f', '#e8c3b9', '#c45850']; // Фиксированные цвета для линий
const PrometheusChart2 = ({ metricName }) => {
const [chartData, setChartData] = useState({});
const [metricType, setMetricType] = useState('');
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(`http://192.168.2.33:3000/metrics?metric=node_network_iface_link`);
const metrics = response.data;
if (!Array.isArray(metrics) || metrics.length === 0) {
throw new Error('No metrics data available');
}
const type = metrics[0].type;
setMetricType(type);
// Обработка данных для counter, gauge, unknown
const newDataPoints = metrics.map(m => ({
time: new Date(m.timestamp).toLocaleTimeString(),
value: m.value,
instance: m.instance // Добавляем идентификатор инстанса
}));
// Обновляем данные для каждого инстанса
setChartData(prevData => {
const updatedData = { ...prevData };
newDataPoints.forEach(point => {
if (!updatedData[point.instance]) {
updatedData[point.instance] = [];
}
// Добавляем новую точку и ограничиваем количество точек
updatedData[point.instance] = [...updatedData[point.instance], point].slice(-MAX_POINTS);
});
return updatedData;
});
} catch (error) {
console.error('Error fetching metrics:', error);
}
};
fetchData(); // Вызываем сразу при монтировании
const interval = setInterval(fetchData, 5000); // Обновляем каждые 5 секунд
return () => clearInterval(interval); // Очищаем интервал при размонтировании
}, [metricName]);
if (!Object.keys(chartData).length) return <p>Loading...</p>;
return (
<div>
<h2>{metricName} ({metricType})</h2>
<ResponsiveContainer width="100%" height={400}>
<LineChart>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="time" />
<YAxis />
<Tooltip />
<Legend />
{Object.keys(chartData).map((instance, index) => (
<Line
key={instance}
type="monotone"
dataKey="value"
data={chartData[instance]}
name={instance}
stroke={COLORS[index % COLORS.length]}
/>
))}
</LineChart>
</ResponsiveContainer>
</div>
);
};
export default PrometheusChart2;

View File

@ -1,76 +0,0 @@
import React, { useEffect, useRef, useState } from "react";
import { Line } from "react-chartjs-2";
import {
Chart as ChartJS,
LineElement,
PointElement,
LinearScale,
CategoryScale,
} from "chart.js";
import ExpandableInfo from "../Components/ExpandableInfo"
ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale);
const RamUsageChart = () => {
const chartRef = useRef(null);
const [data, setData] = useState({
labels: Array(10).fill("").map((_, i) => i), // 20 точек по X
datasets: [
{
label: "Загруженность RAM (%)",
data: Array(20).fill(50), // Начальные значения (например, 50%)
borderColor: "green",
borderWidth: 2,
fill: false,
cubicInterpolationMode: "monotone", // Сглаживание
tension: 0.4, // Делаем линию плавнее
},
],
});
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]; // Сдвигаем данные влево
return {
labels: newLabels,
datasets: [{ ...prevData.datasets[0], data: newDataset }],
};
});
}, 1000); // Обновление каждую секунду
return () => clearInterval(interval);
}, []);
// Пример данных для меню "Подробнее"
const details = [
{ label: "Используется", value: " 6,2 ГБ" },
{ 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>
<Line
ref={chartRef}
data={data}
options={{
animation: false, // Отключаем анимацию обновления (чтобы был плавный сдвиг)
scales: {
x: { display: true },
y: { min: 0, max: 100 }, // Ограничиваем Y (например, 30-80°C)
},
}}
/>
<ExpandableInfo details={details} />
</div>
);
};
export default RamUsageChart;

View File

@ -1,165 +0,0 @@
import React, { useEffect, useState, useRef } from 'react';
import axios from 'axios';
import { Line } from 'react-chartjs-2';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
TimeScale,
} from 'chart.js';
import 'chartjs-adapter-date-fns'; // Импортируем адаптер дат
// Регистрируем компоненты Chart.js
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
TimeScale // Регистрируем временную шкалу
);
const NetworkSpeedChart = () => {
const [chartData, setChartData] = useState({
labels: [],
datasets: [],
});
const chartRef = useRef(null); // Референс на график
// Функция для загрузки данных
const fetchData = async () => {
try {
const response = await axios.get('http://192.168.2.33:3000/metrics?metric=zvks_abonents_total');
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(() => {
fetchData();
const interval = setInterval(fetchData, 5000);
// Очищаем интервал и уничтожаем график при размонтировании компонента
return () => {
clearInterval(interval);
if (chartRef.current) {
chartRef.current.destroy();
}
};
}, []);
// Опции графика
const options = {
responsive: true,
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: 'node_network_receive_bytes_total',
},
},
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 (
<div style={{ width: '800px', height: '400px' }}>
<Line ref={chartRef} data={chartData} options={options} />
</div>
);
};
export default NetworkSpeedChart;

View File

@ -1,68 +0,0 @@
import React, { useEffect, useState } from 'react';
import { Line } from 'react-chartjs-2';
import axios from 'axios';
import { Chart as ChartJS, Title, Tooltip, Legend, LineElement, CategoryScale, LinearScale } from 'chart.js';
// Регистрация компонентов Chart.js
ChartJS.register(Title, Tooltip, Legend, LineElement, CategoryScale, LinearScale);
const SimpleGraph = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
// Загружаем данные из файла с использованием axios
const response = await axios.get('/data.json'); // Путь должен быть относительно папки public
const rawData = response.data;
// Проверяем, что данные действительно массив
if (Array.isArray(rawData)) {
const chartData = rawData.map(item => ({
timestamp: item.timestamp,
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 chartOptions = {
responsive: true,
plugins: {
title: {
display: true,
text: 'Simple Data Graph',
},
},
};
const chartData = {
labels: data.map(item => item.timestamp), // Массив меток для оси X
datasets: [
{
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 SimpleGraph;

View File

@ -1,101 +0,0 @@
import React, { useState, useEffect } from "react";
import SidebarMenu from "./SidebarMenu";
import SystemStatusTable from "../Charts/SystemStatusTable";
import SystemStatusTableSoftware from "../Charts/SystemStatusTableSoftware";
import TreeChart from "./TreeChart";
import "../Style/Dashboard.css";
import ErrorIndicator from "./ErrorIndicator";
import tabContentData from "./tabContent";
import menuData from "./menuData.json"; // Загружаем новое меню
const Dashboard = () => {
const [tabs, setTabs] = useState([]);
const [activeTab, setActiveTab] = useState("Главная");
const [tabContent, setTabContent] = useState({});
const [treeData, setTreeData] = useState(null);
useEffect(() => {
setTabContent(tabContentData);
setTreeData(menuData); // Теперь menuData - объект, а не массив
}, []);
const handleOpenTab = (id, title) => {
if (!tabs.includes(id)) {
setTabs([...tabs, id]);
}
setActiveTab(id);
};
const handleCloseTab = (id) => {
const newTabs = tabs.filter((tab) => tab !== id);
setTabs(newTabs);
if (activeTab === id) {
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 (
<div className="dashboard-container">
<SidebarMenu onOpenTab={handleOpenTab} />
<div className="main-content">
<div className="tabs">
<div
className={`tab ${activeTab === "Главная" ? "active" : ""}`}
onClick={() => setActiveTab("Главная")}
>
Главная
</div>
<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="content">
{renderTabContent()}
</div>
</div>
</div>
);
};
export default Dashboard;

View File

@ -0,0 +1,75 @@
import React, { useState, useEffect } from "react";
import SidebarMenu from "./SidebarMenu";
import TreeChart from "../TreeChart/TreeChart";
import "../../Style/Dashboard.css";
import ErrorIndicator from "../UI/ErrorIndicator";
import tabContentData from "../TreeChart/tabContent";
import Tabs from "../UI/Tabs";
import menuData from "../TreeChart//menuData.json"; // Загружаем новое меню
import TableComponent from '../UI/TreeTable';
import TreeTable from "../UI/TreeTable";
const Dashboard = () => {
const [tabs, setTabs] = useState([]);
const [activeTab, setActiveTab] = useState("Главная");
const [tabContent, setTabContent] = useState({});
const [treeData, setTreeData] = useState(null);
useEffect(() => {
setTabContent(tabContentData);
setTreeData(menuData);
}, []);
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>
<ErrorIndicator />
<TreeTable data={menuData.items} />
</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 (
<div className="dashboard-container">
<SidebarMenu onOpenTab={handleOpenTab} />
<div className="main-content">
<Tabs
tabs={tabs}
activeTab={activeTab}
onTabClick={(id) => setActiveTab(id)}
onCloseTab={handleCloseTab}
/>
<div className="content">
{renderTabContent()}
</div>
</div>
</div>
);
};
export default Dashboard;

View File

@ -1,10 +1,24 @@
import React, { useState } from "react";
import "../Style/SidebarMenu.css";
import menuData from "./menuData.json";
import "../../Style/SidebarMenu.css";
import menuData from "../TreeChart/menuData.json";
const getStatusColor = (status) => {
switch (status) {
case "green":
return "#4CAF50"; // Зеленый
case "yellow":
return "#FFEB3B"; // Желтый
case "red":
return "#F44336"; // Красный
default:
return "#3d74c7"; // Белый (или любой другой стандартный цвет)
}
};
const MenuItem = ({ item, onSelectItem }) => {
const [isOpen, setIsOpen] = useState(false);
const hasChildren = Array.isArray(item.items) && item.items.length > 0;
const backgroundColor = getStatusColor(item.status);
const handleClick = () => {
if (hasChildren) {
@ -16,7 +30,7 @@ const MenuItem = ({ item, onSelectItem }) => {
return (
<div className="menu-item">
<div onClick={handleClick} className="menu-item-header">
<div onClick={handleClick} className="menu-item-header" style={{ backgroundColor }}>
<span>{item.title}</span>
{hasChildren && <span>{isOpen ? "▲" : "▼"}</span>}
</div>
@ -34,7 +48,7 @@ const MenuItem = ({ item, onSelectItem }) => {
function SidebarMenu({ onOpenTab }) {
const handleSelectItem = (item) => {
onOpenTab(item.id, item.title); // Передаем id и title
};
};
return (
<div className="sidebar">

View File

@ -1,49 +0,0 @@
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;

View File

@ -11,7 +11,7 @@ const TreeChart = ({ data, onNodeClick }) => {
d3.select(chartRef.current).selectAll("*").remove();
const width = 928;
const height = 600;
const height = 1000;
const root = d3.hierarchy(data, (d) => d.items);
const links = root.links();
@ -19,8 +19,8 @@ const TreeChart = ({ data, onNodeClick }) => {
const simulation = d3
.forceSimulation(nodes)
.force("link", d3.forceLink(links).id((d) => d.data.title).distance(80).strength(1)) // Увеличил дистанцию
.force("charge", d3.forceManyBody().strength(-500)) // Увеличил отталкивание узлов
.force("link", d3.forceLink(links).id((d) => d.data.title).distance(80).strength(1))
.force("charge", d3.forceManyBody().strength(-500))
.force("x", d3.forceX())
.force("y", d3.forceY());
@ -46,9 +46,21 @@ const TreeChart = ({ data, onNodeClick }) => {
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("fill", (d) => (d.children ? "#555" : "#000"))
.attr("fill", (d) => {
// Окрашиваем узлы в зависимости от статуса
switch (d.data.status) {
case "green":
return "#4CAF50"; // Зеленый
case "yellow":
return "#FFEB3B"; // Желтый
case "red":
return "#F44336"; // Красный
default:
return "#555"; // Серый по умолчанию
}
})
.attr("stroke", "#fff")
.attr("r", 7) // Немного увеличил размер узлов для удобства клика
.attr("r", 7)
.call(drag(simulation));
// Добавляем текстовые подписи
@ -57,13 +69,13 @@ const TreeChart = ({ data, onNodeClick }) => {
.attr("fill", "#000")
.attr("font-family", "Arial")
.attr("font-size", 12)
.attr("pointer-events", "none") // Отключаем обработку событий текста
.attr("pointer-events", "none")
.selectAll("text")
.data(nodes)
.join("text")
.text((d) => d.data.title)
.attr("dx", 12) // Отодвигаем текст дальше от узла
.attr("dy", 4) // Немного поднимаем текст
.attr("dx", 12)
.attr("dy", 4);
node.append("title").text((d) => d.data.title);
@ -85,7 +97,7 @@ const TreeChart = ({ data, onNodeClick }) => {
.attr("cy", (d) => d.y);
text
.attr("x", (d) => d.x + 12) // Смещаем текст правее узла
.attr("x", (d) => d.x + 12)
.attr("y", (d) => d.y + 4);
});

View File

@ -0,0 +1,225 @@
{
"title": "Сервис ВКС",
"status": "red",
"items": [
{
"title": "Функциональные задачи",
"status": "yellow",
"items": [
{
"id": "system_control",
"title": "Контроль системы",
"status": "red"
},
{
"id": "system_management",
"title": "Система управления",
"status": "green"
},
{
"id": "conference",
"title": "Проведение ВКС",
"status": "green"
},
{
"id": "backup",
"title": "Резервное копирование",
"status": "green"
},
{
"id": "relay_info",
"title": "Ретрансляция информации",
"status": "green"
}
]
},
{
"title": "Медиа сервер",
"items": [
{
"title": "Аппаратное обеспечение",
"items": [
{
"id": "media_system_software_1",
"title": "Центральный процессор"
},
{
"id": "media_system_software_2",
"title": "Оперативная память"
},
{
"id": "media_system_software_3",
"title": "Жесткий диск"
},
{
"id": "media_system_software_4",
"title": "Сетевые адаптеры"
}
]
},
{
"title": "Программное обеспечение",
"items": [
{
"id": "media_software_1",
"title": "ПО"
},
{
"id": "media_software_2",
"title": "ПО"
},
{
"id": "media_software_3",
"title": "ПО"
},
{
"id": "media_software_4",
"title": "ПО"
}
]
}
]
},
{
"title": "Сервер резервного копирования",
"items": [
{
"title": "Аппаратное обеспечение",
"items": [
{
"id": "copy_system_software_1",
"title": "Центральный процессор"
},
{
"id": "copy_system_software_2",
"title": "Оперативная память"
},
{
"id": "copy_system_software_3",
"title": "Жесткий диск"
},
{
"id": "copy_system_software_4",
"title": "Сетевые адаптеры"
}
]
},
{
"title": "Программное обеспечение",
"items": [
{
"id": "copy_software_1",
"title": "ПО"
},
{
"id": "copy_software_2",
"title": "ПО"
},
{
"id": "copy_software_3",
"title": "ПО"
},
{
"id": "copy_software_4",
"title": "ПО"
}
]
}
]
},
{
"title": "Сервер системы управления",
"items": [
{
"title": "Аппаратное обеспечение",
"items": [
{
"id": "control_system_software_1",
"title": "Центральный процессор"
},
{
"id": "control_system_software_2",
"title": "Оперативная память"
},
{
"id": "control_system_software_3",
"title": "Жесткий диск"
},
{
"id": "control_system_software_4",
"title": "Сетевые адаптеры"
}
]
},
{
"title": "Программное обеспечение",
"items": [
{
"id": "control_software_1",
"title": "ПО"
},
{
"id": "control_software_2",
"title": "ПО"
},
{
"id": "control_software_3",
"title": "ПО"
},
{
"id": "control_software_4",
"title": "ПО"
}
]
}
]
},
{
"title": "Сервер сбора и ретрансляции информации",
"items": [
{
"title": "Аппаратное обеспечение",
"items": [
{
"id": "system_software_1",
"title": "Центральный процессор"
},
{
"id": "system_software_2",
"title": "Оперативная память"
},
{
"id": "system_software_3",
"title": "Жесткий диск"
},
{
"id": "system_software_4",
"title": "Сетевые адаптеры"
}
]
},
{
"title": "Программное обеспечение",
"items": [
{
"id": "software_1",
"title": "ПО"
},
{
"id": "software_2",
"title": "ПО"
},
{
"id": "software_3",
"title": "ПО"
},
{
"id": "software_4",
"title": "ПО"
}
]
}
]
}
]
}

View File

@ -0,0 +1,117 @@
{
"title": "Сервис ВКС",
"items": [
{
"title": "Функциональные задачи",
"items": [
{
"title": "Тест",
"items": [
{
"id": "test1",
"title": ест2"
},
{
"id": "test2",
"title": "Тест3"
}
]
},
{
"id": "system_control",
"title": "Контроль системы"
},
{
"id": "system_management",
"title": "Система управления"
},
{
"id": "conference",
"title": "Проведение ВКС"
},
{
"id": "backup",
"title": "Резервное копирование"
},
{
"id": "relay_info",
"title": "Ретрансляция информации"
}
]
},
{
"title": "Аппаратное обеспечение",
"items": [
{
"id": "hardware_software_1",
"title": "Сервер системы управления"
},
{
"id": "hardware_software_2",
"title": "Сервер системы управления"
},
{
"id": "hardware_software_3",
"title": "Медиа-сервер"
},
{
"id": "hardware_software_4",
"title": "Медиа-сервер"
},
{
"id": "hardware_software_5",
"title": "Медиа-сервер"
},
{
"id": "hardware_software_6",
"title": "Медиа-сервер"
},
{
"id": "hardware_software_7",
"title": "Сервер резервного копирования"
},
{
"id": "hardware_software_8",
"title": "Сервер сбора и ретрансляции информации"
}
]
},
{
"title": "Программное обеспечение",
"items": [
{
"id": "software_1",
"title": "БП/ППО"
},
{
"id": "software_2",
"title": "БП/ППО"
},
{
"id": "software_3",
"title": "БП/ППО"
},
{
"id": "software_4",
"title": "БП/ППО"
},
{
"id": "software_5",
"title": "БП/ППО"
},
{
"id": "software_6",
"title": "БП/ППО"
},
{
"id": "software_7",
"title": "БП/ППО"
},
{
"id": "software_8",
"title": "БП/ППО"
}
]
}
]
}

View File

@ -0,0 +1,56 @@
import React from "react";
import PrometheusChart from '../../Charts/PrometheusChart';
const tabContent = {
// Сервис ВКС
service1: { title: "Сервис ВКС", content: <div><h2>Сервис ВКС</h2></div> },
// Функциональные задачи
system_control: { title: "Контроль системы", content: <div><h2>Контроль системы</h2><p>Описание контроля.</p></div> },
system_management: { title: "Система управления", content: <div><h2>Система управления</h2><p>Описание системы управления.</p></div> },
conference: { title: "Проведение ВКС", content: <div><h2>Проведение ВКС</h2><p>Информация о проведении ВКС.</p></div> },
backup: { title: "Резервное копирование", content: <div><h2>Резервное копирование</h2><p>Процесс резервного копирования.</p></div> },
relay_info: { title: "Ретрансляция информации", content: <div><h2>Ретрансляция информации</h2><p>Детали ретрансляции.</p></div> },
// Медиа сервер
media_system_software_1: { title: "Центральный процессор", content: <div><h2>Центральный процессор</h2><p>Описание центрального процессора медиа сервера.</p></div> },
media_system_software_2: { title: "Оперативная память", content: <div><h2>Оперативная память</h2><p>Описание оперативной памяти медиа сервера.</p></div> },
media_system_software_3: { title: "Жесткий диск", content: <div><h2>Жесткий диск</h2><p>Описание жесткого диска медиа сервера.</p></div> },
media_system_software_4: { title: "Сетевые адаптеры", content: <div><h2>Сетевые адаптеры</h2><p>Описание сетевых адаптеров медиа сервера.</p></div> },
media_software_1: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><PrometheusChart /></div> },
media_software_2: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
media_software_3: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
media_software_4: { title: "ПО", content: <div><h2>Программное обеспечение медиа сервера</h2><p>Описание ПО медиа сервера.</p></div> },
// Сервер резервного копирования
copy_system_software_1: { title: "Центральный процессор", content: <div><h2>Центральный процессор</h2><p>Описание центрального процессора сервера резервного копирования.</p></div> },
copy_system_software_2: { title: "Оперативная память", content: <div><h2>Оперативная память</h2><p>Описание оперативной памяти сервера резервного копирования.</p></div> },
copy_system_software_3: { title: "Жесткий диск", content: <div><h2>Жесткий диск</h2><p>Описание жесткого диска сервера резервного копирования.</p></div> },
copy_system_software_4: { title: "Сетевые адаптеры", content: <div><h2>Сетевые адаптеры</h2><p>Описание сетевых адаптеров сервера резервного копирования.</p></div> },
copy_software_1: { title: "ПО", content: <div><h2>Программное обеспечение сервера резервного копирования</h2><p>Описание ПО сервера резервного копирования.</p></div> },
copy_software_2: { title: "ПО", content: <div><h2>Программное обеспечение сервера резервного копирования</h2><p>Описание ПО сервера резервного копирования.</p></div> },
copy_software_3: { title: "ПО", content: <div><h2>Программное обеспечение сервера резервного копирования</h2><p>Описание ПО сервера резервного копирования.</p></div> },
copy_software_4: { title: "ПО", content: <div><h2>Программное обеспечение сервера резервного копирования</h2><p>Описание ПО сервера резервного копирования.</p></div> },
// Сервер системы управления
control_system_software_1: { title: "Центральный процессор", content: <div><h2>Центральный процессор</h2><p>Описание центрального процессора сервера системы управления.</p></div> },
control_system_software_2: { title: "Оперативная память", content: <div><h2>Оперативная память</h2><p>Описание оперативной памяти сервера системы управления.</p></div> },
control_system_software_3: { title: "Жесткий диск", content: <div><h2>Жесткий диск</h2><p>Описание жесткого диска сервера системы управления.</p></div> },
control_system_software_4: { title: "Сетевые адаптеры", content: <div><h2>Сетевые адаптеры</h2><p>Описание сетевых адаптеров сервера системы управления.</p></div> },
control_software_1: { title: "ПО", content: <div><h2>Программное обеспечение сервера системы управления</h2><p>Описание ПО сервера системы управления.</p></div> },
control_software_2: { title: "ПО", content: <div><h2>Программное обеспечение сервера системы управления</h2><p>Описание ПО сервера системы управления.</p></div> },
control_software_3: { title: "ПО", content: <div><h2>Программное обеспечение сервера системы управления</h2><p>Описание ПО сервера системы управления.</p></div> },
control_software_4: { title: "ПО", content: <div><h2>Программное обеспечение сервера системы управления</h2><p>Описание ПО сервера системы управления.</p></div> },
// Сервер сбора и ретрансляции информации
system_software_1: { title: "Центральный процессор", content: <div><h2>Центральный процессор</h2><p>Описание центрального процессора сервера сбора и ретрансляции информации.</p></div> },
system_software_2: { title: "Оперативная память", content: <div><h2>Оперативная память</h2><p>Описание оперативной памяти сервера сбора и ретрансляции информации.</p></div> },
system_software_3: { title: "Жесткий диск", content: <div><h2>Жесткий диск</h2><p>Описание жесткого диска сервера сбора и ретрансляции информации.</p></div> },
system_software_4: { title: "Сетевые адаптеры", content: <div><h2>Сетевые адаптеры</h2><p>Описание сетевых адаптеров сервера сбора и ретрансляции информации.</p></div> },
software_1: { title: "ПО", content: <div><h2>Программное обеспечение сервера сбора и ретрансляции информации</h2><p>Описание ПО сервера сбора и ретрансляции информации.</p></div> },
software_2: { title: "ПО", content: <div><h2>Программное обеспечение сервера сбора и ретрансляции информации</h2><p>Описание ПО сервера сбора и ретрансляции информации.</p></div> },
software_3: { title: "ПО", content: <div><h2>Программное обеспечение сервера сбора и ретрансляции информации</h2><p>Описание ПО сервера сбора и ретрансляции информации.</p></div> },
software_4: { title: "ПО", content: <div><h2>Программное обеспечение сервера сбора и ретрансляции информации</h2><p>Описание ПО сервера сбора и ретрансляции информации.</p></div> },
};
export default tabContent;

View File

@ -0,0 +1,31 @@
import React from "react";
import NetworkSpeedChart2 from '../../Charts/TestCharts2';
import PrometheusChart from '../../Charts/PrometheusChart';
import PrometheusChart2 from '../../Charts/PrometheusChart2';
const tabContent = {
service1: { title: "Сервис ВКС", content: <div><h2>Сервис ВКС</h2></div> },
system_control: { title: "Контроль системы", content: <div><h2>Контроль системы</h2><p>Описание контроля.</p></div> },
system_management: { title: "Система управления", content: <div><h2>Система управления</h2><p>Описание системы управления.</p></div> },
conference: { title: "Проведение ВКС", content: <div><h2>Проведение ВКС</h2><p>Информация о проведении ВКС.</p></div> },
backup: { title: "Резервное копирование", content: <div><h2>Резервное копирование</h2><p>Процесс резервного копирования.</p></div> },
relay_info: { title: "Ретрансляция информации", content: <div><h2>Ретрансляция информации</h2><p>Детали ретрансляции.</p></div> },
hardware_software_1: { title: "Сервер системы управления", content: <div><h2>Сервер системы управления</h2><PrometheusChart /></div> },
hardware_software_2: { title: "Сервер системы управления", content: <div><h2>Сервер системы управления</h2></div> },
hardware_software_3: { title: "Медиа-сервер", content: <div><h2>Медиа-сервер</h2></div> },
hardware_software_4: { title: "Медиа-сервер", content: <div><h2>Медиа-сервер</h2></div> },
hardware_software_5: { title: "Медиа-сервер", content: <div><h2>Медиа-сервер</h2></div> },
hardware_software_6: { title: "Медиа-сервер", content: <div><h2>Медиа-сервер</h2></div> },
hardware_software_7: { title: "Сервер резервного копирования", content: <div><h2>Сервер резервного копирования</h2></div> },
hardware_software_8: { title: "Сервер сбора и ретрансляции информации", content: <div><h2>Сервер сбора и ретрансляции информации</h2></div> },
software_1: { title: "БП/ППО", content: <div><h2>БП/ППО</h2></div> },
software_2: { title: "БП/ППО", content: <div><h2>БП/ППО</h2></div> },
software_3: { title: "БП/ППО", content: <div><h2>БП/ППО</h2></div> },
software_4: { title: "БП/ППО", content: <div><h2>БП/ППО</h2></div> },
software_5: { title: "БП/ППО", content: <div><h2>БП/ППО</h2></div> },
software_6: { title: "БП/ППО", content: <div><h2>БП/ППО</h2></div> },
software_7: { title: "БП/ППО", content: <div><h2>БП/ППО</h2></div> },
software_8: { title: "БП/ППО", content: <div><h2>БП/ППО</h2></div> },
};
export default tabContent;

View File

@ -1,7 +1,7 @@
import React from "react";
import criticalIcon from "../assets/images/critical.png"; // Красный треугольник
import warningIcon from "../assets/images/warning.png"; // Желтый треугольник
import "../Style/ErrorIndicator.css"; // Подключаем стили
import criticalIcon from "../../assets/images/critical.png"; // Красный треугольник
import warningIcon from "../../assets/images/warning.png"; // Желтый треугольник
import "../../Style/ErrorIndicator.css"; // Подключаем стили
const ErrorIndicator = ({ criticalCount, warningCount }) => {
return (

View File

@ -0,0 +1,49 @@
import React, { useState } from "react";
import Modal from "./Modal";
import "../../Style/LoginModal.css";
const LoginModal = ({ 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 (
<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>
{error && <p className="error">{error}</p>}
<button type="submit">Войти</button>
</form>
</Modal>
);
};
export default LoginModal;

View File

@ -0,0 +1,14 @@
import React from "react";
const Modal = ({ children, onClose }) => {
return (
<div className="modal-overlay">
<div className="modal">
{children}
<button onClick={onClose}>Закрыть</button>
</div>
</div>
);
};
export default Modal;

View File

@ -0,0 +1,55 @@
import React from "react";
import "../../Style/common.css"; // Общие стили для табов
const Tabs = ({ tabs, activeTab, onTabClick, onCloseTab }) => {
const handleMouseDown = (e, id) => {
// Проверяем, была ли нажата средняя кнопка мыши (button === 1)
if (e.button === 1) {
e.preventDefault(); // Предотвращаем стандартное поведение (например, прокрутку)
onCloseTab(id); // Закрываем вкладку
}
};
return (
<div className="tabs">
{/* Всегда отображаемые вкладки */}
<div
className={`tab ${activeTab === "Главная" ? "active" : ""}`}
onClick={() => onTabClick("Главная")}
onMouseDown={(e) => handleMouseDown(e, "Главная")} // Добавляем обработчик для СКМ
>
<span>Главная</span>
</div>
<div
className={`tab ${activeTab === "Визуализация" ? "active" : ""}`}
onClick={() => onTabClick("Визуализация")}
onMouseDown={(e) => handleMouseDown(e, "Визуализация")} // Добавляем обработчик для СКМ
>
<span>Визуализация</span>
</div>
{/* Динамически добавляемые вкладки */}
{tabs.map((tab) => (
<div
key={tab.id}
className={`tab ${activeTab === tab.id ? "active" : ""}`}
onClick={() => onTabClick(tab.id)}
onMouseDown={(e) => handleMouseDown(e, tab.id)} // Добавляем обработчик для СКМ
>
<span>{tab.title}</span>
<button
className="close-tab"
onClick={(e) => {
e.stopPropagation();
onCloseTab(tab.id);
}}
>
×
</button>
</div>
))}
</div>
);
};
export default Tabs;

View File

@ -0,0 +1,36 @@
import React from "react";
import "../../Style/TreeTable.css"; // Подключаем стили
const TreeTable = ({ data }) => {
return (
<div className="tree-table">
{/* Первый уровень заголовков */}
<div className="tree-table-header">
{data.map((item, index) => (
<div key={index} className="tree-table-column">
<div className="tree-table-title">{item.title}</div>
</div>
))}
</div>
{/* Вложенные элементы */}
<div className="tree-table-body">
{data.map((item, index) => (
<div key={index} className="tree-table-column">
{item.items && (
<div className="tree-table-items">
{item.items.map((child, childIndex) => (
<div key={childIndex} className="tree-table-item">
{child.title}
</div>
))}
</div>
)}
</div>
))}
</div>
</div>
);
};
export default TreeTable;

View File

@ -1,55 +0,0 @@
{
"title": "Сервис ВКС",
"items": [
{
"title": "Функциональные задачи",
"items": [
{
"id": "system_control",
"title": "Контроль системы"
},
{
"id": "system_management",
"title": "Система управления"
},
{
"id": "conference",
"title": "Проведение ВКС"
},
{
"id": "backup",
"title": "Резервное копирование"
},
{
"id": "relay_info",
"title": "Ретрансляция информации"
}
]
},
{
"title": "Аппаратное ПО",
"items": [
{
"id": "hardware_software_1",
"title": О1"
},
{
"id": "hardware_software_2",
"title": О2"
},
{
"id": "hardware_software_3",
"title": О3"
},
{
"id": "hardware_software_4",
"title": О4"
},
{
"id": "hardware_software_5",
"title": О5"
}
]
}
]
}

View File

@ -1,18 +0,0 @@
import React from "react";
import NetworkSpeedChart2 from '../Charts/TestCharts2';
const tabContent = {
service1: { title: "Сервис ВКС", content: <div><h2>Сервис ВКС</h2></div> },
system_control: { title: "Контроль системы", content: <div><h2>Контроль системы</h2><p>Описание контроля.</p></div> },
system_management: { title: "Система управления", content: <div><h2>Система управления</h2><p>Описание системы управления.</p></div> },
conference: { title: "Проведение ВКС", content: <div><h2>Проведение ВКС</h2><p>Информация о проведении ВКС.</p></div> },
backup: { title: "Резервное копирование", content: <div><h2>Резервное копирование</h2><p>Процесс резервного копирования.</p></div> },
relay_info: { title: "Ретрансляция информации", content: <div><h2>Ретрансляция информации</h2><p>Детали ретрансляции.</p></div> },
hardware_software_1: { title: "График скорости сети", content: <div><h2>График скорости сети</h2><NetworkSpeedChart2 /></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;

View File

@ -18,53 +18,12 @@
/* Ограничиваем высоту */
}
/* Вкладки */
.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);
box-shadow: 0 2px 5px rgba(29, 1, 1, 0.521);
}
.default-content {

View File

@ -2,6 +2,7 @@
display: flex;
align-items: center;
gap: 15px;
padding-bottom: 20px;
}
.error-item {
@ -33,4 +34,5 @@
align-items: center;
gap: 15px;
justify-content: center;
}

View File

@ -11,7 +11,7 @@
}
.modal {
background: white;
background: rgb(255, 255, 255);
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
@ -26,6 +26,7 @@
.modal label {
display: block;
margin-bottom: 5px;
color: black;
}
.modal input {
@ -38,7 +39,8 @@
.modal button {
padding: 10px 20px;
background: #007bff;
margin-bottom: 5px;
background: #08294b;
color: white;
border: none;
border-radius: 4px;

View File

@ -1,10 +1,10 @@
/* Боковое меню */
.sidebar {
width: 250px;
background-color: #333;
width: 270px;
background-color: #3d74c7;
padding: 20px;
box-sizing: border-box;
border-right: 1px solid #444;
border-right: 1px solid white;
height: 100vh;
/* Занимает всю высоту экрана */
overflow-y: auto;
@ -36,14 +36,15 @@ h2 {
justify-content: space-between;
align-items: center;
padding: 10px;
background-color: #444;
background-color: #3d74c7;
border-radius: 5px;
border: 1px solid white;
cursor: pointer;
transition: background-color 0.3s ease;
}
.menu-item-header:hover {
background-color: #222;
background-color: #195fc9;
}
.submenu {
@ -57,8 +58,8 @@ h2 {
.tab {
padding: 10px;
background-color: #444;
border: 1px solid #333;
background-color: #3d74c7;
border: 1px solid white;
border-radius: 5px;
margin-bottom: 5px;
cursor: pointer;
@ -66,5 +67,5 @@ h2 {
}
.tab:hover {
background-color: #222;
background-color: #3d74c7;
}

67
src/Style/TreeTable.css Normal file
View File

@ -0,0 +1,67 @@
.tree-table {
max-width: 90vw; /* Ограничение ширины таблицы, чтобы не растягивалась */
min-width: 400px; /* Минимальная ширина для нормального отображения */
margin: 0 auto; /* Центрирование */
overflow-x: auto;
padding: 10px;
box-sizing: border-box;
}
.tree-table-header {
display: flex;
justify-content: space-around;
width: 100%;
padding-bottom: 10px;
border-bottom: 2px solid #ccc;
}
.tree-table-column {
flex: 1;
text-align: center;
min-width: 150px;
max-width: 100%;
}
.tree-table-title {
font-weight: bold;
font-size: 18px;
color: #333;
}
.tree-table-body {
display: flex;
justify-content: space-around;
width: 100%;
margin-top: 15px;
}
.tree-table-items {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
width: 100%;
}
/* Ограничение по ширине, чтобы элементы не растягивали таблицу */
.tree-table-item {
width: 170px;
/* Фиксированная ширина для всех элементов */
height: 40px;
/* Фиксированная высота */
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 10px;
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.tree-table-item:hover {
transform: scale(1.05);
background: #f1f1f1;
}

43
src/Style/common.css Normal file
View File

@ -0,0 +1,43 @@
/* src/Style/common.css */
/* Вкладки */
.tabs {
display: flex;
gap: 5px;
padding: 5px;
background-color: #3d74c7;
border-bottom: 2px solid #195fc9;
overflow-x: auto;
border-radius: 5px;
white-space: nowrap;
}
.tab {
display: flex;
align-items: center;
background-color: #3d74c7;
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: #195fc9;
}
.close-tab {
background: none;
border: none;
cursor: pointer;
font-size: 16px;
padding: 0;
}
.error {
color: red;
margin-bottom: 10px;
}