refactor(mcp): improve server list pagination and form validation
This commit is contained in:
@@ -8,6 +8,7 @@ import type { IFactory, IMyLlmModel } from "@/interfaces/database/llm";
|
|||||||
import type { LLMFactory } from "@/constants/llm";
|
import type { LLMFactory } from "@/constants/llm";
|
||||||
import type { IMcpServer, IMcpServerListResponse } from "@/interfaces/database/mcp";
|
import type { IMcpServer, IMcpServerListResponse } from "@/interfaces/database/mcp";
|
||||||
import type { IImportMcpServersRequestBody, ITestMcpRequestBody, ICreateMcpServerRequestBody } from "@/interfaces/request/mcp";
|
import type { IImportMcpServersRequestBody, ITestMcpRequestBody, ICreateMcpServerRequestBody } from "@/interfaces/request/mcp";
|
||||||
|
import type { IPaginationBody } from "@/interfaces/request/base";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 个人中心设置
|
* 个人中心设置
|
||||||
@@ -141,7 +142,7 @@ export function useTeamSetting() {
|
|||||||
// 获取租户用户列表
|
// 获取租户用户列表
|
||||||
const fetchTenantUsers = useCallback(async () => {
|
const fetchTenantUsers = useCallback(async () => {
|
||||||
if (!tenantInfo?.tenant_id) return;
|
if (!tenantInfo?.tenant_id) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await userService.listTenantUser(tenantInfo.tenant_id);
|
const response = await userService.listTenantUser(tenantInfo.tenant_id);
|
||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
@@ -254,20 +255,23 @@ export function useTeamSetting() {
|
|||||||
/**
|
/**
|
||||||
* MCP 设置
|
* MCP 设置
|
||||||
*/
|
*/
|
||||||
export function useMcpSetting() {
|
export function useMcpSetting({ refreshServer }: { refreshServer: (initial?: boolean) => Promise<void> }) {
|
||||||
const [mcpServers, setMcpServers] = useState<IMcpServer[]>([]);
|
const [mcpServers, setMcpServers] = useState<IMcpServer[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
|
|
||||||
// 获取 MCP 服务器列表
|
// 获取 MCP 服务器列表
|
||||||
const fetchMcpServers = useCallback(async () => {
|
const fetchMcpServers = useCallback(async (params?: IPaginationBody & {
|
||||||
|
keyword?: string;
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const res = await userService.listMcpServer({
|
const res = await userService.listMcpServer({
|
||||||
page: 1,
|
page: params?.page || 1,
|
||||||
size: 10,
|
size: params?.size || 8,
|
||||||
|
keyword: params?.keyword,
|
||||||
});
|
});
|
||||||
if (res.data.code === 0) {
|
if (res.data.code === 0) {
|
||||||
const data: IMcpServerListResponse = res.data.data;
|
const data: IMcpServerListResponse = res.data.data;
|
||||||
@@ -292,7 +296,7 @@ export function useMcpSetting() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
const res = await userService.importMcpServer(data);
|
const res = await userService.importMcpServer(data);
|
||||||
if (res.data.code === 0) {
|
if (res.data.code === 0) {
|
||||||
await fetchMcpServers(); // 重新获取列表
|
await refreshServer();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res.data.message || '导入 MCP 服务器失败');
|
throw new Error(res.data.message || '导入 MCP 服务器失败');
|
||||||
@@ -305,7 +309,7 @@ export function useMcpSetting() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [fetchMcpServers]);
|
}, []);
|
||||||
|
|
||||||
// 导出 MCP 服务器
|
// 导出 MCP 服务器
|
||||||
const exportMcpServers = useCallback(async (mcpIds: string[]) => {
|
const exportMcpServers = useCallback(async (mcpIds: string[]) => {
|
||||||
@@ -386,7 +390,7 @@ export function useMcpSetting() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
const res = await userService.createMcpServer(data);
|
const res = await userService.createMcpServer(data);
|
||||||
if (res.data.code === 0) {
|
if (res.data.code === 0) {
|
||||||
await fetchMcpServers(); // 重新获取列表
|
await refreshServer(true); // 重新获取列表
|
||||||
return { success: true, data: res.data.data };
|
return { success: true, data: res.data.data };
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res.data.message || '创建 MCP 服务器失败');
|
throw new Error(res.data.message || '创建 MCP 服务器失败');
|
||||||
@@ -399,7 +403,7 @@ export function useMcpSetting() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [fetchMcpServers]);
|
}, []);
|
||||||
|
|
||||||
// 更新 MCP 服务器
|
// 更新 MCP 服务器
|
||||||
const updateMcpServer = useCallback(async (data: ICreateMcpServerRequestBody & { mcp_id: string }) => {
|
const updateMcpServer = useCallback(async (data: ICreateMcpServerRequestBody & { mcp_id: string }) => {
|
||||||
@@ -408,7 +412,7 @@ export function useMcpSetting() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
const res = await userService.updateMcpServer(data);
|
const res = await userService.updateMcpServer(data);
|
||||||
if (res.data.code === 0) {
|
if (res.data.code === 0) {
|
||||||
await fetchMcpServers(); // 重新获取列表
|
await refreshServer(); // 刷新列表
|
||||||
return { success: true, data: res.data.data };
|
return { success: true, data: res.data.data };
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res.data.message || '更新 MCP 服务器失败');
|
throw new Error(res.data.message || '更新 MCP 服务器失败');
|
||||||
@@ -421,7 +425,7 @@ export function useMcpSetting() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [fetchMcpServers]);
|
}, []);
|
||||||
|
|
||||||
// 删除 MCP 服务器
|
// 删除 MCP 服务器
|
||||||
const deleteMcpServer = useCallback(async (mcpId: string) => {
|
const deleteMcpServer = useCallback(async (mcpId: string) => {
|
||||||
@@ -430,7 +434,7 @@ export function useMcpSetting() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
const res = await userService.removeMcpServer([mcpId]);
|
const res = await userService.removeMcpServer([mcpId]);
|
||||||
if (res.data.code === 0) {
|
if (res.data.code === 0) {
|
||||||
await fetchMcpServers(); // 重新获取列表
|
await refreshServer(); // 刷新列表
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res.data.message || '删除 MCP 服务器失败');
|
throw new Error(res.data.message || '删除 MCP 服务器失败');
|
||||||
@@ -443,10 +447,10 @@ export function useMcpSetting() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [fetchMcpServers]);
|
}, []);
|
||||||
|
|
||||||
// 批量删除 MCP 服务器
|
// 批量删除 MCP 服务器
|
||||||
const deleteMcpServers = useCallback(async (mcpIds: string[]) => {
|
const batchDeleteMcpServers = useCallback(async (mcpIds: string[]) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -465,7 +469,7 @@ export function useMcpSetting() {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [fetchMcpServers]);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 状态
|
// 状态
|
||||||
@@ -473,7 +477,7 @@ export function useMcpSetting() {
|
|||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
total,
|
total,
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
fetchMcpServers,
|
fetchMcpServers,
|
||||||
importMcpServers,
|
importMcpServers,
|
||||||
@@ -483,7 +487,7 @@ export function useMcpSetting() {
|
|||||||
createMcpServer,
|
createMcpServer,
|
||||||
updateMcpServer,
|
updateMcpServer,
|
||||||
deleteMcpServer,
|
deleteMcpServer,
|
||||||
deleteMcpServers,
|
batchDeleteMcpServers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,8 +83,6 @@ const McpForm: React.FC<McpFormProps> = ({
|
|||||||
|
|
||||||
const [testLoading, setTestLoading] = useState(false);
|
const [testLoading, setTestLoading] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const [testResult, setTestResult] = useState<{
|
const [testResult, setTestResult] = useState<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
tools?: McpTool[];
|
tools?: McpTool[];
|
||||||
@@ -121,7 +119,7 @@ const McpForm: React.FC<McpFormProps> = ({
|
|||||||
return t
|
return t
|
||||||
});
|
});
|
||||||
setTestResult({
|
setTestResult({
|
||||||
success: true,
|
success: tools.length > 0,
|
||||||
tools,
|
tools,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -201,7 +199,7 @@ const McpForm: React.FC<McpFormProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTestResult({
|
setTestResult({
|
||||||
success: true,
|
success: tools.length > 0,
|
||||||
tools,
|
tools,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -359,7 +357,7 @@ const McpForm: React.FC<McpFormProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={loading || disabled}
|
disabled={loading || disabled || !testResult?.success}
|
||||||
startIcon={loading ? <CircularProgress size={16} /> : undefined}
|
startIcon={loading ? <CircularProgress size={16} /> : undefined}
|
||||||
>
|
>
|
||||||
{loading ? '保存中...' : '保存'}
|
{loading ? '保存中...' : '保存'}
|
||||||
|
|||||||
@@ -69,6 +69,19 @@ const SearchContainer = styled(Box)(({ theme }) => ({
|
|||||||
|
|
||||||
|
|
||||||
export default function McpSettingPage() {
|
export default function McpSettingPage() {
|
||||||
|
|
||||||
|
const handleRefreshServer = async (initial?: boolean) => {
|
||||||
|
if (initial) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
} else {
|
||||||
|
await fetchMcpServers({
|
||||||
|
page: currentPage,
|
||||||
|
size: pageSize,
|
||||||
|
keyword: searchString,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mcpServers,
|
mcpServers,
|
||||||
loading,
|
loading,
|
||||||
@@ -81,8 +94,8 @@ export default function McpSettingPage() {
|
|||||||
createMcpServer,
|
createMcpServer,
|
||||||
updateMcpServer,
|
updateMcpServer,
|
||||||
deleteMcpServer,
|
deleteMcpServer,
|
||||||
deleteMcpServers,
|
batchDeleteMcpServers,
|
||||||
} = useMcpSetting();
|
} = useMcpSetting({ refreshServer: handleRefreshServer });
|
||||||
|
|
||||||
// 本地状态
|
// 本地状态
|
||||||
const [searchString, setSearchString] = useState('');
|
const [searchString, setSearchString] = useState('');
|
||||||
@@ -93,24 +106,22 @@ export default function McpSettingPage() {
|
|||||||
const [editingServer, setEditingServer] = useState<Partial<IMcpServer> | undefined | null>(null);
|
const [editingServer, setEditingServer] = useState<Partial<IMcpServer> | undefined | null>(null);
|
||||||
const [importDialogOpen, setImportDialogOpen] = useState(false);
|
const [importDialogOpen, setImportDialogOpen] = useState(false);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [pageSize] = useState(6);
|
const [pageSize] = useState(8);
|
||||||
const [importJson, setImportJson] = useState('');
|
const [importJson, setImportJson] = useState('');
|
||||||
|
|
||||||
const showMessage = useMessage()
|
const showMessage = useMessage()
|
||||||
|
|
||||||
// 初始化加载数据
|
// 初始化加载数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchMcpServers();
|
fetchMcpServers({
|
||||||
}, [fetchMcpServers]);
|
page: currentPage,
|
||||||
|
size: pageSize,
|
||||||
|
keyword: searchString,
|
||||||
|
});
|
||||||
|
}, [fetchMcpServers, currentPage, pageSize, searchString]);
|
||||||
|
|
||||||
// 过滤和分页逻辑
|
const totalPages = Math.ceil(total / pageSize);
|
||||||
const filteredServers = mcpServers.filter(server =>
|
const paginatedServers = mcpServers.slice(
|
||||||
server.name.toLowerCase().includes(searchString.toLowerCase()) ||
|
|
||||||
server.url.toLowerCase().includes(searchString.toLowerCase())
|
|
||||||
);
|
|
||||||
|
|
||||||
const totalPages = Math.ceil(filteredServers.length / pageSize);
|
|
||||||
const paginatedServers = filteredServers.slice(
|
|
||||||
(currentPage - 1) * pageSize,
|
(currentPage - 1) * pageSize,
|
||||||
currentPage * pageSize
|
currentPage * pageSize
|
||||||
);
|
);
|
||||||
@@ -185,7 +196,7 @@ export default function McpSettingPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleBulkDelete = async () => {
|
const handleBulkDelete = async () => {
|
||||||
const result = await deleteMcpServers(selectedServers);
|
const result = await batchDeleteMcpServers(selectedServers);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
showMessage.success('批量删除成功');
|
showMessage.success('批量删除成功');
|
||||||
setSelectedServers([]);
|
setSelectedServers([]);
|
||||||
@@ -309,6 +320,7 @@ export default function McpSettingPage() {
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<SearchContainer>
|
<SearchContainer>
|
||||||
|
<Box>
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
placeholder="Search MCP servers..."
|
placeholder="Search MCP servers..."
|
||||||
@@ -319,6 +331,7 @@ export default function McpSettingPage() {
|
|||||||
}}
|
}}
|
||||||
sx={{ width: 300 }}
|
sx={{ width: 300 }}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
{selectedServers.length > 0 && (
|
{selectedServers.length > 0 && (
|
||||||
<Box display="flex" gap={1}>
|
<Box display="flex" gap={1}>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user