Files
TERES_web_frontend/src/pages/MCP.tsx
guangfei.zhao 5f93573e57 feat: implement RAG dashboard with MUI and react-router
Add new RAG dashboard feature including:
- Main application layout with header and sidebar
- Multiple pages (Home, KnowledgeBase, PipelineConfig, Dashboard, MCP)
- Theme configuration and styling
- Routing setup with protected routes
- Login page and authentication flow
- Various UI components and mock data for dashboard views
2025-10-09 17:23:15 +08:00

516 lines
18 KiB
TypeScript

import React, { useState } from 'react';
import {
Box,
Typography,
Card,
CardContent,
Grid,
Button,
Switch,
FormControlLabel,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Chip,
IconButton,
Menu,
MenuItem,
TextField,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Alert,
Tabs,
Tab,
} from '@mui/material';
import {
Add as AddIcon,
MoreVert as MoreVertIcon,
Settings as SettingsIcon,
Link as LinkIcon,
Security as SecurityIcon,
Speed as SpeedIcon,
CheckCircle as CheckCircleIcon,
Error as ErrorIcon,
} from '@mui/icons-material';
import { styled } from '@mui/material/styles';
const PageContainer = styled(Box)(({ theme }) => ({
padding: '1.5rem',
backgroundColor: '#F8F9FA',
minHeight: 'calc(100vh - 60px)',
}));
const PageHeader = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '1.5rem',
}));
const StatusCard = styled(Card)(({ theme }) => ({
height: '100%',
border: '1px solid #E5E5E5',
transition: 'all 0.2s ease-in-out',
'&:hover': {
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
},
}));
const StatusChip = styled(Chip)<{ status: string }>(({ status, theme }) => ({
fontSize: '0.75rem',
height: '24px',
backgroundColor:
status === 'connected' ? '#E8F5E8' :
status === 'connecting' ? '#FFF3CD' :
status === 'error' ? '#F8D7DA' : '#F8F9FA',
color:
status === 'connected' ? '#155724' :
status === 'connecting' ? '#856404' :
status === 'error' ? '#721C24' : '#666',
}));
const mockMCPServers = [
{
id: 1,
name: 'File System Server',
description: '文件系统操作服务器',
url: 'mcp://localhost:3001',
status: 'connected',
version: '1.0.0',
tools: ['read_file', 'write_file', 'list_directory'],
lastPing: '2024-01-15 14:30:25',
},
{
id: 2,
name: 'Database Server',
description: '数据库查询服务器',
url: 'mcp://db.example.com:3002',
status: 'connected',
version: '1.2.1',
tools: ['query_sql', 'execute_sql', 'get_schema'],
lastPing: '2024-01-15 14:30:20',
},
{
id: 3,
name: 'Web Scraper',
description: '网页抓取服务器',
url: 'mcp://scraper.example.com:3003',
status: 'connecting',
version: '0.9.5',
tools: ['scrape_url', 'extract_text', 'get_links'],
lastPing: '2024-01-15 14:28:15',
},
{
id: 4,
name: 'API Gateway',
description: 'API网关服务器',
url: 'mcp://api.example.com:3004',
status: 'error',
version: '2.1.0',
tools: ['call_api', 'auth_token', 'rate_limit'],
lastPing: '2024-01-15 14:25:10',
},
];
const MCP: React.FC = () => {
const [tabValue, setTabValue] = useState(0);
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [selectedServer, setSelectedServer] = useState<number | null>(null);
const [dialogOpen, setDialogOpen] = useState(false);
const [newServer, setNewServer] = useState({
name: '',
url: '',
description: '',
});
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
};
const handleMenuClick = (event: React.MouseEvent<HTMLElement>, serverId: number) => {
setAnchorEl(event.currentTarget);
setSelectedServer(serverId);
};
const handleMenuClose = () => {
setAnchorEl(null);
setSelectedServer(null);
};
const handleAddServer = () => {
setDialogOpen(true);
};
const handleDialogClose = () => {
setDialogOpen(false);
setNewServer({ name: '', url: '', description: '' });
};
const handleSaveServer = () => {
// 保存服务器逻辑
console.log('添加服务器:', newServer);
handleDialogClose();
};
const connectedServers = mockMCPServers.filter(s => s.status === 'connected').length;
const totalTools = mockMCPServers.reduce((acc, server) => acc + server.tools.length, 0);
return (
<PageContainer>
<PageHeader>
<Box>
<Typography variant="h4" fontWeight={600} color="#333">
MCP
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
Model Context Protocol -
</Typography>
</Box>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={handleAddServer}
sx={{ borderRadius: '6px' }}
>
</Button>
</PageHeader>
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<Tabs value={tabValue} onChange={handleTabChange}>
<Tab label="服务器管理" />
<Tab label="工具配置" />
<Tab label="协议监控" />
</Tabs>
</Box>
{tabValue === 0 && (
<>
{/* 状态概览 */}
<Grid container spacing={3} sx={{ mb: 3 }}>
<Grid item xs={12} sm={6} md={3}>
<StatusCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="success.main">
{connectedServers}
</Typography>
</Box>
<CheckCircleIcon color="success" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</StatusCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<StatusCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="primary">
{mockMCPServers.length}
</Typography>
</Box>
<LinkIcon color="primary" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</StatusCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<StatusCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="info.main">
{totalTools}
</Typography>
</Box>
<SettingsIcon color="info" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</StatusCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<StatusCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="warning.main">
45ms
</Typography>
</Box>
<SpeedIcon color="warning" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</StatusCard>
</Grid>
</Grid>
{/* 服务器列表 */}
<Card sx={{ border: '1px solid #E5E5E5' }}>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
MCP
</Typography>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell></TableCell>
<TableCell>URL</TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{mockMCPServers.map((server) => (
<TableRow key={server.id} hover>
<TableCell>
<Box>
<Typography variant="body2" fontWeight={600}>
{server.name}
</Typography>
<Typography variant="caption" color="text.secondary">
{server.description}
</Typography>
</Box>
</TableCell>
<TableCell>
<Typography variant="body2" fontFamily="monospace">
{server.url}
</Typography>
</TableCell>
<TableCell>
<StatusChip
status={server.status}
label={
server.status === 'connected' ? '已连接' :
server.status === 'connecting' ? '连接中' : '错误'
}
size="small"
/>
</TableCell>
<TableCell>
<Chip
label={server.version}
size="small"
variant="outlined"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{server.tools.length}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" color="text.secondary">
{server.lastPing}
</Typography>
</TableCell>
<TableCell>
<IconButton
size="small"
onClick={(e) => handleMenuClick(e, server.id)}
>
<MoreVertIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{tabValue === 1 && (
<Card sx={{ border: '1px solid #E5E5E5' }}>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
</Typography>
<Alert severity="info" sx={{ mb: 2 }}>
MCP
</Alert>
<Grid container spacing={2}>
{mockMCPServers.map((server) => (
<Grid item xs={12} md={6} key={server.id}>
<Card variant="outlined">
<CardContent>
<Typography variant="subtitle1" fontWeight={600} mb={1}>
{server.name}
</Typography>
<Box display="flex" flexDirection="column" gap={1}>
{server.tools.map((tool, index) => (
<FormControlLabel
key={index}
control={<Switch defaultChecked size="small" />}
label={
<Typography variant="body2" fontFamily="monospace">
{tool}
</Typography>
}
/>
))}
</Box>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</CardContent>
</Card>
)}
{tabValue === 2 && (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card sx={{ border: '1px solid #E5E5E5' }}>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
<SecurityIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
</Typography>
<Box display="flex" flexDirection="column" gap={2}>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="body2">SSL/TLS </Typography>
<CheckCircleIcon color="success" />
</Box>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="body2"></Typography>
<CheckCircleIcon color="success" />
</Box>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="body2">访</Typography>
<CheckCircleIcon color="success" />
</Box>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="body2"></Typography>
<ErrorIcon color="error" />
</Box>
</Box>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card sx={{ border: '1px solid #E5E5E5' }}>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
<SpeedIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
</Typography>
<Box display="flex" flexDirection="column" gap={2}>
<Box>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="body2"></Typography>
<Typography variant="body2" fontWeight={600}>45ms</Typography>
</Box>
<Box bgcolor="#F8F9FA" p={1} borderRadius="4px">
<Typography variant="caption" color="text.secondary">
24
</Typography>
</Box>
</Box>
<Box>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="body2"></Typography>
<Typography variant="body2" fontWeight={600} color="success.main">
99.2%
</Typography>
</Box>
<Box bgcolor="#F8F9FA" p={1} borderRadius="4px">
<Typography variant="caption" color="text.secondary">
24
</Typography>
</Box>
</Box>
</Box>
</CardContent>
</Card>
</Grid>
</Grid>
)}
{/* 菜单 */}
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
>
<MenuItem onClick={handleMenuClose}>
<SettingsIcon sx={{ mr: 1 }} fontSize="small" />
</MenuItem>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose} sx={{ color: 'error.main' }}>
</MenuItem>
</Menu>
{/* 添加服务器对话框 */}
<Dialog open={dialogOpen} onClose={handleDialogClose} maxWidth="sm" fullWidth>
<DialogTitle> MCP </DialogTitle>
<DialogContent>
<Box display="flex" flexDirection="column" gap={2} pt={1}>
<TextField
fullWidth
label="服务器名称"
value={newServer.name}
onChange={(e) => setNewServer(prev => ({ ...prev, name: e.target.value }))}
/>
<TextField
fullWidth
label="服务器 URL"
placeholder="mcp://localhost:3000"
value={newServer.url}
onChange={(e) => setNewServer(prev => ({ ...prev, url: e.target.value }))}
/>
<TextField
fullWidth
label="描述"
multiline
rows={3}
value={newServer.description}
onChange={(e) => setNewServer(prev => ({ ...prev, description: e.target.value }))}
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleDialogClose}></Button>
<Button onClick={handleSaveServer} variant="contained">
</Button>
</DialogActions>
</Dialog>
</PageContainer>
);
};
export default MCP;