Update dependencies and enhance UptimeStatusIsland component with timezone and locale handling
- Added luxon library for improved date handling. - Updated UptimeStatusIsland component to format and display local time for heartbeats. - Enhanced state management to track user timezone and locale. - Ensured UTC formatting for timestamps in API responses.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { FiAward, FiPercent, FiActivity } from 'react-icons/fi';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
function getStatusColor(validCert) {
|
||||
if (validCert === false) return 'bg-red-600';
|
||||
@@ -38,19 +39,31 @@ function getAvgPingBg(avgPing) {
|
||||
|
||||
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';
|
||||
if (uptime >= 99) return 'bg-green-600 text-white dark:bg-green-700 dark:text-white';
|
||||
if (uptime >= 95) 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 formatLocalTime(utcTime) {
|
||||
if (!utcTime) return '';
|
||||
const dt = DateTime.fromISO(utcTime, { zone: 'utc' });
|
||||
// Always show in UTC, format: dd-MM-yyyy, HH:mm:ss UTC
|
||||
return dt.toFormat('dd-MM-yyyy, HH:mm:ss') + ' UTC';
|
||||
}
|
||||
|
||||
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 [userZone, setUserZone] = useState(null);
|
||||
const [userLocale, setUserLocale] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
setUserZone(Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||
setUserLocale(navigator.language || 'en-US');
|
||||
}, []);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -68,7 +81,9 @@ export default function UptimeStatusIsland() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
fetchData(); // initial fetch
|
||||
const interval = setInterval(fetchData, 300000); // fetch every 5 minutes
|
||||
return () => clearInterval(interval);
|
||||
}, [fetchData]);
|
||||
|
||||
// Helper to toggle badge info
|
||||
@@ -106,6 +121,11 @@ export default function UptimeStatusIsland() {
|
||||
return badgeRefs.current[monitorId][badge];
|
||||
};
|
||||
|
||||
// Only render cards when timezone and locale are available
|
||||
if (!userZone || !userLocale) {
|
||||
return <div className="text-center text-gray-500 dark:text-gray-400">Loading timezone...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="grid gap-10 md:grid-cols-2">
|
||||
@@ -161,27 +181,28 @@ export default function UptimeStatusIsland() {
|
||||
{monitor.avgPing !== undefined && (
|
||||
<>
|
||||
{/* Desktop: full badge */}
|
||||
<span className={`hidden sm:inline px-2 py-0 rounded-full text-xs font-medium ${getAvgPingBg(monitor.avgPing)}`}>
|
||||
Avg.: {monitor.avgPing < 100 ? monitor.avgPing.toFixed(1) : Math.round(monitor.avgPing)} ms
|
||||
<span className={`hidden sm:inline px-2 py-0 rounded-full text-xs font-medium ${getAvgPingBg(monitor.avgPing)}`}
|
||||
title={`Avg. response: ${monitor.avgPing} ms`}>
|
||||
Avg: {monitor.avgPing} ms
|
||||
</span>
|
||||
{/* Mobile: icon badge with click */}
|
||||
<span
|
||||
ref={getBadgeRef(monitor.id, 'avg')}
|
||||
ref={getBadgeRef(monitor.id, 'avgPing')}
|
||||
className={`sm:hidden px-2 py-0 rounded-full text-xs font-medium flex items-center justify-center ${getAvgPingBg(monitor.avgPing)}`}
|
||||
onClick={() => toggleBadge(monitor.id, 'avg')}
|
||||
onClick={() => toggleBadge(monitor.id, 'avgPing')}
|
||||
style={{ cursor: 'pointer', position: 'relative' }}
|
||||
>
|
||||
<FiActivity className="w-4 h-4" />
|
||||
{openBadge[monitor.id]?.avg && (
|
||||
{openBadge[monitor.id]?.avgPing && (
|
||||
<span className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 z-20 bg-white dark:bg-slate-800 text-gray-900 dark:text-gray-100 rounded-lg shadow-lg px-3 py-2 text-xs whitespace-nowrap border border-gray-200 dark:border-gray-700">
|
||||
{monitor.avgPing < 100 ? monitor.avgPing.toFixed(1) : Math.round(monitor.avgPing)} ms
|
||||
{monitor.avgPing} ms
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{/* 24h (right, col-span-1) */}
|
||||
{/* 24h Uptime (right, col-span-1) */}
|
||||
<div className="flex flex-col items-center col-span-1 relative">
|
||||
{monitor.uptime24h !== undefined && (
|
||||
<>
|
||||
@@ -214,10 +235,11 @@ export default function UptimeStatusIsland() {
|
||||
{Array.from({ length: 40 }).map((_, i) => {
|
||||
const hbArr = monitor.heartbeatHistory ?? [];
|
||||
const hb = hbArr.slice(-40)[i];
|
||||
const localTime = hb ? formatLocalTime(hb.time) : '';
|
||||
return (
|
||||
<span
|
||||
key={i}
|
||||
title={hb ? `Status: ${hb.status === 1 ? 'Up' : 'Down'}\nTime: ${hb.time}` : ''}
|
||||
title={hb ? `Status: ${hb.status === 1 ? 'Up' : 'Down'}\nTime: ${localTime}` : ''}
|
||||
className={`w-full h-4 rounded-sm ${hb ? getHeartbeatColor(hb.status) : 'bg-gray-400 dark:bg-gray-600'} block`}
|
||||
></span>
|
||||
);
|
||||
|
Reference in New Issue
Block a user