rc #10

Merged
deployer3000 merged 8 commits from rc into main 2025-03-10 15:31:12 +03:00
4 changed files with 114 additions and 57 deletions

16
Jenkinsfile vendored
View File

@ -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")
}
}
}

View File

@ -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 (
<div className="custom-tooltip" style={{ padding: '10px' }}>
<p>{`Время: ${label}`}</p> {/* Время из label */}
<p>{`Время: ${label}`}</p>
{payload.map((entry, index) => (
<p key={index} style={{}}>
{`Значение: ${entry.value}`} {/* Имя и значение из payload */}
{`Значение: ${entry.value}`}
</p>
))}
</div>
@ -38,13 +65,12 @@ const LineChartComponent = ({ chartData, metricName, metricType, colors, descrip
return (
<div>
<h2>{description}</h2>
<ResponsiveContainer width="100%" height={400}>
<LineChart data={data}>
<LineChart data={displayData} onClick={handleClick}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="time" />
<YAxis />
<Tooltip content={<CustomTooltip />} /> {/* Подключаем кастомный Tooltip */}
<Tooltip content={<CustomTooltip />} />
<Legend />
{Object.keys(chartData).map((key, index) => (
<Line
@ -55,13 +81,6 @@ const LineChartComponent = ({ chartData, metricName, metricType, colors, descrip
name={key}
/>
))}
<Brush
dataKey="time"
height={30}
stroke="#8884d8"
startIndex={0} // Начальный индекс для Brush
endIndex={data.length - 1} // Конечный индекс для Brush (весь диапазон)
/>
</LineChart>
</ResponsiveContainer>
</div>

View File

@ -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 <p>Loading...</p>;
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 (
<div>
<div>
@ -184,11 +208,10 @@ const PrometheusChart = ({ metricName }) => {
<LineChartComponent
chartData={chartData}
metricName={metricName}
metricType={metricType}
colors={COLORS}
description={metricDescription}
brushRange={brushRange}
onBrushChange={setBrushRange}
description={metricName}
onRangeSelect={setSelectedGraphRange}
filteredData={filteredData}
/>
</div>
);

View File

@ -94,7 +94,6 @@ const Dashboard = () => {
return (
<div>
<h2>Общий мониторинг</h2>
<ErrorIndicator />
<TreeTable data={treeData.items} /> {/* Используем актуальные данные */}
</div>
);