mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 15:22:12 +03:00
8.1 — 403 Forbidden Page - New: src/app/forbidden/page.js - Gradient code + Access Denied message + Dashboard link - Consistent design with not-found.js 8.2 — Password Recovery Flow - New: src/app/forgot-password/page.js - Two methods: CLI reset + manual database reset - Added 'Forgot password?' link to login page 8.3 — Health/Status Dashboard - New: src/app/(dashboard)/dashboard/health/page.js - New: src/app/api/monitoring/health/route.js - Cards: Uptime, Version, Memory, Provider count - Provider health (circuit breaker states with color coding) - Rate limit status table - Active lockouts list - Auto-refresh every 15s - Added Health nav link to Sidebar 8.4 — Maintenance Banner - New: src/shared/components/MaintenanceBanner.js - Auto-detects server health issues every 10s - Shows/hides automatically, dismissible - Wired into DashboardLayout 8.5 — Empty States - Verified EmptyState component applied in 6+ pages - Providers page has its own empty handling Bonus: - Fixed stale GitHub link in Sidebar (decolua → diegosouzapw) Tests: 295 pass | Build: success
64 lines
1.9 KiB
JavaScript
64 lines
1.9 KiB
JavaScript
"use client";
|
|
|
|
/**
|
|
* Maintenance Banner — Phase 8.4
|
|
*
|
|
* Shows a warning banner at the top of the dashboard when the server
|
|
* is restarting or in maintenance mode. Auto-dismisses when the server
|
|
* comes back online.
|
|
*/
|
|
|
|
import { useState, useEffect, useCallback } from "react";
|
|
|
|
export default function MaintenanceBanner() {
|
|
const [show, setShow] = useState(false);
|
|
const [message, setMessage] = useState("");
|
|
|
|
const checkHealth = useCallback(async () => {
|
|
try {
|
|
const res = await fetch("/api/monitoring/health", {
|
|
signal: AbortSignal.timeout(3000),
|
|
});
|
|
if (res.ok) {
|
|
// Server is healthy — hide banner if shown
|
|
if (show) {
|
|
setShow(false);
|
|
setMessage("");
|
|
}
|
|
} else {
|
|
setShow(true);
|
|
setMessage("Server is experiencing issues. Some features may be unavailable.");
|
|
}
|
|
} catch {
|
|
setShow(true);
|
|
setMessage("Server is unreachable. Reconnecting...");
|
|
}
|
|
}, [show]);
|
|
|
|
useEffect(() => {
|
|
// Check health every 10 seconds
|
|
const interval = setInterval(checkHealth, 10000);
|
|
return () => clearInterval(interval);
|
|
}, [checkHealth]);
|
|
|
|
if (!show) return null;
|
|
|
|
return (
|
|
<div className="bg-amber-500/10 border-b border-amber-500/20 px-4 py-2.5 flex items-center justify-between gap-3 animate-in slide-in-from-top">
|
|
<div className="flex items-center gap-2.5">
|
|
<span className="material-symbols-outlined text-amber-500 text-[18px] animate-pulse">
|
|
warning
|
|
</span>
|
|
<span className="text-sm text-amber-200">{message}</span>
|
|
</div>
|
|
<button
|
|
onClick={() => setShow(false)}
|
|
className="p-1 rounded hover:bg-white/5 text-text-muted hover:text-text-main transition-colors"
|
|
aria-label="Dismiss"
|
|
>
|
|
<span className="material-symbols-outlined text-[16px]">close</span>
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|