feat: add system status monitoring with real-time updates and error handling

This commit is contained in:
2026-01-29 13:39:17 +02:00
parent 51d5272aa8
commit d1162a5f9d

View File

@@ -1,6 +1,6 @@
"use client";
import React, { useEffect, useState, useCallback } from "react";
import React, { useEffect, useState, useCallback, useMemo } from "react";
import { RefreshCw, Settings, Radio, BarChart3, X } from "lucide-react";
import { useToast } from "@/components/ui/use-toast";
import {
@@ -40,6 +40,71 @@ export function OverviewTab({
const { refresh: refreshStats } = useAdminStats();
const [isRefreshing, setIsRefreshing] = useState(false);
const [systemStatus, setSystemStatus] = useState<Record<string, { state: "checking" | "ok" | "degraded" | "error"; label: string }>>({
website: { state: "checking", label: "Checking..." },
rsvp: { state: "checking", label: "Checking..." },
photos: { state: "checking", label: "Checking..." },
music: { state: "checking", label: "Checking..." },
guestbook: { state: "checking", label: "Checking..." },
gate: { state: "checking", label: "Checking..." },
});
const statusConfig = useMemo(() => ([
{ id: "website", label: "Website", endpoint: "/api/health", okLabel: "Online" },
{ id: "rsvp", label: "RSVP System", endpoint: "/api/admin/rsvp", okLabel: "Active" },
{ id: "photos", label: "Photo Upload", endpoint: "/api/admin/gallery", okLabel: "Operational" },
{ id: "music", label: "Music Requests", endpoint: "/api/admin/music", okLabel: "Operational" },
{ id: "guestbook", label: "Guestbook", endpoint: "/api/admin/guestbook", okLabel: "Operational" },
{ id: "gate", label: "Security Gate", endpoint: "/api/admin/security-gate/secret", okLabel: "Online" },
] as const), []);
const checkSystemStatus = useCallback(async (setChecking = false) => {
if (setChecking) {
setSystemStatus((prev) => {
const next = { ...prev };
statusConfig.forEach((item) => {
next[item.id] = { state: "checking", label: "Checking..." };
});
return next;
});
}
await Promise.all(
statusConfig.map(async (item) => {
try {
const response = await fetch(item.endpoint, {
method: "GET",
credentials: "include",
cache: "no-store",
});
if (response.ok) {
setSystemStatus((prev) => ({
...prev,
[item.id]: { state: "ok", label: item.okLabel },
}));
return;
}
const label = response.status === 401
? "Auth required"
: response.status >= 500
? "Service error"
: `Degraded (${response.status})`;
setSystemStatus((prev) => ({
...prev,
[item.id]: { state: response.status >= 500 ? "error" : "degraded", label },
}));
} catch (error) {
setSystemStatus((prev) => ({
...prev,
[item.id]: { state: "error", label: "Offline" },
}));
}
})
);
}, [statusConfig]);
const handleRefresh = useCallback(async () => {
setIsRefreshing(true);
@@ -48,11 +113,12 @@ export function OverviewTab({
fetchRecentActivity(),
fetchMusic(),
refreshStats(),
checkSystemStatus(true),
]);
} finally {
setIsRefreshing(false);
}
}, [fetchRecentActivity, fetchMusic, refreshStats]);
}, [fetchRecentActivity, fetchMusic, refreshStats, checkSystemStatus]);
useEffect(() => {
const interval = setInterval(() => {
@@ -62,6 +128,15 @@ export function OverviewTab({
return () => clearInterval(interval);
}, [fetchRecentActivity]);
useEffect(() => {
checkSystemStatus(true);
const interval = setInterval(() => {
checkSystemStatus();
}, 30000);
return () => clearInterval(interval);
}, [checkSystemStatus]);
useAdminRealtime({
onRSVPUpdate: () => handleRefresh(),
onGuestbookUpdate: () => fetchRecentActivity(),
@@ -216,27 +291,35 @@ export function OverviewTab({
<CardTitle className="font-heading">System Status</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm font-body">Website</span>
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="text-xs text-green-600">Online</span>
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-body">RSVP System</span>
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="text-xs text-green-600">Active</span>
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-body">Photo Upload</span>
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="text-xs text-green-600">Working</span>
</div>
</div>
{statusConfig.map((item) => {
const status = systemStatus[item.id] || { state: "checking", label: "Checking..." };
const dotClass =
status.state === "ok"
? "bg-green-500"
: status.state === "degraded"
? "bg-amber-500"
: status.state === "error"
? "bg-red-500"
: "bg-gray-400";
const textClass =
status.state === "ok"
? "text-green-600"
: status.state === "degraded"
? "text-amber-600"
: status.state === "error"
? "text-red-600"
: "text-gray-500";
return (
<div key={item.id} className="flex items-center justify-between">
<span className="text-sm font-body">{item.label}</span>
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${dotClass}`}></div>
<span className={`text-xs ${textClass}`}>{status.label}</span>
</div>
</div>
);
})}
</CardContent>
</Card>