Files
Admin_dash/components/widgets/UptimeCard.tsx

91 lines
3.9 KiB
TypeScript
Raw Normal View History

2026-02-08 02:32:45 -05:00
'use client';
import { Activity, RefreshCcw, AlertCircle } from 'lucide-react';
import { useState, useEffect } from 'react';
interface ServiceStatus {
name: string;
url: string;
status: 'up' | 'down';
latency: number;
}
export function UptimeCard() {
const [services, setServices] = useState<ServiceStatus[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const fetchStatus = async () => {
setLoading(true);
try {
const res = await fetch('/api/status');
if (!res.ok) throw new Error('Failed to fetch');
const data = await res.json();
setServices(data);
setError(false);
} catch (e) {
console.error(e);
setError(true);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchStatus();
// Poll every 30 seconds
const interval = setInterval(fetchStatus, 30000);
return () => clearInterval(interval);
}, []);
// Calculate overall health
const upCount = services.filter(s => s.status === 'up').length;
const healthPercentage = services.length > 0 ? Math.round((upCount / services.length) * 100) : 0;
return (
<div className="col-span-1 md:col-span-2 lg:col-span-2 row-span-1 bg-neutral-900 border border-neutral-800 rounded-xl p-6 flex flex-col justify-between hover:border-neutral-700 transition-colors">
<div className="flex justify-between items-start">
<div className="flex items-center gap-2 text-neutral-400">
<Activity size={18} />
<span className="text-sm font-medium">Uptime Monitor</span>
{loading && <RefreshCcw size={12} className="animate-spin ml-2 opacity-50" />}
</div>
{!loading && !error && (
<span className={`text-xs px-2 py-1 rounded-full font-mono ${healthPercentage === 100 ? 'bg-emerald-500/10 text-emerald-500' : 'bg-red-500/10 text-red-500'}`}>
{healthPercentage}%
</span>
)}
</div>
{error ? (
<div className="flex flex-col items-center justify-center h-full text-red-400 gap-2">
<AlertCircle size={24} />
<span className="text-xs">Failed to load status</span>
</div>
) : (
<div className="space-y-3 mt-4">
{services.length === 0 && loading ? (
// Skeleton loader
[1, 2, 3].map(i => (
<div key={i} className="flex items-center justify-between animate-pulse">
<div className="h-4 w-20 bg-neutral-800 rounded"></div>
<div className="h-2 w-2 bg-neutral-800 rounded-full"></div>
</div>
))
) : (
services.map((service) => (
<div key={service.name} className="flex items-center justify-between group">
<span className="text-sm text-neutral-300 font-medium group-hover:text-white transition-colors">{service.name}</span>
<div className="flex items-center gap-2">
<span className="text-xs text-neutral-500 font-mono">{service.status === 'up' ? `${service.latency}ms` : 'DOWN'}</span>
<div className={`w-2 h-2 rounded-full shadow-[0_0_8px_rgba(0,0,0,0.4)] ${service.status === 'up' ? 'bg-emerald-500 shadow-emerald-500/40' : 'bg-red-500 shadow-red-500/40'}`} />
</div>
</div>
))
)}
</div>
)}
</div>
);
}