From d0ea618a510487c9423a7ab5f814754362b31434 Mon Sep 17 00:00:00 2001 From: yuobrezkov Date: Fri, 21 Feb 2025 12:34:18 +0300 Subject: [PATCH 01/10] Changed Jenkinsfile --- Jenkinsfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 93053b4..83ae4af 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -41,7 +41,6 @@ pipeline { always { script { echo "Cleaning up workspace..." - sh "rm -rf ${env.WORKSPACE}/package/ || true" sh "rm -rf ${env.WORKSPACE}/rc/ || true" } } @@ -56,7 +55,7 @@ pipeline { -u "${GITEA_USER}:${GITEA_PASS}" \ -H "Content-Type: application/json" \ -d '{"do":"merge"}' \ - http://git.entcor/api/v1/repos/DmitriyA/trust-module-frontend/pulls/${prId}/merge + http://git.entcor/api/v1/repos/deployer3000/trust-module-frontend/pulls/${prId}/merge """ echo "PR ${prId} merged successfully into master!" } -- 2.40.1 From afc3cec846007825085de1ac0b482ca591c67353 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 26 Feb 2025 08:42:46 +0000 Subject: [PATCH 02/10] =?UTF-8?q?=D0=9E=D1=82=D1=80=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BB=20=D0=BA=D0=BE=D0=B4,=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D1=83=D0=BD=D0=B8?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA,=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=BB=20=D0=B4=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D0=BE,=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + src/App.jsx | 4 +- src/Charts/Components/BarChartComponent.jsx | 45 ++++ src/Charts/Components/CounterComponent.jsx | 12 + src/Charts/Components/LineChartComponent.jsx | 48 ++++ .../Components/ScatterChartComponent.jsx | 29 +++ src/Charts/GpuTemperatureChart.jsx | 77 ------ src/Charts/PrometheusChart.jsx | 134 +++++++++++ src/Charts/PrometheusChart2.jsx | 83 +++++++ src/Charts/RamUsageChart.jsx | 76 ------ src/Charts/TestCharts.jsx | 165 ------------- src/Charts/TestCharts3.jsx | 68 ------ src/Components/Dashboard.jsx | 101 -------- src/Components/Layout/Dashboard.jsx | 75 ++++++ src/Components/{ => Layout}/SidebarMenu.jsx | 22 +- src/Components/LoginModal.jsx | 49 ---- src/Components/{ => TreeChart}/TreeChart.jsx | 32 ++- src/Components/TreeChart/menuData.json | 225 ++++++++++++++++++ src/Components/TreeChart/menuData3.json | 117 +++++++++ src/Components/TreeChart/tabContent.jsx | 56 +++++ src/Components/TreeChart/tabContent3.jsx | 31 +++ src/Components/{ => UI}/ErrorIndicator.jsx | 6 +- src/Components/{ => UI}/ExpandableInfo.jsx | 0 src/Components/UI/LoginModal.jsx | 49 ++++ src/Components/UI/Modal.jsx | 14 ++ src/Components/UI/Tabs.jsx | 55 +++++ src/Components/UI/TreeTable.jsx | 36 +++ src/Components/menuData.json | 55 ----- src/Components/tabContent.jsx | 18 -- src/Style/Dashboard.css | 43 +--- src/Style/ErrorIndicator.css | 2 + src/Style/LoginModal.css | 6 +- src/Style/SidebarMenu.css | 17 +- src/Style/TreeTable.css | 67 ++++++ src/Style/common.css | 43 ++++ 35 files changed, 1182 insertions(+), 680 deletions(-) create mode 100644 src/Charts/Components/BarChartComponent.jsx create mode 100644 src/Charts/Components/CounterComponent.jsx create mode 100644 src/Charts/Components/LineChartComponent.jsx create mode 100644 src/Charts/Components/ScatterChartComponent.jsx delete mode 100644 src/Charts/GpuTemperatureChart.jsx create mode 100644 src/Charts/PrometheusChart.jsx create mode 100644 src/Charts/PrometheusChart2.jsx delete mode 100644 src/Charts/RamUsageChart.jsx delete mode 100644 src/Charts/TestCharts.jsx delete mode 100644 src/Charts/TestCharts3.jsx delete mode 100644 src/Components/Dashboard.jsx create mode 100644 src/Components/Layout/Dashboard.jsx rename src/Components/{ => Layout}/SidebarMenu.jsx (69%) delete mode 100644 src/Components/LoginModal.jsx rename src/Components/{ => TreeChart}/TreeChart.jsx (77%) create mode 100644 src/Components/TreeChart/menuData.json create mode 100644 src/Components/TreeChart/menuData3.json create mode 100644 src/Components/TreeChart/tabContent.jsx create mode 100644 src/Components/TreeChart/tabContent3.jsx rename src/Components/{ => UI}/ErrorIndicator.jsx (70%) rename src/Components/{ => UI}/ExpandableInfo.jsx (100%) create mode 100644 src/Components/UI/LoginModal.jsx create mode 100644 src/Components/UI/Modal.jsx create mode 100644 src/Components/UI/Tabs.jsx create mode 100644 src/Components/UI/TreeTable.jsx delete mode 100644 src/Components/menuData.json delete mode 100644 src/Components/tabContent.jsx create mode 100644 src/Style/TreeTable.css create mode 100644 src/Style/common.css diff --git a/package.json b/package.json index a4e7e89..3ab7c61 100755 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/App.jsx b/src/App.jsx index 21396db..f127d25 100755 --- a/src/App.jsx +++ b/src/App.jsx @@ -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() { diff --git a/src/Charts/Components/BarChartComponent.jsx b/src/Charts/Components/BarChartComponent.jsx new file mode 100644 index 0000000..b0d425c --- /dev/null +++ b/src/Charts/Components/BarChartComponent.jsx @@ -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 ( +
+

{metricName} ({metricType})

+ + + + + + + + {allQuantiles.map((quantile, index) => ( + + ))} + + +
+ ); +}; + +export default BarChartComponent; \ No newline at end of file diff --git a/src/Charts/Components/CounterComponent.jsx b/src/Charts/Components/CounterComponent.jsx new file mode 100644 index 0000000..209076a --- /dev/null +++ b/src/Charts/Components/CounterComponent.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +const CounterComponent = ({ value, metricName }) => { + return ( +
+

{metricName}

+

{value}

+
+ ); +}; + +export default CounterComponent; \ No newline at end of file diff --git a/src/Charts/Components/LineChartComponent.jsx b/src/Charts/Components/LineChartComponent.jsx new file mode 100644 index 0000000..1ded03c --- /dev/null +++ b/src/Charts/Components/LineChartComponent.jsx @@ -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 ( +
+

{metricName} ({metricType})

+ + + + + + + + {Object.keys(chartData).map((key, index) => ( + + ))} + + +
+ ); +}; + +export default LineChartComponent; \ No newline at end of file diff --git a/src/Charts/Components/ScatterChartComponent.jsx b/src/Charts/Components/ScatterChartComponent.jsx new file mode 100644 index 0000000..15d5e90 --- /dev/null +++ b/src/Charts/Components/ScatterChartComponent.jsx @@ -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 ( +
+

{metricName} ({metricType})

+ + + + + + + + {Object.keys(chartData).map((instance, index) => ( + + ))} + + +
+ ); +}; + +export default ScatterChartComponent; \ No newline at end of file diff --git a/src/Charts/GpuTemperatureChart.jsx b/src/Charts/GpuTemperatureChart.jsx deleted file mode 100644 index a8a71d0..0000000 --- a/src/Charts/GpuTemperatureChart.jsx +++ /dev/null @@ -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 ( -
-

График температуры ГП

- - -
- ); -}; - -export default GpuTemperatureChart; \ No newline at end of file diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx new file mode 100644 index 0000000..a80eafb --- /dev/null +++ b/src/Charts/PrometheusChart.jsx @@ -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

Loading...

; + + const renderChart = () => { + switch (metricType) { + case 'counter': + case 'gauge': + return ( + + ); + case 'summary': + return ( + + ); + case 'unknown': + return ( + + ); + default: + return

Unsupported metric type

; + } + }; + + return renderChart(); +}; + +export default PrometheusChart; \ No newline at end of file diff --git a/src/Charts/PrometheusChart2.jsx b/src/Charts/PrometheusChart2.jsx new file mode 100644 index 0000000..eb1046a --- /dev/null +++ b/src/Charts/PrometheusChart2.jsx @@ -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

Loading...

; + + return ( +
+

{metricName} ({metricType})

+ + + + + + + + {Object.keys(chartData).map((instance, index) => ( + + ))} + + +
+ ); +}; + +export default PrometheusChart2; \ No newline at end of file diff --git a/src/Charts/RamUsageChart.jsx b/src/Charts/RamUsageChart.jsx deleted file mode 100644 index 26f5555..0000000 --- a/src/Charts/RamUsageChart.jsx +++ /dev/null @@ -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 ( -
-

График загруженности ОЗУ

- - -
- ); -}; - -export default RamUsageChart; \ No newline at end of file diff --git a/src/Charts/TestCharts.jsx b/src/Charts/TestCharts.jsx deleted file mode 100644 index b66f5eb..0000000 --- a/src/Charts/TestCharts.jsx +++ /dev/null @@ -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 ( -
- -
- ); -}; - -export default NetworkSpeedChart; \ No newline at end of file diff --git a/src/Charts/TestCharts3.jsx b/src/Charts/TestCharts3.jsx deleted file mode 100644 index cb8a36f..0000000 --- a/src/Charts/TestCharts3.jsx +++ /dev/null @@ -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
Loading...
; - - // Настройки графика - 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 ; -}; - -export default SimpleGraph; diff --git a/src/Components/Dashboard.jsx b/src/Components/Dashboard.jsx deleted file mode 100644 index a1c9a34..0000000 --- a/src/Components/Dashboard.jsx +++ /dev/null @@ -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 ( -
-

Общий мониторинг

- - - -
- ); - } else if (activeTab === "Визуализация") { - return handleOpenTab(id, title)} />; - } else { - const tabData = tabContent[activeTab]; - return tabData ? tabData.content :

Нет данных

; - } - }; - - return ( -
- - -
-
-
setActiveTab("Главная")} - > - Главная -
-
setActiveTab("Визуализация")} - > - Визуализация -
- {tabs.map((tab) => ( -
setActiveTab(tab)} - > - {tab} - -
- ))} -
- -
- {renderTabContent()} -
-
-
- ); -}; - -export default Dashboard; \ No newline at end of file diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx new file mode 100644 index 0000000..2791d02 --- /dev/null +++ b/src/Components/Layout/Dashboard.jsx @@ -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 ( +
+

Общий мониторинг

+ + +
+ ); + } else if (activeTab === "Визуализация") { + return handleOpenTab(id, title)} />; + } else { + const tabData = tabContent[activeTab]; + return tabData ? tabData.content :

Нет данных

; + } + }; + + return ( +
+ + +
+ setActiveTab(id)} + onCloseTab={handleCloseTab} + /> +
+ {renderTabContent()} +
+
+
+ ); +}; + +export default Dashboard; \ No newline at end of file diff --git a/src/Components/SidebarMenu.jsx b/src/Components/Layout/SidebarMenu.jsx similarity index 69% rename from src/Components/SidebarMenu.jsx rename to src/Components/Layout/SidebarMenu.jsx index 2c5adb9..f32cbaf 100644 --- a/src/Components/SidebarMenu.jsx +++ b/src/Components/Layout/SidebarMenu.jsx @@ -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 (
-
+
{item.title} {hasChildren && {isOpen ? "▲" : "▼"}}
@@ -34,7 +48,7 @@ const MenuItem = ({ item, onSelectItem }) => { function SidebarMenu({ onOpenTab }) { const handleSelectItem = (item) => { onOpenTab(item.id, item.title); // Передаем id и title - }; + }; return (
diff --git a/src/Components/LoginModal.jsx b/src/Components/LoginModal.jsx deleted file mode 100644 index 895df58..0000000 --- a/src/Components/LoginModal.jsx +++ /dev/null @@ -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 ( -
-
-

Авторизация

-
-
- - setUsername(e.target.value)} - required - /> -
-
- - setPassword(e.target.value)} - required - /> -
- {error &&

{error}

} - -
-
-
- ); -}; - -export default Login; \ No newline at end of file diff --git a/src/Components/TreeChart.jsx b/src/Components/TreeChart/TreeChart.jsx similarity index 77% rename from src/Components/TreeChart.jsx rename to src/Components/TreeChart/TreeChart.jsx index c008388..fc61b48 100644 --- a/src/Components/TreeChart.jsx +++ b/src/Components/TreeChart/TreeChart.jsx @@ -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); }); @@ -118,4 +130,4 @@ const TreeChart = ({ data, onNodeClick }) => { return ; }; -export default TreeChart; +export default TreeChart; \ No newline at end of file diff --git a/src/Components/TreeChart/menuData.json b/src/Components/TreeChart/menuData.json new file mode 100644 index 0000000..73144fd --- /dev/null +++ b/src/Components/TreeChart/menuData.json @@ -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": "ПО" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/Components/TreeChart/menuData3.json b/src/Components/TreeChart/menuData3.json new file mode 100644 index 0000000..1c0eeda --- /dev/null +++ b/src/Components/TreeChart/menuData3.json @@ -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": "БП/ППО" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/Components/TreeChart/tabContent.jsx b/src/Components/TreeChart/tabContent.jsx new file mode 100644 index 0000000..922e777 --- /dev/null +++ b/src/Components/TreeChart/tabContent.jsx @@ -0,0 +1,56 @@ +import React from "react"; +import PrometheusChart from '../../Charts/PrometheusChart'; + +const tabContent = { + // Сервис ВКС + service1: { title: "Сервис ВКС", content:

Сервис ВКС

}, + + // Функциональные задачи + system_control: { title: "Контроль системы", content:

Контроль системы

Описание контроля.

}, + system_management: { title: "Система управления", content:

Система управления

Описание системы управления.

}, + conference: { title: "Проведение ВКС", content:

Проведение ВКС

Информация о проведении ВКС.

}, + backup: { title: "Резервное копирование", content:

Резервное копирование

Процесс резервного копирования.

}, + relay_info: { title: "Ретрансляция информации", content:

Ретрансляция информации

Детали ретрансляции.

}, + + // Медиа сервер + media_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, + media_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, + media_system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, + media_system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, + media_software_1: { title: "ПО", content:

Программное обеспечение медиа сервера

}, + media_software_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + + // Сервер резервного копирования + copy_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора сервера резервного копирования.

}, + copy_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти сервера резервного копирования.

}, + copy_system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска сервера резервного копирования.

}, + copy_system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров сервера резервного копирования.

}, + copy_software_1: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, + copy_software_2: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, + copy_software_3: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, + copy_software_4: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, + + // Сервер системы управления + control_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора сервера системы управления.

}, + control_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти сервера системы управления.

}, + control_system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска сервера системы управления.

}, + control_system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров сервера системы управления.

}, + control_software_1: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, + control_software_2: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, + control_software_3: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, + control_software_4: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, + + // Сервер сбора и ретрансляции информации + system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора сервера сбора и ретрансляции информации.

}, + system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти сервера сбора и ретрансляции информации.

}, + system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска сервера сбора и ретрансляции информации.

}, + system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров сервера сбора и ретрансляции информации.

}, + software_1: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, + software_2: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, + software_3: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, + software_4: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, +}; + +export default tabContent; \ No newline at end of file diff --git a/src/Components/TreeChart/tabContent3.jsx b/src/Components/TreeChart/tabContent3.jsx new file mode 100644 index 0000000..4124a32 --- /dev/null +++ b/src/Components/TreeChart/tabContent3.jsx @@ -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:

Сервис ВКС

}, + 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: "Сервер системы управления", content:

Сервер системы управления

}, + hardware_software_3: { title: "Медиа-сервер", content:

Медиа-сервер

}, + hardware_software_4: { title: "Медиа-сервер", content:

Медиа-сервер

}, + hardware_software_5: { title: "Медиа-сервер", content:

Медиа-сервер

}, + hardware_software_6: { title: "Медиа-сервер", content:

Медиа-сервер

}, + hardware_software_7: { title: "Сервер резервного копирования", content:

Сервер резервного копирования

}, + hardware_software_8: { title: "Сервер сбора и ретрансляции информации", content:

Сервер сбора и ретрансляции информации

}, + software_1: { title: "БП/ППО", content:

БП/ППО

}, + software_2: { title: "БП/ППО", content:

БП/ППО

}, + software_3: { title: "БП/ППО", content:

БП/ППО

}, + software_4: { title: "БП/ППО", content:

БП/ППО

}, + software_5: { title: "БП/ППО", content:

БП/ППО

}, + software_6: { title: "БП/ППО", content:

БП/ППО

}, + software_7: { title: "БП/ППО", content:

БП/ППО

}, + software_8: { title: "БП/ППО", content:

БП/ППО

}, +}; + +export default tabContent; \ No newline at end of file diff --git a/src/Components/ErrorIndicator.jsx b/src/Components/UI/ErrorIndicator.jsx similarity index 70% rename from src/Components/ErrorIndicator.jsx rename to src/Components/UI/ErrorIndicator.jsx index 7d71840..7215211 100644 --- a/src/Components/ErrorIndicator.jsx +++ b/src/Components/UI/ErrorIndicator.jsx @@ -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 ( diff --git a/src/Components/ExpandableInfo.jsx b/src/Components/UI/ExpandableInfo.jsx similarity index 100% rename from src/Components/ExpandableInfo.jsx rename to src/Components/UI/ExpandableInfo.jsx diff --git a/src/Components/UI/LoginModal.jsx b/src/Components/UI/LoginModal.jsx new file mode 100644 index 0000000..c4d5f09 --- /dev/null +++ b/src/Components/UI/LoginModal.jsx @@ -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 ( + +

Авторизация

+
+
+ + setUsername(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + /> +
+ {error &&

{error}

} + +
+
+ ); +}; + +export default LoginModal; \ No newline at end of file diff --git a/src/Components/UI/Modal.jsx b/src/Components/UI/Modal.jsx new file mode 100644 index 0000000..3718035 --- /dev/null +++ b/src/Components/UI/Modal.jsx @@ -0,0 +1,14 @@ +import React from "react"; + +const Modal = ({ children, onClose }) => { + return ( +
+
+ {children} + +
+
+ ); +}; + +export default Modal; \ No newline at end of file diff --git a/src/Components/UI/Tabs.jsx b/src/Components/UI/Tabs.jsx new file mode 100644 index 0000000..c8c18bb --- /dev/null +++ b/src/Components/UI/Tabs.jsx @@ -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 ( +
+ {/* Всегда отображаемые вкладки */} +
onTabClick("Главная")} + onMouseDown={(e) => handleMouseDown(e, "Главная")} // Добавляем обработчик для СКМ + > + Главная +
+
onTabClick("Визуализация")} + onMouseDown={(e) => handleMouseDown(e, "Визуализация")} // Добавляем обработчик для СКМ + > + Визуализация +
+ + {/* Динамически добавляемые вкладки */} + {tabs.map((tab) => ( +
onTabClick(tab.id)} + onMouseDown={(e) => handleMouseDown(e, tab.id)} // Добавляем обработчик для СКМ + > + {tab.title} + +
+ ))} +
+ ); +}; + +export default Tabs; \ No newline at end of file diff --git a/src/Components/UI/TreeTable.jsx b/src/Components/UI/TreeTable.jsx new file mode 100644 index 0000000..60a52d3 --- /dev/null +++ b/src/Components/UI/TreeTable.jsx @@ -0,0 +1,36 @@ +import React from "react"; +import "../../Style/TreeTable.css"; // Подключаем стили + +const TreeTable = ({ data }) => { + return ( +
+ {/* Первый уровень заголовков */} +
+ {data.map((item, index) => ( +
+
{item.title}
+
+ ))} +
+ + {/* Вложенные элементы */} +
+ {data.map((item, index) => ( +
+ {item.items && ( +
+ {item.items.map((child, childIndex) => ( +
+ {child.title} +
+ ))} +
+ )} +
+ ))} +
+
+ ); +}; + +export default TreeTable; diff --git a/src/Components/menuData.json b/src/Components/menuData.json deleted file mode 100644 index daf3ef1..0000000 --- a/src/Components/menuData.json +++ /dev/null @@ -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" - } - ] - } - ] -} diff --git a/src/Components/tabContent.jsx b/src/Components/tabContent.jsx deleted file mode 100644 index 9f7aa40..0000000 --- a/src/Components/tabContent.jsx +++ /dev/null @@ -1,18 +0,0 @@ -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/Dashboard.css b/src/Style/Dashboard.css index 8a83bd3..e39b9ad 100644 --- a/src/Style/Dashboard.css +++ b/src/Style/Dashboard.css @@ -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 { diff --git a/src/Style/ErrorIndicator.css b/src/Style/ErrorIndicator.css index efce90a..115f992 100644 --- a/src/Style/ErrorIndicator.css +++ b/src/Style/ErrorIndicator.css @@ -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; + } \ No newline at end of file diff --git a/src/Style/LoginModal.css b/src/Style/LoginModal.css index 0d6d28c..ae99fe0 100644 --- a/src/Style/LoginModal.css +++ b/src/Style/LoginModal.css @@ -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; diff --git a/src/Style/SidebarMenu.css b/src/Style/SidebarMenu.css index 2956424..749045c 100644 --- a/src/Style/SidebarMenu.css +++ b/src/Style/SidebarMenu.css @@ -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; } \ No newline at end of file diff --git a/src/Style/TreeTable.css b/src/Style/TreeTable.css new file mode 100644 index 0000000..4549aee --- /dev/null +++ b/src/Style/TreeTable.css @@ -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; +} \ No newline at end of file diff --git a/src/Style/common.css b/src/Style/common.css new file mode 100644 index 0000000..cbc0f84 --- /dev/null +++ b/src/Style/common.css @@ -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; +} \ No newline at end of file -- 2.40.1 From 9ba64c71d54db50db3d537ecc6840c1ae54e169f Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 26 Feb 2025 09:02:47 +0000 Subject: [PATCH 03/10] =?UTF-8?q?=D0=9E=D1=82=D1=80=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BB=20=D0=BA=D0=BE=D0=B4,=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D1=83=D0=BD=D0=B8?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA,=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=BB=20=D0=B4=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D0=BE,=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Components/TreeChart/menuData.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/TreeChart/menuData.json b/src/Components/TreeChart/menuData.json index 73144fd..aff02c9 100644 --- a/src/Components/TreeChart/menuData.json +++ b/src/Components/TreeChart/menuData.json @@ -4,7 +4,7 @@ "items": [ { "title": "Функциональные задачи", - "status": "yellow", + "status": "red", "items": [ { "id": "system_control", -- 2.40.1 From eaf706ddfe165398ebcbd2e9f6163dc4af53d175 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Thu, 27 Feb 2025 09:54:04 -0500 Subject: [PATCH 04/10] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D1=83,=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D1=82=D1=83=D1=81=D1=8B=20=D0=B2=20=D0=B2=D0=B8?= =?UTF-8?q?=D0=B4=D0=B5=20=D0=B7=D0=B0=D1=88=D0=BB=D1=83=D1=88=D0=B5=D0=BA?= =?UTF-8?q?=20=D0=B8=20=D0=B8=D1=85=20=D1=80=D0=B0=D0=BD=D0=B4=D0=BE=D0=BC?= =?UTF-8?q?=D0=BD=D1=83=D1=8E=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Charts/PrometheusChart.jsx | 2 +- src/Components/Layout/Dashboard.jsx | 26 ++- src/Components/Layout/SidebarMenu.jsx | 21 +- src/Components/TreeChart/TreeChart.jsx | 94 +++++---- src/Components/TreeChart/dataUtils.jsx | 53 +++++ src/Components/TreeChart/menuData.json | 256 ++++++++++++++++++------ src/Components/TreeChart/tabContent.jsx | 42 +++- src/Components/UI/TreeTable.jsx | 96 ++++++--- src/Style/TreeTable.css | 107 +++++----- vite.config.js | 3 +- 10 files changed, 480 insertions(+), 220 deletions(-) create mode 100644 src/Components/TreeChart/dataUtils.jsx diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index a80eafb..c4f4bd0 100644 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -14,7 +14,7 @@ const PrometheusChart = ({ metricName }) => { useEffect(() => { const fetchData = async () => { try { - const response = await axios.get(`http://192.168.2.33:3000/metrics?metric=prometheus_target_metadata_cache_bytes`); + const response = await axios.get(`http://192.168.2.39:3000/metrics?metric=zvks_apiforsnmp_ifOutUnicastPacket1`); const result = response.data; // Проверяем структуру данных diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx index 2791d02..8a409a7 100644 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -5,20 +5,29 @@ 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 menuData from "../TreeChart/menuData.json"; // Исходные данные меню import TreeTable from "../UI/TreeTable"; - +import { updateStatuses } from "../TreeChart/dataUtils"; // Функция обновления статусов const Dashboard = () => { const [tabs, setTabs] = useState([]); const [activeTab, setActiveTab] = useState("Главная"); const [tabContent, setTabContent] = useState({}); - const [treeData, setTreeData] = useState(null); + const [treeData, setTreeData] = useState(menuData); // Загружаем меню в state + // Обновление treeData каждые 10 секунд useEffect(() => { setTabContent(tabContentData); - setTreeData(menuData); + + const interval = setInterval(() => { + setTreeData((prevData) => { + const updatedData = JSON.parse(JSON.stringify(prevData)); // Клонируем данные + updateStatuses(updatedData); // Обновляем статусы + return updatedData; + }); + }, 10000); + + return () => clearInterval(interval); }, []); const handleOpenTab = (id, title) => { @@ -42,7 +51,7 @@ const Dashboard = () => {

Общий мониторинг

- + {/* Теперь используем актуальные данные */}
); } else if (activeTab === "Визуализация") { @@ -55,8 +64,7 @@ const Dashboard = () => { return (
- - + {/* Передаём обновлённые данные */}
{ ); }; -export default Dashboard; \ No newline at end of file +export default Dashboard; diff --git a/src/Components/Layout/SidebarMenu.jsx b/src/Components/Layout/SidebarMenu.jsx index f32cbaf..6e927eb 100644 --- a/src/Components/Layout/SidebarMenu.jsx +++ b/src/Components/Layout/SidebarMenu.jsx @@ -1,19 +1,6 @@ import React, { useState } from "react"; 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"; // Белый (или любой другой стандартный цвет) - } -}; +import { getStatusColor } from "../TreeChart/dataUtils"; // Импортируем только нужную функцию const MenuItem = ({ item, onSelectItem }) => { const [isOpen, setIsOpen] = useState(false); @@ -45,15 +32,15 @@ const MenuItem = ({ item, onSelectItem }) => { ); }; -function SidebarMenu({ onOpenTab }) { +function SidebarMenu({ data, onOpenTab }) { // Теперь получаем `data` из пропсов const handleSelectItem = (item) => { - onOpenTab(item.id, item.title); // Передаем id и title + onOpenTab(item.id, item.title); }; return (

Меню

- +
); } diff --git a/src/Components/TreeChart/TreeChart.jsx b/src/Components/TreeChart/TreeChart.jsx index fc61b48..51c05b6 100644 --- a/src/Components/TreeChart/TreeChart.jsx +++ b/src/Components/TreeChart/TreeChart.jsx @@ -1,71 +1,75 @@ import React, { useRef, useEffect } from "react"; import * as d3 from "d3"; +import { getStatusColor } from "./dataUtils"; const TreeChart = ({ data, onNodeClick }) => { const chartRef = useRef(); useEffect(() => { - if (!data) return; + if (!data || !data.items) return; - // Очищаем старый граф перед отрисовкой - d3.select(chartRef.current).selectAll("*").remove(); - - const width = 928; + const width = 1000; const height = 1000; 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); + svg.selectAll("*").remove(); - const svg = d3 - .select(chartRef.current) + svg .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") + const link = svg.append("g") .attr("stroke", "#999") .attr("stroke-opacity", 0.6) .selectAll("line") .data(links) - .join("line"); + .join("line"); // Используем join для обновления связей - const node = svg - .append("g") + const node = svg.append("g") .attr("stroke", "#000") .attr("stroke-width", 1.5) .selectAll("circle") .data(nodes) - .join("circle") - .attr("fill", (d) => { - // Окрашиваем узлы в зависимости от статуса - switch (d.data.status) { - case "green": - return "#4CAF50"; // Зеленый - case "yellow": - return "#FFEB3B"; // Желтый - case "red": - return "#F44336"; // Красный - default: - return "#555"; // Серый по умолчанию - } - }) + .join("circle") // Используем join для обновления узлов + .attr("fill", (d) => getStatusColor(d.data.status)) .attr("stroke", "#fff") .attr("r", 7) - .call(drag(simulation)); + .call(drag()); - // Добавляем текстовые подписи - const text = svg - .append("g") + // Обновляем только те узлы, которые нуждаются в изменении + node.each(function (d) { + if (d.data.status === "red") { + d3.select(this) + .transition() + .duration(500) + .ease(d3.easeLinear) + .style("opacity", 0.3) + .transition() + .duration(500) + .ease(d3.easeLinear) + .style("opacity", 1) + .on("end", function repeat() { + d3.select(this) + .transition() + .duration(500) + .ease(d3.easeLinear) + .style("opacity", 0.3) + .transition() + .duration(500) + .ease(d3.easeLinear) + .style("opacity", 1) + .on("end", repeat); + }); + } + }); + + const text = svg.append("g") .attr("fill", "#000") .attr("font-family", "Arial") .attr("font-size", 12) @@ -81,10 +85,16 @@ const TreeChart = ({ data, onNodeClick }) => { node.on("click", (event, d) => { if (onNodeClick) { - onNodeClick(d.data.id, d.data.title); // Передаем id и title + onNodeClick(d.data.id, d.data.title); } }); + 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()); + simulation.on("tick", () => { link .attr("x1", (d) => d.source.x) @@ -101,14 +111,11 @@ const TreeChart = ({ data, onNodeClick }) => { .attr("y", (d) => d.y + 4); }); - return () => { - simulation.stop(); - }; + return () => simulation.stop(); }, [data, onNodeClick]); - const drag = (simulation) => { + const drag = () => { function dragstarted(event, d) { - if (!event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } @@ -119,7 +126,6 @@ const TreeChart = ({ data, onNodeClick }) => { } function dragended(event, d) { - if (!event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } @@ -130,4 +136,4 @@ const TreeChart = ({ data, onNodeClick }) => { return ; }; -export default TreeChart; \ No newline at end of file +export default TreeChart; diff --git a/src/Components/TreeChart/dataUtils.jsx b/src/Components/TreeChart/dataUtils.jsx new file mode 100644 index 0000000..c506f21 --- /dev/null +++ b/src/Components/TreeChart/dataUtils.jsx @@ -0,0 +1,53 @@ +// Функция для генерации случайных статусов +const getRandomStatus = () => { + const statuses = [ + "green", "green", "green", "green", "green", "green", "green", // 7/10 chance + "yellow", // 1/10 chance + "orange", // 1/10 chance + "red", // 1/10 chance + ]; + return statuses[Math.floor(Math.random() * statuses.length)]; +}; + +// Функция для обновления статусов в дереве +const updateStatuses = (data) => { + if (!data.items || data.items.length === 0) { + // Если это элемент нижнего уровня, генерируем случайный статус + data.status = getRandomStatus(); + return data.status; + } + + // Рекурсивно обновляем статусы для всех дочерних элементов + let childStatuses = data.items.map((child) => updateStatuses(child)); + + // Определяем статус текущего элемента на основе статусов дочерних элементов + if (childStatuses.includes("red")) { + data.status = "red"; + } else if (childStatuses.includes("orange")) { + data.status = "orange"; + } else if (childStatuses.includes("yellow")) { + data.status = "yellow"; + } else { + data.status = "green"; + } + + return data.status; +}; + +// Функция для получения цвета по статусу +const getStatusColor = (status) => { + switch (status) { + case "green": + return "#4CAF50"; // Зеленый + case "yellow": + return "#FFEB3B"; // Желтый + case "orange": + return "#FF9800"; // Оранжевый + case "red": + return "#F44336"; // Красный + default: + return "#3d74c7"; // Синий (или любой другой стандартный цвет) + } +}; + +export { getRandomStatus, updateStatuses, getStatusColor }; \ No newline at end of file diff --git a/src/Components/TreeChart/menuData.json b/src/Components/TreeChart/menuData.json index aff02c9..9e9a2e8 100644 --- a/src/Components/TreeChart/menuData.json +++ b/src/Components/TreeChart/menuData.json @@ -1,35 +1,28 @@ { "title": "Сервис ВКС", - "status": "red", "items": [ { "title": "Функциональные задачи", - "status": "red", "items": [ { "id": "system_control", - "title": "Контроль системы", - "status": "red" + "title": "Контроль системы" }, { "id": "system_management", - "title": "Система управления", - "status": "green" + "title": "Система управления" }, { "id": "conference", - "title": "Проведение ВКС", - "status": "green" + "title": "Проведение ВКС" }, { "id": "backup", - "title": "Резервное копирование", - "status": "green" + "title": "Резервное копирование" }, { "id": "relay_info", - "title": "Ретрансляция информации", - "status": "green" + "title": "Ретрансляция информации" } ] }, @@ -81,7 +74,195 @@ ] }, { - "title": "Сервер резервного копирования", + "title": "Медиа сервер", + "items": [ + { + "title": "Аппаратное обеспечение", + "items": [ + { + "id": "media_system_software_1_2", + "title": "Центральный процессор" + }, + { + "id": "media_system_software_2_2", + "title": "Оперативная память" + }, + { + "id": "media_system_software_3_2", + "title": "Жесткий диск" + }, + { + "id": "media_system_software_4_2", + "title": "Сетевые адаптеры" + } + ] + }, + { + "title": "Программное обеспечение", + "items": [ + { + "id": "media_software_1_2", + "title": "ПО" + }, + { + "id": "media_software_2_2", + "title": "ПО" + }, + { + "id": "media_software_3_2", + "title": "ПО" + }, + { + "id": "media_software_4_2", + "title": "ПО" + } + ] + } + ] + }, + { + "title": "Медиа сервер", + "items": [ + { + "title": "Аппаратное обеспечение", + "items": [ + { + "id": "media_system_software_1_3", + "title": "Центральный процессор" + }, + { + "id": "media_system_software_2_3", + "title": "Оперативная память" + }, + { + "id": "media_system_software_3_3", + "title": "Жесткий диск" + }, + { + "id": "media_system_software_4_3", + "title": "Сетевые адаптеры" + } + ] + }, + { + "title": "Программное обеспечение", + "items": [ + { + "id": "media_software_1_3", + "title": "ПО" + }, + { + "id": "media_software_2_3", + "title": "ПО" + }, + { + "id": "media_software_3_3", + "title": "ПО" + }, + { + "id": "media_software_4_3", + "title": "ПО" + } + ] + } + ] + }, + { + "title": "Медиа сервер", + "items": [ + { + "title": "Аппаратное обеспечение", + "items": [ + { + "id": "media_system_software_1_4", + "title": "Центральный процессор" + }, + { + "id": "media_system_software_2_4", + "title": "Оперативная память" + }, + { + "id": "media_system_software_3_4", + "title": "Жесткий диск" + }, + { + "id": "media_system_software_4_4", + "title": "Сетевые адаптеры" + } + ] + }, + { + "title": "Программное обеспечение", + "items": [ + { + "id": "media_software_1_4", + "title": "ПО" + }, + { + "id": "media_software_2_4", + "title": "ПО" + }, + { + "id": "media_software_3_4", + "title": "ПО" + }, + { + "id": "media_software_4_4", + "title": "ПО" + } + ] + } + ] + }, + { + "title": "Медиа сервер", + "items": [ + { + "title": "Аппаратное обеспечение", + "items": [ + { + "id": "media_system_software_1_5", + "title": "Центральный процессор" + }, + { + "id": "media_system_software_2_5", + "title": "Оперативная память" + }, + { + "id": "media_system_software_3_5", + "title": "Жесткий диск" + }, + { + "id": "media_system_software_4_5", + "title": "Сетевые адаптеры" + } + ] + }, + { + "title": "Программное обеспечение", + "items": [ + { + "id": "media_software_1_5", + "title": "ПО" + }, + { + "id": "media_software_2_5", + "title": "ПО" + }, + { + "id": "media_software_3_5", + "title": "ПО" + }, + { + "id": "media_software_4_5", + "title": "ПО" + } + ] + } + ] + }, + { + "title": "Сервер систем", "items": [ { "title": "Аппаратное обеспечение", @@ -128,7 +309,7 @@ ] }, { - "title": "Сервер системы управления", + "title": "Сервер систем", "items": [ { "title": "Аппаратное обеспечение", @@ -173,53 +354,6 @@ ] } ] - }, - { - "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": "ПО" - } - ] - } - ] } ] } \ No newline at end of file diff --git a/src/Components/TreeChart/tabContent.jsx b/src/Components/TreeChart/tabContent.jsx index 922e777..47dc890 100644 --- a/src/Components/TreeChart/tabContent.jsx +++ b/src/Components/TreeChart/tabContent.jsx @@ -12,7 +12,7 @@ const tabContent = { backup: { title: "Резервное копирование", content:

Резервное копирование

Процесс резервного копирования.

}, relay_info: { title: "Ретрансляция информации", content:

Ретрансляция информации

Детали ретрансляции.

}, - // Медиа сервер + // Медиа сервер 1 media_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, media_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, media_system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, @@ -22,6 +22,46 @@ const tabContent = { media_software_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, media_software_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + // Медиа сервер 2 + media_system_software_1_2: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, + media_system_software_2_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, + media_system_software_3_2: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, + media_system_software_4_2: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, + media_software_1_2: { title: "ПО", content:

Программное обеспечение медиа сервера

}, + media_software_2_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_3_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_4_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + + // Медиа сервер 3 + media_system_software_1_3: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, + media_system_software_2_3: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, + media_system_software_3_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, + media_system_software_4_3: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, + media_software_1_3: { title: "ПО", content:

Программное обеспечение медиа сервера

}, + media_software_2_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_3_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_4_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + + // Медиа сервер 4 + media_system_software_1_4: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, + media_system_software_2_4: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, + media_system_software_3_4: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, + media_system_software_4_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, + media_software_1_4: { title: "ПО", content:

Программное обеспечение медиа сервера

}, + media_software_2_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_3_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_4_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + + // Медиа сервер 5 + media_system_software_1_5: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, + media_system_software_2_5: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, + media_system_software_3_5: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, + media_system_software_4_5: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, + media_software_1_5: { title: "ПО", content:

Программное обеспечение медиа сервера

}, + media_software_2_5: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_3_5: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_4_5: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + // Сервер резервного копирования copy_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора сервера резервного копирования.

}, copy_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти сервера резервного копирования.

}, diff --git a/src/Components/UI/TreeTable.jsx b/src/Components/UI/TreeTable.jsx index 60a52d3..9f392d4 100644 --- a/src/Components/UI/TreeTable.jsx +++ b/src/Components/UI/TreeTable.jsx @@ -1,36 +1,76 @@ import React from "react"; -import "../../Style/TreeTable.css"; // Подключаем стили +import "../../Style/TreeTable.css"; +import { getStatusColor } from "../TreeChart/dataUtils"; // Импортируем функцию const TreeTable = ({ data }) => { - return ( -
- {/* Первый уровень заголовков */} -
- {data.map((item, index) => ( -
-
{item.title}
-
- ))} -
+ // Фильтруем данные, чтобы убрать "Функциональные задачи" + const filteredData = data.filter((item) => item.title !== "Функциональные задачи"); - {/* Вложенные элементы */} -
- {data.map((item, index) => ( -
- {item.items && ( -
- {item.items.map((child, childIndex) => ( -
- {child.title} -
- ))} -
- )} -
- ))} -
+ return ( +
+ + + {/* Первый уровень: Заголовки "Медиа сервер" */} + + {filteredData.map((item, index) => ( + + ))} + + {/* Второй уровень: "АО" и "ПО" */} + + {filteredData.map((item, index) => ( + + + + + ))} + + + + {/* Третий уровень: Вложенные элементы "АО" и "ПО" */} + {/*renderRows(filteredData)*/} + +
+ {item.title} +
+ АО + + ПО +
); }; -export default TreeTable; +// Функция для отображения строк с вложенными элементами +const renderRows = (data) => { + const maxItems = Math.max( + ...data.map((item) => + Math.max( + item.items[0]?.items?.length || 0, // АО + item.items[1]?.items?.length || 0 // ПО + ) + ) + ); + + const rows = []; + for (let i = 0; i < maxItems; i++) { + rows.push( + + {data.map((item, index) => ( + + + {item.items[0]?.items[i]?.title || ""} + + + {item.items[1]?.items[i]?.title || ""} + + + ))} + + ); + } + + return rows; +}; + +export default TreeTable; \ No newline at end of file diff --git a/src/Style/TreeTable.css b/src/Style/TreeTable.css index 4549aee..d2093b1 100644 --- a/src/Style/TreeTable.css +++ b/src/Style/TreeTable.css @@ -1,67 +1,58 @@ -.tree-table { - max-width: 90vw; /* Ограничение ширины таблицы, чтобы не растягивалась */ - min-width: 400px; /* Минимальная ширина для нормального отображения */ - margin: 0 auto; /* Центрирование */ +/* Контейнер для таблицы с прокруткой */ +.table-container { + width: 100%; + /* Занимает всю доступную ширину */ overflow-x: auto; - padding: 10px; - box-sizing: border-box; + /* Горизонтальная прокрутка при необходимости */ + margin: 0 auto; + /* Центрирование контейнера */ } -.tree-table-header { - display: flex; - justify-content: space-around; - width: 100%; - padding-bottom: 10px; - border-bottom: 2px solid #ccc; +/* Стили для таблицы */ +.tree-table { + width: auto; + /* Автоматическая ширина, чтобы таблица могла расширяться */ + min-width: 95%; + /* Минимальная ширина таблицы */ + border-collapse: collapse; + margin: 0 auto; + /* Центрирование таблицы */ } -.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; +/* Заголовки таблицы (первый уровень) */ +.tree-table th { + border: 1px solid #ddd; + padding: 8px; + text-align: left; 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); + /* Запрет на перенос текста */ + font-weight: bold; + /* Жирный шрифт для заголовков */ } -.tree-table-item:hover { - transform: scale(1.05); - background: #f1f1f1; +/* Подзаголовки (второй уровень: "АО" и "ПО") */ +.tree-table-subheader { + font-weight: 500; + /* Жирный шрифт для подзаголовков */ +} + +/* Ячейки таблицы */ +.tree-table td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; + white-space: nowrap; + /* Запрет на перенос текста */ + font-weight: normal; + /* Обычный шрифт для ячеек */ +} + +/* Цвет фона для заголовков */ +.tree-table-header { + background-color: #f4f4f4; +} + +/* Чередование цвета строк */ +.tree-table-row:nth-child(even) { + background-color: #f9f9f9; } \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index 6bb98ac..b034150 100755 --- a/vite.config.js +++ b/vite.config.js @@ -5,6 +5,7 @@ import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { - host: true + host: true, + allowedHosts: ['dev.msf.enode'] } }) -- 2.40.1 From 750f06a4e55e5319d562528369b4b99420350ad1 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Fri, 28 Feb 2025 08:42:48 -0500 Subject: [PATCH 05/10] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B4=D0=B8=D0=B7=D0=B0=D0=B9=D0=BD=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D1=82=D1=83=D1=81=D0=BE=D0=B2=20=D0=B8=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=B0=D0=B4=D0=B0=D0=BF=D1=82?= =?UTF-8?q?=D0=B8=D0=B2=D0=BD=D1=8B=D0=B9=20=D1=81=D0=B0=D0=B9=D0=B4=D0=B1?= =?UTF-8?q?=D0=B0=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Charts/PrometheusChart.jsx | 2 +- src/Components/Layout/Dashboard.jsx | 65 ++++++++++++-- src/Components/Layout/SidebarMenu.jsx | 27 ++++-- src/Components/TreeChart/dataUtils.jsx | 8 +- src/Style/Dashboard.css | 67 +++++++++----- src/Style/SidebarMenu.css | 115 ++++++++++++++++++------- src/Style/common.css | 45 ++++++++-- 7 files changed, 249 insertions(+), 80 deletions(-) diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index c4f4bd0..ddfe219 100644 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -14,7 +14,7 @@ const PrometheusChart = ({ metricName }) => { useEffect(() => { const fetchData = async () => { try { - const response = await axios.get(`http://192.168.2.39:3000/metrics?metric=zvks_apiforsnmp_ifOutUnicastPacket1`); + const response = await axios.get(`http://192.168.2.39:3000/metrics?metric=zvks_apiforsnmp_cpurawsystem`); const result = response.data; // Проверяем структуру данных diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx index 8a409a7..fd733a7 100644 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import SidebarMenu from "./SidebarMenu"; import TreeChart from "../TreeChart/TreeChart"; import "../../Style/Dashboard.css"; @@ -14,8 +14,11 @@ const Dashboard = () => { const [activeTab, setActiveTab] = useState("Главная"); const [tabContent, setTabContent] = useState({}); const [treeData, setTreeData] = useState(menuData); // Загружаем меню в state + const [sidebarWidth, setSidebarWidth] = useState(250); // Начальная ширина сайдбара + const [isResizing, setIsResizing] = useState(false); // Состояние перетаскивания + const sidebarRef = useRef(null); // Референс на сайдбар - // Обновление treeData каждые 10 секунд + // Обновление treeData каждые 30 секунд useEffect(() => { setTabContent(tabContentData); @@ -25,11 +28,48 @@ const Dashboard = () => { updateStatuses(updatedData); // Обновляем статусы return updatedData; }); - }, 10000); + }, 30000); return () => clearInterval(interval); }, []); + // Обработчик начала перетаскивания + const startResizing = (e) => { + e.preventDefault(); + setIsResizing(true); + }; + + // Обработчик движения мыши + const resize = (e) => { + if (isResizing) { + const newWidth = e.clientX; // Новая ширина сайдбара + if (newWidth > 100 && newWidth < 400) { // Ограничиваем минимальную и максимальную ширину + setSidebarWidth(newWidth); + } + } + }; + + // Обработчик окончания перетаскивания + const stopResizing = () => { + setIsResizing(false); + }; + + // Добавляем обработчики событий + useEffect(() => { + const handleMouseMove = (e) => resize(e); + const handleMouseUp = () => stopResizing(); + + if (isResizing) { + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mouseup", handleMouseUp); + } + + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mouseup", handleMouseUp); + }; + }, [isResizing]); + const handleOpenTab = (id, title) => { if (!tabs.some((tab) => tab.id === id)) { setTabs([...tabs, { id, title }]); @@ -51,7 +91,8 @@ const Dashboard = () => {

Общий мониторинг

- {/* Теперь используем актуальные данные */} + {/* Используем актуальные данные */} +
); } else if (activeTab === "Визуализация") { @@ -64,8 +105,20 @@ const Dashboard = () => { return (
- {/* Передаём обновлённые данные */} -
+
+ + {/* Элемент для перетаскивания */} +
+
+ +
{ +const MenuItem = ({ item, onSelectItem, sidebarWidth }) => { const [isOpen, setIsOpen] = useState(false); const hasChildren = Array.isArray(item.items) && item.items.length > 0; - const backgroundColor = getStatusColor(item.status); + const statusColor = getStatusColor(item.status); const handleClick = () => { if (hasChildren) { @@ -16,15 +16,20 @@ const MenuItem = ({ item, onSelectItem }) => { }; return ( -
-
+
{/* Динамическая ширина */} +
+ {/* Круглый индикатор статуса */} +
{item.title} {hasChildren && {isOpen ? "▲" : "▼"}}
{isOpen && hasChildren && (
{item.items.map((child, index) => ( - + ))}
)} @@ -32,15 +37,21 @@ const MenuItem = ({ item, onSelectItem }) => { ); }; -function SidebarMenu({ data, onOpenTab }) { // Теперь получаем `data` из пропсов +function SidebarMenu({ data, onOpenTab, sidebarWidth }) { const handleSelectItem = (item) => { onOpenTab(item.id, item.title); }; return (
-

Меню

- +
{/* Динамическая ширина */} +

Меню

+ +
+
{/* Динамическая ширина */} +

Помощь

+

Настройка

+
); } diff --git a/src/Components/TreeChart/dataUtils.jsx b/src/Components/TreeChart/dataUtils.jsx index c506f21..85b5217 100644 --- a/src/Components/TreeChart/dataUtils.jsx +++ b/src/Components/TreeChart/dataUtils.jsx @@ -1,10 +1,10 @@ // Функция для генерации случайных статусов const getRandomStatus = () => { const statuses = [ - "green", "green", "green", "green", "green", "green", "green", // 7/10 chance - "yellow", // 1/10 chance - "orange", // 1/10 chance - "red", // 1/10 chance + ...Array(90).fill("green"), // 63/70 chance (примерно 90%) + ...Array(6).fill("yellow"), // 1/70 chance (примерно 1.43%) + ...Array(3).fill("orange"), // 1/70 chance (примерно 1.43%) + ...Array(1).fill("red"), // 1/70 chance (примерно 1.43%) ]; return statuses[Math.floor(Math.random() * statuses.length)]; }; diff --git a/src/Style/Dashboard.css b/src/Style/Dashboard.css index e39b9ad..d1f7b33 100644 --- a/src/Style/Dashboard.css +++ b/src/Style/Dashboard.css @@ -1,21 +1,56 @@ +/* Основной контейнер */ .dashboard-container { display: flex; height: 100vh; - width: 100vw; + width: 98vw; overflow: hidden; - /* Запрещаем появление скролла */ + margin-left: 20px; } +/* Сайдбар */ +.sidebar { + height: 100vh; + background-color: #3d74c7; + color: white; + position: fixed; + left: 0; + top: 0; + z-index: 999; + overflow: hidden; + transition: width 0.2s ease; + /* Плавное изменение ширины */ + display: flex; + flex-direction: column; +} + +/* Элемент для перетаскивания */ +.sidebar-resizer { + width: 5px; + /* Ширина элемента перетаскивания */ + height: 100%; + background-color: rgba(255, 255, 255, 0.1); + position: absolute; + right: 0; + top: 0; + cursor: ew-resize; + /* Курсор "изменить размер" */ + transition: background-color 0.2s ease; + z-index: 1000; + /* Убедимся, что элемент поверх других */ +} + +.sidebar-resizer:hover { + background-color: rgba(255, 255, 255, 0.3); + /* Эффект при наведении */ +} + +/* Основной контент */ .main-content { flex: 1; - min-width: 400px; - max-width: calc(100vw - 250px); padding: 20px; - box-sizing: border-box; - overflow-y: auto; - /* Добавляем вертикальную прокрутку */ - height: 100vh; - /* Ограничиваем высоту */ + margin-left: 50px; + transition: margin-left 0.2s ease; + /* Плавное изменение отступа */ } /* Контент */ @@ -26,19 +61,7 @@ box-shadow: 0 2px 5px rgba(29, 1, 1, 0.521); } -.default-content { - display: flex; - flex-direction: column; - gap: 50px; -} - -.tab-content { - background-color: #fff; - padding: 20px; - border-radius: 10px; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); -} - +/* Заголовки */ h2 { color: #444; } diff --git a/src/Style/SidebarMenu.css b/src/Style/SidebarMenu.css index 749045c..9fcb8d6 100644 --- a/src/Style/SidebarMenu.css +++ b/src/Style/SidebarMenu.css @@ -1,71 +1,124 @@ /* Боковое меню */ .sidebar { - width: 270px; - background-color: #3d74c7; - padding: 20px; - box-sizing: border-box; - border-right: 1px solid white; height: 100vh; - /* Занимает всю высоту экрана */ - overflow-y: auto; - /* Прокрутка внутри меню, если контент не помещается */ - position: sticky; - /* Фиксируем меню */ + background-color: #3d74c7; + color: white; + position: fixed; + left: 0; top: 0; - /* Прилипаем к верху */ + z-index: 999; + overflow: hidden; + transition: width 0.2s ease; + /* Плавное изменение ширины */ + display: flex; + flex-direction: column; } +/* Контейнер для основного контента меню */ +.sidebar-content { + flex: 1; + overflow-y: auto; + /* Вертикальная прокрутка */ + overflow-x: hidden; + /* Убираем горизонтальную прокрутку */ + padding-bottom: 20px; + /* Отступ для "Помощи" и "Настроек" */ +} + +/* Заголовок меню */ .sidebar-title { margin-bottom: 20px; font-size: 18px; font-weight: bold; color: white; + padding: 10px; + /* Добавляем отступы */ } +/* Элементы меню */ .menu-item { margin-bottom: 10px; color: white; -} - -h2 { - color: white + width: 100%; + /* Ширина на всю ширину сайдбара */ } .menu-item-header { display: flex; - justify-content: space-between; align-items: center; + /* Выравниваем элементы по центру */ padding: 10px; - background-color: #3d74c7; border-radius: 5px; - border: 1px solid white; cursor: pointer; transition: background-color 0.3s ease; } .menu-item-header:hover { - background-color: #195fc9; + background-color: rgba(255, 255, 255, 0.1); + /* Легкий эффект при наведении */ } +/* Круглый индикатор статуса */ +.status-indicator { + width: 10px; + height: 10px; + border-radius: 50%; + /* Делаем круглым */ + margin-right: 10px; + /* Отступ от текста */ + flex-shrink: 0; + /* Запрещаем сжатие */ +} + +/* Анимация мигания для красного индикатора */ +@keyframes blink { + 0% { + opacity: 1; + } + + /* Полная видимость */ + 50% { + opacity: 0.3; + } + + /* Полупрозрачность */ + 100% { + opacity: 1; + } + + /* Полная видимость */ +} + +.status-indicator.blinking { + animation: blink 1s infinite; + /* Бесконечная анимация с интервалом 1 секунда */ +} + +/* Подменю */ .submenu { margin-left: 20px; margin-top: 10px; } -.tabs-container { - margin-top: 20px; -} - -.tab { +/* Футер сайдбара */ +.sidebar-footer { padding: 10px; background-color: #3d74c7; - border: 1px solid white; - border-radius: 5px; - margin-bottom: 5px; - cursor: pointer; - transition: background-color 0.3s ease; + text-align: center; + border-top: 1px solid rgba(255, 255, 255, 0.1); + /* Разделительная линия */ + flex-shrink: 0; + /* Запрещаем сжатие */ + width: 100%; + /* Ширина на всю ширину сайдбара */ } -.tab:hover { - background-color: #3d74c7; +.help, +.settings { + color: white; + margin: 5px 0; + /* Отступы между элементами */ + overflow-x: hidden; + /* Убираем горизонтальную прокрутку */ + text-align: left; } \ No newline at end of file diff --git a/src/Style/common.css b/src/Style/common.css index cbc0f84..d170447 100644 --- a/src/Style/common.css +++ b/src/Style/common.css @@ -1,43 +1,72 @@ /* 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; + /* Цвет текста */ + padding: 5px 15px; + /* Отступы внутри вкладки */ border-radius: 5px 5px 0 0; + /* Скругление углов */ cursor: pointer; - max-width: 250px; - min-width: 100px; + /* Курсор при наведении */ flex-shrink: 0; - position: relative; + /* Запрет сжатия */ + transition: background-color 0.3s ease; + /* Плавное изменение цвета */ } +/* Активная вкладка */ .tab.active { background-color: #195fc9; + /* Цвет фона активной вкладки */ } +/* Кнопка закрытия вкладки */ .close-tab { background: none; border: none; + color: white; + /* Цвет крестика */ cursor: pointer; font-size: 16px; + margin-left: 10px; + /* Отступ от текста */ padding: 0; + transition: color 0.3s ease; + /* Плавное изменение цвета */ } -.error { - color: red; - margin-bottom: 10px; +/* Эффект при наведении на кнопку закрытия */ +.close-tab:hover { + color: #ff6b6b; + /* Цвет крестика при наведении */ +} + +/* Эффект при наведении на вкладку */ +.tab:hover { + background-color: #195fc9; + /* Цвет фона при наведении */ } \ No newline at end of file -- 2.40.1 From dbb06623669bc69d934f9261e7f687c910a36176 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Mon, 3 Mar 2025 04:05:21 -0500 Subject: [PATCH 06/10] =?UTF-8?q?=D0=9F=D0=BE=D1=84=D0=B8=D0=BA=D1=81?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B3=D1=80=D0=B0=D1=84=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Components/TreeChart/TreeChart.jsx | 109 ++++++++++++++----------- 1 file changed, 60 insertions(+), 49 deletions(-) diff --git a/src/Components/TreeChart/TreeChart.jsx b/src/Components/TreeChart/TreeChart.jsx index 51c05b6..260854b 100644 --- a/src/Components/TreeChart/TreeChart.jsx +++ b/src/Components/TreeChart/TreeChart.jsx @@ -1,48 +1,68 @@ -import React, { useRef, useEffect } from "react"; +import React, { useRef, useEffect, useMemo } from "react"; import * as d3 from "d3"; import { getStatusColor } from "./dataUtils"; const TreeChart = ({ data, onNodeClick }) => { const chartRef = useRef(); + const simulationRef = useRef(null); - useEffect(() => { - if (!data || !data.items) return; - - const width = 1000; - const height = 1000; - + const { root, nodes, links } = useMemo(() => { + if (!data || !data.items) return { root: null, nodes: [], links: [] }; const root = d3.hierarchy(data, (d) => d.items); const links = root.links(); const nodes = root.descendants(); + return { root, nodes, links }; + }, [data]); + + useEffect(() => { + if (!chartRef.current) return; + + const svg = d3.select(chartRef.current) + .attr("width", 2000) + .attr("height", 1000) + .attr("viewBox", [-500, -500, 1000, 1000]) + .attr("style", "max-width: 100%; height: auto;"); + }, []); + + useEffect(() => { + if (!root || !chartRef.current) return; const svg = d3.select(chartRef.current); - svg.selectAll("*").remove(); - svg - .attr("width", width) - .attr("height", height) - .attr("viewBox", [-width / 2, -height / 2, width, height]) - .attr("style", "max-width: 100%; height: auto;"); + if (simulationRef.current) { + simulationRef.current.stop(); + } - const link = svg.append("g") - .attr("stroke", "#999") - .attr("stroke-opacity", 0.6) + const link = svg.select(".links") .selectAll("line") - .data(links) - .join("line"); // Используем join для обновления связей + .data(links, (d) => `${d.source.data.id}-${d.target.data.id}`) + .join("line") + .attr("stroke", "#999") + .attr("stroke-opacity", 0.6); - const node = svg.append("g") - .attr("stroke", "#000") - .attr("stroke-width", 1.5) + const node = svg.select(".nodes") .selectAll("circle") - .data(nodes) - .join("circle") // Используем join для обновления узлов + .data(nodes, (d) => d.data.id) + .join("circle") .attr("fill", (d) => getStatusColor(d.data.status)) .attr("stroke", "#fff") .attr("r", 7) .call(drag()); - // Обновляем только те узлы, которые нуждаются в изменении + node.on("click", (event, d) => { + if (onNodeClick) { + onNodeClick(d.data.id, d.data.title); + } + }); + + const text = svg.select(".labels") + .selectAll("text") + .data(nodes, (d) => d.data.id) + .join("text") + .text((d) => d.data.title) + .attr("dx", 12) + .attr("dy", 4); + node.each(function (d) { if (d.data.status === "red") { d3.select(this) @@ -69,28 +89,8 @@ const TreeChart = ({ data, onNodeClick }) => { } }); - 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); - } - }); - const simulation = d3.forceSimulation(nodes) - .force("link", d3.forceLink(links).id((d) => d.data.title).distance(80).strength(1)) + .force("link", d3.forceLink(links).id((d) => d.data.id).distance(80).strength(1)) .force("charge", d3.forceManyBody().strength(-500)) .force("x", d3.forceX()) .force("y", d3.forceY()); @@ -111,11 +111,14 @@ const TreeChart = ({ data, onNodeClick }) => { .attr("y", (d) => d.y + 4); }); + simulationRef.current = simulation; + return () => simulation.stop(); - }, [data, onNodeClick]); + }, [root, links, nodes, onNodeClick]); const drag = () => { function dragstarted(event, d) { + if (!event.active) simulationRef.current.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } @@ -126,14 +129,22 @@ const TreeChart = ({ data, onNodeClick }) => { } function dragended(event, d) { - d.fx = null; - d.fy = null; + if (!event.active) simulationRef.current.alphaTarget(0); + d.fx = d.x; + d.fy = d.y; } return d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended); }; - return ; + return ( + + + + + + ); }; export default TreeChart; + -- 2.40.1 From 90d6565a5a54be3ff871d9076e2f764996256625 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 5 Mar 2025 12:55:50 -0500 Subject: [PATCH 07/10] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BC=D0=B5=D0=BD=D1=8E,=20=D1=83=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D1=88=D0=B8=D0=BB=20=D0=BD=D0=B0=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4=D0=BE=D0=BA?= =?UTF-8?q?,=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B8=D0=BB=20=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=84=D0=B8=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- src/App.css | 30 ++ src/Charts/Components/LineChartComponent.jsx | 32 +- src/Charts/PrometheusChart.jsx | 267 ++++++++---- src/Charts/PrometheusChart2.jsx | 83 ---- src/Charts/TestCharts2.jsx | 95 ----- src/Components/Layout/Dashboard.jsx | 17 +- src/Components/TreeChart/TreeChart.jsx | 118 +++--- src/Components/TreeChart/dataUtils.jsx | 2 +- src/Components/TreeChart/menuData.json | 416 ++++++++++++++++++- src/Components/TreeChart/menuData2.json | 382 +++++++++++++++++ src/Components/TreeChart/menuData3.json | 117 ------ src/Components/TreeChart/tabContent.jsx | 164 ++++---- src/Components/TreeChart/tabContent2.jsx | 96 +++++ src/Components/TreeChart/tabContent3.jsx | 31 -- src/Components/UI/Tabs.jsx | 1 + src/Components/UI/TreeTable.jsx | 2 +- src/Style/Dashboard.css | 6 +- src/Style/DatePicker.css | 60 +++ src/index.css | 24 ++ 20 files changed, 1363 insertions(+), 583 deletions(-) delete mode 100644 src/Charts/PrometheusChart2.jsx delete mode 100644 src/Charts/TestCharts2.jsx create mode 100644 src/Components/TreeChart/menuData2.json delete mode 100644 src/Components/TreeChart/menuData3.json create mode 100644 src/Components/TreeChart/tabContent2.jsx delete mode 100644 src/Components/TreeChart/tabContent3.jsx create mode 100644 src/Style/DatePicker.css diff --git a/package.json b/package.json index 3ab7c61..9d0c2c7 100755 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "chart.js": "^4.0.0", "chartjs-chart-box-and-violin-plot": "^4.0.0", "react-chartjs-2": "^5.0.0", - "axios": "^1.7.9" + "axios": "^1.7.9", + "react-datepicker": "^8.1.0" }, "devDependencies": { "@eslint/js": "^9.17.0", diff --git a/src/App.css b/src/App.css index 9c7629a..40842f6 100755 --- a/src/App.css +++ b/src/App.css @@ -42,4 +42,34 @@ .read-the-docs { color: #888; +} + +/* Глобальный стиль для WebKit-браузеров (Chrome, Edge, Safari) */ +::-webkit-scrollbar { + width: 10px; /* Толщина вертикального скролла */ + height: 10px; /* Толщина горизонтального скролла */ +} + +/* Фон скроллбара */ +::-webkit-scrollbar-track { + background: #f1f1f1; /* Цвет фона */ + border-radius: 10px; /* Скругление углов */ +} + +/* Ползунок */ +::-webkit-scrollbar-thumb { + background: #3d74c7; /* Основной цвет */ + border-radius: 10px; /* Скругляем края */ + border: 2px solid #f1f1f1; /* Белая обводка */ +} + +/* Эффект при наведении */ +::-webkit-scrollbar-thumb:hover { + background: #2b5aa5; /* Чуть темнее при наведении */ +} + +/* Глобальный стиль для Firefox */ +* { + scrollbar-width: thin; /* Делаем тонким */ + scrollbar-color: #3d74c7 #f1f1f1; /* Ползунок + фон */ } \ No newline at end of file diff --git a/src/Charts/Components/LineChartComponent.jsx b/src/Charts/Components/LineChartComponent.jsx index 1ded03c..23ae6da 100644 --- a/src/Charts/Components/LineChartComponent.jsx +++ b/src/Charts/Components/LineChartComponent.jsx @@ -1,7 +1,7 @@ import React from 'react'; -import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer } from 'recharts'; +import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer, Brush } from 'recharts'; -const LineChartComponent = ({ chartData, metricName, metricType, colors }) => { +const LineChartComponent = ({ chartData, metricName, metricType, colors, description, isLongRange }) => { // Создаем массив уникальных временных меток const allTimes = Object.values(chartData) .flat() @@ -20,25 +20,47 @@ const LineChartComponent = ({ chartData, metricName, metricType, colors }) => { console.log('Processed Data:', data); // Логируем данные для графика + // Кастомный Tooltip для отображения значения + const CustomTooltip = ({ active, payload, label }) => { + if (active && payload && payload.length) { + return ( +
+

{`${payload[0].value}`}

+
+ ); + } + + return null; + }; + return (
-

{metricName} ({metricType})

+

{description}

- + } /> {Object.keys(chartData).map((key, index) => ( ))} + {isLongRange && ( // Добавляем Brush только для длительных периодов + + )}
diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index ddfe219..a5fc8e5 100644 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -1,95 +1,204 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import axios from 'axios'; import LineChartComponent from './Components/LineChartComponent'; import BarChartComponent from './Components/BarChartComponent'; import ScatterChartComponent from './Components/ScatterChartComponent'; +import DatePicker from 'react-datepicker'; +import '../Style/DatePicker.css'; const MAX_POINTS = 20; // Ограничение точек на графике const COLORS = ['#3e95cd', '#8e5ea2', '#3cba9f', '#e8c3b9', '#c45850']; // Фиксированные цвета для линий +// Компонент для выбора временного диапазона +const TimeRangeSelector = ({ onRangeChange }) => { + return ( +
+ + + +
+ ); +}; + +// Компонент для выбора произвольного диапазона дат +const DateRangeSelector = ({ onDateChange }) => { + const [startDate, setStartDate] = useState(new Date()); + const [endDate, setEndDate] = useState(new Date()); + + const handleDateChange = (dates) => { + const [start, end] = dates; + setStartDate(start); + setEndDate(end); + onDateChange({ start, end }); + }; + + return ( +
+ +
+ ); +}; + const PrometheusChart = ({ metricName }) => { const [chartData, setChartData] = useState({}); const [metricType, setMetricType] = useState(''); + const [metricDescription, setMetricDescription] = useState(''); + const [timeRange, setTimeRange] = useState('24h'); // По умолчанию выбран диапазон "24 часа" + const [customRange, setCustomRange] = useState({ start: null, end: null }); + const intervalRef = useRef(null); + + const isLongRange = (range) => { + return range === '2w' || range === 'custom'; // "2w" — две недели, "custom" — кастомный диапазон + }; + + const fetchData = async (range, customStart, customEnd) => { + try { + const end = customEnd ? Math.floor(customEnd.getTime() / 1000) : Math.floor(Date.now() / 1000); + let start; + + if (customStart) { + start = Math.floor(customStart.getTime() / 1000); + } else { + switch (range) { + case '1h': + start = end - 60 * 60; // 1 час назад + break; + case '24h': + start = end - 24 * 60 * 60; // 24 часа назад + break; + case '2w': + start = end - 14 * 24 * 60 * 60; // 2 недели назад + break; + default: + start = end - 24 * 60 * 60; // По умолчанию 24 часа + } + } + + const step = range === '2w' ? 3600 : 60; // Для двух недель увеличиваем шаг до 1 часа + + const response = await axios.get(`http://192.168.2.39:3000/metrics`, { + params: { + metric: metricName, + start, + end, + step, + }, + }); + + const result = response.data; + + // Проверяем структуру данных + let metrics; + if (Array.isArray(result)) { + metrics = result; + } else if (result.data && Array.isArray(result.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); + + // Устанавливаем описание метрики + setMetricDescription(metrics[0].description); + + // Очищаем предыдущие данные + setChartData({}); + + 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 + const updatedData = {}; + + newDataPoints.forEach(point => { + const key = `${point.instance}-${point.device}`; // Уникальный ключ + if (!updatedData[key]) { + updatedData[key] = []; + } + updatedData[key].push({ + time: point.time, + value: point.value, + }); + + // Ограничиваем количество точек до MAX_POINTS + if (updatedData[key].length > MAX_POINTS) { + updatedData[key] = updatedData[key].slice(-MAX_POINTS); // Оставляем последние MAX_POINTS точек + } + }); + + setChartData(updatedData); + } + } catch (error) { + console.error('Error fetching metrics:', error); + } + }; useEffect(() => { - const fetchData = async () => { - try { - const response = await axios.get(`http://192.168.2.39:3000/metrics?metric=zvks_apiforsnmp_cpurawsystem`); - const result = response.data; + fetchData(timeRange, customRange.start, customRange.end); // Первоначальная загрузка данных - // Проверяем структуру данных - 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 (!isLongRange(timeRange)) { // Обновляем только для коротких диапазонов + intervalRef.current = setInterval(() => { + fetchData(timeRange, customRange.start, customRange.end); + }, 5000); // Обновляем каждые 5 секунд + } - 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); + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); // Очищаем интервал при размонтировании } }; + }, [metricName, timeRange, customRange]); - fetchData(); // Вызываем сразу при монтировании - const interval = setInterval(fetchData, 5000); // Обновляем каждые 5 секунд - return () => clearInterval(interval); // Очищаем интервал при размонтировании - }, [metricName]); + const handleRangeChange = (range) => { + setTimeRange(range); + setCustomRange({ start: null, end: null }); + + if (!isLongRange(range)) { + clearInterval(intervalRef.current); // Останавливаем обновление + } + }; + + const handleDateChange = ({ start, end }) => { + setCustomRange({ start, end }); + setTimeRange('custom'); // Устанавливаем кастомный диапазон + }; if (!Object.keys(chartData).length) return

Loading...

; @@ -103,6 +212,8 @@ const PrometheusChart = ({ metricName }) => { metricName={metricName} metricType={metricType} colors={COLORS} + description={metricDescription} + isLongRange={isLongRange(timeRange)} // Передаем флаг /> ); case 'summary': @@ -128,7 +239,13 @@ const PrometheusChart = ({ metricName }) => { } }; - return renderChart(); + return ( +
+ + + {renderChart()} +
+ ); }; export default PrometheusChart; \ No newline at end of file diff --git a/src/Charts/PrometheusChart2.jsx b/src/Charts/PrometheusChart2.jsx deleted file mode 100644 index eb1046a..0000000 --- a/src/Charts/PrometheusChart2.jsx +++ /dev/null @@ -1,83 +0,0 @@ -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

Loading...

; - - return ( -
-

{metricName} ({metricType})

- - - - - - - - {Object.keys(chartData).map((instance, index) => ( - - ))} - - -
- ); -}; - -export default PrometheusChart2; \ No newline at end of file diff --git a/src/Charts/TestCharts2.jsx b/src/Charts/TestCharts2.jsx deleted file mode 100644 index 931e22b..0000000 --- a/src/Charts/TestCharts2.jsx +++ /dev/null @@ -1,95 +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'; - -ChartJS.register( - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Legend, - TimeScale -); - -const MAX_DATA_POINTS = 50; - -const NetworkSpeedChart2 = () => { - const [chartData, setChartData] = useState({ labels: [], datasets: [] }); - - const fetchData = async () => { - try { - const response = await axios.get('http://192.168.2.33:3000/metrics?metric=node_time_seconds'); - const newData = response.data; - - setChartData((prevChartData) => { - const newGroupedData = newData.reduce((acc, 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) => { - const existingDataset = prevChartData.datasets.find((d) => d.label === `Device: ${device}`); - const updatedData = existingDataset ? [...existingDataset.data, ...newGroupedData[device]] : newGroupedData[device]; - - return { - label: `Device: ${device}`, - 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, - }; - }); - - return { labels: newDatasets[0]?.data.map((d) => d.x) || [], datasets: newDatasets }; - }); - } catch (error) { - console.error('Ошибка при загрузке метрик:', error); - } - }; - - useEffect(() => { - fetchData(); - const interval = setInterval(fetchData, 5000); - return () => clearInterval(interval); - }, []); - - const options = { - responsive: true, - plugins: { - 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: 'Value' } }, - }, - animation: { duration: 1000, easing: 'linear' }, - }; - - return ( -
- -
- ); -}; - -export default NetworkSpeedChart2; diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx index fd733a7..ce9c706 100644 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -3,25 +3,29 @@ 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 menuData from "../TreeChart/menuData.json"; // Импортируем JSON-данные import TreeTable from "../UI/TreeTable"; import { updateStatuses } from "../TreeChart/dataUtils"; // Функция обновления статусов +import generateTabContent from "../TreeChart/tabContent"; // Импортируем функцию generateTabContent const Dashboard = () => { const [tabs, setTabs] = useState([]); const [activeTab, setActiveTab] = useState("Главная"); - const [tabContent, setTabContent] = useState({}); + const [tabContent, setTabContent] = useState({}); // Состояние для контента вкладок const [treeData, setTreeData] = useState(menuData); // Загружаем меню в state const [sidebarWidth, setSidebarWidth] = useState(250); // Начальная ширина сайдбара const [isResizing, setIsResizing] = useState(false); // Состояние перетаскивания const sidebarRef = useRef(null); // Референс на сайдбар + // Генерация контента для вкладок на основе menuData + useEffect(() => { + const generatedTabContent = generateTabContent(menuData); + setTabContent(generatedTabContent); + }, []); + // Обновление treeData каждые 30 секунд useEffect(() => { - setTabContent(tabContentData); - const interval = setInterval(() => { setTreeData((prevData) => { const updatedData = JSON.parse(JSON.stringify(prevData)); // Клонируем данные @@ -92,7 +96,6 @@ const Dashboard = () => {

Общий мониторинг

{/* Используем актуальные данные */} -
); } else if (activeTab === "Визуализация") { @@ -133,4 +136,4 @@ const Dashboard = () => { ); }; -export default Dashboard; +export default Dashboard; \ No newline at end of file diff --git a/src/Components/TreeChart/TreeChart.jsx b/src/Components/TreeChart/TreeChart.jsx index 260854b..37705e8 100644 --- a/src/Components/TreeChart/TreeChart.jsx +++ b/src/Components/TreeChart/TreeChart.jsx @@ -5,12 +5,32 @@ import { getStatusColor } from "./dataUtils"; const TreeChart = ({ data, onNodeClick }) => { const chartRef = useRef(); const simulationRef = useRef(null); + const nodePositions = useRef(new Map()); const { root, nodes, links } = useMemo(() => { if (!data || !data.items) return { root: null, nodes: [], links: [] }; + const root = d3.hierarchy(data, (d) => d.items); - const links = root.links(); const nodes = root.descendants(); + const links = root.links(); + + // Применяем сохраненные позиции к узлам + nodes.forEach((node) => { + const prev = nodePositions.current.get(node.data.id); + if (prev) { + node.x = prev.x; + node.y = prev.y; + node.fx = prev.fx ?? null; // Если фиксированные координаты были, сохраняем + node.fy = prev.fy ?? null; + } else { + // Если узел новый, задаем ему позицию рядом с родителем + const parent = node.parent; + node.x = parent ? parent.x + Math.random() * 50 - 25 : Math.random() * 1000; + node.y = parent ? parent.y + Math.random() * 50 - 25 : Math.random() * 1000; + } + nodePositions.current.set(node.data.id, { x: node.x, y: node.y, fx: node.fx, fy: node.fy }); + }); + return { root, nodes, links }; }, [data]); @@ -22,25 +42,52 @@ const TreeChart = ({ data, onNodeClick }) => { .attr("height", 1000) .attr("viewBox", [-500, -500, 1000, 1000]) .attr("style", "max-width: 100%; height: auto;"); + + svg.append("g").attr("class", "links"); + svg.append("g").attr("class", "nodes"); + svg.append("g").attr("class", "labels"); + + // Инициализация симуляции + simulationRef.current = d3.forceSimulation() + .force("link", d3.forceLink().id((d) => d.data.id).distance(80).strength(1)) + .force("charge", d3.forceManyBody().strength(-200)) + .force("center", d3.forceCenter(0, 0)) + .force("collision", d3.forceCollide().radius(20)) + .force("x", d3.forceX(0).strength(0.05)) // Ограничиваем разлет по X + .force("y", d3.forceY(0).strength(0.05)) // Ограничиваем разлет по Y + .force("radial", d3.forceRadial(200, 0, 0).strength(0.02)) // Держим узлы ближе к центру + .alphaDecay(0.02) // Замедляем затухание + .alphaTarget(0.1); + + // Запускаем симуляцию на 15 секунд, затем отключаем + setTimeout(() => { + simulationRef.current.stop(); // Останавливаем симуляцию + nodes.forEach((node) => { + node.fx = node.x; // Фиксируем текущие позиции узлов + node.fy = node.y; + }); + }, 15000); // 15 секунд + }, []); useEffect(() => { if (!root || !chartRef.current) return; const svg = d3.select(chartRef.current); + const linkGroup = svg.select(".links"); + const nodeGroup = svg.select(".nodes"); + const labelGroup = svg.select(".labels"); - if (simulationRef.current) { - simulationRef.current.stop(); - } - - const link = svg.select(".links") + // Обновляем связи + const link = linkGroup .selectAll("line") .data(links, (d) => `${d.source.data.id}-${d.target.data.id}`) .join("line") .attr("stroke", "#999") .attr("stroke-opacity", 0.6); - const node = svg.select(".nodes") + // Обновляем узлы + const node = nodeGroup .selectAll("circle") .data(nodes, (d) => d.data.id) .join("circle") @@ -55,7 +102,8 @@ const TreeChart = ({ data, onNodeClick }) => { } }); - const text = svg.select(".labels") + // Обновляем текстовые метки + const text = labelGroup .selectAll("text") .data(nodes, (d) => d.data.id) .join("text") @@ -63,39 +111,12 @@ const TreeChart = ({ data, onNodeClick }) => { .attr("dx", 12) .attr("dy", 4); - node.each(function (d) { - if (d.data.status === "red") { - d3.select(this) - .transition() - .duration(500) - .ease(d3.easeLinear) - .style("opacity", 0.3) - .transition() - .duration(500) - .ease(d3.easeLinear) - .style("opacity", 1) - .on("end", function repeat() { - d3.select(this) - .transition() - .duration(500) - .ease(d3.easeLinear) - .style("opacity", 0.3) - .transition() - .duration(500) - .ease(d3.easeLinear) - .style("opacity", 1) - .on("end", repeat); - }); - } - }); + // Обновляем симуляцию + simulationRef.current.nodes(nodes); + simulationRef.current.force("link").links(links); + simulationRef.current.alphaTarget(0.1).restart(); - const simulation = d3.forceSimulation(nodes) - .force("link", d3.forceLink(links).id((d) => d.data.id).distance(80).strength(1)) - .force("charge", d3.forceManyBody().strength(-500)) - .force("x", d3.forceX()) - .force("y", d3.forceY()); - - simulation.on("tick", () => { + simulationRef.current.on("tick", () => { link .attr("x1", (d) => d.source.x) .attr("y1", (d) => d.source.y) @@ -111,9 +132,6 @@ const TreeChart = ({ data, onNodeClick }) => { .attr("y", (d) => d.y + 4); }); - simulationRef.current = simulation; - - return () => simulation.stop(); }, [root, links, nodes, onNodeClick]); const drag = () => { @@ -130,21 +148,13 @@ const TreeChart = ({ data, onNodeClick }) => { function dragended(event, d) { if (!event.active) simulationRef.current.alphaTarget(0); - d.fx = d.x; - d.fy = d.y; + nodePositions.current.set(d.data.id, { x: d.x, y: d.y, fx: d.fx, fy: d.fy }); } return d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended); }; - return ( - - - - - - ); + return ; }; -export default TreeChart; - +export default TreeChart; \ No newline at end of file diff --git a/src/Components/TreeChart/dataUtils.jsx b/src/Components/TreeChart/dataUtils.jsx index 85b5217..fa67130 100644 --- a/src/Components/TreeChart/dataUtils.jsx +++ b/src/Components/TreeChart/dataUtils.jsx @@ -46,7 +46,7 @@ const getStatusColor = (status) => { case "red": return "#F44336"; // Красный default: - return "#3d74c7"; // Синий (или любой другой стандартный цвет) + return "#4CAF50"; // Синий (или любой другой стандартный цвет) } }; diff --git a/src/Components/TreeChart/menuData.json b/src/Components/TreeChart/menuData.json index 9e9a2e8..4cdcaff 100644 --- a/src/Components/TreeChart/menuData.json +++ b/src/Components/TreeChart/menuData.json @@ -1,5 +1,6 @@ { - "title": "Сервис ВКС", + "title": "Сервер ЗВКС", + "id": "1", "items": [ { "title": "Функциональные задачи", @@ -27,47 +28,422 @@ ] }, { - "title": "Медиа сервер", + "id": "18", + "title": "Graviton S2082I (device$18)", "items": [ { - "title": "Аппаратное обеспечение", + "id": "4", + "title": "OS Linux (module$4) АО", "items": [ { - "id": "media_system_software_1", - "title": "Центральный процессор" + "id": "188", + "title": "Наименование" }, { - "id": "media_system_software_2", - "title": "Оперативная память" + "id": "189", + "title": "Время работы" }, { - "id": "media_system_software_3", - "title": "Жесткий диск" + "id": "190", + "title": "Загрузка процессора за 1 минуту" }, { - "id": "media_system_software_4", - "title": "Сетевые адаптеры" + "id": "191", + "title": "Загрузка процессора за 5 минут" + }, + { + "id": "192", + "title": "Загрузка процессора за 15 минут" + }, + { + "id": "197", + "title": "Общий объем SWAP-файла" + }, + { + "id": "198", + "title": "Используемый объем SWAP-файла" + }, + { + "id": "199", + "title": "Общий объем физической оперативной памяти" + }, + { + "id": "200", + "title": "Доступный объем физической оперативной памяти" + }, + { + "id": "201", + "title": "Свободный объем физической и виртуальной оперативной памяти" + }, + { + "id": "202", + "title": "Буферизованный объем оперативной памяти" + }, + { + "id": "203", + "title": "Кэшированый объем оперативной памяти" + }, + { + "id": "274", + "title": "Используемый объем SWAP-файла" + }, + { + "id": "275", + "title": "Время затраченное процессором на процессы с пониженным приоритетом" + }, + { + "id": "276", + "title": "Время затраченное процессором на процессы ядра ОС" + }, + { + "id": "277", + "title": "Время простоя процессора" + }, + { + "id": "278", + "title": "Общая емкость жестких дисков" + }, + { + "id": "279", + "title": "Доступная емкость жестких дисков" } ] }, { - "title": "Программное обеспечение", + "id": "5", + "title": "Vinteo (module$5) ПО", "items": [ { - "id": "media_software_1", - "title": "ПО" + "id": "31", + "title": "Общее количество участников" }, { - "id": "media_software_2", - "title": "ПО" + "id": "32", + "title": "Ожидание соединения" }, { - "id": "media_software_3", - "title": "ПО" + "id": "33", + "title": "Зарегистрированные абоненты" }, { - "id": "media_software_4", - "title": "ПО" + "id": "34", + "title": "Количество пользоватей HLS" + }, + { + "id": "35", + "title": "Общее количество P2P комнат" + }, + { + "id": "36", + "title": "Общее количество конференций" + }, + { + "id": "37", + "title": "Общее количество активных конференций" + }, + { + "id": "38", + "title": "Статус записи" + }, + { + "id": "39", + "title": "Общее количество сохранённых записей" + } + ] + }, + { + "id": "261", + "title": "Сетевой адаптер №1 (port$261) Eth_1", + "items": [ + { + "id": "206", + "title": "Наименование порта Eth_1" + }, + { + "id": "207", + "title": "Скорость порта Eth_1" + }, + { + "id": "208", + "title": "Физический адрес порта Eth_1" + }, + { + "id": "209", + "title": "Административное состояние порта Eth_1" + }, + { + "id": "210", + "title": "Оперативное состояние порта Eth_1" + }, + { + "id": "211", + "title": "Общее количество отправленных октетов Eth_1" + }, + { + "id": "212", + "title": "Количество входящих Multicast пакетов Eth_1" + }, + { + "id": "213", + "title": "Количество иcходящих Multiicast пакетов Eth_1" + }, + { + "id": "214", + "title": "Количество входящих Broadcast пакетов Eth_1" + }, + { + "id": "215", + "title": "Количество иcходящих Broadcast пакетов Eth_1" + }, + { + "id": "216", + "title": "Количество входящих Unicast пакетов Eth_1" + }, + { + "id": "217", + "title": "Количество иcходящих Unicast пакетов Eth_1" + }, + { + "id": "218", + "title": "Количество входящих пакетов помеченные как отброшенные Eth_1" + }, + { + "id": "219", + "title": "Количество иcходящих пакетов помеченные как отброшенные Eth_1" + }, + { + "id": "220", + "title": "Количество входящих пакетов с ошибкой Eth_1" + }, + { + "id": "221", + "title": "Количество исходящих пакетов с ошибкой Eth_1" + }, + { + "id": "222", + "title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_1" + } + ] + }, + { + "id": "262", + "title": "Сетевой адаптер №2 (port$262) Eth_2", + "items": [ + { + "id": "223", + "title": "Наименование порта Eth_2" + }, + { + "id": "224", + "title": "Скорость порта Eth_2" + }, + { + "id": "225", + "title": "Физический адрес порта Eth_2" + }, + { + "id": "226", + "title": "Административное состояние порта Eth_2" + }, + { + "id": "227", + "title": "Оперативное состояние порта Eth_2" + }, + { + "id": "228", + "title": "Общее количество отправленных октетов Eth_2" + }, + { + "id": "229", + "title": "Количество входящих Multicast пакетов Eth_2" + }, + { + "id": "230", + "title": "Количество иcходящих Multiicast пакетов Eth_2" + }, + { + "id": "231", + "title": "Количество входящих Broadcast пакетов Eth_2" + }, + { + "id": "232", + "title": "Количество иcходящих Broadcast пакетов Eth_2" + }, + { + "id": "233", + "title": "Количество входящих Unicast пакетов Eth_2" + }, + { + "id": "234", + "title": "Количество иcходящих Unicast пакетов Eth_2" + }, + { + "id": "235", + "title": "Количество входящих пакетов помеченные как отброшенные Eth_2" + }, + { + "id": "236", + "title": "Количество иcходящих пакетов помеченные как отброшенные Eth_2" + }, + { + "id": "237", + "title": "Количество входящих пакетов с ошибкой Eth_2" + }, + { + "id": "238", + "title": "Количество исходящих пакетов с ошибкой Eth_2" + }, + { + "id": "239", + "title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_2" + } + ] + }, + { + "id": "263", + "title": "Сетевой адаптер №3 (port$263) Eth_3", + "items": [ + { + "id": "240", + "title": "Наименование порта Eth_3" + }, + { + "id": "241", + "title": "Скорость порта Eth_3" + }, + { + "id": "242", + "title": "Физический адрес порта Eth_3" + }, + { + "id": "243", + "title": "Административное состояние порта Eth_3" + }, + { + "id": "244", + "title": "Оперативное состояние порта Eth_3" + }, + { + "id": "245", + "title": "Общее количество отправленных октетов Eth_3" + }, + { + "id": "246", + "title": "Количество входящих Multicast пакетов Eth_3" + }, + { + "id": "247", + "title": "Количество иcходящих Multiicast пакетов Eth_3" + }, + { + "id": "248", + "title": "Количество входящих Broadcast пакетов Eth_3" + }, + { + "id": "249", + "title": "Количество иcходящих Broadcast пакетов Eth_3" + }, + { + "id": "250", + "title": "Количество входящих Unicast пакетов Eth_3" + }, + { + "id": "251", + "title": "Количество иcходящих Unicast пакетов Eth_3" + }, + { + "id": "252", + "title": "Количество входящих пакетов помеченные как отброшенные Eth_3" + }, + { + "id": "253", + "title": "Количество иcходящих пакетов помеченные как отброшенные Eth_3" + }, + { + "id": "254", + "title": "Количество входящих пакетов с ошибкой Eth_3" + }, + { + "id": "255", + "title": "Количество исходящих пакетов с ошибкой Eth_3" + }, + { + "id": "256", + "title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_3" + } + ] + }, + { + "id": "264", + "title": "Сетевой адаптер №4 (port$264) Eth_4", + "items": [ + { + "id": "257", + "title": "Наименование порта Eth_4" + }, + { + "id": "258", + "title": "Скорость порта Eth_4" + }, + { + "id": "259", + "title": "Физический адрес порта Eth_4" + }, + { + "id": "260", + "title": "Административное состояние порта Eth_4" + }, + { + "id": "261", + "title": "Оперативное состояние порта Eth_4" + }, + { + "id": "262", + "title": "Общее количество отправленных октетов Eth_4" + }, + { + "id": "263", + "title": "Количество входящих Multicast пакетов Eth_4" + }, + { + "id": "264", + "title": "Количество иcходящих Multiicast пакетов Eth_4" + }, + { + "id": "265", + "title": "Количество входящих Broadcast пакетов Eth_4" + }, + { + "id": "266", + "title": "Количество иcходящих Broadcast пакетов Eth_4" + }, + { + "id": "267", + "title": "Количество входящих Unicast пакетов Eth_4" + }, + { + "id": "268", + "title": "Количество иcходящих Unicast пакетов Eth_4" + }, + { + "id": "269", + "title": "Количество входящих пакетов помеченные как отброшенные Eth_4" + }, + { + "id": "270", + "title": "Количество иcходящих пакетов помеченные как отброшенные Eth_4" + }, + { + "id": "271", + "title": "Количество входящих пакетов с ошибкой Eth_4" + }, + { + "id": "272", + "title": "Количество исходящих пакетов с ошибкой Eth_4" + }, + { + "id": "273", + "title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_4" } ] } diff --git a/src/Components/TreeChart/menuData2.json b/src/Components/TreeChart/menuData2.json new file mode 100644 index 0000000..645a8ef --- /dev/null +++ b/src/Components/TreeChart/menuData2.json @@ -0,0 +1,382 @@ +{ + "title": "Сервис ВКС", + "id":"service_VKS", + "items": [ + { + "title": "Функциональные задачи", + "id":"functions", + "items": [ + { + "id": "system_control", + "title": "Контроль системы" + }, + { + "id": "system_management", + "title": "Система управления" + }, + { + "id": "conference", + "title": "Проведение ВКС" + }, + { + "id": "backup", + "title": "Резервное копирование" + }, + { + "id": "relay_info", + "title": "Ретрансляция информации" + } + ] + }, + { + "title": "Медиа сервер", + "id":"media_server_1", + "items": [ + { + "title": "Аппаратное обеспечение", + "id":"system_software_1", + "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": "Программное обеспечение", + "id":"software_1", + "items": [ + { + "id": "media_software_1", + "title": "ПО" + }, + { + "id": "media_software_2", + "title": "ПО" + }, + { + "id": "media_software_3", + "title": "ПО" + }, + { + "id": "media_software_4", + "title": "ПО" + } + ] + } + ] + }, + { + "title": "Медиа сервер", + "id":"media_server_2", + "items": [ + { + "title": "Аппаратное обеспечение", + "id":"system_software_2", + "items": [ + { + "id": "media_system_software_1_2", + "title": "Центральный процессор" + }, + { + "id": "media_system_software_2_2", + "title": "Оперативная память" + }, + { + "id": "media_system_software_3_2", + "title": "Жесткий диск" + }, + { + "id": "media_system_software_4_2", + "title": "Сетевые адаптеры" + } + ] + }, + { + "title": "Программное обеспечение", + "id":"software_2", + "items": [ + { + "id": "media_software_1_2", + "title": "ПО" + }, + { + "id": "media_software_2_2", + "title": "ПО" + }, + { + "id": "media_software_3_2", + "title": "ПО" + }, + { + "id": "media_software_4_2", + "title": "ПО" + } + ] + } + ] + }, + { + "title": "Медиа сервер", + "id":"media_server_3", + "items": [ + { + "title": "Аппаратное обеспечение", + "id":"system_software_3", + "items": [ + { + "id": "media_system_software_1_3", + "title": "Центральный процессор" + }, + { + "id": "media_system_software_2_3", + "title": "Оперативная память" + }, + { + "id": "media_system_software_3_3", + "title": "Жесткий диск" + }, + { + "id": "media_system_software_4_3", + "title": "Сетевые адаптеры" + } + ] + }, + { + "title": "Программное обеспечение", + "id":"software_3", + "items": [ + { + "id": "media_software_1_3", + "title": "ПО" + }, + { + "id": "media_software_2_3", + "title": "ПО" + }, + { + "id": "media_software_3_3", + "title": "ПО" + }, + { + "id": "media_software_4_3", + "title": "ПО" + } + ] + } + ] + }, + { + "title": "Медиа сервер", + "id":"media_server_4", + "items": [ + { + "title": "Аппаратное обеспечение", + "id":"system_software_4", + "items": [ + { + "id": "media_system_software_1_4", + "title": "Центральный процессор" + }, + { + "id": "media_system_software_2_4", + "title": "Оперативная память" + }, + { + "id": "media_system_software_3_4", + "title": "Жесткий диск" + }, + { + "id": "media_system_software_4_4", + "title": "Сетевые адаптеры" + } + ] + }, + { + "title": "Программное обеспечение", + "id":"software_4", + "items": [ + { + "id": "media_software_1_4", + "title": "ПО" + }, + { + "id": "media_software_2_4", + "title": "ПО" + }, + { + "id": "media_software_3_4", + "title": "ПО" + }, + { + "id": "media_software_4_4", + "title": "ПО" + } + ] + } + ] + }, + { + "title": "Медиа сервер", + "id":"media_server_5", + "items": [ + { + "title": "Аппаратное обеспечение", + "id":"system_software_5", + "items": [ + { + "id": "media_system_software_1_5", + "title": "Центральный процессор" + }, + { + "id": "media_system_software_2_5", + "title": "Оперативная память" + }, + { + "id": "media_system_software_3_5", + "title": "Жесткий диск" + }, + { + "id": "media_system_software_4_5", + "title": "Сетевые адаптеры" + } + ] + }, + { + "title": "Программное обеспечение", + "id":"software_5", + "items": [ + { + "id": "media_software_1_5", + "title": "ПО" + }, + { + "id": "media_software_2_5", + "title": "ПО" + }, + { + "id": "media_software_3_5", + "title": "ПО" + }, + { + "id": "media_software_4_5", + "title": "ПО" + } + ] + } + ] + }, + { + "title": "Сервер систем", + "id":"system_server_1", + "items": [ + { + "title": "Аппаратное обеспечение", + "id":"system_software_6", + "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": "Программное обеспечение", + "id":"software_6", + "items": [ + { + "id": "copy_software_1", + "title": "ПО" + }, + { + "id": "copy_software_2", + "title": "ПО" + }, + { + "id": "copy_software_3", + "title": "ПО" + }, + { + "id": "copy_software_4", + "title": "ПО" + } + ] + } + ] + }, + { + "title": "Сервер систем", + "id":"system_server_2", + "items": [ + { + "title": "Аппаратное обеспечение", + "id":"system_software_7", + "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": "Программное обеспечение", + "id":"software_7", + "items": [ + { + "id": "control_software_1", + "title": "ПО" + }, + { + "id": "control_software_2", + "title": "ПО" + }, + { + "id": "control_software_3", + "title": "ПО" + }, + { + "id": "control_software_4", + "title": "ПО" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/Components/TreeChart/menuData3.json b/src/Components/TreeChart/menuData3.json deleted file mode 100644 index 1c0eeda..0000000 --- a/src/Components/TreeChart/menuData3.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "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": "БП/ППО" - } - ] - } - ] -} \ No newline at end of file diff --git a/src/Components/TreeChart/tabContent.jsx b/src/Components/TreeChart/tabContent.jsx index 47dc890..eecfcc2 100644 --- a/src/Components/TreeChart/tabContent.jsx +++ b/src/Components/TreeChart/tabContent.jsx @@ -1,96 +1,78 @@ -import React from "react"; -import PrometheusChart from '../../Charts/PrometheusChart'; +import React, { lazy, Suspense } from "react"; -const tabContent = { - // Сервис ВКС - service1: { title: "Сервис ВКС", content:

Сервис ВКС

}, +const PrometheusChart = lazy(() => import('../../Charts/PrometheusChart')); - // Функциональные задачи - system_control: { title: "Контроль системы", content:

Контроль системы

Описание контроля.

}, - system_management: { title: "Система управления", content:

Система управления

Описание системы управления.

}, - conference: { title: "Проведение ВКС", content:

Проведение ВКС

Информация о проведении ВКС.

}, - backup: { title: "Резервное копирование", content:

Резервное копирование

Процесс резервного копирования.

}, - relay_info: { title: "Ретрансляция информации", content:

Ретрансляция информации

Детали ретрансляции.

}, +// Вкладки, для которых нужно отобразить график +const tabsWithCharts = ["188", "189"]; - // Медиа сервер 1 - media_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, - media_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, - media_system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, - media_system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, - media_software_1: { title: "ПО", content:

Программное обеспечение медиа сервера

}, - media_software_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - - // Медиа сервер 2 - media_system_software_1_2: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, - media_system_software_2_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, - media_system_software_3_2: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, - media_system_software_4_2: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, - media_software_1_2: { title: "ПО", content:

Программное обеспечение медиа сервера

}, - media_software_2_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_3_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_4_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - - // Медиа сервер 3 - media_system_software_1_3: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, - media_system_software_2_3: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, - media_system_software_3_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, - media_system_software_4_3: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, - media_software_1_3: { title: "ПО", content:

Программное обеспечение медиа сервера

}, - media_software_2_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_3_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_4_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - - // Медиа сервер 4 - media_system_software_1_4: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, - media_system_software_2_4: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, - media_system_software_3_4: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, - media_system_software_4_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, - media_software_1_4: { title: "ПО", content:

Программное обеспечение медиа сервера

}, - media_software_2_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_3_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_4_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - - // Медиа сервер 5 - media_system_software_1_5: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, - media_system_software_2_5: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, - media_system_software_3_5: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, - media_system_software_4_5: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, - media_software_1_5: { title: "ПО", content:

Программное обеспечение медиа сервера

}, - media_software_2_5: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_3_5: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - media_software_4_5: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, - - // Сервер резервного копирования - copy_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора сервера резервного копирования.

}, - copy_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти сервера резервного копирования.

}, - copy_system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска сервера резервного копирования.

}, - copy_system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров сервера резервного копирования.

}, - copy_software_1: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, - copy_software_2: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, - copy_software_3: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, - copy_software_4: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, - - // Сервер системы управления - control_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора сервера системы управления.

}, - control_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти сервера системы управления.

}, - control_system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска сервера системы управления.

}, - control_system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров сервера системы управления.

}, - control_software_1: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, - control_software_2: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, - control_software_3: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, - control_software_4: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, - - // Сервер сбора и ретрансляции информации - system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора сервера сбора и ретрансляции информации.

}, - system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти сервера сбора и ретрансляции информации.

}, - system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска сервера сбора и ретрансляции информации.

}, - system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров сервера сбора и ретрансляции информации.

}, - software_1: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, - software_2: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, - software_3: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, - software_4: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, +// Маппинг id на метрики +const metricMapping = { + 188: "zvks_apiforsnmp_measure_277", + 189: "zvks_apiforsnmp_measure_36", }; -export default tabContent; \ No newline at end of file +const generateTabContent = (data) => { + const tabContent = {}; + + console.log("jsonData:", data); + console.log("jsonData.items:", data.items); + + const generateContent = (nodes) => { + nodes.forEach((node) => { + console.log("Обрабатываем узел:", node); + + // Если у узла есть вложенные элементы, рекурсивно обрабатываем их + if (node.items && node.items.length > 0) { + console.log("Идём вглубь:", node.items); + generateContent(node.items); + } + + // Если у узла есть id, добавляем его в tabContent + if (node.id) { + console.log("Добавляем в tabContent:", node.id); + + let content = ( +
+

{node.title}

+

Контент для {node.title}.

+
+ ); + + // Если id узла есть в списке tabsWithCharts, добавляем график + if (tabsWithCharts.includes(node.id)) { + console.log("Добавляем график для:", node.id); + + // Получаем метрику для текущего id + const metricName = metricMapping[node.id]; + + content = ( +
+

{node.title}

+

Контент для {node.title}.

+ Загрузка графика...
}> + + +
+ ); + } + + // Сохраняем контент для текущего id + tabContent[node.id] = { + title: node.title, + content: content, + }; + } + }); + }; + + // Начинаем обработку с корневого уровня + if (data.items && data.items.length > 0) { + generateContent(data.items); + } else { + console.warn("Данные отсутствуют или массив items пуст"); + } + + return tabContent; +}; + +export default generateTabContent; // Экспортируем только функцию \ No newline at end of file diff --git a/src/Components/TreeChart/tabContent2.jsx b/src/Components/TreeChart/tabContent2.jsx new file mode 100644 index 0000000..47dc890 --- /dev/null +++ b/src/Components/TreeChart/tabContent2.jsx @@ -0,0 +1,96 @@ +import React from "react"; +import PrometheusChart from '../../Charts/PrometheusChart'; + +const tabContent = { + // Сервис ВКС + service1: { title: "Сервис ВКС", content:

Сервис ВКС

}, + + // Функциональные задачи + system_control: { title: "Контроль системы", content:

Контроль системы

Описание контроля.

}, + system_management: { title: "Система управления", content:

Система управления

Описание системы управления.

}, + conference: { title: "Проведение ВКС", content:

Проведение ВКС

Информация о проведении ВКС.

}, + backup: { title: "Резервное копирование", content:

Резервное копирование

Процесс резервного копирования.

}, + relay_info: { title: "Ретрансляция информации", content:

Ретрансляция информации

Детали ретрансляции.

}, + + // Медиа сервер 1 + media_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, + media_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, + media_system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, + media_system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, + media_software_1: { title: "ПО", content:

Программное обеспечение медиа сервера

}, + media_software_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + + // Медиа сервер 2 + media_system_software_1_2: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, + media_system_software_2_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, + media_system_software_3_2: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, + media_system_software_4_2: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, + media_software_1_2: { title: "ПО", content:

Программное обеспечение медиа сервера

}, + media_software_2_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_3_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_4_2: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + + // Медиа сервер 3 + media_system_software_1_3: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, + media_system_software_2_3: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, + media_system_software_3_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, + media_system_software_4_3: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, + media_software_1_3: { title: "ПО", content:

Программное обеспечение медиа сервера

}, + media_software_2_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_3_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_4_3: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + + // Медиа сервер 4 + media_system_software_1_4: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, + media_system_software_2_4: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, + media_system_software_3_4: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, + media_system_software_4_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, + media_software_1_4: { title: "ПО", content:

Программное обеспечение медиа сервера

}, + media_software_2_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_3_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_4_4: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + + // Медиа сервер 5 + media_system_software_1_5: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора медиа сервера.

}, + media_system_software_2_5: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти медиа сервера.

}, + media_system_software_3_5: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска медиа сервера.

}, + media_system_software_4_5: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров медиа сервера.

}, + media_software_1_5: { title: "ПО", content:

Программное обеспечение медиа сервера

}, + media_software_2_5: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_3_5: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + media_software_4_5: { title: "ПО", content:

Программное обеспечение медиа сервера

Описание ПО медиа сервера.

}, + + // Сервер резервного копирования + copy_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора сервера резервного копирования.

}, + copy_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти сервера резервного копирования.

}, + copy_system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска сервера резервного копирования.

}, + copy_system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров сервера резервного копирования.

}, + copy_software_1: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, + copy_software_2: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, + copy_software_3: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, + copy_software_4: { title: "ПО", content:

Программное обеспечение сервера резервного копирования

Описание ПО сервера резервного копирования.

}, + + // Сервер системы управления + control_system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора сервера системы управления.

}, + control_system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти сервера системы управления.

}, + control_system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска сервера системы управления.

}, + control_system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров сервера системы управления.

}, + control_software_1: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, + control_software_2: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, + control_software_3: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, + control_software_4: { title: "ПО", content:

Программное обеспечение сервера системы управления

Описание ПО сервера системы управления.

}, + + // Сервер сбора и ретрансляции информации + system_software_1: { title: "Центральный процессор", content:

Центральный процессор

Описание центрального процессора сервера сбора и ретрансляции информации.

}, + system_software_2: { title: "Оперативная память", content:

Оперативная память

Описание оперативной памяти сервера сбора и ретрансляции информации.

}, + system_software_3: { title: "Жесткий диск", content:

Жесткий диск

Описание жесткого диска сервера сбора и ретрансляции информации.

}, + system_software_4: { title: "Сетевые адаптеры", content:

Сетевые адаптеры

Описание сетевых адаптеров сервера сбора и ретрансляции информации.

}, + software_1: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, + software_2: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, + software_3: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, + software_4: { title: "ПО", content:

Программное обеспечение сервера сбора и ретрансляции информации

Описание ПО сервера сбора и ретрансляции информации.

}, +}; + +export default tabContent; \ No newline at end of file diff --git a/src/Components/TreeChart/tabContent3.jsx b/src/Components/TreeChart/tabContent3.jsx deleted file mode 100644 index 4124a32..0000000 --- a/src/Components/TreeChart/tabContent3.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from "react"; -import NetworkSpeedChart2 from '../../Charts/TestCharts2'; -import PrometheusChart from '../../Charts/PrometheusChart'; -import PrometheusChart2 from '../../Charts/PrometheusChart2'; - -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: "Сервер системы управления", content:

Сервер системы управления

}, - hardware_software_3: { title: "Медиа-сервер", content:

Медиа-сервер

}, - hardware_software_4: { title: "Медиа-сервер", content:

Медиа-сервер

}, - hardware_software_5: { title: "Медиа-сервер", content:

Медиа-сервер

}, - hardware_software_6: { title: "Медиа-сервер", content:

Медиа-сервер

}, - hardware_software_7: { title: "Сервер резервного копирования", content:

Сервер резервного копирования

}, - hardware_software_8: { title: "Сервер сбора и ретрансляции информации", content:

Сервер сбора и ретрансляции информации

}, - software_1: { title: "БП/ППО", content:

БП/ППО

}, - software_2: { title: "БП/ППО", content:

БП/ППО

}, - software_3: { title: "БП/ППО", content:

БП/ППО

}, - software_4: { title: "БП/ППО", content:

БП/ППО

}, - software_5: { title: "БП/ППО", content:

БП/ППО

}, - software_6: { title: "БП/ППО", content:

БП/ППО

}, - software_7: { title: "БП/ППО", content:

БП/ППО

}, - software_8: { title: "БП/ППО", content:

БП/ППО

}, -}; - -export default tabContent; \ No newline at end of file diff --git a/src/Components/UI/Tabs.jsx b/src/Components/UI/Tabs.jsx index c8c18bb..78ec099 100644 --- a/src/Components/UI/Tabs.jsx +++ b/src/Components/UI/Tabs.jsx @@ -9,6 +9,7 @@ const Tabs = ({ tabs, activeTab, onTabClick, onCloseTab }) => { onCloseTab(id); // Закрываем вкладку } }; + return (
diff --git a/src/Components/UI/TreeTable.jsx b/src/Components/UI/TreeTable.jsx index 9f392d4..8a4618e 100644 --- a/src/Components/UI/TreeTable.jsx +++ b/src/Components/UI/TreeTable.jsx @@ -34,7 +34,7 @@ const TreeTable = ({ data }) => { {/* Третий уровень: Вложенные элементы "АО" и "ПО" */} - {/*renderRows(filteredData)*/} + {renderRows(filteredData)}
diff --git a/src/Style/Dashboard.css b/src/Style/Dashboard.css index d1f7b33..537a8ca 100644 --- a/src/Style/Dashboard.css +++ b/src/Style/Dashboard.css @@ -2,7 +2,7 @@ .dashboard-container { display: flex; height: 100vh; - width: 98vw; + width: calc(100vw - 20px); /* Учитываем отступ */ overflow: hidden; margin-left: 20px; } @@ -50,7 +50,7 @@ padding: 20px; margin-left: 50px; transition: margin-left 0.2s ease; - /* Плавное изменение отступа */ + overflow: auto; /* Позволяет прокручивать контент, если он не влезает */ } /* Контент */ @@ -59,6 +59,8 @@ padding: 20px; border-radius: 10px; box-shadow: 0 2px 5px rgba(29, 1, 1, 0.521); + max-width: 100%; /* Гарантируем, что контент не выйдет за границы */ + overflow: auto; /* Включаем скролл, если нужно */ } /* Заголовки */ diff --git a/src/Style/DatePicker.css b/src/Style/DatePicker.css new file mode 100644 index 0000000..94a43f5 --- /dev/null +++ b/src/Style/DatePicker.css @@ -0,0 +1,60 @@ +.react-datepicker-wrapper { + width: 100%; +} + +.react-datepicker { + position: absolute !important; + z-index: 1000 !important; + width: auto; + max-width: 300px; + background-color: white; + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +.react-datepicker__header { + background-color: #f0f0f0; + border-bottom: 1px solid #ccc; + border-radius: 4px 4px 0 0; + padding: 8px; +} + +.react-datepicker__current-month { + font-size: 1.1em; + font-weight: bold; +} + +.react-datepicker__day { + padding: 5px; + margin: 2px; + border-radius: 4px; +} + +.react-datepicker__day--selected, +.react-datepicker__day--keyboard-selected { + background-color: #3e95cd; + color: white; +} + +.react-datepicker__day:hover { + background-color: #f0f0f0; +} + +.react-datepicker__navigation { + top: 10px; +} + +.react-datepicker__navigation--previous { + border-right-color: #333; +} + +.react-datepicker__navigation--next { + border-left-color: #333; +} + +/* Исправление странного появления в центре экрана */ +.react-datepicker-popper { + z-index: 9999 !important; + transform: translate3d(0px, 0px, 0px) !important; +} diff --git a/src/index.css b/src/index.css index 5a6ad3e..6c94c43 100755 --- a/src/index.css +++ b/src/index.css @@ -71,4 +71,28 @@ button:focus-visible { button { background-color: #f9f9f9; } +} + +/* Глобальный стиль для WebKit-браузеров (Chrome, Edge, Safari) */ +::-webkit-scrollbar { + width: 10px; /* Толщина вертикального скролла */ + height: 10px; /* Толщина горизонтального скролла */ +} + +/* Фон скроллбара */ +::-webkit-scrollbar-track { + background: #f1f1f1; /* Цвет фона */ + border-radius: 10px; /* Скругление углов */ +} + +/* Ползунок */ +::-webkit-scrollbar-thumb { + background: #3d74c7; /* Основной цвет */ + border-radius: 10px; /* Скругляем края */ + border: 1px solid #1c36c9; /* Белая обводка */ +} + +/* Эффект при наведении */ +::-webkit-scrollbar-thumb:hover { + background: #2b5aa5; /* Чуть темнее при наведении */ } \ No newline at end of file -- 2.40.1 From a01d0972f53475a8e7a604f6f8aad6cf23b85415 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Thu, 6 Mar 2025 03:29:46 -0500 Subject: [PATCH 08/10] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BC=D0=B5=D0=BD=D1=8E,=20=D1=83=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D1=88=D0=B8=D0=BB=20=D0=BD=D0=B0=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4=D0=BE=D0=BA?= =?UTF-8?q?,=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B8=D0=BB=20=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=84=D0=B8=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Charts/PrometheusChart.jsx | 11 +- src/Components/Layout/SidebarMenu.jsx | 33 +++++- src/Components/TreeChart/tabContent.jsx | 65 +++++++---- src/Components/UI/TreeTable.jsx | 16 +-- src/Style/DatePicker.css | 139 ++++++++++++++++++------ 5 files changed, 196 insertions(+), 68 deletions(-) diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index a5fc8e5..f6ce9be 100644 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -33,7 +33,7 @@ const DateRangeSelector = ({ onDateChange }) => { }; return ( -
+
{ onChange={handleDateChange} isClearable dateFormat="yyyy-MM-dd" + popperPlacement="right-start" // Открывать справа от поля ввода + popperModifiers={[ + { + name: 'offset', + options: { + offset: [0, 10], // Смещение календаря относительно поля ввода + }, + }, + ]} />
); diff --git a/src/Components/Layout/SidebarMenu.jsx b/src/Components/Layout/SidebarMenu.jsx index c9300ec..5661716 100644 --- a/src/Components/Layout/SidebarMenu.jsx +++ b/src/Components/Layout/SidebarMenu.jsx @@ -7,23 +7,46 @@ const MenuItem = ({ item, onSelectItem, sidebarWidth }) => { const hasChildren = Array.isArray(item.items) && item.items.length > 0; const statusColor = getStatusColor(item.status); - const handleClick = () => { + // Обработчик одинарного клика (разворачивание/сворачивание или открытие элемента) + const handleSingleClick = () => { if (hasChildren) { - setIsOpen(!isOpen); + setIsOpen(!isOpen); // Разворачиваем/сворачиваем дочерние элементы } else { - onSelectItem(item); + onSelectItem(item); // Если нет потомков, открываем элемент как вкладку } }; + // Обработчик клика для открытия родителя + const handleOpenParent = (e) => { + e.stopPropagation(); // Останавливаем всплытие события, чтобы не сработал handleSingleClick + onSelectItem(item); // Открываем родителя + }; + return (
{/* Динамическая ширина */} -
+
{/* Круглый индикатор статуса */}
{item.title} + + {/* Иконка для открытия родителя */} + {hasChildren && ( + + 📂 + + )} + + {/* Иконка для разворачивания/сворачивания */} {hasChildren && {isOpen ? "▲" : "▼"}}
{isOpen && hasChildren && ( @@ -56,4 +79,4 @@ function SidebarMenu({ data, onOpenTab, sidebarWidth }) { ); } -export default SidebarMenu; +export default SidebarMenu; \ No newline at end of file diff --git a/src/Components/TreeChart/tabContent.jsx b/src/Components/TreeChart/tabContent.jsx index eecfcc2..26ab955 100644 --- a/src/Components/TreeChart/tabContent.jsx +++ b/src/Components/TreeChart/tabContent.jsx @@ -2,34 +2,42 @@ import React, { lazy, Suspense } from "react"; const PrometheusChart = lazy(() => import('../../Charts/PrometheusChart')); -// Вкладки, для которых нужно отобразить график -const tabsWithCharts = ["188", "189"]; - -// Маппинг id на метрики -const metricMapping = { - 188: "zvks_apiforsnmp_measure_277", - 189: "zvks_apiforsnmp_measure_36", +// Функция для генерации названия метрики на основе id +const getMetricName = (id) => { + return `zvks_apiforsnmp_measure_${id}`; }; -const generateTabContent = (data) => { +// Функция для рекурсивного сбора всех id потомков +const getAllChildIds = (node) => { + let ids = []; + if (node.id) { + ids.push(node.id); // Добавляем id текущего узла + } + if (node.items && node.items.length > 0) { + node.items.forEach((child) => { + ids = ids.concat(getAllChildIds(child)); // Рекурсивно собираем id потомков + }); + } + return ids; +}; + +const tabContent = (data) => { const tabContent = {}; - console.log("jsonData:", data); - console.log("jsonData.items:", data.items); const generateContent = (nodes) => { nodes.forEach((node) => { - console.log("Обрабатываем узел:", node); + // Если у узла есть вложенные элементы, рекурсивно обрабатываем их if (node.items && node.items.length > 0) { - console.log("Идём вглубь:", node.items); + generateContent(node.items); } // Если у узла есть id, добавляем его в tabContent if (node.id) { - console.log("Добавляем в tabContent:", node.id); + let content = (
@@ -38,13 +46,32 @@ const generateTabContent = (data) => {
); - // Если id узла есть в списке tabsWithCharts, добавляем график - if (tabsWithCharts.includes(node.id)) { - console.log("Добавляем график для:", node.id); + // Если у узла есть потомки, добавляем графики для всех потомков + if (node.items && node.items.length > 0) { + const childIds = getAllChildIds(node); // Получаем все id потомков + const charts = childIds.map((id) => { + const metricName = getMetricName(id); + return ( +
+

{node.title} - {id}

+ Загрузка графика...
}> + + +
+ ); + }); - // Получаем метрику для текущего id - const metricName = metricMapping[node.id]; + content = ( +
+

{node.title}

+

Контент для {node.title}.

+ {charts} +
+ ); + } else { + // Если у узла нет потомков, добавляем график для него + const metricName = getMetricName(node.id); content = (

{node.title}

@@ -75,4 +102,4 @@ const generateTabContent = (data) => { return tabContent; }; -export default generateTabContent; // Экспортируем только функцию \ No newline at end of file +export default tabContent; // Экспортируем только функцию \ No newline at end of file diff --git a/src/Components/UI/TreeTable.jsx b/src/Components/UI/TreeTable.jsx index 8a4618e..28b2105 100644 --- a/src/Components/UI/TreeTable.jsx +++ b/src/Components/UI/TreeTable.jsx @@ -43,16 +43,17 @@ const TreeTable = ({ data }) => { // Функция для отображения строк с вложенными элементами const renderRows = (data) => { + const rows = []; + + // Находим максимальное количество элементов среди всех "АО" и "ПО" const maxItems = Math.max( - ...data.map((item) => - Math.max( - item.items[0]?.items?.length || 0, // АО - item.items[1]?.items?.length || 0 // ПО - ) - ) + ...data.flatMap((item) => [ + item.items[0]?.items?.length || 0, // АО + item.items[1]?.items?.length || 0 // ПО + ]) ); - const rows = []; + // Генерируем строки for (let i = 0; i < maxItems; i++) { rows.push( @@ -72,5 +73,4 @@ const renderRows = (data) => { return rows; }; - export default TreeTable; \ No newline at end of file diff --git a/src/Style/DatePicker.css b/src/Style/DatePicker.css index 94a43f5..3b564d1 100644 --- a/src/Style/DatePicker.css +++ b/src/Style/DatePicker.css @@ -1,60 +1,129 @@ +/* DatePicker.css */ + .react-datepicker-wrapper { - width: 100%; + width: auto; + display: inline-block; +} + +.react-datepicker__input-container input { + width: 200px; + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 14px; + color: #333; + background-color: #fff; + transition: border-color 0.2s ease; +} + +.react-datepicker__input-container input:focus { + border-color: #0078d4; + outline: none; } .react-datepicker { - position: absolute !important; - z-index: 1000 !important; - width: auto; - max-width: 300px; - background-color: white; - border: 1px solid #ccc; - border-radius: 4px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + font-family: 'Segoe UI', sans-serif; + border: 1px solid #e0e0e0; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + background-color: #fff; + /* Непрозрачный фон */ + z-index: 1000; + /* Календарь поверх других элементов */ +} + +.react-datepicker-popper { + z-index: 1000; + /* Календарь поверх других элементов */ + pointer-events: auto; + /* Разрешить взаимодействие только с календарем */ } .react-datepicker__header { - background-color: #f0f0f0; - border-bottom: 1px solid #ccc; - border-radius: 4px 4px 0 0; - padding: 8px; + background-color: #f8f8f8; + /* Непрозрачный фон заголовка */ + border-bottom: 1px solid #e0e0e0; + border-radius: 8px 8px 0 0; + padding: 12px; } .react-datepicker__current-month { - font-size: 1.1em; - font-weight: bold; + font-size: 14px; + font-weight: 600; + color: #333; +} + +.react-datepicker__navigation { + top: 12px; + border: 0.45rem solid transparent; +} + +.react-datepicker__navigation--previous { + left: 12px; + border-right-color: #666; +} + +.react-datepicker__navigation--next { + right: 12px; + border-left-color: #666; +} + +.react-datepicker__day-names { + display: flex; + justify-content: space-between; + padding: 0 8px; + margin-top: 8px; +} + +.react-datepicker__day-name { + width: 28px; + line-height: 28px; + text-align: center; + font-size: 12px; + color: #666; +} + +.react-datepicker__month { + background-color: #fff; + /* Непрозрачный фон месяца */ + margin: 0; + padding: 8px; +} + +.react-datepicker__week { + display: flex; + justify-content: space-between; } .react-datepicker__day { - padding: 5px; - margin: 2px; - border-radius: 4px; -} - -.react-datepicker__day--selected, -.react-datepicker__day--keyboard-selected { - background-color: #3e95cd; - color: white; + width: 28px; + line-height: 28px; + text-align: center; + font-size: 12px; + color: #333; + cursor: pointer; + border-radius: 50%; + transition: background-color 0.2s ease, color 0.2s ease; } .react-datepicker__day:hover { background-color: #f0f0f0; } -.react-datepicker__navigation { - top: 10px; +.react-datepicker__day--selected { + background-color: #0078d4; + color: #fff; } -.react-datepicker__navigation--previous { - border-right-color: #333; +.react-datepicker__day--selected:hover { + background-color: #005bb5; } -.react-datepicker__navigation--next { - border-left-color: #333; +.react-datepicker__day--outside-month { + color: #ccc; } -/* Исправление странного появления в центре экрана */ -.react-datepicker-popper { - z-index: 9999 !important; - transform: translate3d(0px, 0px, 0px) !important; -} +.react-datepicker__day--disabled { + color: #ccc; + cursor: not-allowed; +} \ No newline at end of file -- 2.40.1 From 5613fcf205ab7945c1052550dffafba4da77e533 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Fri, 7 Mar 2025 09:02:32 -0500 Subject: [PATCH 09/10] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B0=D0=BB=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA,=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=BE=D0=BD=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BA=D0=B0=D0=B7=D1=8B=D0=B2=D0=B0=D0=B5=D1=82=20=D0=BA?= =?UTF-8?q?=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=BD=D1=8B=D0=B5=20=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B7=D0=B0=20=D0=B4=D0=B8?= =?UTF-8?q?=D0=B0=D0=BF=D0=B0=D0=B7=D0=BE=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.css | 30 -- src/Charts/Components/LineChartComponent.jsx | 28 +- src/Charts/PrometheusChart.jsx | 301 ++++++------------- src/Components/TreeChart/tabContent.jsx | 2 + 4 files changed, 105 insertions(+), 256 deletions(-) diff --git a/src/App.css b/src/App.css index 40842f6..9c7629a 100755 --- a/src/App.css +++ b/src/App.css @@ -42,34 +42,4 @@ .read-the-docs { color: #888; -} - -/* Глобальный стиль для WebKit-браузеров (Chrome, Edge, Safari) */ -::-webkit-scrollbar { - width: 10px; /* Толщина вертикального скролла */ - height: 10px; /* Толщина горизонтального скролла */ -} - -/* Фон скроллбара */ -::-webkit-scrollbar-track { - background: #f1f1f1; /* Цвет фона */ - border-radius: 10px; /* Скругление углов */ -} - -/* Ползунок */ -::-webkit-scrollbar-thumb { - background: #3d74c7; /* Основной цвет */ - border-radius: 10px; /* Скругляем края */ - border: 2px solid #f1f1f1; /* Белая обводка */ -} - -/* Эффект при наведении */ -::-webkit-scrollbar-thumb:hover { - background: #2b5aa5; /* Чуть темнее при наведении */ -} - -/* Глобальный стиль для Firefox */ -* { - scrollbar-width: thin; /* Делаем тонким */ - scrollbar-color: #3d74c7 #f1f1f1; /* Ползунок + фон */ } \ No newline at end of file diff --git a/src/Charts/Components/LineChartComponent.jsx b/src/Charts/Components/LineChartComponent.jsx index 23ae6da..f607307 100644 --- a/src/Charts/Components/LineChartComponent.jsx +++ b/src/Charts/Components/LineChartComponent.jsx @@ -1,12 +1,12 @@ import React from 'react'; -import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer, Brush } from 'recharts'; +import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer } from 'recharts'; -const LineChartComponent = ({ chartData, metricName, metricType, colors, description, isLongRange }) => { +const LineChartComponent = ({ chartData, metricName, metricType, colors, description }) => { // Создаем массив уникальных временных меток const allTimes = Object.values(chartData) .flat() .map(point => point.time) - .filter((time, index, self) => self.indexOf(time) === index); // Убираем дубликаты + .filter((time, index, self) => self.indexOf(time) === index); // Формируем данные для графика const data = allTimes.map(time => { @@ -18,14 +18,17 @@ const LineChartComponent = ({ chartData, metricName, metricType, colors, descrip return point; }); - console.log('Processed Data:', data); // Логируем данные для графика - // Кастомный Tooltip для отображения значения const CustomTooltip = ({ active, payload, label }) => { if (active && payload && payload.length) { return ( -
-

{`${payload[0].value}`}

+
+

{`Время: ${label}`}

{/* Время из label */} + {payload.map((entry, index) => ( +

+ {`Значение: ${entry.value}`} {/* Имя и значение из payload */} +

+ ))}
); } @@ -41,7 +44,7 @@ const LineChartComponent = ({ chartData, metricName, metricType, colors, descrip - } /> + } /> {/* Подключаем кастомный Tooltip */} {Object.keys(chartData).map((key, index) => ( ))} - {isLongRange && ( // Добавляем Brush только для длительных периодов - - )}
diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index f6ce9be..159afba 100644 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -1,258 +1,141 @@ import React, { useEffect, useState, useRef } from 'react'; import axios from 'axios'; import LineChartComponent from './Components/LineChartComponent'; -import BarChartComponent from './Components/BarChartComponent'; -import ScatterChartComponent from './Components/ScatterChartComponent'; -import DatePicker from 'react-datepicker'; -import '../Style/DatePicker.css'; const MAX_POINTS = 20; // Ограничение точек на графике const COLORS = ['#3e95cd', '#8e5ea2', '#3cba9f', '#e8c3b9', '#c45850']; // Фиксированные цвета для линий -// Компонент для выбора временного диапазона -const TimeRangeSelector = ({ onRangeChange }) => { - return ( -
- - - -
- ); -}; - -// Компонент для выбора произвольного диапазона дат -const DateRangeSelector = ({ onDateChange }) => { - const [startDate, setStartDate] = useState(new Date()); - const [endDate, setEndDate] = useState(new Date()); - - const handleDateChange = (dates) => { - const [start, end] = dates; - setStartDate(start); - setEndDate(end); - onDateChange({ start, end }); - }; - - return ( -
- -
- ); -}; +// Список временных диапазонов и интервалов обновления +const TIME_RANGES = [ + { label: '1 минута', value: 60, interval: 3000 }, + { label: '5 минут', value: 300, interval: 15000 }, + { label: '30 минут', value: 1800, interval: 90000 }, + { label: '1 час', value: 3600, interval: 180000 }, + { label: '3 часа', value: 10800, interval: 540000 }, + { label: '6 часов', value: 21600, interval: 1080000 }, + { label: '12 часов', value: 43200, interval: 2160000 }, + { label: '24 часа', value: 86400, interval: 4320000 }, + { label: '2 дня', value: 172800, interval: 8640000 }, + { label: '7 дней', value: 604800, interval: 30240000 }, + { label: '30 дней', value: 2592000, interval: 129600000 }, + { label: '90 дней', value: 7776000, interval: 388800000 }, + { label: '6 месяцев', value: 15552000, interval: 777600000 }, + { label: '9 месяцев', value: 23328000, interval: 1166400000 }, + { label: '1 год', value: 31536000, interval: 1576800000 }, +]; const PrometheusChart = ({ metricName }) => { const [chartData, setChartData] = useState({}); const [metricType, setMetricType] = useState(''); const [metricDescription, setMetricDescription] = useState(''); - const [timeRange, setTimeRange] = useState('24h'); // По умолчанию выбран диапазон "24 часа" - const [customRange, setCustomRange] = useState({ start: null, end: null }); + const [selectedRange, setSelectedRange] = useState(TIME_RANGES[0]); // По умолчанию 1 минута const intervalRef = useRef(null); - const isLongRange = (range) => { - return range === '2w' || range === 'custom'; // "2w" — две недели, "custom" — кастомный диапазон - }; - - const fetchData = async (range, customStart, customEnd) => { + const fetchData = async () => { try { - const end = customEnd ? Math.floor(customEnd.getTime() / 1000) : Math.floor(Date.now() / 1000); - let start; + const end = Math.floor(Date.now() / 1000); + const start = end - selectedRange.value; - if (customStart) { - start = Math.floor(customStart.getTime() / 1000); - } else { - switch (range) { - case '1h': - start = end - 60 * 60; // 1 час назад - break; - case '24h': - start = end - 24 * 60 * 60; // 24 часа назад - break; - case '2w': - start = end - 14 * 24 * 60 * 60; // 2 недели назад - break; - default: - start = end - 24 * 60 * 60; // По умолчанию 24 часа - } - } + // Динамический шаг (чем больше диапазон, тем больше шаг) + let step; + if (selectedRange.value <= 3600) step = 5; // 1 час и меньше → 5 сек + else if (selectedRange.value <= 21600) step = 30; // 1-6 часов → 30 сек + else if (selectedRange.value <= 86400) step = 120; // 6-24 часа → 2 минуты + else step = 300; // > 24 часов → 5 минут - const step = range === '2w' ? 3600 : 60; // Для двух недель увеличиваем шаг до 1 часа + console.log(`Запрашиваем данные с шагом ${step} сек`); const response = await axios.get(`http://192.168.2.39:3000/metrics`, { - params: { - metric: metricName, - start, - end, - step, - }, + params: { metric: metricName, start, end, step }, }); const result = response.data; - - // Проверяем структуру данных - let metrics; - if (Array.isArray(result)) { - metrics = result; - } else if (result.data && Array.isArray(result.data)) { - metrics = result.data; - } else { - throw new Error('Invalid data format'); - } + let metrics = Array.isArray(result) ? result : result.data || []; if (!Array.isArray(metrics) || metrics.length === 0) { - throw new Error('No metrics data available'); + console.warn('No metrics data available, filling with empty values.'); + metrics = []; } - const type = metrics[0].type; - setMetricType(type); + // 1. Генерация временных точек с учетом диапазона + const timePoints = []; + for (let t = start; t <= end; t += step) { + const date = new Date(t * 1000); + const formattedTime = selectedRange.value > 86400 + ? date.toLocaleString([], { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) + : date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); - // Устанавливаем описание метрики - setMetricDescription(metrics[0].description); - - // Очищаем предыдущие данные - setChartData({}); - - 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 - const updatedData = {}; - - newDataPoints.forEach(point => { - const key = `${point.instance}-${point.device}`; // Уникальный ключ - if (!updatedData[key]) { - updatedData[key] = []; - } - updatedData[key].push({ - time: point.time, - value: point.value, - }); - - // Ограничиваем количество точек до MAX_POINTS - if (updatedData[key].length > MAX_POINTS) { - updatedData[key] = updatedData[key].slice(-MAX_POINTS); // Оставляем последние MAX_POINTS точек - } - }); - - setChartData(updatedData); + timePoints.push(formattedTime); } + + // 2. Обработка данных + const updatedData = {}; + metrics.forEach(m => { + const date = new Date(m.timestamp); + const formattedTime = selectedRange.value > 86400 + ? date.toLocaleString([], { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) + : date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); + + const key = `${m.instance}-${m.device || m.scrape_job}`; + if (!updatedData[key]) updatedData[key] = {}; + updatedData[key][formattedTime] = m.value; + }); + + // 3. Заполнение пропусков + const chartData = {}; + Object.keys(updatedData).forEach(key => { + chartData[key] = timePoints.map(time => ({ + time, + value: updatedData[key][time] ?? null, + })); + }); + + setChartData(chartData); } catch (error) { - console.error('Error fetching metrics:', error); + console.error('Ошибка при загрузке метрик:', error); } }; - useEffect(() => { - fetchData(timeRange, customRange.start, customRange.end); // Первоначальная загрузка данных - if (!isLongRange(timeRange)) { // Обновляем только для коротких диапазонов - intervalRef.current = setInterval(() => { - fetchData(timeRange, customRange.start, customRange.end); - }, 5000); // Обновляем каждые 5 секунд - } + useEffect(() => { + fetchData(); // Первоначальная загрузка данных + + intervalRef.current = setInterval(() => { + fetchData(); + }, selectedRange.interval); // Обновляем с выбранным интервалом return () => { if (intervalRef.current) { clearInterval(intervalRef.current); // Очищаем интервал при размонтировании } }; - }, [metricName, timeRange, customRange]); + }, [metricName, selectedRange]); // Зависимость от metricName и selectedRange - const handleRangeChange = (range) => { - setTimeRange(range); - setCustomRange({ start: null, end: null }); - - if (!isLongRange(range)) { - clearInterval(intervalRef.current); // Останавливаем обновление - } - }; - - const handleDateChange = ({ start, end }) => { - setCustomRange({ start, end }); - setTimeRange('custom'); // Устанавливаем кастомный диапазон + const handleRangeChange = (event) => { + const selectedValue = event.target.value; + const range = TIME_RANGES.find(range => range.value === parseInt(selectedValue, 10)); + setSelectedRange(range); }; if (!Object.keys(chartData).length) return

Loading...

; - const renderChart = () => { - switch (metricType) { - case 'counter': - case 'gauge': - return ( - - ); - case 'summary': - return ( - - ); - case 'unknown': - return ( - - ); - default: - return

Unsupported metric type

; - } - }; - return (
- - - {renderChart()} +
+ + +
+
); }; diff --git a/src/Components/TreeChart/tabContent.jsx b/src/Components/TreeChart/tabContent.jsx index 26ab955..9c55909 100644 --- a/src/Components/TreeChart/tabContent.jsx +++ b/src/Components/TreeChart/tabContent.jsx @@ -7,6 +7,8 @@ const getMetricName = (id) => { return `zvks_apiforsnmp_measure_${id}`; }; +//!!!!!!!!!!Пофиксить вкладуи с eth4, во всех eth 1-4 открывается именно 4 !!!!!!!!!!!!! + // Функция для рекурсивного сбора всех id потомков const getAllChildIds = (node) => { let ids = []; -- 2.40.1 From 502da09527872c171c64d5a3c2bb61334db76018 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Mon, 10 Mar 2025 03:24:04 -0400 Subject: [PATCH 10/10] =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Charts/Components/LineChartComponent.jsx | 9 ++- src/Charts/PrometheusChart.jsx | 76 +++++++++++++++++--- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/Charts/Components/LineChartComponent.jsx b/src/Charts/Components/LineChartComponent.jsx index f607307..37d76da 100644 --- a/src/Charts/Components/LineChartComponent.jsx +++ b/src/Charts/Components/LineChartComponent.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer } from 'recharts'; +import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer, Brush } from 'recharts'; const LineChartComponent = ({ chartData, metricName, metricType, colors, description }) => { // Создаем массив уникальных временных меток @@ -55,6 +55,13 @@ const LineChartComponent = ({ chartData, metricName, metricType, colors, descrip name={key} /> ))} +
diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index 159afba..d91f9cc 100644 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -1,5 +1,7 @@ import React, { useEffect, useState, useRef } from 'react'; import axios from 'axios'; +import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; import LineChartComponent from './Components/LineChartComponent'; const MAX_POINTS = 20; // Ограничение точек на графике @@ -29,23 +31,37 @@ const PrometheusChart = ({ metricName }) => { const [metricType, setMetricType] = useState(''); const [metricDescription, setMetricDescription] = useState(''); const [selectedRange, setSelectedRange] = useState(TIME_RANGES[0]); // По умолчанию 1 минута + const [startDate, setStartDate] = useState(new Date()); // Начальная дата для кастомного диапазона + const [endDate, setEndDate] = useState(new Date()); // Конечная дата для кастомного диапазона + const [useCustomRange, setUseCustomRange] = useState(false); // Флаг для выбора кастомного диапазона + const [brushRange, setBrushRange] = useState({ startIndex: 0, endIndex: 0 }); // Состояние Brush const intervalRef = useRef(null); const fetchData = async () => { try { - const end = Math.floor(Date.now() / 1000); - const start = end - selectedRange.value; + let start, end; + + if (useCustomRange) { + // Используем кастомный диапазон + start = Math.floor(startDate.getTime() / 1000); + end = Math.floor(endDate.getTime() / 1000); + } else { + // Используем предустановленный диапазон + end = Math.floor(Date.now() / 1000); + start = end - selectedRange.value; + } // Динамический шаг (чем больше диапазон, тем больше шаг) let step; - if (selectedRange.value <= 3600) step = 5; // 1 час и меньше → 5 сек - else if (selectedRange.value <= 21600) step = 30; // 1-6 часов → 30 сек - else if (selectedRange.value <= 86400) step = 120; // 6-24 часа → 2 минуты - else step = 300; // > 24 часов → 5 минут + const range = end - start; + if (range <= 3600) step = 5; // 1 час и меньше → 5 сек + else if (range <= 21600) step = 30; // 1-6 часов → 30 сек + else if (range <= 86400) step = 120; // 6-24 часа → 2 минуты + else step = 300; // > 24 часов → 5 минут console.log(`Запрашиваем данные с шагом ${step} сек`); - const response = await axios.get(`http://192.168.2.39:3000/metrics`, { + const response = await axios.get('http://192.168.2.39:3000/metrics', { params: { metric: metricName, start, end, step }, }); @@ -61,7 +77,7 @@ const PrometheusChart = ({ metricName }) => { const timePoints = []; for (let t = start; t <= end; t += step) { const date = new Date(t * 1000); - const formattedTime = selectedRange.value > 86400 + const formattedTime = range > 86400 ? date.toLocaleString([], { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); @@ -72,7 +88,7 @@ const PrometheusChart = ({ metricName }) => { const updatedData = {}; metrics.forEach(m => { const date = new Date(m.timestamp); - const formattedTime = selectedRange.value > 86400 + const formattedTime = range > 86400 ? date.toLocaleString([], { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); @@ -91,12 +107,17 @@ const PrometheusChart = ({ metricName }) => { }); setChartData(chartData); + + // Устанавливаем Brush на весь диапазон + setBrushRange({ + startIndex: 0, + endIndex: timePoints.length - 1, + }); } catch (error) { console.error('Ошибка при загрузке метрик:', error); } }; - useEffect(() => { fetchData(); // Первоначальная загрузка данных @@ -109,12 +130,17 @@ const PrometheusChart = ({ metricName }) => { clearInterval(intervalRef.current); // Очищаем интервал при размонтировании } }; - }, [metricName, selectedRange]); // Зависимость от metricName и selectedRange + }, [metricName, selectedRange, useCustomRange, startDate, endDate]); // Зависимость от metricName, selectedRange, useCustomRange, startDate и endDate const handleRangeChange = (event) => { const selectedValue = event.target.value; const range = TIME_RANGES.find(range => range.value === parseInt(selectedValue, 10)); setSelectedRange(range); + setUseCustomRange(false); // Переключаемся на предустановленный диапазон + }; + + const handleCustomRangeChange = () => { + setUseCustomRange(true); // Переключаемся на кастомный диапазон }; if (!Object.keys(chartData).length) return

Loading...

; @@ -129,12 +155,40 @@ const PrometheusChart = ({ metricName }) => { ))}
+
+ +
+ + setStartDate(date)} + showTimeSelect + timeFormat="HH:mm" + timeIntervals={15} + dateFormat="yyyy-MM-dd HH:mm" + /> +
+
+ + setEndDate(date)} + showTimeSelect + timeFormat="HH:mm" + timeIntervals={15} + dateFormat="yyyy-MM-dd HH:mm" + /> +
+ +
); -- 2.40.1