|
import { useState, useEffect } from "react"; |
|
import { BACKEND_URL, APP_CONFIG } from "../config/constants"; |
|
|
|
interface BackendHealthCheckProps { |
|
backendUrl?: string; |
|
checkInterval?: number; |
|
} |
|
|
|
export function BackendHealthCheck({ |
|
checkInterval = APP_CONFIG.HEALTH_CHECK_INTERVAL |
|
}: BackendHealthCheckProps) { |
|
|
|
if (import.meta.env.PROD) { |
|
return null; |
|
} |
|
|
|
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 (error) { |
|
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> |
|
); |
|
} |