Spaces:
Sleeping
Sleeping
| import { useState, useEffect } from "react"; | |
| import { BACKEND_URL, APP_CONFIG } from "../constants"; | |
| interface BackendHealthCheckProps { | |
| checkInterval?: number; | |
| } | |
| export function BackendHealthCheck({ | |
| checkInterval = APP_CONFIG.HEALTH_CHECK_INTERVAL, | |
| }: BackendHealthCheckProps) { | |
| const [backendStatus, setBackendStatus] = useState< | |
| "loading" | "online" | "offline" | |
| >("loading"); | |
| const [showTooltip, setShowTooltip] = useState(false); | |
| const [copied, setCopied] = useState(false); | |
| const healthCheckUrl = `${BACKEND_URL}/api/health`; | |
| useEffect(() => { | |
| const checkBackendHealth = async () => { | |
| try { | |
| const response = await fetch(healthCheckUrl); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| if (data.status === "ok") { | |
| setBackendStatus("online"); | |
| } else { | |
| setBackendStatus("offline"); | |
| } | |
| } else { | |
| setBackendStatus("offline"); | |
| } | |
| } catch { | |
| setBackendStatus("offline"); | |
| } | |
| }; | |
| checkBackendHealth(); | |
| // Check health at specified interval | |
| const interval = setInterval(checkBackendHealth, checkInterval); | |
| return () => clearInterval(interval); | |
| }, [healthCheckUrl, checkInterval]); | |
| const handleCopyCode = async () => { | |
| const codeText = `cd backend/\nmake install\nmake backend`; | |
| try { | |
| await navigator.clipboard.writeText(codeText); | |
| setCopied(true); | |
| setTimeout(() => setCopied(false), 2000); | |
| } catch (err) { | |
| console.error("Failed to copy text: ", err); | |
| } | |
| }; | |
| const getStatusColor = () => { | |
| switch (backendStatus) { | |
| case "online": | |
| return "bg-green-500"; | |
| case "offline": | |
| return "bg-red-500"; | |
| case "loading": | |
| return "bg-yellow-500"; | |
| default: | |
| return "bg-gray-500"; | |
| } | |
| }; | |
| const getStatusText = () => { | |
| switch (backendStatus) { | |
| case "online": | |
| return "Backend Online"; | |
| case "offline": | |
| return "Backend Offline"; | |
| case "loading": | |
| return "Checking Backend..."; | |
| default: | |
| return "Unknown Status"; | |
| } | |
| }; | |
| return ( | |
| <div className="absolute top-4 right-4"> | |
| <div | |
| className="relative flex items-center space-x-2 bg-gray-800 rounded-lg px-3 py-2 cursor-pointer hover:bg-gray-700 transition-colors" | |
| onMouseEnter={() => backendStatus === "offline" && setShowTooltip(true)} | |
| onMouseLeave={() => setShowTooltip(false)} | |
| > | |
| <div | |
| className={`w-3 h-3 rounded-full ${getStatusColor()} animate-pulse`} | |
| ></div> | |
| <span className="text-sm font-medium">{getStatusText()}</span> | |
| {/* Info icon when backend is offline */} | |
| {backendStatus === "offline" && ( | |
| <svg | |
| className="w-5 h-5 text-red-400" | |
| fill="currentColor" | |
| viewBox="0 0 20 20" | |
| > | |
| <path | |
| fillRule="evenodd" | |
| d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" | |
| clipRule="evenodd" | |
| /> | |
| </svg> | |
| )} | |
| {/* Tooltip for offline backend */} | |
| {showTooltip && backendStatus === "offline" && ( | |
| <div | |
| className="absolute top-full right-0 mt-2 w-fit bg-gray-900 text-white rounded-lg p-4 shadow-xl border border-gray-700 z-50" | |
| onMouseEnter={() => setShowTooltip(true)} | |
| onMouseLeave={() => setShowTooltip(false)} | |
| > | |
| <div className="flex items-start space-x-3"> | |
| <div className="flex-1 min-w-0"> | |
| <h3 className="text-sm font-semibold text-400 mb-3 text-left"> | |
| Start the server with: | |
| </h3> | |
| <div className="relative"> | |
| <code className="text-xs bg-gray-800 text-green-400 px-3 py-2 rounded font-mono block w-fit overflow-x-auto text-left pr-12"> | |
| cd backend/ | |
| <br /> | |
| make install | |
| <br /> | |
| make backend | |
| </code> | |
| <button | |
| onClick={handleCopyCode} | |
| className="absolute top-1 right-1 p-1 bg-gray-700 hover:bg-gray-600 rounded text-gray-300 hover:text-white transition-colors" | |
| title="Copy to clipboard" | |
| > | |
| {copied ? ( | |
| <svg | |
| className="w-4 h-4 text-green-400" | |
| fill="currentColor" | |
| viewBox="0 0 20 20" | |
| > | |
| <path | |
| fillRule="evenodd" | |
| d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" | |
| clipRule="evenodd" | |
| /> | |
| </svg> | |
| ) : ( | |
| <svg | |
| className="w-4 h-4" | |
| fill="currentColor" | |
| viewBox="0 0 20 20" | |
| > | |
| <path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" /> | |
| <path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" /> | |
| </svg> | |
| )} | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Arrow pointing up */} | |
| <div className="absolute bottom-full right-4 w-0 h-0 border-l-4 border-r-4 border-b-4 border-transparent border-b-gray-900"></div> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |