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'; import './CommandCenterPages.css'; import { formatChartTime } from './utils/timeUtils'; const asNumberOrZero = (value) => { const n = Number(value); return Number.isFinite(n) ? n : 0; }; function History() { const [historyData, setHistoryData] = useState([]); const [days, setDays] = useState(7); const [maxDays, setMaxDays] = useState(30); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [showAveraged, setShowAveraged] = useState(true); const [showTrends, setShowTrends] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); const [totalItems, setTotalItems] = useState(0); const [effectiveDays, setEffectiveDays] = useState(7); const [limitedByRetention, setLimitedByRetention] = useState(false); useEffect(() => { // Fetch retention setting from server const fetchRetention = async () => { try { const response = await axios.get('/api/settings'); const retentionDays = response.data.retention_days || 30; setMaxDays(parseInt(retentionDays)); // Adjust current days if it exceeds retention if (days > retentionDays) { setDays(parseInt(retentionDays)); } } catch (error) { // Failed to fetch retention setting, use default } }; fetchRetention(); }, []); // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { const loadHistory = async () => { setLoading(true); setError(null); try { // Ask backend for averaged data (server-side bucketing) when enabled const response = await axios.get('/api/history/overview', { params: { days, page: currentPage, limit: pageSize, averaged: showAveraged } }); let data = []; let total = 0; // Handle both response formats if (response.data.resources && Array.isArray(response.data.resources)) { data = response.data.resources; total = response.data.total || data.length; setEffectiveDays(response.data.effective_days || days); setLimitedByRetention(Boolean(response.data.limited_by_retention)); } else if (Array.isArray(response.data)) { data = response.data; total = data.length; } // Fetch trends data if enabled if (showTrends && data.length > 0) { const trendsPromises = data.map(async (resource) => { try { const trendsResponse = await axios.get(`/api/resources/${resource.id}/trends`, { params: { days } }); return { ...resource, trendsData: trendsResponse.data }; } catch (err) { return resource; } }); data = await Promise.all(trendsPromises); } setHistoryData(data); setTotalItems(total); setLoading(false); } catch (error) { setError(error.message); setLoading(false); } }; loadHistory(); }, [days, currentPage, pageSize, showAveraged, showTrends]); if (loading) return
Add resources and wait for checks to complete
Type: {resource.type.toUpperCase()} Uptime: = 95 ? '#22c55e' : resource.uptime >= 80 ? '#f59e0b' : '#ef4444' }}>{resource.uptime}% Avg Response: {resource.avgResponseTime}ms
{chartData.length > 0 ? (Total Checks
{resource.checks?.length || 0}
Uptime
= 95 ? '#22c55e' : resource.uptime >= 80 ? '#f59e0b' : '#ef4444' }}>{resource.uptime}%
Avg Response
{resource.avgResponseTime}ms
Successful Checks
{(resource.checks || []).filter((c) => c.status === 'up').length}/{resource.checks?.length || 0}