| | 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(); |
| |
|
| | |
| | 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> |
| | ); |
| | } |
| |
|