feat: add TestConnection API endpoint and corresponding frontend functionality
parent
2735540dd6
commit
fc05d26eaf
|
|
@ -5,11 +5,11 @@ import json
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import re
|
import re
|
||||||
import requests
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
|
import requests
|
||||||
import uuid_utils.compat as uuid
|
import uuid_utils.compat as uuid
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
@ -356,9 +356,6 @@ class ToolSerializer(serializers.Serializer):
|
||||||
ToolCreateRequest(data=instance).is_valid(raise_exception=True)
|
ToolCreateRequest(data=instance).is_valid(raise_exception=True)
|
||||||
# 校验代码是否包括禁止的关键字
|
# 校验代码是否包括禁止的关键字
|
||||||
ToolExecutor().validate_banned_keywords(instance.get('code', ''))
|
ToolExecutor().validate_banned_keywords(instance.get('code', ''))
|
||||||
# 校验mcp json
|
|
||||||
if instance.get('tool_type') == ToolType.MCP.value:
|
|
||||||
validate_mcp_config(json.loads(instance.get('code')))
|
|
||||||
|
|
||||||
tool_id = uuid.uuid7()
|
tool_id = uuid.uuid7()
|
||||||
Tool(
|
Tool(
|
||||||
|
|
@ -386,6 +383,18 @@ class ToolSerializer(serializers.Serializer):
|
||||||
'id': tool_id, 'workspace_id': self.data.get('workspace_id')
|
'id': tool_id, 'workspace_id': self.data.get('workspace_id')
|
||||||
}).one()
|
}).one()
|
||||||
|
|
||||||
|
class TestConnection(serializers.Serializer):
|
||||||
|
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
|
||||||
|
code = serializers.CharField(required=True, label=_('tool content'))
|
||||||
|
|
||||||
|
def test_connection(self):
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
# 校验代码是否包括禁止的关键字
|
||||||
|
ToolExecutor().validate_banned_keywords(self.data.get('code', ''))
|
||||||
|
# 校验mcp json
|
||||||
|
validate_mcp_config(json.loads(self.data.get('code')))
|
||||||
|
return True
|
||||||
|
|
||||||
class Debug(serializers.Serializer):
|
class Debug(serializers.Serializer):
|
||||||
user_id = serializers.UUIDField(required=True, label=_('user id'))
|
user_id = serializers.UUIDField(required=True, label=_('user id'))
|
||||||
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
|
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
|
||||||
|
|
@ -475,9 +484,7 @@ class ToolSerializer(serializers.Serializer):
|
||||||
ToolEditRequest(data=instance).is_valid(raise_exception=True)
|
ToolEditRequest(data=instance).is_valid(raise_exception=True)
|
||||||
# 校验代码是否包括禁止的关键字
|
# 校验代码是否包括禁止的关键字
|
||||||
ToolExecutor().validate_banned_keywords(instance.get('code', ''))
|
ToolExecutor().validate_banned_keywords(instance.get('code', ''))
|
||||||
# 校验mcp json
|
|
||||||
if instance.get('tool_type') == ToolType.MCP.value:
|
|
||||||
validate_mcp_config(json.loads(instance.get('code')))
|
|
||||||
|
|
||||||
if not QuerySet(Tool).filter(id=self.data.get('id')).exists():
|
if not QuerySet(Tool).filter(id=self.data.get('id')).exists():
|
||||||
raise serializers.ValidationError(_('Tool not found'))
|
raise serializers.ValidationError(_('Tool not found'))
|
||||||
|
|
@ -755,7 +762,8 @@ class ToolSerializer(serializers.Serializer):
|
||||||
versions = tool.get('versions', [])
|
versions = tool.get('versions', [])
|
||||||
tool['label'] = tag_dict[tool.get('tags')[0]] if tool.get('tags') else ''
|
tool['label'] = tag_dict[tool.get('tags')[0]] if tool.get('tags') else ''
|
||||||
tool['version'] = next(
|
tool['version'] = next(
|
||||||
(version.get('name') for version in versions if version.get('downloadUrl') == tool['downloadUrl']),
|
(version.get('name') for version in versions if
|
||||||
|
version.get('downloadUrl') == tool['downloadUrl']),
|
||||||
)
|
)
|
||||||
filter_apps.append(tool)
|
filter_apps.append(tool)
|
||||||
|
|
||||||
|
|
@ -836,7 +844,8 @@ class ToolSerializer(serializers.Serializer):
|
||||||
raise AppApiException(500, _('Tool does not exist'))
|
raise AppApiException(500, _('Tool does not exist'))
|
||||||
# 查找匹配的版本名称
|
# 查找匹配的版本名称
|
||||||
version_name = next(
|
version_name = next(
|
||||||
(version.get('name') for version in self.data.get('versions') if version.get('downloadUrl') == self.data.get('download_url')),
|
(version.get('name') for version in self.data.get('versions') if
|
||||||
|
version.get('downloadUrl') == self.data.get('download_url')),
|
||||||
)
|
)
|
||||||
res = requests.get(self.data.get('download_url'), timeout=5)
|
res = requests.get(self.data.get('download_url'), timeout=5)
|
||||||
tool_data = RestrictedUnpickler(io.BytesIO(res.content)).load().tool
|
tool_data = RestrictedUnpickler(io.BytesIO(res.content)).load().tool
|
||||||
|
|
@ -855,7 +864,6 @@ class ToolSerializer(serializers.Serializer):
|
||||||
return ToolModelSerializer(tool).data
|
return ToolModelSerializer(tool).data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ToolTreeSerializer(serializers.Serializer):
|
class ToolTreeSerializer(serializers.Serializer):
|
||||||
class Query(serializers.Serializer):
|
class Query(serializers.Serializer):
|
||||||
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
|
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ urlpatterns = [
|
||||||
path('workspace/<str:workspace_id>/tool/pylint', views.ToolView.Pylint.as_view()),
|
path('workspace/<str:workspace_id>/tool/pylint', views.ToolView.Pylint.as_view()),
|
||||||
path('workspace/<str:workspace_id>/tool/debug', views.ToolView.Debug.as_view()),
|
path('workspace/<str:workspace_id>/tool/debug', views.ToolView.Debug.as_view()),
|
||||||
path('workspace/<str:workspace_id>/tool/tool_list', views.ToolView.Query.as_view()),
|
path('workspace/<str:workspace_id>/tool/tool_list', views.ToolView.Query.as_view()),
|
||||||
|
path('workspace/<str:workspace_id>/tool/test_connection', views.ToolView.TestConnection.as_view()),
|
||||||
path('workspace/<str:workspace_id>/tool/<str:tool_id>', views.ToolView.Operate.as_view()),
|
path('workspace/<str:workspace_id>/tool/<str:tool_id>', views.ToolView.Operate.as_view()),
|
||||||
path('workspace/<str:workspace_id>/tool/<str:tool_id>/edit_icon', views.ToolView.EditIcon.as_view()),
|
path('workspace/<str:workspace_id>/tool/<str:tool_id>/edit_icon', views.ToolView.EditIcon.as_view()),
|
||||||
path('workspace/<str:workspace_id>/tool/<str:tool_id>/export', views.ToolView.Export.as_view()),
|
path('workspace/<str:workspace_id>/tool/<str:tool_id>/export', views.ToolView.Export.as_view()),
|
||||||
|
|
|
||||||
|
|
@ -360,6 +360,31 @@ class ToolView(APIView):
|
||||||
'image': request.FILES.get('file')
|
'image': request.FILES.get('file')
|
||||||
}).edit(request.data))
|
}).edit(request.data))
|
||||||
|
|
||||||
|
class TestConnection(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
methods=['POST'],
|
||||||
|
description=_("Test tool connection"),
|
||||||
|
summary=_("Test tool connection"),
|
||||||
|
operation_id=_("Test tool connection"), # type: ignore
|
||||||
|
request=ToolReadAPI.get_request(),
|
||||||
|
responses=ToolReadAPI.get_response(),
|
||||||
|
tags=[_("Tool")] # type: ignore
|
||||||
|
)
|
||||||
|
@has_permissions(
|
||||||
|
PermissionConstants.TOOL_CREATE.get_workspace_permission(),
|
||||||
|
PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(),
|
||||||
|
PermissionConstants.TOOL_EDIT.get_workspace_permission(),
|
||||||
|
PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(),
|
||||||
|
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()
|
||||||
|
)
|
||||||
|
def post(self, request: Request, workspace_id: str):
|
||||||
|
return result.success(ToolSerializer.TestConnection(data={
|
||||||
|
'workspace_id': workspace_id,
|
||||||
|
'code': request.data.get('code'),
|
||||||
|
}).test_connection())
|
||||||
|
|
||||||
class InternalTool(APIView):
|
class InternalTool(APIView):
|
||||||
authentication_classes = [TokenAuth]
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,17 @@ const putTool: (tool_id: string, data: toolData, loading?: Ref<boolean>) => Prom
|
||||||
return put(`${prefix}/${tool_id}`, data, undefined, loading)
|
return put(`${prefix}/${tool_id}`, data, undefined, loading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param 参数
|
||||||
|
*/
|
||||||
|
const postToolTestConnection: (data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
) => {
|
||||||
|
return post(`${prefix}/test_connection`, data, undefined, loading)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取工具详情
|
* 获取工具详情
|
||||||
* @param tool_id 工具id
|
* @param tool_id 工具id
|
||||||
|
|
@ -176,5 +187,6 @@ export default {
|
||||||
delTool,
|
delTool,
|
||||||
addInternalTool,
|
addInternalTool,
|
||||||
addStoreTool,
|
addStoreTool,
|
||||||
updateStoreTool
|
updateStoreTool,
|
||||||
|
postToolTestConnection
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,17 @@ const putTool: (tool_id: string, data: toolData, loading?: Ref<boolean>) => Prom
|
||||||
return put(`${prefix.value}/${tool_id}`, data, undefined, loading)
|
return put(`${prefix.value}/${tool_id}`, data, undefined, loading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param 参数
|
||||||
|
*/
|
||||||
|
const postToolTestConnection: (data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
) => {
|
||||||
|
return post(`${prefix.value}/test_connection`, data, undefined, loading)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取工具详情
|
* 获取工具详情
|
||||||
* @param tool_id 工具id
|
* @param tool_id 工具id
|
||||||
|
|
@ -184,5 +195,6 @@ export default {
|
||||||
delTool,
|
delTool,
|
||||||
addInternalTool,
|
addInternalTool,
|
||||||
addStoreTool,
|
addStoreTool,
|
||||||
updateStoreTool
|
updateStoreTool,
|
||||||
|
postToolTestConnection
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div>
|
<div>
|
||||||
|
<el-button :loading="loading" @click="testConnection">{{ $t('views.system.test') }}</el-button>
|
||||||
<el-button :loading="loading" @click="visible = false">{{ $t('common.cancel') }}</el-button>
|
<el-button :loading="loading" @click="visible = false">{{ $t('common.cancel') }}</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|
@ -275,6 +276,21 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testConnection() {
|
||||||
|
if (!form.value.code) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading.value = true
|
||||||
|
loadSharedApi({ type: 'tool', systemType: apiType.value })
|
||||||
|
.postToolTestConnection({ code: form.value.code }, loading)
|
||||||
|
.then(() => {
|
||||||
|
MsgSuccess(t('views.system.testSuccess'))
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const open = (data: any) => {
|
const open = (data: any) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
isEdit.value = data?.id ? true : false
|
isEdit.value = data?.id ? true : false
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue