style:1.重构整个项目的主题颜色,样式风格采用中国风元素 2.优化更新日志逻辑,不再间隔1h自动刷新过于频繁触发403响应
This commit is contained in:
+281
-255
@@ -17,7 +17,8 @@ import {
|
||||
Empty,
|
||||
Alert,
|
||||
Descriptions,
|
||||
Layout,
|
||||
Row,
|
||||
Col,
|
||||
} from 'antd';
|
||||
import {
|
||||
PlusOutlined,
|
||||
@@ -35,7 +36,6 @@ import type { MCPPlugin, MCPTool } from '../types';
|
||||
|
||||
const { Paragraph, Text, Title } = Typography;
|
||||
const { TextArea } = Input;
|
||||
const { Header, Content } = Layout;
|
||||
|
||||
export default function MCPPluginsPage() {
|
||||
const navigate = useNavigate();
|
||||
@@ -85,7 +85,7 @@ export default function MCPPluginsPage() {
|
||||
|
||||
const handleEdit = (plugin: MCPPlugin) => {
|
||||
setEditingPlugin(plugin);
|
||||
|
||||
|
||||
// 重构为标准MCP配置格式
|
||||
const mcpConfig: any = {
|
||||
mcpServers: {
|
||||
@@ -94,7 +94,7 @@ export default function MCPPluginsPage() {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (plugin.plugin_type === 'http') {
|
||||
mcpConfig.mcpServers[plugin.plugin_name].url = plugin.server_url;
|
||||
mcpConfig.mcpServers[plugin.plugin_name].headers = plugin.headers || {};
|
||||
@@ -103,7 +103,7 @@ export default function MCPPluginsPage() {
|
||||
mcpConfig.mcpServers[plugin.plugin_name].args = plugin.args || [];
|
||||
mcpConfig.mcpServers[plugin.plugin_name].env = plugin.env || {};
|
||||
}
|
||||
|
||||
|
||||
form.setFieldsValue({
|
||||
config_json: JSON.stringify(mcpConfig, null, 2),
|
||||
enabled: plugin.enabled,
|
||||
@@ -145,10 +145,10 @@ export default function MCPPluginsPage() {
|
||||
setTestingPluginId(pluginId);
|
||||
try {
|
||||
const result = await mcpPluginApi.testPlugin(pluginId);
|
||||
|
||||
|
||||
// 测试完成后,无论成功失败都刷新插件列表以更新状态
|
||||
await loadPlugins();
|
||||
|
||||
|
||||
if (result.success) {
|
||||
Modal.success({
|
||||
title: '✅ 测试成功',
|
||||
@@ -156,7 +156,7 @@ export default function MCPPluginsPage() {
|
||||
content: (
|
||||
<div>
|
||||
<p style={{ marginBottom: 16, fontSize: '15px', fontWeight: 500 }}>{result.message}</p>
|
||||
|
||||
|
||||
{/* 显示详细的测试结果 */}
|
||||
{result.suggestions && result.suggestions.length > 0 && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
@@ -184,21 +184,21 @@ export default function MCPPluginsPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* 显示工具数量 */}
|
||||
{result.tools_count !== undefined && (
|
||||
<div style={{ marginTop: 12 }}>
|
||||
<Text type="secondary">🔧 可用工具数: <strong>{result.tools_count}</strong></Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* 显示响应时间 */}
|
||||
{result.response_time_ms !== undefined && (
|
||||
<div style={{ marginTop: 4 }}>
|
||||
<Text type="secondary">⏱️ 响应时间: <strong>{result.response_time_ms}ms</strong></Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<div style={{
|
||||
marginTop: 16,
|
||||
padding: 12,
|
||||
@@ -220,7 +220,7 @@ export default function MCPPluginsPage() {
|
||||
content: (
|
||||
<div>
|
||||
<p style={{ marginBottom: 16, fontSize: '15px', fontWeight: 500 }}>{result.message}</p>
|
||||
|
||||
|
||||
{/* 显示错误信息 */}
|
||||
{result.error && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
@@ -228,9 +228,9 @@ export default function MCPPluginsPage() {
|
||||
<div style={{
|
||||
marginTop: 8,
|
||||
padding: 12,
|
||||
background: '#fff2f0',
|
||||
background: 'var(--color-error-bg)',
|
||||
borderRadius: 4,
|
||||
color: '#cf1322',
|
||||
color: 'var(--color-error)',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
@@ -242,7 +242,7 @@ export default function MCPPluginsPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* 显示建议 */}
|
||||
{result.suggestions && result.suggestions.length > 0 && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
@@ -256,12 +256,12 @@ export default function MCPPluginsPage() {
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<div style={{
|
||||
marginTop: 16,
|
||||
padding: 12,
|
||||
background: '#fff7e6',
|
||||
border: '1px solid #ffd591',
|
||||
background: 'var(--color-warning-bg)',
|
||||
border: '1px solid var(--color-warning-border)',
|
||||
borderRadius: 4
|
||||
}}>
|
||||
<Text style={{ fontSize: '13px', color: '#ad6800' }}>
|
||||
@@ -340,267 +340,293 @@ export default function MCPPluginsPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh', background: '#f0f2f5' }}>
|
||||
{/* 顶部导航栏 */}
|
||||
<Header style={{
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
padding: isMobile ? '0 16px' : '0 24px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 1000,
|
||||
height: isMobile ? 56 : 64
|
||||
}}>
|
||||
<Space size={isMobile ? 12 : 16}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate('/')}
|
||||
style={{
|
||||
color: '#fff',
|
||||
fontSize: isMobile ? 16 : 18,
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
{!isMobile && '返回'}
|
||||
</Button>
|
||||
<Title level={isMobile ? 4 : 3} style={{
|
||||
margin: 0,
|
||||
color: '#fff',
|
||||
fontSize: isMobile ? 18 : 24
|
||||
}}>
|
||||
MCP插件管理
|
||||
</Title>
|
||||
</Space>
|
||||
</Header>
|
||||
|
||||
{/* 主内容区 */}
|
||||
<Content style={{
|
||||
marginTop: isMobile ? 56 : 64,
|
||||
padding: isMobile ? '16px' : '24px',
|
||||
<div style={{
|
||||
minHeight: '100vh',
|
||||
background: 'linear-gradient(180deg, var(--color-bg-base) 0%, #EEF2F3 100%)',
|
||||
padding: isMobile ? '20px 16px' : '40px 24px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
<div style={{
|
||||
maxWidth: 1400,
|
||||
margin: '0 auto',
|
||||
width: '100%',
|
||||
margin: `${isMobile ? 56 : 64}px auto 0`,
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
{/* 顶部导航卡片 */}
|
||||
<Card
|
||||
variant="borderless"
|
||||
style={{
|
||||
borderRadius: isMobile ? 8 : 12,
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
|
||||
marginBottom: isMobile ? 16 : 24
|
||||
background: 'linear-gradient(135deg, var(--color-primary) 0%, #5A9BA5 50%, var(--color-primary-hover) 100%)',
|
||||
borderRadius: isMobile ? 16 : 24,
|
||||
boxShadow: '0 12px 40px rgba(77, 128, 136, 0.25), 0 4px 12px rgba(0, 0, 0, 0.06)',
|
||||
marginBottom: isMobile ? 20 : 24,
|
||||
border: 'none',
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: isMobile ? 16 : 20
|
||||
}}>
|
||||
<Title level={isMobile ? 5 : 4} style={{ margin: 0 }}>
|
||||
我的插件
|
||||
</Title>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleCreate}
|
||||
size={isMobile ? 'middle' : 'large'}
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
border: 'none'
|
||||
}}
|
||||
>
|
||||
添加插件
|
||||
</Button>
|
||||
</div>
|
||||
{/* 装饰性背景元素 */}
|
||||
<div style={{ position: 'absolute', top: -60, right: -60, width: 200, height: 200, borderRadius: '50%', background: 'rgba(255, 255, 255, 0.08)', pointerEvents: 'none' }} />
|
||||
<div style={{ position: 'absolute', bottom: -40, left: '30%', width: 120, height: 120, borderRadius: '50%', background: 'rgba(255, 255, 255, 0.05)', pointerEvents: 'none' }} />
|
||||
<div style={{ position: 'absolute', top: '50%', right: '15%', width: 80, height: 80, borderRadius: '50%', background: 'rgba(255, 255, 255, 0.06)', pointerEvents: 'none' }} />
|
||||
|
||||
<Row align="middle" justify="space-between" gutter={[16, 16]} style={{ position: 'relative', zIndex: 1 }}>
|
||||
<Col xs={24} sm={12}>
|
||||
<Space direction="vertical" size={4}>
|
||||
<Space align="center">
|
||||
<Title level={isMobile ? 3 : 2} style={{ margin: 0, color: '#fff', textShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>
|
||||
<ToolOutlined style={{ color: 'rgba(255,255,255,0.9)', marginRight: 8 }} />
|
||||
MCP插件管理
|
||||
</Title>
|
||||
</Space>
|
||||
<Text style={{ fontSize: isMobile ? 12 : 14, color: 'rgba(255,255,255,0.85)', marginLeft: isMobile ? 40 : 48 }}>
|
||||
扩展AI能力,连接外部工具与服务
|
||||
</Text>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col xs={24} sm={12}>
|
||||
<Space size={12} style={{ display: 'flex', justifyContent: isMobile ? 'flex-start' : 'flex-end', width: '100%' }}>
|
||||
<Button
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate('/')}
|
||||
style={{
|
||||
borderRadius: 12,
|
||||
background: 'rgba(255, 255, 255, 0.15)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.3)',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
color: '#fff',
|
||||
backdropFilter: 'blur(10px)',
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.25)';
|
||||
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.15)';
|
||||
e.currentTarget.style.transform = 'none';
|
||||
}}
|
||||
>
|
||||
返回主页
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleCreate}
|
||||
style={{
|
||||
borderRadius: 12,
|
||||
background: 'rgba(255, 193, 7, 0.95)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.3)',
|
||||
boxShadow: '0 4px 16px rgba(255, 193, 7, 0.4)',
|
||||
color: '#fff',
|
||||
fontWeight: 600
|
||||
}}
|
||||
>
|
||||
添加插件
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 使用提示 */}
|
||||
<Alert
|
||||
message="什么是 MCP 插件?"
|
||||
message={
|
||||
<Space align="center">
|
||||
<InfoCircleOutlined style={{ fontSize: 16, color: 'var(--color-primary)' }} />
|
||||
<Text strong style={{ fontSize: isMobile ? 13 : 14, color: 'var(--color-text-primary)' }}>什么是 MCP 插件?</Text>
|
||||
</Space>
|
||||
}
|
||||
description={
|
||||
<div style={{ fontSize: isMobile ? '12px' : '14px' }}>
|
||||
<p style={{ margin: '8px 0' }}>
|
||||
MCP (Model Context Protocol) 是一个标准化的协议,允许 AI 调用外部工具获取数据。
|
||||
</p>
|
||||
<p style={{ margin: '8px 0 0 0' }}>
|
||||
通过添加 MCP 插件,AI 可以访问搜索引擎、数据库、API 等外部服务,增强创作能力。
|
||||
</p>
|
||||
<div>
|
||||
<Text style={{ fontSize: isMobile ? 12 : 13, display: 'block', marginBottom: 8 }}>
|
||||
• <strong>MCP (Model Context Protocol)</strong> 是一个标准化的协议,允许 AI 调用外部工具获取数据。
|
||||
</Text>
|
||||
<Text style={{ fontSize: isMobile ? 12 : 13, display: 'block' }}>
|
||||
• 通过添加 MCP 插件,AI 可以访问搜索引擎、数据库、API 等外部服务,增强创作能力。
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
type="info"
|
||||
showIcon
|
||||
icon={<InfoCircleOutlined />}
|
||||
style={{ marginBottom: isMobile ? 16 : 20 }}
|
||||
showIcon={false}
|
||||
style={{
|
||||
marginTop: isMobile ? 16 : 24,
|
||||
borderRadius: 12,
|
||||
background: 'rgba(230, 247, 255, 0.6)',
|
||||
border: '1px solid rgba(145, 213, 255, 0.6)',
|
||||
backdropFilter: 'blur(5px)'
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 插件列表 */}
|
||||
<Spin spinning={loading}>
|
||||
{plugins.length === 0 ? (
|
||||
<Empty
|
||||
description="还没有添加任何插件"
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
style={{ padding: isMobile ? '40px 0' : '60px 0' }}
|
||||
>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
|
||||
添加第一个插件
|
||||
</Button>
|
||||
</Empty>
|
||||
) : (
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
{plugins.map((plugin) => (
|
||||
<Card
|
||||
key={plugin.id}
|
||||
size="small"
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
border: '1px solid #f0f0f0',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
{/* 主内容区 */}
|
||||
<div style={{ flex: 1 }}>
|
||||
|
||||
{/* 插件列表 */}
|
||||
<Spin spinning={loading}>
|
||||
{plugins.length === 0 ? (
|
||||
<Empty
|
||||
description="还没有添加任何插件"
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
style={{ padding: isMobile ? '40px 0' : '60px 0' }}
|
||||
>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
|
||||
添加第一个插件
|
||||
</Button>
|
||||
</Empty>
|
||||
) : (
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
{plugins.map((plugin) => (
|
||||
<Card
|
||||
key={plugin.id}
|
||||
size="small"
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
gap: '16px',
|
||||
flexWrap: isMobile ? 'wrap' : 'nowrap',
|
||||
borderRadius: 8,
|
||||
border: '1px solid #f0f0f0',
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>
|
||||
<Text strong style={{ fontSize: isMobile ? '14px' : '16px' }}>
|
||||
{plugin.display_name || plugin.plugin_name}
|
||||
</Text>
|
||||
{getStatusTag(plugin)}
|
||||
<Tag color={plugin.plugin_type === 'http' ? 'blue' : 'cyan'}>
|
||||
{plugin.plugin_type?.toUpperCase() || 'UNKNOWN'}
|
||||
</Tag>
|
||||
{plugin.category && plugin.category !== 'general' && (
|
||||
<Tag color="purple">{plugin.category}</Tag>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
gap: '16px',
|
||||
flexWrap: isMobile ? 'wrap' : 'nowrap',
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>
|
||||
<Text strong style={{ fontSize: isMobile ? '14px' : '16px' }}>
|
||||
{plugin.display_name || plugin.plugin_name}
|
||||
</Text>
|
||||
{getStatusTag(plugin)}
|
||||
<Tag color={plugin.plugin_type === 'http' ? 'blue' : 'cyan'}>
|
||||
{plugin.plugin_type?.toUpperCase() || 'UNKNOWN'}
|
||||
</Tag>
|
||||
{plugin.category && plugin.category !== 'general' && (
|
||||
<Tag color="purple">{plugin.category}</Tag>
|
||||
)}
|
||||
</div>
|
||||
{plugin.description && (
|
||||
<Paragraph
|
||||
type="secondary"
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: isMobile ? '12px' : '13px',
|
||||
}}
|
||||
ellipsis={{ rows: 2 }}
|
||||
>
|
||||
{plugin.description}
|
||||
</Paragraph>
|
||||
)}
|
||||
</div>
|
||||
{plugin.description && (
|
||||
<Paragraph
|
||||
type="secondary"
|
||||
|
||||
{/* 只显示有值的URL或命令,脱敏处理敏感信息 */}
|
||||
{plugin.plugin_type === 'http' && plugin.server_url && (
|
||||
<div style={{ fontSize: isMobile ? '11px' : '12px' }}>
|
||||
<Text type="secondary" code>
|
||||
{(() => {
|
||||
// 脱敏处理:隐藏URL中的API Key
|
||||
const url = plugin.server_url;
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
// 替换查询参数中的敏感信息
|
||||
const params = new URLSearchParams(urlObj.search);
|
||||
let maskedUrl = `${urlObj.protocol}//${urlObj.host}${urlObj.pathname}`;
|
||||
|
||||
const sensitiveKeys = ['apiKey', 'api_key', 'key', 'token', 'secret', 'password', 'auth'];
|
||||
let hasParams = false;
|
||||
|
||||
params.forEach((value, key) => {
|
||||
const isSensitive = sensitiveKeys.some(k => key.toLowerCase().includes(k.toLowerCase()));
|
||||
const maskedValue = isSensitive ? '***' : value;
|
||||
maskedUrl += (hasParams ? '&' : '?') + `${key}=${maskedValue}`;
|
||||
hasParams = true;
|
||||
});
|
||||
|
||||
return maskedUrl;
|
||||
} catch {
|
||||
// 如果URL解析失败,尝试简单替换
|
||||
return url.replace(/([?&])(apiKey|api_key|key|token|secret|password|auth)=([^&]+)/gi, '$1$2=***');
|
||||
}
|
||||
})()}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{plugin.plugin_type === 'stdio' && plugin.command && (
|
||||
<div style={{ fontSize: isMobile ? '11px' : '12px' }}>
|
||||
<Text type="secondary" code>
|
||||
{plugin.command} {plugin.args?.join(' ')}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 显示最后错误信息 */}
|
||||
{plugin.last_error && (
|
||||
<Text type="danger" style={{ fontSize: isMobile ? '11px' : '12px' }}>
|
||||
错误: {plugin.last_error}
|
||||
</Text>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Space size="small" wrap>
|
||||
<Tooltip title={plugin.enabled ? '禁用插件' : '启用插件'}>
|
||||
<Switch
|
||||
checked={plugin.enabled}
|
||||
onChange={(checked) => handleToggle(plugin, checked)}
|
||||
size={isMobile ? 'small' : 'default'}
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: isMobile ? '12px' : '13px',
|
||||
flexShrink: 0,
|
||||
height: isMobile ? 16 : 22,
|
||||
minHeight: isMobile ? 16 : 22,
|
||||
lineHeight: isMobile ? '16px' : '22px'
|
||||
}}
|
||||
ellipsis={{ rows: 2 }}
|
||||
>
|
||||
{plugin.description}
|
||||
</Paragraph>
|
||||
)}
|
||||
|
||||
{/* 只显示有值的URL或命令,脱敏处理敏感信息 */}
|
||||
{plugin.plugin_type === 'http' && plugin.server_url && (
|
||||
<div style={{ fontSize: isMobile ? '11px' : '12px' }}>
|
||||
<Text type="secondary" code>
|
||||
{(() => {
|
||||
// 脱敏处理:隐藏URL中的API Key
|
||||
const url = plugin.server_url;
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
// 替换查询参数中的敏感信息
|
||||
const params = new URLSearchParams(urlObj.search);
|
||||
let maskedUrl = `${urlObj.protocol}//${urlObj.host}${urlObj.pathname}`;
|
||||
|
||||
const sensitiveKeys = ['apiKey', 'api_key', 'key', 'token', 'secret', 'password', 'auth'];
|
||||
let hasParams = false;
|
||||
|
||||
params.forEach((value, key) => {
|
||||
const isSensitive = sensitiveKeys.some(k => key.toLowerCase().includes(k.toLowerCase()));
|
||||
const maskedValue = isSensitive ? '***' : value;
|
||||
maskedUrl += (hasParams ? '&' : '?') + `${key}=${maskedValue}`;
|
||||
hasParams = true;
|
||||
});
|
||||
|
||||
return maskedUrl;
|
||||
} catch {
|
||||
// 如果URL解析失败,尝试简单替换
|
||||
return url.replace(/([?&])(apiKey|api_key|key|token|secret|password|auth)=([^&]+)/gi, '$1$2=***');
|
||||
}
|
||||
})()}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{plugin.plugin_type === 'stdio' && plugin.command && (
|
||||
<div style={{ fontSize: isMobile ? '11px' : '12px' }}>
|
||||
<Text type="secondary" code>
|
||||
{plugin.command} {plugin.args?.join(' ')}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 显示最后错误信息 */}
|
||||
{plugin.last_error && (
|
||||
<Text type="danger" style={{ fontSize: isMobile ? '11px' : '12px' }}>
|
||||
错误: {plugin.last_error}
|
||||
</Text>
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="测试连接">
|
||||
<Button
|
||||
icon={<ThunderboltOutlined />}
|
||||
onClick={() => handleTest(plugin.id)}
|
||||
loading={testingPluginId === plugin.id}
|
||||
size={isMobile ? 'small' : 'middle'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="查看工具">
|
||||
<Button
|
||||
icon={<ToolOutlined />}
|
||||
onClick={() => handleViewTools(plugin.id)}
|
||||
disabled={!plugin.enabled || plugin.status !== 'active'}
|
||||
size={isMobile ? 'small' : 'middle'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="编辑">
|
||||
<Button
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleEdit(plugin)}
|
||||
size={isMobile ? 'small' : 'middle'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="删除">
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => handleDelete(plugin)}
|
||||
size={isMobile ? 'small' : 'middle'}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Space size="small" wrap>
|
||||
<Tooltip title={plugin.enabled ? '禁用插件' : '启用插件'}>
|
||||
<Switch
|
||||
checked={plugin.enabled}
|
||||
onChange={(checked) => handleToggle(plugin, checked)}
|
||||
size={isMobile ? 'small' : 'default'}
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
height: isMobile ? 16 : 22,
|
||||
minHeight: isMobile ? 16 : 22,
|
||||
lineHeight: isMobile ? '16px' : '22px'
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="测试连接">
|
||||
<Button
|
||||
icon={<ThunderboltOutlined />}
|
||||
onClick={() => handleTest(plugin.id)}
|
||||
loading={testingPluginId === plugin.id}
|
||||
size={isMobile ? 'small' : 'middle'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="查看工具">
|
||||
<Button
|
||||
icon={<ToolOutlined />}
|
||||
onClick={() => handleViewTools(plugin.id)}
|
||||
disabled={!plugin.enabled || plugin.status !== 'active'}
|
||||
size={isMobile ? 'small' : 'middle'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="编辑">
|
||||
<Button
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleEdit(plugin)}
|
||||
size={isMobile ? 'small' : 'middle'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="删除">
|
||||
<Button
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => handleDelete(plugin)}
|
||||
size={isMobile ? 'small' : 'middle'}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
</Spin>
|
||||
</Content>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 创建/编辑插件模态框 */}
|
||||
<Modal
|
||||
@@ -710,6 +736,6 @@ export default function MCPPluginsPage() {
|
||||
</Space>
|
||||
)}
|
||||
</Modal>
|
||||
</Layout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user