diff --git a/package.json b/package.json
index 72c68e5..a4e7e89 100755
--- a/package.json
+++ b/package.json
@@ -10,6 +10,8 @@
"preview": "vite preview"
},
"dependencies": {
+ "chartjs-adapter-date-fns": "^3.0.0",
+ "d3": "^7.9.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"chart.js": "^4.0.0",
diff --git a/public/data.json b/public/data.json
index 65e363b..e0b1e37 100644
--- a/public/data.json
+++ b/public/data.json
@@ -1,9 +1,22 @@
-{
- "labels": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
- "datasets": [
- {
- "data": [50, 52, 55, 53, 60, 58, 65, 62, 66, 72],
- "data2": [20,56,74,45,21,20,56,74,45,21]
- }
- ]
-}
\ No newline at end of file
+[
+ {
+ "timestamp": "2025-02-18 12:00",
+ "value": 10
+ },
+ {
+ "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
+ }
+]
\ No newline at end of file
diff --git a/src/App.jsx b/src/App.jsx
index 304270b..21396db 100755
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,28 +1,24 @@
-import React from "react";
+import React, { useState } from "react";
import Dashboard from "./Components/Dashboard";
-import NetworkSpeedChart from './Charts/TestCharts';
-import NetworkSpeedChart2 from './Charts/TestCharts2'
-import NetworkSpeedChart3 from './Charts/TestCharts3'
+import LoginModal from "./Components/LoginModal"; // Импортируем компонент авторизации
+import "./Style/LoginModal.css"; // Импортируем стили
function App() {
- /*return (
-
-
Dashboard
-
-
-
Примеры импорта данных
-
-
-
-
+
+ {!isAuthenticated && showLoginModal && (
+ setShowLoginModal(false)} />
+ )}
+
+ {isAuthenticated && }
);
}
diff --git a/src/Charts/TestCharts2.jsx b/src/Charts/TestCharts2.jsx
index 66dc35e..931e22b 100644
--- a/src/Charts/TestCharts2.jsx
+++ b/src/Charts/TestCharts2.jsx
@@ -12,9 +12,8 @@ import {
Legend,
TimeScale,
} from 'chart.js';
-import 'chartjs-adapter-date-fns'; // Импортируем адаптер дат
+import 'chartjs-adapter-date-fns';
-// Регистрируем компоненты Chart.js
ChartJS.register(
CategoryScale,
LinearScale,
@@ -23,143 +22,74 @@ ChartJS.register(
Title,
Tooltip,
Legend,
- TimeScale // Регистрируем временную шкалу
+ TimeScale
);
+const MAX_DATA_POINTS = 50;
+
const NetworkSpeedChart2 = () => {
- const [chartData, setChartData] = useState({
- labels: [],
- datasets: [],
- });
+ 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=node_time_seconds');
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);
+ if (!acc[entry.device]) acc[entry.device] = [];
+ acc[entry.device].push({ x: new Date(entry.timestamp), y: entry.value });
return acc;
}, {});
- // Создаем новый набор данных
const newDatasets = Object.keys(newGroupedData).map((device, index) => {
- // Находим существующий dataset для этого устройства
- const existingDataset = prevChartData.datasets.find((dataset) => dataset.label === `Device: ${device}`);
+ const existingDataset = prevChartData.datasets.find((d) => d.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 {
label: `Device: ${device}`,
- data: newGroupedData[device].map((entry) => ({
- x: new Date(entry.timestamp),
- y: entry.value,
- })),
+ data: updatedData.slice(-MAX_DATA_POINTS),
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,
- };
+ return { labels: newDatasets[0]?.data.map((d) => d.x) || [], datasets: newDatasets };
});
} catch (error) {
console.error('Ошибка при загрузке метрик:', error);
}
};
- // Загружаем данные при монтировании компонента и обновляем каждые 5 секунд
useEffect(() => {
fetchData();
const interval = setInterval(fetchData, 5000);
-
- // Очищаем интервал и уничтожаем график при размонтировании компонента
- return () => {
- clearInterval(interval);
- if (chartRef.current) {
- chartRef.current.destroy();
- }
- };
+ return () => clearInterval(interval);
}, []);
- // Опции графика
const options = {
responsive: true,
plugins: {
- legend: {
- position: 'top',
- },
- title: {
- display: true,
- text: 'node_time_seconds',
- },
+ legend: { position: 'top' },
+ title: { display: true, text: 'node_time_seconds' },
},
scales: {
x: {
- type: 'time', // Используем временную шкалу
- time: {
- unit: 'second', // Единица времени
- displayFormats: {
- second: 'HH:mm:ss', // Формат отображения времени
- },
- },
- title: {
- display: true,
- text: 'Time',
- },
- },
- y: {
- title: {
- display: true,
- text: 'Данные',
- },
+ type: 'time',
+ time: { unit: 'second', displayFormats: { second: 'HH:mm:ss' } },
+ title: { display: true, text: 'Time' },
},
+ y: { title: { display: true, text: 'Value' } },
},
- animation: {
- duration: 1000, // Длительность анимации
- easing: 'linear', // Тип анимации
- },
+ animation: { duration: 1000, easing: 'linear' },
};
return (
-
+
);
};
-export default NetworkSpeedChart2;
\ No newline at end of file
+export default NetworkSpeedChart2;
diff --git a/src/Charts/TestCharts3.jsx b/src/Charts/TestCharts3.jsx
index 9b66a5e..cb8a36f 100644
--- a/src/Charts/TestCharts3.jsx
+++ b/src/Charts/TestCharts3.jsx
@@ -1,165 +1,68 @@
-import React, { useEffect, useState, useRef } from 'react';
-import axios from 'axios';
+import React, { useEffect, useState } from 'react';
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'; // Импортируем адаптер дат
+import axios from 'axios';
+import { Chart as ChartJS, Title, Tooltip, Legend, LineElement, CategoryScale, LinearScale } from 'chart.js';
-// Регистрируем компоненты Chart.js
-ChartJS.register(
- CategoryScale,
- LinearScale,
- PointElement,
- LineElement,
- Title,
- Tooltip,
- Legend,
- TimeScale // Регистрируем временную шкалу
-);
+// Регистрация компонентов Chart.js
+ChartJS.register(Title, Tooltip, Legend, LineElement, CategoryScale, LinearScale);
-const NetworkSpeedChart3 = () => {
- const [chartData, setChartData] = useState({
- labels: [],
- datasets: [],
- });
+const SimpleGraph = () => {
+ const [data, setData] = useState([]);
- 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(() => {
- fetchData();
- const interval = setInterval(fetchData, 5000);
+ const fetchData = async () => {
+ try {
+ // Загружаем данные из файла с использованием axios
+ const response = await axios.get('/data.json'); // Путь должен быть относительно папки public
+ const rawData = response.data;
- // Очищаем интервал и уничтожаем график при размонтировании компонента
- return () => {
- clearInterval(interval);
- if (chartRef.current) {
- chartRef.current.destroy();
+ // Проверяем, что данные действительно массив
+ 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();
}, []);
- // Опции графика
- const options = {
+ if (data.length === 0) return
Loading...
;
+
+ // Настройки графика
+ const chartOptions = {
responsive: true,
plugins: {
- legend: {
- position: 'top',
- },
title: {
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 = {
+ 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
;
};
-export default NetworkSpeedChart3;
\ No newline at end of file
+export default SimpleGraph;
diff --git a/src/Components/Dashboard.jsx b/src/Components/Dashboard.jsx
index 51465f8..a1c9a34 100644
--- a/src/Components/Dashboard.jsx
+++ b/src/Components/Dashboard.jsx
@@ -1,46 +1,62 @@
-import React, { useState } from "react";
+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";
-
-const tabContent = {
- "Сервис ВКС":
Сервис 1
,
- "Сервис 2":
Сервис 2
,
- "Сервис 3":
Сервис 3
,
- "Контроль системы":
Контроль системы
Описание контроля.
,
- "Система управления":
Система управления
Описание системы управления.
,
- "Проведение ВКС":
Проведение ВКС
Информация о проведении ВКС.
,
- "Резервное копирование":
Резервное копирование
Процесс резервного копирования.
,
- "Ретрансляция информации":
Ретрансляция информации
Детали ретрансляции.
,
-};
+import tabContentData from "./tabContent";
+import menuData from "./menuData.json"; // Загружаем новое меню
const Dashboard = () => {
- const [tabs, setTabs] = useState([]); // Открытые вкладки
- const [activeTab, setActiveTab] = useState("Главная"); // Текущая активная вкладка
+ const [tabs, setTabs] = useState([]);
+ const [activeTab, setActiveTab] = useState("Главная");
+ const [tabContent, setTabContent] = useState({});
+ const [treeData, setTreeData] = useState(null);
- const handleOpenTab = (tabName) => {
- if (!tabs.includes(tabName)) {
- setTabs([...tabs, tabName]);
+ useEffect(() => {
+ setTabContent(tabContentData);
+ setTreeData(menuData); // Теперь menuData - объект, а не массив
+ }, []);
+
+ const handleOpenTab = (id, title) => {
+ if (!tabs.includes(id)) {
+ setTabs([...tabs, id]);
}
- setActiveTab(tabName);
+ setActiveTab(id);
};
- const handleCloseTab = (tabName) => {
- const newTabs = tabs.filter(tab => tab !== tabName);
+ const handleCloseTab = (id) => {
+ const newTabs = tabs.filter((tab) => tab !== id);
setTabs(newTabs);
- if (activeTab === tabName) {
+ if (activeTab === id) {
setActiveTab(newTabs.length > 0 ? newTabs[newTabs.length - 1] : "Главная");
}
};
+ const renderTabContent = () => {
+ if (activeTab === "Главная") {
+ return (
+
+
Общий мониторинг
+
+
+
+
+ );
+ } else if (activeTab === "Визуализация") {
+ return
handleOpenTab(id, title)} />;
+ } else {
+ const tabData = tabContent[activeTab];
+ return tabData ? tabData.content : Нет данных
;
+ }
+ };
+
return (
- {/* Вкладки */}
{
>
Главная
- {tabs.map(tab => (
+
setActiveTab("Визуализация")}
+ >
+ Визуализация
+
+ {tabs.map((tab) => (
{
{tab}
@@ -65,18 +90,8 @@ const Dashboard = () => {
))}
- {/* Контент */}
- {activeTab === "Главная" ? (
-
-
Общий мониторинг
-
-
-
-
- ) : (
- tabContent[activeTab] ||
Нет контента
- )}
+ {renderTabContent()}
diff --git a/src/Components/LoginModal.jsx b/src/Components/LoginModal.jsx
new file mode 100644
index 0000000..895df58
--- /dev/null
+++ b/src/Components/LoginModal.jsx
@@ -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 (
+
+ );
+};
+
+export default Login;
\ No newline at end of file
diff --git a/src/Components/SidebarMenu.jsx b/src/Components/SidebarMenu.jsx
index a908741..2c5adb9 100644
--- a/src/Components/SidebarMenu.jsx
+++ b/src/Components/SidebarMenu.jsx
@@ -1,54 +1,16 @@
import React, { useState } from "react";
import "../Style/SidebarMenu.css";
+import menuData from "./menuData.json";
-const menuItems = [
- {
- title: "Выбор сервиса",
- items: ["Сервис ВКС", "Сервис 2", "Сервис 3"],
- },
- {
- title: "Функциональные задачи",
- items: ["Контроль системы", "Система управления", "Проведение ВКС", "Резервное копирование", "Ретрансляция информации"],
- },
- {
- title: "Программное обеспечение",
- items: [
- {
- title: "ПО 1",
- items: ["компонент ПО1", "компонент ПО2"],
- },
- {
- title: "ПО 2",
- items: ["компонент ПО3"],
- },
- ],
- },
- {
- title: "Аппаратное обеспечение",
- items: [
- {
- title: "Оборудование 1",
- items: ["компонент Оборудование 1"],
- },
- {
- title: "Оборудование 2",
- items: ["компонент Оборудование 2"],
- },
- ],
- },
-];
-
-// Рекурсивный компонент для отображения меню
const MenuItem = ({ item, onSelectItem }) => {
const [isOpen, setIsOpen] = useState(false);
-
const hasChildren = Array.isArray(item.items) && item.items.length > 0;
const handleClick = () => {
if (hasChildren) {
- setIsOpen(!isOpen); // Раскрываем/сворачиваем подменю
+ setIsOpen(!isOpen);
} else {
- onSelectItem(item); // Выбираем конечный элемент
+ onSelectItem(item);
}
};
@@ -61,11 +23,7 @@ const MenuItem = ({ item, onSelectItem }) => {
{isOpen && hasChildren && (
{item.items.map((child, index) => (
-
+
))}
)}
@@ -73,21 +31,17 @@ const MenuItem = ({ item, onSelectItem }) => {
);
};
-// Основной компонент SidebarMenu
function SidebarMenu({ onOpenTab }) {
const handleSelectItem = (item) => {
- onOpenTab(item.title); // Передаем название вкладки в родительский компонент
- };
+ onOpenTab(item.id, item.title); // Передаем id и title
+ };
return (
-
Уровень доверия:
Меню
- {menuItems.map((section, index) => (
-
- ))}
+
);
}
-export default SidebarMenu;
\ No newline at end of file
+export default SidebarMenu;
diff --git a/src/Components/TreeChart.jsx b/src/Components/TreeChart.jsx
new file mode 100644
index 0000000..c008388
--- /dev/null
+++ b/src/Components/TreeChart.jsx
@@ -0,0 +1,121 @@
+import React, { useRef, useEffect } from "react";
+import * as d3 from "d3";
+
+const TreeChart = ({ data, onNodeClick }) => {
+ const chartRef = useRef();
+
+ useEffect(() => {
+ if (!data) return;
+
+ // Очищаем старый граф перед отрисовкой
+ d3.select(chartRef.current).selectAll("*").remove();
+
+ const width = 928;
+ const height = 600;
+
+ const root = d3.hierarchy(data, (d) => d.items);
+ const links = root.links();
+ const nodes = root.descendants();
+
+ 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("x", d3.forceX())
+ .force("y", d3.forceY());
+
+ const svg = d3
+ .select(chartRef.current)
+ .attr("width", width)
+ .attr("height", height)
+ .attr("viewBox", [-width / 2, -height / 2, width, height])
+ .attr("style", "max-width: 100%; height: auto;");
+
+ const link = svg
+ .append("g")
+ .attr("stroke", "#999")
+ .attr("stroke-opacity", 0.6)
+ .selectAll("line")
+ .data(links)
+ .join("line");
+
+ const node = svg
+ .append("g")
+ .attr("stroke", "#000")
+ .attr("stroke-width", 1.5)
+ .selectAll("circle")
+ .data(nodes)
+ .join("circle")
+ .attr("fill", (d) => (d.children ? "#555" : "#000"))
+ .attr("stroke", "#fff")
+ .attr("r", 7) // Немного увеличил размер узлов для удобства клика
+ .call(drag(simulation));
+
+ // Добавляем текстовые подписи
+ const text = svg
+ .append("g")
+ .attr("fill", "#000")
+ .attr("font-family", "Arial")
+ .attr("font-size", 12)
+ .attr("pointer-events", "none") // Отключаем обработку событий текста
+ .selectAll("text")
+ .data(nodes)
+ .join("text")
+ .text((d) => d.data.title)
+ .attr("dx", 12) // Отодвигаем текст дальше от узла
+ .attr("dy", 4) // Немного поднимаем текст
+
+ node.append("title").text((d) => d.data.title);
+
+ node.on("click", (event, d) => {
+ if (onNodeClick) {
+ onNodeClick(d.data.id, d.data.title); // Передаем id и title
+ }
+ });
+
+ simulation.on("tick", () => {
+ link
+ .attr("x1", (d) => d.source.x)
+ .attr("y1", (d) => d.source.y)
+ .attr("x2", (d) => d.target.x)
+ .attr("y2", (d) => d.target.y);
+
+ node
+ .attr("cx", (d) => d.x)
+ .attr("cy", (d) => d.y);
+
+ text
+ .attr("x", (d) => d.x + 12) // Смещаем текст правее узла
+ .attr("y", (d) => d.y + 4);
+ });
+
+ return () => {
+ simulation.stop();
+ };
+ }, [data, onNodeClick]);
+
+ const drag = (simulation) => {
+ function dragstarted(event, d) {
+ if (!event.active) simulation.alphaTarget(0.3).restart();
+ d.fx = d.x;
+ d.fy = d.y;
+ }
+
+ function dragged(event, d) {
+ d.fx = event.x;
+ d.fy = event.y;
+ }
+
+ function dragended(event, d) {
+ if (!event.active) simulation.alphaTarget(0);
+ d.fx = null;
+ d.fy = null;
+ }
+
+ return d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended);
+ };
+
+ return
;
+};
+
+export default TreeChart;
diff --git a/src/Components/menuData.json b/src/Components/menuData.json
new file mode 100644
index 0000000..daf3ef1
--- /dev/null
+++ b/src/Components/menuData.json
@@ -0,0 +1,55 @@
+{
+ "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"
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/Components/tabContent.jsx b/src/Components/tabContent.jsx
new file mode 100644
index 0000000..9f7aa40
--- /dev/null
+++ b/src/Components/tabContent.jsx
@@ -0,0 +1,18 @@
+import React from "react";
+import NetworkSpeedChart2 from '../Charts/TestCharts2';
+
+const tabContent = {
+ service1: { title: "Сервис ВКС", content:
Сервис ВКС
},
+ system_control: { title: "Контроль системы", content:
Контроль системы
Описание контроля.
},
+ system_management: { title: "Система управления", content:
Система управления
Описание системы управления.
},
+ conference: { title: "Проведение ВКС", content:
Проведение ВКС
Информация о проведении ВКС.
},
+ backup: { title: "Резервное копирование", content:
Резервное копирование
Процесс резервного копирования.
},
+ relay_info: { title: "Ретрансляция информации", content:
Ретрансляция информации
Детали ретрансляции.
},
+ hardware_software_1: { title: "График скорости сети", content:
График скорости сети
},
+ hardware_software_2: { title: "ПО2", content:
ПО2
},
+ hardware_software_3: { title: "ПО3", content:
ПО3
},
+ hardware_software_4: { title: "ПО4", content:
ПО4
},
+ hardware_software_5: { title: "ПО5", content:
ПО5
},
+};
+
+export default tabContent;
\ No newline at end of file
diff --git a/src/Style/LoginModal.css b/src/Style/LoginModal.css
new file mode 100644
index 0000000..0d6d28c
--- /dev/null
+++ b/src/Style/LoginModal.css
@@ -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;
+}
\ No newline at end of file
diff --git a/src/Style/SystemStatusTable.css b/src/Style/SystemStatusTable.css
index bd14d6a..5711e1e 100644
--- a/src/Style/SystemStatusTable.css
+++ b/src/Style/SystemStatusTable.css
@@ -1,53 +1,57 @@
table {
- width: 100%;
- table-layout: fixed; /* Фиксированная ширина столбцов */
- }
-
- th, td {
- width: 25%; /* Равномерное распределение ширины для 4 столбцов */
- padding: 10px;
- text-align: left;
- border-bottom: 1px solid #ddd;
- }
-
- .status {
- padding: 5px 10px;
- border-radius: 5px;
- color: white;
- }
-
- .status.normal {
- background-color: green;
- }
-
- .status.warning {
- background-color: orange;
- }
-
- .status.critical {
- background-color: red;
- }
-
- .details {
- padding: 10px;
- background-color: #f9f9f9;
- border-radius: 5px;
- }
-
- button {
- background-color: #007bff;
- color: white;
- border: none;
- padding: 5px 10px;
- border-radius: 5px;
- cursor: pointer;
- }
-
- button:hover {
- background-color: #0056b3;
- }
+ width: 100%;
+ table-layout: fixed;
+ /* Фиксированная ширина столбцов */
+}
- caption {
- position: relative;
- margin-right: 100% ;
- }
\ No newline at end of file
+th,
+td {
+ width: 25%;
+ /* Равномерное распределение ширины для 4 столбцов */
+ padding: 10px;
+ text-align: left;
+ border-bottom: 1px solid #ddd;
+ color: #333;
+}
+
+.status {
+ padding: 5px 10px;
+ border-radius: 5px;
+ color: white;
+}
+
+.status.normal {
+ background-color: green;
+}
+
+.status.warning {
+ background-color: orange;
+}
+
+.status.critical {
+ background-color: red;
+}
+
+.details {
+ padding: 10px;
+ background-color: #f9f9f9;
+ border-radius: 5px;
+}
+
+button {
+ background-color: #007bff;
+ color: white;
+ border: none;
+ padding: 5px 10px;
+ border-radius: 5px;
+ cursor: pointer;
+}
+
+button:hover {
+ background-color: #0056b3;
+}
+
+caption {
+ position: relative;
+ margin-right: 100%;
+}
\ No newline at end of file