diff --git a/Jenkinsfile b/Jenkinsfile index 83ae4af..ea5c429 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,3 +1,16 @@ +def notify(String giteaUser, String giteaPass, String repositoryUrl, String repositoryName, String prId, String buildStatus) { + def status = buildStatus == 'success' ? 'success' : 'failure' + def description = buildStatus == 'success' ? 'Build succeeded' : 'Build failed' + + sh """ + curl -X POST \ + -u "${giteaUser}:${giteaPass}" \ + -H "Content-Type: application/json" \ + -d '{"state": "${status}", "description": "${description}", "context": "ci/jenkins"}' \ + ${repositoryUrl}/api/v1/repos/${repositoryName}/statuses/${prId} + """ +} + pipeline { agent any environment { @@ -50,6 +63,7 @@ pipeline { echo "Attempting to merge PR ${env.CHANGE_ID} into master..." withCredentials([usernamePassword(credentialsId: 'gitea_creds', usernameVariable: 'GITEA_USER', passwordVariable: 'GITEA_PASS')]) { def prId = env.CHANGE_ID + // Merge the PR sh """ curl -X POST \ -u "${GITEA_USER}:${GITEA_PASS}" \ @@ -58,6 +72,8 @@ pipeline { http://git.entcor/api/v1/repos/deployer3000/trust-module-frontend/pulls/${prId}/merge """ echo "PR ${prId} merged successfully into master!" + // Notify Gitea with the PR status + notify(GITEA_USER, GITEA_PASS, GITEA_REPOSITORY_URL, "trust-module-frontend", prId, "success") } } } diff --git a/src/Charts/Components/LineChartComponent.jsx b/src/Charts/Components/LineChartComponent.jsx index 37d76da..d684d1c 100644 --- a/src/Charts/Components/LineChartComponent.jsx +++ b/src/Charts/Components/LineChartComponent.jsx @@ -1,7 +1,10 @@ -import React from 'react'; -import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer, Brush } from 'recharts'; +import React, { useState } from 'react'; +import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer } from 'recharts'; + +const LineChartComponent = ({ chartData, metricName, colors, description, onRangeSelect, filteredData }) => { + const [selectionStart, setSelectionStart] = useState(null); + const [selectionEnd, setSelectionEnd] = useState(null); -const LineChartComponent = ({ chartData, metricName, metricType, colors, description }) => { // Создаем массив уникальных временных меток const allTimes = Object.values(chartData) .flat() @@ -18,15 +21,39 @@ const LineChartComponent = ({ chartData, metricName, metricType, colors, descrip return point; }); + // Используем отфильтрованные данные, если они есть + const displayData = filteredData || data; + + // Обработчик клика на графике + const handleClick = (e) => { + if (!e || !e.activeLabel) return; + + const clickedTime = e.activeLabel; + + if (!selectionStart) { + setSelectionStart(clickedTime); + } else if (!selectionEnd) { + setSelectionEnd(clickedTime); + + const startIndex = data.findIndex(point => point.time === selectionStart); + const endIndex = data.findIndex(point => point.time === clickedTime); + + onRangeSelect({ startIndex, endIndex }); + + setSelectionStart(null); + setSelectionEnd(null); + } + }; + // Кастомный Tooltip для отображения значения const CustomTooltip = ({ active, payload, label }) => { if (active && payload && payload.length) { return (
-

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

{/* Время из label */} +

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

{payload.map((entry, index) => (

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

))}
@@ -38,13 +65,12 @@ const LineChartComponent = ({ chartData, metricName, metricType, colors, descrip return (
-

{description}

- + - } /> {/* Подключаем кастомный Tooltip */} + } /> {Object.keys(chartData).map((key, index) => ( ))} -
diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index d91f9cc..1a0d741 100644 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -4,10 +4,8 @@ import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; import LineChartComponent from './Components/LineChartComponent'; -const MAX_POINTS = 20; // Ограничение точек на графике -const COLORS = ['#3e95cd', '#8e5ea2', '#3cba9f', '#e8c3b9', '#c45850']; // Фиксированные цвета для линий - -// Список временных диапазонов и интервалов обновления +const MAX_POINTS = 20; +const COLORS = ['#3e95cd', '#8e5ea2', '#3cba9f', '#e8c3b9', '#c45850']; const TIME_RANGES = [ { label: '1 минута', value: 60, interval: 3000 }, { label: '5 минут', value: 300, interval: 15000 }, @@ -28,13 +26,12 @@ const TIME_RANGES = [ const PrometheusChart = ({ metricName }) => { const [chartData, setChartData] = useState({}); - 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 [selectedRange, setSelectedRange] = useState(TIME_RANGES[0]); + const [startDate, setStartDate] = useState(new Date()); + const [endDate, setEndDate] = useState(new Date()); + const [useCustomRange, setUseCustomRange] = useState(false); + const [selectedGraphRange, setSelectedGraphRange] = useState(null); // Выбранный диапазон + const [filteredData, setFilteredData] = useState(null); // Отфильтрованные данные const intervalRef = useRef(null); const fetchData = async () => { @@ -42,24 +39,19 @@ const PrometheusChart = ({ metricName }) => { 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; 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} сек`); + if (range <= 3600) step = 5; + else if (range <= 21600) step = 30; + else if (range <= 86400) step = 120; + else step = 300; const response = await axios.get('http://192.168.2.39:3000/metrics', { params: { metric: metricName, start, end, step }, @@ -68,12 +60,10 @@ const PrometheusChart = ({ metricName }) => { const result = response.data; let metrics = Array.isArray(result) ? result : result.data || []; - if (!Array.isArray(metrics) || metrics.length === 0) { - console.warn('No metrics data available, filling with empty values.'); + if (!Array.isArray(metrics)) { metrics = []; } - // 1. Генерация временных точек с учетом диапазона const timePoints = []; for (let t = start; t <= end; t += step) { const date = new Date(t * 1000); @@ -84,7 +74,6 @@ const PrometheusChart = ({ metricName }) => { timePoints.push(formattedTime); } - // 2. Обработка данных const updatedData = {}; metrics.forEach(m => { const date = new Date(m.timestamp); @@ -97,7 +86,6 @@ const PrometheusChart = ({ metricName }) => { updatedData[key][formattedTime] = m.value; }); - // 3. Заполнение пропусков const chartData = {}; Object.keys(updatedData).forEach(key => { chartData[key] = timePoints.map(time => ({ @@ -107,44 +95,80 @@ const PrometheusChart = ({ metricName }) => { }); setChartData(chartData); - - // Устанавливаем Brush на весь диапазон - setBrushRange({ - startIndex: 0, - endIndex: timePoints.length - 1, - }); } catch (error) { console.error('Ошибка при загрузке метрик:', error); } }; useEffect(() => { - fetchData(); // Первоначальная загрузка данных + fetchData(); intervalRef.current = setInterval(() => { fetchData(); - }, selectedRange.interval); // Обновляем с выбранным интервалом + }, selectedRange.interval); return () => { if (intervalRef.current) { - clearInterval(intervalRef.current); // Очищаем интервал при размонтировании + clearInterval(intervalRef.current); } }; - }, [metricName, selectedRange, useCustomRange, startDate, endDate]); // Зависимость от 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); // Переключаемся на предустановленный диапазон + setUseCustomRange(false); + setSelectedGraphRange(null); // Сбрасываем выбранный диапазон + setFilteredData(null); // Сбрасываем отфильтрованные данные }; const handleCustomRangeChange = () => { - setUseCustomRange(true); // Переключаемся на кастомный диапазон + setUseCustomRange(true); + setSelectedGraphRange(null); // Сбрасываем выбранный диапазон + setFilteredData(null); // Сбрасываем отфильтрованные данные }; + useEffect(() => { + if (selectedGraphRange) { + const { startIndex, endIndex } = selectedGraphRange; + 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; + }); + + const filtered = data.slice(startIndex, endIndex + 1); + setFilteredData(filtered); // Сохраняем отфильтрованные данные + } else { + setFilteredData(null); // Сбрасываем фильтрацию, если диапазон не выбран + } + }, [selectedGraphRange, chartData]); + if (!Object.keys(chartData).length) return

Loading...

; + 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; + }); + return (
@@ -184,11 +208,10 @@ const PrometheusChart = ({ metricName }) => {
); diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx index ce9c706..040dff0 100644 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -94,7 +94,6 @@ const Dashboard = () => { return (

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

- {/* Используем актуальные данные */}
);