diff --git a/apps/function_lib/migrations/0003_functionlib_function_type_functionlib_icon_and_more.py b/apps/function_lib/migrations/0003_functionlib_function_type_functionlib_icon_and_more.py index f7e1aa4cce5..c218651637a 100644 --- a/apps/function_lib/migrations/0003_functionlib_function_type_functionlib_icon_and_more.py +++ b/apps/function_lib/migrations/0003_functionlib_function_type_functionlib_icon_and_more.py @@ -66,6 +66,48 @@ def langsearch(query, apikey): else: raise Exception(f"API请求失败: {response.status_code}, 错误信息: {response.text}") return (response.text)', '{"{\\"name\\": \\"query\\", \\"type\\": \\"string\\", \\"source\\": \\"reference\\", \\"is_required\\": true}"}', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', TRUE, 'PUBLIC', 'INTERNAL', '/src/assets/fx/langsearch/icon.png', '[{"attrs": {"type": "password", "maxlength": 200, "minlength": 1, "show-password": true, "show-word-limit": true}, "field": "apikey", "label": "apikey", "required": true, "input_type": "PasswordInput", "props_info": {"rules": [{"message": "apikey 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "apikey长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}]', '', NULL); +INSERT INTO function_lib (create_time, update_time, id, name, "desc", code, input_field_list, user_id, is_active, permission_type, function_type, icon, init_field_list, init_params, template_id) VALUES ('2025-03-17 08:16:32.626245 +00:00', '2025-03-17 08:16:32.626308 +00:00', '22c21b76-0308-11f0-9694-5618c4394482', 'MySQL 查询', '', e' +def query_mysql(host,port, user, password, database, sql): + import pymysql + import json + from pymysql.cursors import DictCursor + + try: + # 创建连接 + db = pymysql.connect( + host=host, + port=int(port), + user=user, + password=password, + database=database, + cursorclass=DictCursor # 使用字典游标 + ) + + # 使用 cursor() 方法创建一个游标对象 cursor + cursor = db.cursor() + + # 使用 execute() 方法执行 SQL 查询 + cursor.execute(sql) + + # 使用 fetchall() 方法获取所有数据 + data = cursor.fetchall() + + # 处理 bytes 类型的数据 + for row in data: + for key, value in row.items(): + if isinstance(value, bytes): + row[key] = value.decode("utf-8") # 转换为字符串 + + # 将数据序列化为 JSON + json_data = json.dumps(data, ensure_ascii=False) + print(json_data) + return json_data + + # 关闭数据库连接 + db.close() + + except Exception as e: + print(f"Error while connecting to MySQL: {e}")', '{"{\"name\": \"sql\", \"type\": \"string\", \"source\": \"reference\", \"is_required\": true}"}', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', true, 'PUBLIC', 'INTERNAL', '/src/assets/fx/mysql/icon.png', '[{"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "host", "label": "host", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "host 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "host长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}, {"attrs": {"maxlength": 20, "minlength": 1, "show-word-limit": true}, "field": "port", "label": "port", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "port 为必填属性", "required": true}, {"max": 20, "min": 1, "message": "port长度在 1 到 20 个字符", "trigger": "blur"}]}, "default_value": "3306", "show_default_value": false}, {"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "user", "label": "user", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "user 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "user长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "root", "show_default_value": false}, {"attrs": {"type": "password", "maxlength": 200, "minlength": 1, "show-password": true, "show-word-limit": true}, "field": "password", "label": "password", "required": true, "input_type": "PasswordInput", "props_info": {"rules": [{"message": "password 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "password长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}, {"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "database", "label": "database", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "database 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "database长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}]', null, null); INSERT INTO function_lib (create_time, update_time, id, name, "desc", code, input_field_list, user_id, is_active, permission_type, function_type, icon, init_field_list, init_params, template_id) VALUES ('2025-03-17 07:37:54.620836 +00:00', '2025-03-17 07:37:54.620887 +00:00', 'bd1e8b88-0302-11f0-87bb-5618c4394482', 'PostgreSQL 查询', '', e'def queryPgSQL(dbname, user, password, host, port, query): import psycopg2 import json @@ -114,49 +156,7 @@ def default_serializer(obj): cursor.close() if conn: conn.close() -', '{"{\"name\": \"dbname\", \"type\": \"string\", \"source\": \"custom\", \"is_required\": true}","{\"name\": \"user\", \"type\": \"string\", \"source\": \"custom\", \"is_required\": true}","{\"name\": \"password\", \"type\": \"string\", \"source\": \"custom\", \"is_required\": true}","{\"name\": \"host\", \"type\": \"string\", \"source\": \"custom\", \"is_required\": true}","{\"name\": \"port\", \"type\": \"string\", \"source\": \"custom\", \"is_required\": true}","{\"name\": \"query\", \"type\": \"string\", \"source\": \"reference\", \"is_required\": true}"}', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', true, 'PUBLIC', 'INTERNAL', '/src/assets/fx/postgresql/icon.png', '[]', null, null); -INSERT INTO function_lib (create_time, update_time, id, name, "desc", code, input_field_list, user_id, is_active, permission_type, function_type, icon, init_field_list, init_params, template_id) VALUES ('2025-03-17 08:16:32.626245 +00:00', '2025-03-17 08:16:32.626308 +00:00', '22c21b76-0308-11f0-9694-5618c4394482', 'MySQL 查询', '', e' -def query_mysql(host,port, user, password, database, sql): - import pymysql - import json - from pymysql.cursors import DictCursor - - try: - # 创建连接 - db = pymysql.connect( - host=host, - port=int(port), - user=user, - password=password, - database=database, - cursorclass=DictCursor # 使用字典游标 - ) - - # 使用 cursor() 方法创建一个游标对象 cursor - cursor = db.cursor() - - # 使用 execute() 方法执行 SQL 查询 - cursor.execute(sql) - - # 使用 fetchall() 方法获取所有数据 - data = cursor.fetchall() - - # 处理 bytes 类型的数据 - for row in data: - for key, value in row.items(): - if isinstance(value, bytes): - row[key] = value.decode(\"utf-8\") # 转换为字符串 - - # 将数据序列化为 JSON - json_data = json.dumps(data, ensure_ascii=False) - print(json_data) - return json_data - - # 关闭数据库连接 - db.close() - - except Exception as e: - print(f"Error while connecting to MySQL: {e}")', '{"{\"name\": \"host\", \"type\": \"string\", \"source\": \"custom\", \"is_required\": true}","{\"name\": \"port\", \"type\": \"string\", \"source\": \"custom\", \"is_required\": true}","{\"name\": \"user\", \"type\": \"string\", \"source\": \"custom\", \"is_required\": true}","{\"name\": \"password\", \"type\": \"string\", \"source\": \"custom\", \"is_required\": true}","{\"name\": \"database\", \"type\": \"string\", \"source\": \"custom\", \"is_required\": true}","{\"name\": \"sql\", \"type\": \"string\", \"source\": \"reference\", \"is_required\": true}"}', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', true, 'PUBLIC', 'INTERNAL', '/src/assets/fx/mysql/icon.png', '[]', null, null); +', '{"{\"name\": \"query\", \"type\": \"string\", \"source\": \"reference\", \"is_required\": true}"}', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', true, 'PUBLIC', 'INTERNAL', '/src/assets/fx/postgresql/icon.png', '[{"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "dbname", "label": "dbname", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "dbname 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "dbname长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}, {"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "user", "label": "user", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "user 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "user长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "root", "show_default_value": false}, {"attrs": {"type": "password", "maxlength": 200, "minlength": 1, "show-password": true, "show-word-limit": true}, "field": "password", "label": "password", "required": true, "input_type": "PasswordInput", "props_info": {"rules": [{"message": "password 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "password长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}, {"attrs": {"maxlength": 200, "minlength": 1, "show-word-limit": true}, "field": "host", "label": "host", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "host 为必填属性", "required": true}, {"max": 200, "min": 1, "message": "host长度在 1 到 200 个字符", "trigger": "blur"}]}, "default_value": "x", "show_default_value": false}, {"attrs": {"maxlength": 20, "minlength": 1, "show-word-limit": true}, "field": "port", "label": "port", "required": true, "input_type": "TextInput", "props_info": {"rules": [{"message": "port 为必填属性", "required": true}, {"max": 20, "min": 1, "message": "port长度在 1 到 20 个字符", "trigger": "blur"}]}, "default_value": "5432", "show_default_value": false}]', null, null); ''' diff --git a/apps/function_lib/serializers/function_lib_serializer.py b/apps/function_lib/serializers/function_lib_serializer.py index 8ac7892f020..b1b650323b1 100644 --- a/apps/function_lib/serializers/function_lib_serializer.py +++ b/apps/function_lib/serializers/function_lib_serializer.py @@ -158,10 +158,6 @@ def get_query_set(self): query_set = query_set.filter(function_type=self.data.get('function_type')) query_set = query_set.order_by("-create_time") - subquery = FunctionLib.objects.filter(template_id=OuterRef('id')) - subquery = subquery.filter(user_id=self.data.get('user_id')) - query_set = query_set.annotate(added=Exists(subquery)) - return query_set def list(self, with_valid=True): @@ -180,7 +176,6 @@ def page(self, current_page: int, page_size: int, with_valid=True): def post_records_handler(row): return { **FunctionLibModelSerializer(row).data, - 'added': row.added, 'init_params': None } @@ -390,22 +385,19 @@ def edit(self, with_valid=True): class InternalFunction(serializers.Serializer): id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("function ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) + name = serializers.CharField(required=True, error_messages=ErrMessage.char(_("function name"))) def add(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) - if QuerySet(FunctionLib).filter(template_id=self.data.get('id')).filter( - user_id=self.data.get('user_id')).exists(): - raise AppApiException(500, _('Function already exists')) - internal_function_lib = QuerySet(FunctionLib).filter(id=self.data.get('id')).first() if internal_function_lib is None: raise AppApiException(500, _('Function does not exist')) function_lib = FunctionLib( id=uuid.uuid1(), - name=internal_function_lib.name, + name=self.data.get('name'), desc=internal_function_lib.desc, code=internal_function_lib.code, user_id=self.data.get('user_id'), diff --git a/apps/function_lib/views/function_lib_views.py b/apps/function_lib/views/function_lib_views.py index 53cd8a83448..7519ec19b4a 100644 --- a/apps/function_lib/views/function_lib_views.py +++ b/apps/function_lib/views/function_lib_views.py @@ -166,10 +166,11 @@ def put(self, request: Request, id: str): class AddInternalFun(APIView): authentication_classes = [TokenAuth] - @action(methods=['GET'], detail=False) + @action(methods=['POST'], detail=False) @has_permissions(RoleConstants.ADMIN, RoleConstants.USER) @log(menu=_('Function'), operate=_("Add internal function")) - def get(self, request: Request, id: str): + def post(self, request: Request, id: str): return result.success( FunctionLibSerializer.InternalFunction( - data={'id': id, 'user_id': request.user.id}).add()) \ No newline at end of file + data={'id': id, 'user_id': request.user.id, 'name': request.data.get('name')}) + .add()) \ No newline at end of file diff --git a/ui/src/api/function-lib.ts b/ui/src/api/function-lib.ts index 004cd7217cc..4fcf64cff7b 100644 --- a/ui/src/api/function-lib.ts +++ b/ui/src/api/function-lib.ts @@ -122,9 +122,10 @@ const putFunctionLibIcon: ( const addInternalFunction: ( id: string, + data: any, loading?: Ref -) => Promise> = (id, loading) => { - return get(`${prefix}/${id}/add_internal_fun`, undefined, loading) +) => Promise> = (id, data, loading) => { + return post(`${prefix}/${id}/add_internal_fun`, data, undefined, loading) } const importFunctionLib: (data: any, loading?: Ref) => Promise> = ( diff --git a/ui/src/views/function-lib/component/AddInternalFunctionDialog.vue b/ui/src/views/function-lib/component/AddInternalFunctionDialog.vue new file mode 100644 index 00000000000..4e4ab0e31ce --- /dev/null +++ b/ui/src/views/function-lib/component/AddInternalFunctionDialog.vue @@ -0,0 +1,92 @@ + + + diff --git a/ui/src/views/function-lib/index.vue b/ui/src/views/function-lib/index.vue index 37a6a330cbc..29a83ba605a 100644 --- a/ui/src/views/function-lib/index.vue +++ b/ui/src/views/function-lib/index.vue @@ -229,14 +229,12 @@ />
- - {{ $t('views.functionLib.added') }} +