Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | 1x 24x 24x 24x 24x 5x 5x 5x 100x 100x 100x 100x 100x 5x 5x 5x 24x 4x 4x 4x 4x 4x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 24x 24x 24x 24x 24x 24x 171x 171x 171x 171x 171x 171x 171x 165x 6x 171x 171x 171x 24x 24x 24x 24x 24x 24x 24x 24x | import { useMonitorPings } from "../../hooks/usePings";
interface UptimeChartProps {
monitorId: string;
}
export default function UptimeChart({ monitorId }: UptimeChartProps) {
// Use a high limit for the chart to show meaningful bars, paginated list handles display
const { data: pingsResponse, isLoading } = useMonitorPings(monitorId, 0, 50);
const pings = pingsResponse?.items ?? [];
if (isLoading) {
return (
<div className="h-8 flex items-end gap-1 animate-pulse">
{Array.from({ length: 20 }).map((_, i) => (
<div
key={i}
className="flex-1 bg-gray-200 rounded-sm"
style={{ height: "60%" }}
/>
))}
</div>
);
}
if (!pings || pings.length === 0) {
return (
<div className="rounded-lg border border-gray-200 p-6 text-center">
<p className="text-sm text-gray-500">No ping data available yet.</p>
</div>
);
}
const recentChecks = [...pings].reverse();
const uptimePercent = Math.round(
(recentChecks.filter((d) => d.is_up).length / recentChecks.length) * 100,
);
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-700">
Last {recentChecks.length} Checks
</span>
<span
className={`text-sm font-semibold ${uptimePercent >= 99 ? "text-emerald-600" : uptimePercent >= 95 ? "text-yellow-600" : "text-red-600"}`}
>
{uptimePercent}% uptime
</span>
</div>
<div className="flex gap-0.5 h-10 items-end">
{recentChecks.map((check, i) => (
<div
key={i}
className={`flex-1 rounded-[1px] transition-all hover:opacity-80 ${
check.is_up ? "bg-emerald-400" : "bg-red-400"
}`}
style={{
height: check.response_ms
? `${Math.min((check.response_ms / 1000) * 100, 100)}%`
: "100%",
}}
title={`${check.is_up ? "UP" : "DOWN"} — ${check.response_ms ?? "N/A"}ms — ${new Date(check.timestamp).toLocaleString()}`}
/>
))}
</div>
<div className="flex justify-between text-xs text-gray-400">
<span>{recentChecks.length} checks</span>
<span>{recentChecks.filter((c) => c.is_up).length} passed</span>
</div>
</div>
);
}
|