import React, { useState, useEffect, useCallback, useRef } from 'react'; import { FiAward, FiPercent, FiActivity } from 'react-icons/fi'; function getStatusColor(validCert) { if (validCert === false) return 'bg-red-600'; if (validCert === true) return 'bg-green-600'; return 'bg-gray-400'; } function getHeartbeatColor(status) { if (status === 1) return 'bg-green-600'; // UP if (status === 0) return 'bg-red-600'; // DOWN if (status === 2) return 'bg-yellow-400'; // PENDING if (status === 3) return 'bg-blue-500'; // MAINTENANCE return 'bg-gray-300'; } function getCertBg(days) { if (typeof days !== 'number') return 'bg-gray-400 dark:bg-gray-600'; if (days < 0) return 'bg-red-600 animate-pulse border-2 border-red-500 dark:bg-red-800'; if (days < 5) return 'bg-red-600 dark:bg-red-800'; if (days <= 15) return 'bg-yellow-400 dark:bg-yellow-500 text-gray-900'; return 'bg-green-600 dark:bg-green-700'; } function getCertText(days) { if (typeof days !== 'number') return 'Cert Exp'; if (days < 0) return 'Cert Expired!'; return 'Cert Exp'; } function getAvgPingBg(avgPing) { if (typeof avgPing !== 'number') return 'bg-gray-300 dark:bg-gray-700'; if (avgPing <= 80) return 'bg-green-600 text-white dark:bg-green-700 dark:text-white'; if (avgPing <= 200) return 'bg-yellow-400 text-gray-900 dark:bg-yellow-500 dark:text-gray-900'; return 'bg-red-600 text-white dark:bg-red-800 dark:text-white'; } function getUptime24hBg(uptime) { if (typeof uptime !== 'number') return 'bg-gray-300 dark:bg-gray-700'; if (uptime >= 99.9) return 'bg-green-600 text-white dark:bg-green-700 dark:text-white'; if (uptime >= 99) return 'bg-yellow-400 text-gray-900 dark:bg-yellow-500 dark:text-gray-900'; return 'bg-red-600 text-white dark:bg-red-800 dark:text-white'; } export default function UptimeStatusIsland() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Track open badge for each monitor by monitor.id and badge type const [openBadge, setOpenBadge] = useState({}); // Refs for each badge popup const badgeRefs = useRef({}); const fetchData = useCallback(async () => { setLoading(true); setError(null); try { const response = await fetch('/api/uptime'); if (!response.ok) throw new Error('Failed to fetch uptime data'); const json = await response.json(); setData(json); } catch (err) { setError(err.message || 'Unknown error'); } finally { setLoading(false); } }, []); useEffect(() => { fetchData(); }, [fetchData]); // Helper to toggle badge info const toggleBadge = (monitorId, badge) => { setOpenBadge((prev) => ({ ...prev, [monitorId]: { ...prev[monitorId], [badge]: !prev[monitorId]?.[badge], }, })); }; // Close popup on outside click useEffect(() => { function handleClick(e) { let clickedInside = false; Object.values(badgeRefs.current).forEach((monitorBadges) => { Object.values(monitorBadges || {}).forEach((ref) => { if (ref && ref.current && ref.current.contains(e.target)) { clickedInside = true; } }); }); if (!clickedInside) setOpenBadge({}); } document.addEventListener('mousedown', handleClick); return () => document.removeEventListener('mousedown', handleClick); }, []); // Helper to get/create refs for each badge const getBadgeRef = (monitorId, badge) => { if (!badgeRefs.current[monitorId]) badgeRefs.current[monitorId] = {}; if (!badgeRefs.current[monitorId][badge]) badgeRefs.current[monitorId][badge] = React.createRef(); return badgeRefs.current[monitorId][badge]; }; return (
{loading ? (
Loading...
) : error ? (
{error}
) : data && data.publicGroupList && data.publicGroupList.length > 0 ? ( data.publicGroupList.map((group) => (

{group.name}

    {group.monitorList.map((monitor) => (
  • {/* First row: 5-column grid for fixed badge placement, name gets col-span-2 */}
    {/* Name (left, col-span-2, min-w-0) */}
    {monitor.name}
    {/* Cert Exp (center-left, col-span-1) */}
    {monitor.certExpiryDaysRemaining !== undefined && ( <> {/* Desktop: full badge */} {getCertText(monitor.certExpiryDaysRemaining)} {/* Mobile: icon badge with click */} toggleBadge(monitor.id, 'cert')} style={{ cursor: 'pointer', position: 'relative' }} > {openBadge[monitor.id]?.cert && ( {monitor.certExpiryDaysRemaining < 0 ? 'Expired!' : `Expires in ${monitor.certExpiryDaysRemaining} days`} )} )}
    {/* Avg. (center-right, col-span-1) */}
    {monitor.avgPing !== undefined && ( <> {/* Desktop: full badge */} Avg.: {monitor.avgPing < 100 ? monitor.avgPing.toFixed(1) : Math.round(monitor.avgPing)} ms {/* Mobile: icon badge with click */} toggleBadge(monitor.id, 'avg')} style={{ cursor: 'pointer', position: 'relative' }} > {openBadge[monitor.id]?.avg && ( {monitor.avgPing < 100 ? monitor.avgPing.toFixed(1) : Math.round(monitor.avgPing)} ms )} )}
    {/* 24h (right, col-span-1) */}
    {monitor.uptime24h !== undefined && ( <> {/* Desktop: full badge */} 24h: {monitor.uptime24h.toFixed(1)}% {/* Mobile: icon badge with click */} toggleBadge(monitor.id, 'uptime')} style={{ cursor: 'pointer', position: 'relative' }} > {openBadge[monitor.id]?.uptime && ( {monitor.uptime24h.toFixed(1)}% )} )}
    {/* Second row: Heartbeat bar, always full width, evenly distributed squares */} {monitor.heartbeatHistory && monitor.heartbeatHistory.length > 0 && (
    {Array.from({ length: 40 }).map((_, i) => { const hbArr = monitor.heartbeatHistory ?? []; const hb = hbArr.slice(-40)[i]; return ( ); })}
    )}
  • ))}
)) ) : (
No status data available
)}
); }