Enhance ToggleTheme component and add Uptime link in Footer with translations
- Updated ToggleTheme component styles for better visibility in dark mode. - Added a new Uptime link in the Footer for system status monitoring. - Introduced translations for Uptime in English, Dutch, German, and French to support multilingual users.
This commit is contained in:
151
src/components/UptimeStatusIsland.jsx
Normal file
151
src/components/UptimeStatusIsland.jsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
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);
|
||||
|
||||
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]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="grid gap-10 md:grid-cols-2">
|
||||
{loading ? (
|
||||
<div className="text-center text-gray-500 dark:text-gray-400">Loading...</div>
|
||||
) : error ? (
|
||||
<div className="text-center text-red-500 dark:text-red-400">{error}</div>
|
||||
) : data && data.publicGroupList && data.publicGroupList.length > 0 ? (
|
||||
data.publicGroupList.map((group) => (
|
||||
<section key={group.id} className="bg-white dark:bg-slate-800 rounded-2xl shadow-lg p-8 flex flex-col">
|
||||
<h2 className="text-2xl font-bold mb-6 text-gray-900 dark:text-white">{group.name}</h2>
|
||||
<ul className="space-y-6">
|
||||
{group.monitorList.map((monitor) => (
|
||||
<li key={monitor.id} className="flex flex-col gap-2 bg-gray-50 dark:bg-slate-900 rounded-xl px-5 py-4 shadow border border-gray-200 dark:border-gray-700">
|
||||
{/* First row: 5-column grid for fixed badge placement, name gets col-span-2 */}
|
||||
<div className="grid grid-cols-5 items-center w-full gap-2">
|
||||
{/* Name (left, col-span-2, min-w-0) */}
|
||||
<div className="flex items-center min-w-0 col-span-2">
|
||||
<span className={`inline-block w-3 h-3 rounded-full ${getStatusColor(monitor.validCert)} mr-2`}></span>
|
||||
<span className="font-semibold text-lg text-gray-800 dark:text-gray-100 truncate">{monitor.name}</span>
|
||||
</div>
|
||||
{/* Cert Exp (center-left, col-span-1) */}
|
||||
<div className="flex justify-center col-span-1">
|
||||
{monitor.certExpiryDaysRemaining !== undefined && (
|
||||
<span className={`px-2 py-0 rounded-full text-xs font-medium text-white ${getCertBg(monitor.certExpiryDaysRemaining)}`}
|
||||
title={monitor.certExpiryDaysRemaining < 0 ? 'Certificate expired!' : `Certificate expires in ${monitor.certExpiryDaysRemaining} days`}>
|
||||
{getCertText(monitor.certExpiryDaysRemaining)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Avg. (center-right, col-span-1) */}
|
||||
<div className="flex justify-center col-span-1">
|
||||
{monitor.avgPing !== undefined && (
|
||||
<span className={`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>
|
||||
)}
|
||||
</div>
|
||||
{/* 24h (right, col-span-1) */}
|
||||
<div className="flex justify-end col-span-1">
|
||||
{monitor.uptime24h !== undefined && (
|
||||
<span className={`px-2 py-0 rounded-full text-xs font-medium ${getUptime24hBg(monitor.uptime24h)}`}>
|
||||
24h: {monitor.uptime24h.toFixed(1)}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Second row: Heartbeat bar, always full width, evenly distributed squares */}
|
||||
{monitor.heartbeatHistory && monitor.heartbeatHistory.length > 0 && (
|
||||
<div className="flex items-center w-full">
|
||||
<div className="grid grid-cols-40 gap-x-0.5 bg-gray-200 dark:bg-slate-700 rounded px-2 py-1 w-full overflow-x-auto">
|
||||
{Array.from({ length: 40 }).map((_, i) => {
|
||||
const hbArr = monitor.heartbeatHistory ?? [];
|
||||
const hb = hbArr.slice(-40)[i];
|
||||
return (
|
||||
<span
|
||||
key={i}
|
||||
title={hb ? `Status: ${hb.status === 1 ? 'Up' : 'Down'}\nTime: ${hb.time}` : ''}
|
||||
className={`w-full h-4 rounded-sm ${hb ? getHeartbeatColor(hb.status) : 'bg-gray-400 dark:bg-gray-600'} block`}
|
||||
></span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center text-gray-500 dark:text-gray-400">No status data available</div>
|
||||
)}
|
||||
</div>
|
||||
<style>{`
|
||||
.grid-cols-40 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(40, minmax(0, 1fr));
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user