|
|
@@ -89,6 +89,10 @@ export function McpServerCard({ mcpType, config, onLoginSuccess, onLogoutSuccess
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
const [error, setError] = useState('');
|
|
|
const [updateTrigger, setUpdateTrigger] = useState(0);
|
|
|
+ // 新增:健康检查状态
|
|
|
+ const [isHealthy, setIsHealthy] = useState<boolean | null>(null); // null=未检查, true=健康, false=不健康
|
|
|
+ const [isCheckingHealth, setIsCheckingHealth] = useState(false);
|
|
|
+ const [latency, setLatency] = useState<number | null>(null);
|
|
|
|
|
|
// 监听 localStorage 变化和组件挂载
|
|
|
useEffect(() => {
|
|
|
@@ -106,6 +110,24 @@ export function McpServerCard({ mcpType, config, onLoginSuccess, onLogoutSuccess
|
|
|
|
|
|
checkLoginStatus();
|
|
|
|
|
|
+ // 检查服务器健康状态
|
|
|
+ const checkServerHealth = async () => {
|
|
|
+ setIsCheckingHealth(true);
|
|
|
+ try {
|
|
|
+ const result = await mcpTokenManager.checkHealth(mcpType);
|
|
|
+ setIsHealthy(result.healthy);
|
|
|
+ setLatency(result.latency ?? null);
|
|
|
+ console.log(`[McpServerCard ${mcpType}] Health check:`, result);
|
|
|
+ } catch (e) {
|
|
|
+ console.error(`[McpServerCard ${mcpType}] Health check error:`, e);
|
|
|
+ setIsHealthy(false);
|
|
|
+ } finally {
|
|
|
+ setIsCheckingHealth(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ checkServerHealth();
|
|
|
+
|
|
|
// 监听 storage 事件(其他标签页的更改)
|
|
|
const handleStorageChange = (e: StorageEvent) => {
|
|
|
if (e.key?.startsWith('mcp_token_') || e.key?.startsWith('mcp_username_')) {
|
|
|
@@ -115,7 +137,14 @@ export function McpServerCard({ mcpType, config, onLoginSuccess, onLogoutSuccess
|
|
|
};
|
|
|
|
|
|
window.addEventListener('storage', handleStorageChange);
|
|
|
- return () => window.removeEventListener('storage', handleStorageChange);
|
|
|
+
|
|
|
+ // 定期重新检查健康状态(每 30 秒)
|
|
|
+ const healthInterval = setInterval(checkServerHealth, 30000);
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ window.removeEventListener('storage', handleStorageChange);
|
|
|
+ clearInterval(healthInterval);
|
|
|
+ };
|
|
|
}, [mcpType]);
|
|
|
|
|
|
const isLoggedIn = mcpTokenManager.isLoggedIn(mcpType);
|
|
|
@@ -123,6 +152,24 @@ export function McpServerCard({ mcpType, config, onLoginSuccess, onLogoutSuccess
|
|
|
const remainingTime = mcpTokenManager.getTokenRemainingTime(mcpType);
|
|
|
const hoursRemaining = Math.floor(remainingTime / (1000 * 60 * 60));
|
|
|
|
|
|
+ // 计算连接状态显示
|
|
|
+ const getConnectionStatus = () => {
|
|
|
+ if (isHealthy === null) {
|
|
|
+ return { text: '检查中...', className: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400', dotColor: 'bg-gray-400' };
|
|
|
+ }
|
|
|
+ if (isHealthy) {
|
|
|
+ if (isLoggedIn) {
|
|
|
+ return { text: '已连接', className: 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200', dotColor: 'bg-green-500' };
|
|
|
+ } else {
|
|
|
+ return { text: '在线(未登录)', className: 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200', dotColor: 'bg-blue-500' };
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return { text: '离线', className: 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200', dotColor: 'bg-red-500' };
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const connectionStatus = getConnectionStatus();
|
|
|
+
|
|
|
const handleLogin = async (e: React.FormEvent) => {
|
|
|
e.preventDefault();
|
|
|
setIsLoading(true);
|
|
|
@@ -173,15 +220,16 @@ export function McpServerCard({ mcpType, config, onLoginSuccess, onLogoutSuccess
|
|
|
</h3>
|
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
|
|
{config.authType === 'none' ? '无需认证' : '需要登录'}
|
|
|
+ {latency !== null && isHealthy && (
|
|
|
+ <span className="ml-2 text-xs text-gray-400">
|
|
|
+ ({latency}ms)
|
|
|
+ </span>
|
|
|
+ )}
|
|
|
</p>
|
|
|
</div>
|
|
|
- <div className={`flex items-center gap-2 px-3 py-1 rounded-full text-sm ${
|
|
|
- isLoggedIn
|
|
|
- ? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200'
|
|
|
- : 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400'
|
|
|
- }`}>
|
|
|
- <span className={`w-2 h-2 rounded-full ${isLoggedIn ? 'bg-green-500' : 'bg-gray-400'}`} />
|
|
|
- {isLoggedIn ? '已连接' : '未连接'}
|
|
|
+ <div className={`flex items-center gap-2 px-3 py-1 rounded-full text-sm ${connectionStatus.className}`}>
|
|
|
+ <span className={`w-2 h-2 rounded-full ${connectionStatus.dotColor} ${isCheckingHealth ? 'animate-pulse' : ''}`} />
|
|
|
+ {connectionStatus.text}
|
|
|
</div>
|
|
|
</div>
|
|
|
|