Skip to content

Commit fc05d26

Browse files
committed
feat: add TestConnection API endpoint and corresponding frontend functionality
1 parent 2735540 commit fc05d26

File tree

6 files changed

+86
-12
lines changed

6 files changed

+86
-12
lines changed

apps/tools/serializers/tool.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
import os
66
import pickle
77
import re
8-
import requests
98
import tempfile
109
import zipfile
1110
from typing import Dict
1211

12+
import requests
1313
import uuid_utils.compat as uuid
1414
from django.core import validators
1515
from django.db import transaction
@@ -356,9 +356,6 @@ def insert(self, instance, with_valid=True):
356356
ToolCreateRequest(data=instance).is_valid(raise_exception=True)
357357
# 校验代码是否包括禁止的关键字
358358
ToolExecutor().validate_banned_keywords(instance.get('code', ''))
359-
# 校验mcp json
360-
if instance.get('tool_type') == ToolType.MCP.value:
361-
validate_mcp_config(json.loads(instance.get('code')))
362359

363360
tool_id = uuid.uuid7()
364361
Tool(
@@ -386,6 +383,18 @@ def insert(self, instance, with_valid=True):
386383
'id': tool_id, 'workspace_id': self.data.get('workspace_id')
387384
}).one()
388385

386+
class TestConnection(serializers.Serializer):
387+
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
388+
code = serializers.CharField(required=True, label=_('tool content'))
389+
390+
def test_connection(self):
391+
self.is_valid(raise_exception=True)
392+
# 校验代码是否包括禁止的关键字
393+
ToolExecutor().validate_banned_keywords(self.data.get('code', ''))
394+
# 校验mcp json
395+
validate_mcp_config(json.loads(self.data.get('code')))
396+
return True
397+
389398
class Debug(serializers.Serializer):
390399
user_id = serializers.UUIDField(required=True, label=_('user id'))
391400
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
@@ -475,9 +484,7 @@ def edit(self, instance, with_valid=True):
475484
ToolEditRequest(data=instance).is_valid(raise_exception=True)
476485
# 校验代码是否包括禁止的关键字
477486
ToolExecutor().validate_banned_keywords(instance.get('code', ''))
478-
# 校验mcp json
479-
if instance.get('tool_type') == ToolType.MCP.value:
480-
validate_mcp_config(json.loads(instance.get('code')))
487+
481488

482489
if not QuerySet(Tool).filter(id=self.data.get('id')).exists():
483490
raise serializers.ValidationError(_('Tool not found'))
@@ -755,7 +762,8 @@ def get_appstore_tools(self):
755762
versions = tool.get('versions', [])
756763
tool['label'] = tag_dict[tool.get('tags')[0]] if tool.get('tags') else ''
757764
tool['version'] = next(
758-
(version.get('name') for version in versions if version.get('downloadUrl') == tool['downloadUrl']),
765+
(version.get('name') for version in versions if
766+
version.get('downloadUrl') == tool['downloadUrl']),
759767
)
760768
filter_apps.append(tool)
761769

@@ -836,7 +844,8 @@ def update_tool(self, with_valid=True):
836844
raise AppApiException(500, _('Tool does not exist'))
837845
# 查找匹配的版本名称
838846
version_name = next(
839-
(version.get('name') for version in self.data.get('versions') if version.get('downloadUrl') == self.data.get('download_url')),
847+
(version.get('name') for version in self.data.get('versions') if
848+
version.get('downloadUrl') == self.data.get('download_url')),
840849
)
841850
res = requests.get(self.data.get('download_url'), timeout=5)
842851
tool_data = RestrictedUnpickler(io.BytesIO(res.content)).load().tool
@@ -855,7 +864,6 @@ def update_tool(self, with_valid=True):
855864
return ToolModelSerializer(tool).data
856865

857866

858-
859867
class ToolTreeSerializer(serializers.Serializer):
860868
class Query(serializers.Serializer):
861869
workspace_id = serializers.CharField(required=True, label=_('workspace id'))

apps/tools/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
path('workspace/<str:workspace_id>/tool/pylint', views.ToolView.Pylint.as_view()),
1313
path('workspace/<str:workspace_id>/tool/debug', views.ToolView.Debug.as_view()),
1414
path('workspace/<str:workspace_id>/tool/tool_list', views.ToolView.Query.as_view()),
15+
path('workspace/<str:workspace_id>/tool/test_connection', views.ToolView.TestConnection.as_view()),
1516
path('workspace/<str:workspace_id>/tool/<str:tool_id>', views.ToolView.Operate.as_view()),
1617
path('workspace/<str:workspace_id>/tool/<str:tool_id>/edit_icon', views.ToolView.EditIcon.as_view()),
1718
path('workspace/<str:workspace_id>/tool/<str:tool_id>/export', views.ToolView.Export.as_view()),

apps/tools/views/tool.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,31 @@ def put(self, request: Request, tool_id: str, workspace_id: str):
360360
'image': request.FILES.get('file')
361361
}).edit(request.data))
362362

363+
class TestConnection(APIView):
364+
authentication_classes = [TokenAuth]
365+
366+
@extend_schema(
367+
methods=['POST'],
368+
description=_("Test tool connection"),
369+
summary=_("Test tool connection"),
370+
operation_id=_("Test tool connection"), # type: ignore
371+
request=ToolReadAPI.get_request(),
372+
responses=ToolReadAPI.get_response(),
373+
tags=[_("Tool")] # type: ignore
374+
)
375+
@has_permissions(
376+
PermissionConstants.TOOL_CREATE.get_workspace_permission(),
377+
PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(),
378+
PermissionConstants.TOOL_EDIT.get_workspace_permission(),
379+
PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(),
380+
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()
381+
)
382+
def post(self, request: Request, workspace_id: str):
383+
return result.success(ToolSerializer.TestConnection(data={
384+
'workspace_id': workspace_id,
385+
'code': request.data.get('code'),
386+
}).test_connection())
387+
363388
class InternalTool(APIView):
364389
authentication_classes = [TokenAuth]
365390

ui/src/api/system-shared/tool.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@ const putTool: (tool_id: string, data: toolData, loading?: Ref<boolean>) => Prom
6868
return put(`${prefix}/${tool_id}`, data, undefined, loading)
6969
}
7070

71+
/**
72+
* @param 参数
73+
*/
74+
const postToolTestConnection: (data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (
75+
data,
76+
loading,
77+
) => {
78+
return post(`${prefix}/test_connection`, data, undefined, loading)
79+
}
80+
81+
7182
/**
7283
* 获取工具详情
7384
* @param tool_id 工具id
@@ -176,5 +187,6 @@ export default {
176187
delTool,
177188
addInternalTool,
178189
addStoreTool,
179-
updateStoreTool
190+
updateStoreTool,
191+
postToolTestConnection
180192
}

ui/src/api/tool/tool.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ const putTool: (tool_id: string, data: toolData, loading?: Ref<boolean>) => Prom
7575
return put(`${prefix.value}/${tool_id}`, data, undefined, loading)
7676
}
7777

78+
/**
79+
* @param 参数
80+
*/
81+
const postToolTestConnection: (data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (
82+
data,
83+
loading,
84+
) => {
85+
return post(`${prefix.value}/test_connection`, data, undefined, loading)
86+
}
87+
88+
7889
/**
7990
* 获取工具详情
8091
* @param tool_id 工具id
@@ -184,5 +195,6 @@ export default {
184195
delTool,
185196
addInternalTool,
186197
addStoreTool,
187-
updateStoreTool
198+
updateStoreTool,
199+
postToolTestConnection
188200
}

ui/src/views/tool/McpToolFormDrawer.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898

9999
<template #footer>
100100
<div>
101+
<el-button :loading="loading" @click="testConnection">{{ $t('views.system.test') }}</el-button>
101102
<el-button :loading="loading" @click="visible = false">{{ $t('common.cancel') }}</el-button>
102103
<el-button
103104
type="primary"
@@ -275,6 +276,21 @@ const submit = async (formEl: FormInstance | undefined) => {
275276
})
276277
}
277278
279+
function testConnection() {
280+
if (!form.value.code) {
281+
return
282+
}
283+
loading.value = true
284+
loadSharedApi({ type: 'tool', systemType: apiType.value })
285+
.postToolTestConnection({ code: form.value.code }, loading)
286+
.then(() => {
287+
MsgSuccess(t('views.system.testSuccess'))
288+
})
289+
.finally(() => {
290+
loading.value = false
291+
})
292+
}
293+
278294
const open = (data: any) => {
279295
if (data) {
280296
isEdit.value = data?.id ? true : false

0 commit comments

Comments
 (0)