|
|
@@ -43,26 +43,26 @@ function DevTestAccounts({ mcpType }: { mcpType: string }) {
|
|
|
🛠️ 开发环境测试账号
|
|
|
</p>
|
|
|
<div className="space-y-2 text-xs">
|
|
|
- <div className="flex items-center justify-between gap-2">
|
|
|
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
|
|
<span className="text-gray-700 dark:text-gray-300">
|
|
|
邮箱: <span className="font-mono font-medium">{account.email}</span>
|
|
|
</span>
|
|
|
<button
|
|
|
type="button"
|
|
|
onClick={() => copyToClipboard(account.email, 'email')}
|
|
|
- className="px-2 py-1 bg-amber-100 dark:bg-amber-800 text-amber-700 dark:text-amber-200 rounded hover:bg-amber-200 dark:hover:bg-amber-700 transition-colors whitespace-nowrap"
|
|
|
+ className="px-3 py-2 sm:px-2 sm:py-1 bg-amber-100 dark:bg-amber-800 text-amber-700 dark:text-amber-200 rounded hover:bg-amber-200 dark:hover:bg-amber-700 transition-colors whitespace-nowrap text-center min-h-[44px] sm:min-h-0"
|
|
|
>
|
|
|
{copied === 'email' ? '已复制 ✓' : '复制邮箱'}
|
|
|
</button>
|
|
|
</div>
|
|
|
- <div className="flex items-center justify-between gap-2">
|
|
|
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
|
|
<span className="text-gray-700 dark:text-gray-300">
|
|
|
密码: <span className="font-mono font-medium">••••••••••••</span>
|
|
|
</span>
|
|
|
<button
|
|
|
type="button"
|
|
|
onClick={() => copyToClipboard(account.password, 'password')}
|
|
|
- className="px-2 py-1 bg-amber-100 dark:bg-amber-800 text-amber-700 dark:text-amber-200 rounded hover:bg-amber-200 dark:hover:bg-amber-700 transition-colors whitespace-nowrap"
|
|
|
+ className="px-3 py-2 sm:px-2 sm:py-1 bg-amber-100 dark:bg-amber-800 text-amber-700 dark:text-amber-200 rounded hover:bg-amber-200 dark:hover:bg-amber-700 transition-colors whitespace-nowrap text-center min-h-[44px] sm:min-h-0"
|
|
|
>
|
|
|
{copied === 'password' ? '已复制 ✓' : '复制密码'}
|
|
|
</button>
|
|
|
@@ -89,15 +89,24 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
const [error, setError] = useState('');
|
|
|
const [updateTrigger, setUpdateTrigger] = useState(0);
|
|
|
+ // 新增:客户端挂载标志
|
|
|
+ const [mounted, setMounted] = useState(false);
|
|
|
// 新增:健康检查状态
|
|
|
const [isHealthy, setIsHealthy] = useState<boolean | null>(null); // null=未检查, true=健康, false=不健康
|
|
|
const [isCheckingHealth, setIsCheckingHealth] = useState(false);
|
|
|
const [latency, setLatency] = useState<number | null>(null);
|
|
|
- // 新增:启用/禁用状态
|
|
|
- const [isEnabled, setIsEnabled] = useState(() => mcpTokenManager.isEnabled(mcpType));
|
|
|
+ // 新增:启用/禁用状态 - 初始值设为 true 避免闪烁,useEffect 中会更新真实值
|
|
|
+ const [isEnabled, setIsEnabled] = useState(true);
|
|
|
|
|
|
// 监听 localStorage 变化和组件挂载
|
|
|
useEffect(() => {
|
|
|
+ // 设置客户端挂载标志
|
|
|
+ setMounted(true);
|
|
|
+
|
|
|
+ // 初始化启用状态 - 从 localStorage 读取真实值
|
|
|
+ const realEnabledState = mcpTokenManager.isEnabled(mcpType);
|
|
|
+ setIsEnabled(realEnabledState);
|
|
|
+
|
|
|
// 初始化时检查登录状态
|
|
|
const checkLoginStatus = () => {
|
|
|
const token = localStorage.getItem(`mcp_token_${mcpType}`);
|
|
|
@@ -182,10 +191,15 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
|
|
|
}, [isHealthy, isLoggedIn, mcpType, onConnectionStatusChange]);
|
|
|
|
|
|
// 计算连接状态显示
|
|
|
+ // - 禁用 → 灰色圆点
|
|
|
// - 健康 + 已登录 → 已连接 ✓ | 已登录 ✓
|
|
|
// - 健康 + 未登录 → 已连接 ✓ | 未登录
|
|
|
// - 不健康 → 离线
|
|
|
const getConnectionStatus = () => {
|
|
|
+ // 禁用状态优先,显示灰色
|
|
|
+ if (!isEnabled) {
|
|
|
+ return { text: '已禁用', className: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400', dotColor: 'bg-gray-400' };
|
|
|
+ }
|
|
|
if (isHealthy === null) {
|
|
|
return { text: '检查中...', className: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400', dotColor: 'bg-gray-400' };
|
|
|
}
|
|
|
@@ -247,48 +261,60 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
- <div className={`bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 border dark:border-gray-700 transition-all ${!isEnabled ? 'opacity-50 grayscale' : ''}`}>
|
|
|
- <div className="flex items-start justify-between mb-4">
|
|
|
- <div className="flex-1">
|
|
|
- <div className="flex items-center gap-3">
|
|
|
- <h3 className="text-lg font-semibold text-gray-800 dark:text-white">
|
|
|
+ <div
|
|
|
+ className={`bg-white dark:bg-gray-800 rounded-lg shadow-md p-4 md:p-6 border dark:border-gray-700 transition-all ${!isEnabled ? 'opacity-50 grayscale' : ''}`}
|
|
|
+ suppressHydrationWarning
|
|
|
+ >
|
|
|
+ {/* 头部区域 - 移动端优化 */}
|
|
|
+ <div className="flex flex-col gap-3 mb-4">
|
|
|
+ {/* 第一行:名称 + 启用/禁用按钮 */}
|
|
|
+ <div className="flex items-start justify-between gap-2">
|
|
|
+ <div className="flex-1 min-w-0">
|
|
|
+ <h3 className="text-base md:text-lg font-semibold text-gray-800 dark:text-white truncate">
|
|
|
{config.name}
|
|
|
</h3>
|
|
|
- {/* 启用/禁用开关 */}
|
|
|
- <button
|
|
|
- type="button"
|
|
|
- onClick={handleToggleEnabled}
|
|
|
- className={`flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium transition-all ${
|
|
|
- isEnabled
|
|
|
- ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 hover:bg-green-200 dark:hover:bg-green-800'
|
|
|
- : 'bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-600'
|
|
|
- }`}
|
|
|
- title={isEnabled ? '点击禁用此 MCP 服务器' : '点击启用此 MCP 服务器'}
|
|
|
- >
|
|
|
- <span className={`w-1.5 h-1.5 rounded-full ${isEnabled ? 'bg-green-500' : 'bg-gray-400'}`} />
|
|
|
- {isEnabled ? '禁用' : '启用'}
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- <p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
|
|
- {config.authType === 'none' ? '无需认证' : '需要登录'}
|
|
|
- {latency !== null && isHealthy && isEnabled && (
|
|
|
- <span className="ml-2 text-xs text-gray-400">
|
|
|
- ({latency}ms)
|
|
|
- </span>
|
|
|
- )}
|
|
|
- </p>
|
|
|
- {config.description && (
|
|
|
- <p className="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
|
- {config.description}
|
|
|
+ <p className="text-xs md:text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
|
|
+ {config.authType === 'none' ? '无需认证' : '需要登录'}
|
|
|
+ {latency !== null && isHealthy && isEnabled && (
|
|
|
+ <span className="ml-2 text-xs text-gray-400">
|
|
|
+ ({latency}ms)
|
|
|
+ </span>
|
|
|
+ )}
|
|
|
</p>
|
|
|
- )}
|
|
|
+ </div>
|
|
|
+ {/* 启用/禁用按钮 - 移动端加大点击区域 */}
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ onClick={handleToggleEnabled}
|
|
|
+ className={`flex items-center gap-1.5 px-3 py-2 md:px-2.5 md:py-1 rounded-full text-xs md:text-sm font-medium transition-all min-h-[44px] md:min-h-0 ${
|
|
|
+ isEnabled
|
|
|
+ ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 hover:bg-green-200 dark:hover:bg-green-800'
|
|
|
+ : 'bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-600'
|
|
|
+ }`}
|
|
|
+ title={isEnabled ? '点击禁用此 MCP 服务器' : '点击启用此 MCP 服务器'}
|
|
|
+ >
|
|
|
+ <span className={`w-2 h-2 rounded-full ${isEnabled ? 'bg-green-500' : 'bg-gray-400'}`} />
|
|
|
+ {isEnabled ? '禁用' : '启用'}
|
|
|
+ </button>
|
|
|
</div>
|
|
|
- <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 className="flex items-center">
|
|
|
+ <div className={`flex items-center gap-2 px-3 py-1.5 md:py-1 rounded-full text-xs md:text-sm ${connectionStatus.className}`}>
|
|
|
+ <span className={`w-2 h-2 rounded-full ${connectionStatus.dotColor} ${isCheckingHealth ? 'animate-pulse' : ''}`} />
|
|
|
+ {connectionStatus.text}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+
|
|
|
+ {/* 描述(如果有) */}
|
|
|
+ {config.description && (
|
|
|
+ <p className="text-xs text-gray-400 dark:text-gray-500">
|
|
|
+ {config.description}
|
|
|
+ </p>
|
|
|
+ )}
|
|
|
</div>
|
|
|
|
|
|
+ {/* 已登录状态显示 - 移动端优化 */}
|
|
|
{isLoggedIn && (
|
|
|
<div className="mb-4 p-3 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
|
|
<p className="text-sm text-green-800 dark:text-green-200">
|
|
|
@@ -300,9 +326,10 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
|
+ {/* 禁用提示 - 移动端优化 */}
|
|
|
{!isEnabled && (
|
|
|
<div className="mb-4 p-3 bg-gray-100 dark:bg-gray-700/50 rounded-lg border border-gray-200 dark:border-gray-600">
|
|
|
- <p className="text-sm text-gray-600 dark:text-gray-400">
|
|
|
+ <p className="text-xs md:text-sm text-gray-600 dark:text-gray-400">
|
|
|
此 MCP 服务器已禁用。点击右上角的"启用"按钮来启用它。
|
|
|
</p>
|
|
|
</div>
|
|
|
@@ -343,7 +370,7 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
|
|
|
<button
|
|
|
type="submit"
|
|
|
disabled={isLoading}
|
|
|
- className="flex-1 py-2 px-4 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"
|
|
|
+ className="flex-1 py-3 md:py-2 px-4 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors text-sm min-h-[44px] md:min-h-0"
|
|
|
>
|
|
|
{isLoading ? '登录中...' : '登录'}
|
|
|
</button>
|
|
|
@@ -355,7 +382,7 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
|
|
|
setEmail('');
|
|
|
setPassword('');
|
|
|
}}
|
|
|
- className="py-2 px-4 border dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
|
|
+ className="py-3 md:py-2 px-4 border dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-sm min-h-[44px] md:min-h-0"
|
|
|
>
|
|
|
取消
|
|
|
</button>
|
|
|
@@ -400,7 +427,7 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
|
|
|
<div className="flex gap-2">
|
|
|
<button
|
|
|
onClick={handleSetToken}
|
|
|
- className="flex-1 py-2 px-4 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors"
|
|
|
+ className="flex-1 py-3 md:py-2 px-4 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors text-sm min-h-[44px] md:min-h-0"
|
|
|
>
|
|
|
设置 Token
|
|
|
</button>
|
|
|
@@ -411,7 +438,7 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
|
|
|
setManualToken('');
|
|
|
setManualUsername('');
|
|
|
}}
|
|
|
- className="py-2 px-4 border dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
|
|
+ className="py-3 md:py-2 px-4 border dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-sm min-h-[44px] md:min-h-0"
|
|
|
>
|
|
|
取消
|
|
|
</button>
|
|
|
@@ -419,24 +446,27 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
|
- <div className="flex items-center justify-between">
|
|
|
- <p className="text-xs text-gray-500 dark:text-gray-400">
|
|
|
+ {/* 底部操作区 - 移动端优化 */}
|
|
|
+ <div className="flex flex-col gap-3 pt-3 border-t dark:border-gray-700">
|
|
|
+ {/* URL 显示 - 移动端截断 */}
|
|
|
+ <p className="text-xs text-gray-400 dark:text-gray-500 truncate max-w-full overflow-hidden" title={config.url}>
|
|
|
{config.url}
|
|
|
</p>
|
|
|
+ {/* 操作按钮 - 移动端加大点击区域,按钮靠左对齐 */}
|
|
|
{config.authType === 'jwt' && isEnabled && (
|
|
|
- <div className="flex gap-2">
|
|
|
+ <div className="flex gap-3 md:gap-2">
|
|
|
{!isLoggedIn ? (
|
|
|
!showLoginForm && !showTokenForm && (
|
|
|
<>
|
|
|
<button
|
|
|
onClick={() => setShowLoginForm(true)}
|
|
|
- className="text-sm text-blue-500 hover:text-blue-600 font-medium"
|
|
|
+ className="text-sm text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 font-medium px-4 py-2.5 md:px-0 md:py-0 rounded-lg md:rounded-none bg-blue-50 dark:bg-blue-900/30 md:bg-transparent min-h-[44px] md:min-h-0 md:inline"
|
|
|
>
|
|
|
登录
|
|
|
</button>
|
|
|
<button
|
|
|
onClick={() => setShowTokenForm(true)}
|
|
|
- className="text-sm text-green-500 hover:text-green-600 font-medium"
|
|
|
+ className="text-sm text-green-600 hover:text-green-700 dark:text-green-400 dark:hover:text-green-300 font-medium px-4 py-2.5 md:px-0 md:py-0 rounded-lg md:rounded-none bg-green-50 dark:bg-green-900/30 md:bg-transparent min-h-[44px] md:min-h-0 md:inline"
|
|
|
>
|
|
|
设置 Token
|
|
|
</button>
|
|
|
@@ -445,7 +475,7 @@ export function McpServerCard({ mcpType, config, onConnectionStatusChange, onEna
|
|
|
) : (
|
|
|
<button
|
|
|
onClick={handleLogout}
|
|
|
- className="text-sm text-red-500 hover:text-red-600 font-medium"
|
|
|
+ className="text-sm text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-300 font-medium px-4 py-2.5 md:px-0 md:py-0 rounded-lg md:rounded-none bg-red-50 dark:bg-red-900/30 md:bg-transparent min-h-[44px] md:min-h-0 md:inline"
|
|
|
>
|
|
|
登出
|
|
|
</button>
|