mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
feat: add Appstore tool retrieval and store tool API endpoint
This commit is contained in:
parent
a5d046c26b
commit
e988cbca91
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 5.2.4 on 2025-09-09 04:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tools', '0002_alter_tool_tool_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='tool',
|
||||
name='template_id',
|
||||
field=models.CharField(db_index=True, default=None, max_length=128, null=True, verbose_name='模版id'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tool',
|
||||
name='version',
|
||||
field=models.CharField(default=None, max_length=64, null=True, verbose_name='版本号'),
|
||||
),
|
||||
]
|
||||
|
|
@ -48,11 +48,12 @@ class Tool(AppModelMixin):
|
|||
default=ToolScope.WORKSPACE, db_index=True)
|
||||
tool_type = models.CharField(max_length=20, verbose_name='工具类型', choices=ToolType.choices,
|
||||
default=ToolType.CUSTOM, db_index=True)
|
||||
template_id = models.UUIDField(max_length=128, verbose_name="模版id", null=True, default=None, db_index=True)
|
||||
template_id = models.CharField(max_length=128, verbose_name="模版id", null=True, default=None, db_index=True)
|
||||
folder = models.ForeignKey(ToolFolder, on_delete=models.DO_NOTHING, verbose_name="文件夹id", default='default')
|
||||
workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True)
|
||||
init_params = models.CharField(max_length=102400, verbose_name="初始化参数", null=True)
|
||||
label = models.CharField(max_length=128, verbose_name="标签", null=True, db_index=True)
|
||||
version = models.CharField(max_length=64, verbose_name="版本号", null=True, default=None)
|
||||
|
||||
class Meta:
|
||||
db_table = "tool"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import json
|
|||
import os
|
||||
import pickle
|
||||
import re
|
||||
import requests
|
||||
import tempfile
|
||||
import zipfile
|
||||
from typing import Dict
|
||||
|
||||
import uuid_utils.compat as uuid
|
||||
|
|
@ -124,7 +127,7 @@ class ToolModelSerializer(serializers.ModelSerializer):
|
|||
model = Tool
|
||||
fields = ['id', 'name', 'icon', 'desc', 'code', 'input_field_list', 'init_field_list', 'init_params',
|
||||
'scope', 'is_active', 'user_id', 'template_id', 'workspace_id', 'folder_id', 'tool_type', 'label',
|
||||
'create_time', 'update_time']
|
||||
'version', 'create_time', 'update_time']
|
||||
|
||||
|
||||
class ToolExportModelSerializer(serializers.ModelSerializer):
|
||||
|
|
@ -705,6 +708,7 @@ class ToolSerializer(serializers.Serializer):
|
|||
tool_type=ToolType.CUSTOM,
|
||||
folder_id=instance.get('folder_id', self.data.get('workspace_id')),
|
||||
template_id=internal_tool.id,
|
||||
label=internal_tool.label,
|
||||
is_active=False
|
||||
)
|
||||
tool.save()
|
||||
|
|
@ -718,6 +722,140 @@ class ToolSerializer(serializers.Serializer):
|
|||
|
||||
return ToolModelSerializer(tool).data
|
||||
|
||||
class StoreTool(serializers.Serializer):
|
||||
user_id = serializers.UUIDField(required=True, label=_("User ID"))
|
||||
name = serializers.CharField(required=False, label=_("tool name"), allow_null=True, allow_blank=True)
|
||||
|
||||
def get_appstore_tools(self):
|
||||
self.is_valid(raise_exception=True)
|
||||
# 下载zip文件
|
||||
try:
|
||||
res = requests.get('https://apps-assets.fit2cloud.com/stable/maxkb.json.zip', timeout=5)
|
||||
res.raise_for_status()
|
||||
# 创建临时文件保存zip
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix='.zip') as temp_zip:
|
||||
temp_zip.write(res.content)
|
||||
temp_zip_path = temp_zip.name
|
||||
|
||||
try:
|
||||
# 解压zip文件
|
||||
with zipfile.ZipFile(temp_zip_path, 'r') as zip_ref:
|
||||
# 获取zip中的第一个文件(假设只有一个json文件)
|
||||
json_filename = zip_ref.namelist()[0]
|
||||
json_content = zip_ref.read(json_filename)
|
||||
|
||||
# 将json转换为字典
|
||||
tool_store = json.loads(json_content.decode('utf-8'))
|
||||
tag_dict = {tag['name']: tag['key'] for tag in tool_store['additionalProperties']['tags']}
|
||||
filter_apps = []
|
||||
for tool in tool_store['apps']:
|
||||
if self.data.get('name', '') != '':
|
||||
if self.data.get('name').lower() not in tool.get('name', '').lower():
|
||||
continue
|
||||
versions = tool.get('versions', [])
|
||||
tool['label'] = tag_dict[tool.get('tags')[0]] if tool.get('tags') else ''
|
||||
tool['version'] = next(
|
||||
(version.get('name') for version in versions if version.get('downloadUrl') == tool['downloadUrl']),
|
||||
)
|
||||
filter_apps.append(tool)
|
||||
|
||||
tool_store['apps'] = filter_apps
|
||||
return tool_store
|
||||
finally:
|
||||
# 清理临时文件
|
||||
os.unlink(temp_zip_path)
|
||||
except requests.RequestException as e:
|
||||
maxkb_logger.error(f"fetch appstore tools error: {e}")
|
||||
return []
|
||||
|
||||
class AddStoreTool(serializers.Serializer):
|
||||
user_id = serializers.UUIDField(required=True, label=_("User ID"))
|
||||
workspace_id = serializers.CharField(required=True, label=_("workspace id"))
|
||||
tool_id = serializers.CharField(required=True, label=_("tool id"))
|
||||
|
||||
def add(self, instance: Dict, with_valid=True):
|
||||
if with_valid:
|
||||
self.is_valid(raise_exception=True)
|
||||
AddInternalToolRequest(data=instance).is_valid(raise_exception=True)
|
||||
|
||||
versions = instance.get('versions', [])
|
||||
download_url = instance.get('download_url')
|
||||
# 查找匹配的版本名称
|
||||
version_name = next(
|
||||
(version.get('name') for version in versions if version.get('downloadUrl') == download_url),
|
||||
)
|
||||
res = requests.get(download_url, timeout=5)
|
||||
tool_data = RestrictedUnpickler(io.BytesIO(res.content)).load().tool
|
||||
tool_id = uuid.uuid7()
|
||||
tool = Tool(
|
||||
id=tool_id,
|
||||
name=tool_data.get('name'),
|
||||
desc=tool_data.get('desc'),
|
||||
code=tool_data.get('code'),
|
||||
user_id=self.data.get('user_id'),
|
||||
icon=instance.get('icon', ''),
|
||||
workspace_id=self.data.get('workspace_id'),
|
||||
input_field_list=tool_data.get('input_field_list', []),
|
||||
init_field_list=tool_data.get('init_field_list', []),
|
||||
scope=ToolScope.WORKSPACE,
|
||||
tool_type=ToolType.CUSTOM,
|
||||
folder_id=instance.get('folder_id', self.data.get('workspace_id')),
|
||||
template_id=self.data.get('tool_id'),
|
||||
label=instance.get('label'),
|
||||
version=version_name,
|
||||
is_active=False
|
||||
)
|
||||
tool.save()
|
||||
|
||||
# 自动授权给创建者
|
||||
UserResourcePermissionSerializer(data={
|
||||
'workspace_id': self.data.get('workspace_id'),
|
||||
'user_id': self.data.get('user_id'),
|
||||
'auth_target_type': AuthTargetType.TOOL.value
|
||||
}).auth_resource(str(tool_id))
|
||||
try:
|
||||
requests.get(instance.get('download_callback_url'), timeout=5)
|
||||
except Exception as e:
|
||||
maxkb_logger.error(f"callback appstore tool download error: {e}")
|
||||
return ToolModelSerializer(tool).data
|
||||
|
||||
class UpdateStoreTool(serializers.Serializer):
|
||||
user_id = serializers.UUIDField(required=True, label=_("User ID"))
|
||||
workspace_id = serializers.CharField(required=True, label=_("workspace id"))
|
||||
tool_id = serializers.UUIDField(required=True, label=_("tool id"))
|
||||
download_url = serializers.CharField(required=True, label=_("download url"))
|
||||
download_callback_url = serializers.CharField(required=True, label=_("download callback url"))
|
||||
icon = serializers.CharField(required=True, label=_("icon"), allow_null=True, allow_blank=True)
|
||||
versions = serializers.ListField(required=True, label=_("versions"), child=serializers.DictField())
|
||||
|
||||
def update_tool(self, with_valid=True):
|
||||
if with_valid:
|
||||
self.is_valid(raise_exception=True)
|
||||
tool = QuerySet(Tool).filter(id=self.data.get('tool_id')).first()
|
||||
if tool is None:
|
||||
raise AppApiException(500, _('Tool does not exist'))
|
||||
# 查找匹配的版本名称
|
||||
version_name = next(
|
||||
(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)
|
||||
tool_data = RestrictedUnpickler(io.BytesIO(res.content)).load().tool
|
||||
tool.name = tool_data.get('name')
|
||||
tool.desc = tool_data.get('desc')
|
||||
tool.code = tool_data.get('code')
|
||||
tool.input_field_list = tool_data.get('input_field_list', [])
|
||||
tool.init_field_list = tool_data.get('init_field_list', [])
|
||||
tool.icon = self.data.get('icon', tool.icon)
|
||||
tool.version = version_name
|
||||
# tool.is_active = False
|
||||
tool.save()
|
||||
try:
|
||||
requests.get(self.data.get('download_callback_url'), timeout=5)
|
||||
except Exception as e:
|
||||
maxkb_logger.error(f"callback appstore tool download error: {e}")
|
||||
return ToolModelSerializer(tool).data
|
||||
|
||||
|
||||
|
||||
class ToolTreeSerializer(serializers.Serializer):
|
||||
class Query(serializers.Serializer):
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from (select tool."id"::text,
|
|||
tool."update_time",
|
||||
tool.init_field_list,
|
||||
tool.input_field_list,
|
||||
tool.version,
|
||||
tool."is_active"
|
||||
from tool
|
||||
left join "user" on "user".id = user_id ${tool_query_set}
|
||||
|
|
@ -37,6 +38,7 @@ from (select tool."id"::text,
|
|||
tool_folder."update_time",
|
||||
'[]'::jsonb as init_field_list,
|
||||
'[]'::jsonb as input_field_list,
|
||||
'' as version,
|
||||
'true' as "is_active"
|
||||
from tool_folder
|
||||
left join "user" on "user".id = user_id ${folder_query_set}) temp
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ FROM (SELECT tool."id"::text,
|
|||
tool."update_time",
|
||||
tool.init_field_list,
|
||||
tool.input_field_list,
|
||||
tool.version,
|
||||
tool."is_active"
|
||||
FROM (SELECT tool.*
|
||||
FROM tool tool ${tool_query_set}
|
||||
|
|
@ -43,6 +44,7 @@ FROM (SELECT tool."id"::text,
|
|||
tool_folder."update_time",
|
||||
'[]'::jsonb AS init_field_list,
|
||||
'[]'::jsonb AS input_field_list,
|
||||
'' AS version,
|
||||
'true' AS "is_active"
|
||||
FROM tool_folder
|
||||
LEFT JOIN "user" ON "user".id = user_id ${folder_query_set}) temp
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ FROM (SELECT tool."id"::text,
|
|||
tool."update_time",
|
||||
tool.init_field_list,
|
||||
tool.input_field_list,
|
||||
tool.version,
|
||||
tool."is_active"
|
||||
FROM (SELECT tool.*
|
||||
FROM tool tool ${tool_query_set}
|
||||
|
|
@ -53,6 +54,7 @@ FROM (SELECT tool."id"::text,
|
|||
tool_folder."update_time",
|
||||
'[]'::jsonb AS init_field_list,
|
||||
'[]'::jsonb AS input_field_list,
|
||||
'' AS version,
|
||||
'true' AS "is_active"
|
||||
FROM tool_folder
|
||||
LEFT JOIN "user" ON "user".id = user_id ${folder_query_set}) temp
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ app_name = "tool"
|
|||
# @formatter:off
|
||||
urlpatterns = [
|
||||
path('workspace/internal/tool', views.ToolView.InternalTool.as_view()),
|
||||
path('workspace/store/tool', views.ToolView.StoreTool.as_view()),
|
||||
path('workspace/<str:workspace_id>/tool', views.ToolView.as_view()),
|
||||
path('workspace/<str:workspace_id>/tool/import', views.ToolView.Import.as_view()),
|
||||
path('workspace/<str:workspace_id>/tool/pylint', views.ToolView.Pylint.as_view()),
|
||||
|
|
@ -15,5 +16,7 @@ urlpatterns = [
|
|||
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>/add_internal_tool', views.ToolView.AddInternalTool.as_view()),
|
||||
path('workspace/<str:workspace_id>/tool/<str:tool_id>/add_store_tool', views.ToolView.AddStoreTool.as_view()),
|
||||
path('workspace/<str:workspace_id>/tool/<str:tool_id>/update_store_tool', views.ToolView.UpdateStoreTool.as_view()),
|
||||
path('workspace/<str:workspace_id>/tool/<int:current_page>/<int:page_size>', views.ToolView.Page.as_view()),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -407,3 +407,84 @@ class ToolView(APIView):
|
|||
'user_id': request.user.id,
|
||||
'workspace_id': workspace_id
|
||||
}).add(request.data))
|
||||
|
||||
class StoreTool(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@extend_schema(
|
||||
methods=['GET'],
|
||||
description=_("Get Appstore tools"),
|
||||
summary=_("Get Appstore tools"),
|
||||
operation_id=_("Get Appstore tools"), # type: ignore
|
||||
responses=GetInternalToolAPI.get_response(),
|
||||
tags=[_("Tool")] # type: ignore
|
||||
)
|
||||
def get(self, request: Request):
|
||||
return result.success(ToolSerializer.StoreTool(data={
|
||||
'user_id': request.user.id,
|
||||
'name': request.query_params.get('name', ''),
|
||||
}).get_appstore_tools())
|
||||
|
||||
class AddStoreTool(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@extend_schema(
|
||||
methods=['POST'],
|
||||
description=_("Add Appstore tool"),
|
||||
summary=_("Add Appstore tool"),
|
||||
operation_id=_("Add Appstore tool"), # type: ignore
|
||||
parameters=AddInternalToolAPI.get_parameters(),
|
||||
request=AddInternalToolAPI.get_request(),
|
||||
responses=AddInternalToolAPI.get_response(),
|
||||
tags=[_("Tool")] # type: ignore
|
||||
)
|
||||
@has_permissions(
|
||||
PermissionConstants.TOOL_CREATE.get_workspace_permission(),
|
||||
PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(),
|
||||
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
|
||||
RoleConstants.USER.get_workspace_role(),
|
||||
)
|
||||
@log(
|
||||
menu='Tool', operate="Add Appstore tool",
|
||||
get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')),
|
||||
)
|
||||
def post(self, request: Request, tool_id: str, workspace_id: str):
|
||||
return result.success(ToolSerializer.AddStoreTool(data={
|
||||
'tool_id': tool_id,
|
||||
'user_id': request.user.id,
|
||||
'workspace_id': workspace_id,
|
||||
}).add(request.data))
|
||||
|
||||
class UpdateStoreTool(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@extend_schema(
|
||||
methods=['POST'],
|
||||
description=_("Update Appstore tool"),
|
||||
summary=_("Update Appstore tool"),
|
||||
operation_id=_("Update Appstore tool"), # type: ignore
|
||||
parameters=AddInternalToolAPI.get_parameters(),
|
||||
request=AddInternalToolAPI.get_request(),
|
||||
responses=AddInternalToolAPI.get_response(),
|
||||
tags=[_("Tool")] # type: ignore
|
||||
)
|
||||
@has_permissions(
|
||||
PermissionConstants.TOOL_CREATE.get_workspace_permission(),
|
||||
PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(),
|
||||
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
|
||||
RoleConstants.USER.get_workspace_role(),
|
||||
)
|
||||
@log(
|
||||
menu='Tool', operate="Update Appstore tool",
|
||||
get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')),
|
||||
)
|
||||
def post(self, request: Request, tool_id: str, workspace_id: str):
|
||||
return result.success(ToolSerializer.UpdateStoreTool(data={
|
||||
'tool_id': tool_id,
|
||||
'user_id': request.user.id,
|
||||
'workspace_id': workspace_id,
|
||||
'download_url': request.data.get('download_url'),
|
||||
'download_callback_url': request.data.get('download_callback_url'),
|
||||
'icon': request.data.get('icon'),
|
||||
'versions': request.data.get('versions'),
|
||||
}).update_tool(request.data))
|
||||
|
|
@ -142,6 +142,24 @@ const addInternalTool: (
|
|||
return post(`${prefix}/${tool_id}/add_internal_tool`, param, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具商店
|
||||
*/
|
||||
const addStoreTool: (
|
||||
tool_id: string,
|
||||
param: AddInternalToolParam,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<any>> = (tool_id, param, loading) => {
|
||||
return post(`${prefix}/${tool_id}/add_store_tool`, param, undefined, loading)
|
||||
}
|
||||
|
||||
const updateStoreTool: (
|
||||
tool_id: string,
|
||||
param: AddInternalToolParam,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<any>> = (tool_id, param, loading) => {
|
||||
return post(`${prefix}/${tool_id}/update_store_tool`, param, undefined, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
getToolList,
|
||||
|
|
@ -156,5 +174,7 @@ export default {
|
|||
exportTool,
|
||||
putToolIcon,
|
||||
delTool,
|
||||
addInternalTool
|
||||
addInternalTool,
|
||||
addStoreTool,
|
||||
updateStoreTool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,16 @@ const getInternalToolList: (param?: any, loading?: Ref<boolean>) => Promise<Resu
|
|||
return get('/workspace/internal/tool', param, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具商店列表
|
||||
*/
|
||||
const getStoreToolList: (param?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
param,
|
||||
loading,
|
||||
) => {
|
||||
return get('/workspace/store/tool', param, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具商店-添加系统内置
|
||||
*/
|
||||
|
|
@ -33,7 +43,20 @@ const addInternalTool: (
|
|||
return post(`${prefix.value}/${tool_id}/add_internal_tool`, param, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具商店-添加
|
||||
*/
|
||||
const addStoreTool: (
|
||||
tool_id: string,
|
||||
param: AddInternalToolParam,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<any>> = (tool_id, param, loading) => {
|
||||
return post(`${prefix.value}/${tool_id}/add_store_tool`, param, undefined, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
getInternalToolList,
|
||||
getStoreToolList,
|
||||
addInternalTool,
|
||||
addStoreTool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,6 +148,27 @@ const addInternalTool: (
|
|||
return post(`${prefix.value}/${tool_id}/add_internal_tool`, param, undefined, loading)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 工具商店-添加
|
||||
*/
|
||||
const addStoreTool: (
|
||||
tool_id: string,
|
||||
param: AddInternalToolParam,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<any>> = (tool_id, param, loading) => {
|
||||
return post(`${prefix.value}/${tool_id}/add_store_tool`, param, undefined, loading)
|
||||
}
|
||||
|
||||
const updateStoreTool: (
|
||||
tool_id: string,
|
||||
param: AddInternalToolParam,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<any>> = (tool_id, param, loading) => {
|
||||
return post(`${prefix.value}/${tool_id}/update_store_tool`, param, undefined, loading)
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
getToolList,
|
||||
getAllToolList,
|
||||
|
|
@ -162,4 +183,6 @@ export default {
|
|||
putToolIcon,
|
||||
delTool,
|
||||
addInternalTool,
|
||||
addStoreTool,
|
||||
updateStoreTool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ export default {
|
|||
developer: 'Developer',
|
||||
communication: 'Communication',
|
||||
searchResult: '{count} search results for',
|
||||
confirmTip: 'Are you sure to update tool: ',
|
||||
updateStoreToolMessage: 'Updating tools may affect resources in use, so proceed with caution.',
|
||||
},
|
||||
searchBar: {
|
||||
placeholder: 'Search by tool name',
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ export default {
|
|||
developer: '开发者',
|
||||
communication: '通信',
|
||||
searchResult: '的搜索结果 {count} 个',
|
||||
confirmTip: '是否更新工具:',
|
||||
updateStoreToolMessage: '更新工具可能会影响正在使用的资源,请谨慎操作。',
|
||||
},
|
||||
delete: {
|
||||
confirmTitle: '是否刪除工具',
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ export default {
|
|||
developer: '開發者',
|
||||
communication: '通信',
|
||||
searchResult: '的搜索結果 {count} 個',
|
||||
confirmTip: '是否更新工具:',
|
||||
updateStoreToolMessage: '更新工具可能會影響正在使用的資源,請謹慎操作。',
|
||||
},
|
||||
searchBar: {
|
||||
placeholder: '按工具名稱搜尋',
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
<el-table-column prop="tool_type" :label="$t('views.system.resource_management.type')">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.tool_type === 'MCP'"> MCP </span>
|
||||
<span v-else-if="scope.row.version">{{ $t('views.tool.toolStore.title') }}</span>
|
||||
<span v-else>
|
||||
{{
|
||||
$t(
|
||||
|
|
|
|||
|
|
@ -176,6 +176,14 @@
|
|||
</el-avatar>
|
||||
<ToolIcon v-else :size="32" :type="item?.tool_type" />
|
||||
</template>
|
||||
<template #title>
|
||||
<div>
|
||||
{{ item.name }}
|
||||
<el-tag v-if="item.version" class="ml-4">
|
||||
{{ item.version }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #subTitle>
|
||||
<el-text class="color-secondary lighter" size="small">
|
||||
{{ $t('common.creator') }}: {{ item.nick_name }}
|
||||
|
|
@ -185,6 +193,12 @@
|
|||
<el-tag v-if="isShared" type="info" class="info-tag">
|
||||
{{ t('views.shared.title') }}
|
||||
</el-tag>
|
||||
<el-button text @click.stop v-if="
|
||||
showUpdateStoreTool(item) && !isShared && permissionPrecise.edit(item.id)
|
||||
" @click="updateStoreTool(item)"
|
||||
>
|
||||
<el-icon><Refresh /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
|
|
@ -353,6 +367,7 @@ import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
|||
import permissionMap from '@/permission'
|
||||
import useStore from '@/stores'
|
||||
import { t } from '@/locales'
|
||||
import ToolStoreApi from "@/api/tool/store.ts";
|
||||
const route = useRoute()
|
||||
const { folder, user, tool } = useStore()
|
||||
onBeforeRouteLeave((to, from) => {
|
||||
|
|
@ -626,6 +641,57 @@ function confirmAddInternalTool(data?: any, isEdit?: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
const storeTools = ref<any[]>([])
|
||||
function getStoreToolList() {
|
||||
ToolStoreApi.getStoreToolList({ name: '' }, loading)
|
||||
.then((res: any) => {
|
||||
storeTools.value = res.data.apps
|
||||
})
|
||||
}
|
||||
|
||||
function showUpdateStoreTool(item: any) {
|
||||
for (const tool of storeTools.value) {
|
||||
if (tool.id === item.template_id && tool.version !== item.version) {
|
||||
item.downloadUrl = tool.downloadUrl
|
||||
item.downloadCallbackUrl = tool.downloadCallbackUrl
|
||||
item.icon = tool.icon
|
||||
item.versions = tool.versions
|
||||
item.label = tool.label
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateStoreTool(item: any) {
|
||||
MsgConfirm(t('views.tool.toolStore.confirmTip') + item.name,
|
||||
t('views.tool.toolStore.updateStoreToolMessage'), {
|
||||
cancelButtonText: t('common.cancel'),
|
||||
confirmButtonText: t('common.confirm'),
|
||||
})
|
||||
.then(() => {
|
||||
const obj = {
|
||||
download_url: item.downloadUrl,
|
||||
download_callback_url: item.downloadCallbackUrl,
|
||||
icon: item.icon,
|
||||
versions: item.versions,
|
||||
label: item.label
|
||||
}
|
||||
loadSharedApi({type: 'tool', systemType: apiType.value})
|
||||
.updateStoreTool(item.id, obj, loading)
|
||||
.then(async (res: any) => {
|
||||
if (res?.data) {
|
||||
tool.setToolList([])
|
||||
return user.profile()
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
getList()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
const elUploadRef = ref()
|
||||
function importTool(file: any) {
|
||||
const formData = new FormData()
|
||||
|
|
@ -745,6 +811,7 @@ onMounted(() => {
|
|||
.then((res: any) => {
|
||||
user_options.value = res.data
|
||||
})
|
||||
getStoreToolList()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@
|
|||
<h4 :id="titleId" class="medium">
|
||||
{{ $t('views.tool.toolStore.title') }}
|
||||
</h4>
|
||||
|
||||
<!-- <el-tag class="store-type default-tag">{{t('views.tool.toolStore.internal')}}</el-tag> -->
|
||||
<el-radio-group v-model="toolType" @change="radioChange" class="app-radio-button-group">
|
||||
<el-radio-button value="INTERNAL">{{ $t('views.tool.toolStore.internal') }}</el-radio-button>
|
||||
<el-radio-button value="APPSTORE">{{ $t('views.tool.toolStore.title') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<div class="flex align-center" style="margin-right: 28px;">
|
||||
<el-input v-model="searchValue" :placeholder="$t('common.search')" prefix-icon="Search" class="w-240 mr-8"
|
||||
|
|
@ -88,6 +90,7 @@ const dialogVisible = ref(false)
|
|||
const loading = ref(false)
|
||||
const searchValue = ref('')
|
||||
const folderId = ref('')
|
||||
const toolType = ref('INTERNAL')
|
||||
|
||||
const categories = ref<ToolCategory[]>([
|
||||
// 第一版不上
|
||||
|
|
@ -139,6 +142,14 @@ onBeforeMount(() => {
|
|||
})
|
||||
|
||||
async function getList() {
|
||||
if (toolType.value === 'INTERNAL') {
|
||||
await getInternalToolList()
|
||||
} else {
|
||||
await getStoreToolList()
|
||||
}
|
||||
}
|
||||
|
||||
async function getInternalToolList() {
|
||||
try {
|
||||
const res = await ToolStoreApi.getInternalToolList({ name: searchValue.value }, loading)
|
||||
if (searchValue.value.length) {
|
||||
|
|
@ -158,16 +169,36 @@ async function getList() {
|
|||
}
|
||||
}
|
||||
|
||||
async function getStoreToolList() {
|
||||
try {
|
||||
const res = await ToolStoreApi.getStoreToolList({ name: searchValue.value }, loading)
|
||||
const tags = res.data.additionalProperties.tags
|
||||
const storeTools = res.data.apps
|
||||
|
||||
categories.value = tags.map((tag: any) => ({
|
||||
id: tag.key,
|
||||
title: tag.name, // 国际化
|
||||
tools: storeTools.filter((tool: any) => tool.label === tag.key)
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
const internalDescDrawerRef = ref<InstanceType<typeof InternalDescDrawer>>()
|
||||
async function handleDetail(tool: any) {
|
||||
const index = tool.icon.replace('icon.png', 'detail.md')
|
||||
const response = await fetch(index)
|
||||
const content = await response.text()
|
||||
internalDescDrawerRef.value?.open(content, tool)
|
||||
if (toolType.value === 'INTERNAL') {
|
||||
const index = tool.icon.replace('icon.png', 'detail.md')
|
||||
const response = await fetch(index)
|
||||
const content = await response.text()
|
||||
internalDescDrawerRef.value?.open(content, tool)
|
||||
} else {
|
||||
internalDescDrawerRef.value?.open(tool.readMe, tool)
|
||||
}
|
||||
}
|
||||
|
||||
const addInternalToolDialogRef = ref<InstanceType<typeof AddInternalToolDialog>>()
|
||||
|
|
@ -177,13 +208,20 @@ function handleOpenAdd(data?: any, isEdit?: boolean) {
|
|||
|
||||
const addLoading = ref(false)
|
||||
async function handleAdd(tool: any) {
|
||||
if (toolType.value === 'INTERNAL') {
|
||||
await handleInternalAdd(tool)
|
||||
} else {
|
||||
await handleStoreAdd(tool)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleInternalAdd(tool: any) {
|
||||
try {
|
||||
await loadSharedApi({ type: 'tool', systemType: props.apiType })
|
||||
.addInternalTool(tool.id, { name: tool.name, folder_id: folderId.value }, addLoading)
|
||||
.then(() => {
|
||||
return user.profile()
|
||||
})
|
||||
// await ToolStoreApi.addInternalTool(tool.id, { name: tool.name, folder_id: folderId.value }, addLoading)
|
||||
emit('refresh')
|
||||
MsgSuccess(t('common.addSuccess'))
|
||||
dialogVisible.value = false
|
||||
|
|
@ -192,6 +230,34 @@ async function handleAdd(tool: any) {
|
|||
}
|
||||
}
|
||||
|
||||
async function handleStoreAdd(tool: any) {
|
||||
try {
|
||||
const obj = {
|
||||
name: tool.name,
|
||||
folder_id: folderId.value,
|
||||
download_url: tool.downloadUrl,
|
||||
download_callback_url: tool.downloadCallbackUrl,
|
||||
icon: tool.icon,
|
||||
versions: tool.versions,
|
||||
label: tool.label
|
||||
}
|
||||
await loadSharedApi({ type: 'tool', systemType: props.apiType })
|
||||
.addStoreTool(tool.id, obj, addLoading)
|
||||
.then(() => {
|
||||
return user.profile()
|
||||
})
|
||||
emit('refresh')
|
||||
MsgSuccess(t('common.addSuccess'))
|
||||
dialogVisible.value = false
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
function radioChange() {
|
||||
getList()
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
|
|
|||
Loading…
Reference in New Issue