import React, { useState, useEffect } from 'react';
import axios from 'axios';
import {
ComposedChart,
Area,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
} from 'recharts';
import './App.css';
function History() {
const [historyData, setHistoryData] = useState([]);
const [days, setDays] = useState(7);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [showAveraged, setShowAveraged] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [totalItems, setTotalItems] = useState(0);
useEffect(() => {
const loadHistory = async () => {
setLoading(true);
setError(null);
try {
const response = await axios.get('/api/history/overview', { params: { days, page: currentPage, limit: pageSize } });
setHistoryData(response.data.resources || []);
setTotalItems(response.data.total || response.data.resources?.length || 0);
setLoading(false);
} catch (error) {
console.error('Error loading history:', error);
setError(error.message);
setLoading(false);
}
};
loadHistory();
}, [days, currentPage, pageSize]);
if (loading) return
Loading history...
;
if (error) return Error loading history: {error}
;
return (
{historyData.length === 0 ? (
No check history
Add resources and wait for checks to complete
) : (
{historyData.map((resource) => {
const chartData = showAveraged
? prepareAveragedChartData(resource.checks || [], days)
: prepareChartData(resource.checks || []);
return (
{resource.name}
Type: {resource.type.toUpperCase()}
Uptime: = 95 ? '#4caf50' : resource.uptime >= 80 ? '#ff9800' : '#f44336' }}>{resource.uptime}%
Avg Response: {resource.avgResponseTime}ms
{chartData.length > 0 ? (
{
if (name === 'Response Time') return `${value}ms`;
if (name === 'Status') return value === 1 ? '✓ UP' : '✗ DOWN';
if (name === 'Uptime %') return `${value}%`;
return value;
}}
/>
) : (
No check data available
)}
{resource.checks?.length || 0}
Total Checks
= 95 ? '#4caf50' : resource.uptime >= 80 ? '#ff9800' : '#f44336', fontSize: '2rem', fontWeight: 'bold' }}>{resource.uptime}%
Uptime
{resource.avgResponseTime}ms
Avg Response
{(resource.checks || []).filter((c) => c.status === 'up').length}/{resource.checks?.length || 0}
Successful Checks
);
})}
{totalItems > pageSize && (
Page
{
const page = Math.max(1, Math.min(Math.ceil(totalItems / pageSize), parseInt(e.target.value) || 1));
setCurrentPage(page);
}}
style={{ width: '50px', padding: '0.5rem', border: '1px solid #ddd', borderRadius: '4px', textAlign: 'center' }}
/>
of {Math.ceil(totalItems / pageSize)}
)}
)}
);
}
function prepareChartData(checks) {
return checks.map((check) => {
const date = new Date(check.checked_at);
const label = `${date.toLocaleDateString()} ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
return {
label,
responseTime: check.response_time || 0,
status: check.status,
statusNumeric: check.status === 'up' ? 1 : 0,
};
});
}
function prepareAveragedChartData(checks, days) {
if (!checks || checks.length === 0) return [];
// Determine interval based on days
let intervalHours;
if (days <= 7) {
intervalHours = 1; // Hourly for 7 days
} else if (days <= 14) {
intervalHours = 3; // Every 3 hours for 14 days
} else {
intervalHours = 6; // Every 6 hours for 30+ days
}
const intervalMs = intervalHours * 60 * 60 * 1000;
const now = Date.now();
const startTime = now - (days * 24 * 60 * 60 * 1000);
// Group checks into time buckets
const buckets = new Map();
checks.forEach(check => {
const checkTime = new Date(check.checked_at).getTime();
if (checkTime < startTime) return;
const bucketKey = Math.floor(checkTime / intervalMs) * intervalMs;
if (!buckets.has(bucketKey)) {
buckets.set(bucketKey, {
checks: [],
upCount: 0,
totalResponse: 0,
});
}
const bucket = buckets.get(bucketKey);
bucket.checks.push(check);
if (check.status === 'up') {
bucket.upCount++;
bucket.totalResponse += check.response_time || 0;
}
});
// Convert buckets to chart data
const chartData = Array.from(buckets.entries())
.sort((a, b) => a[0] - b[0])
.map(([timestamp, data]) => {
const date = new Date(timestamp);
const avgResponse = data.upCount > 0 ? Math.round(data.totalResponse / data.upCount) : 0;
const uptime = Math.round((data.upCount / data.checks.length) * 100);
let label;
if (intervalHours === 1) {
label = `${date.toLocaleDateString()} ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
} else {
label = `${date.toLocaleDateString()} ${date.toLocaleTimeString([], { hour: '2-digit' })}`;
}
return {
label,
responseTime: avgResponse,
statusNumeric: uptime / 100,
status: uptime > 50 ? 'up' : 'down',
uptime: uptime,
};
});
return chartData;
}
export default History;