feat: add system status monitoring with real-time updates and error handling
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user