mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-25 17:22:55 +00:00
feat: 支持函数库脚本代码校验
This commit is contained in:
parent
7070334e86
commit
3a7e9a3cea
|
|
@ -0,0 +1,57 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
@project: MaxKB
|
||||
@Author:虎
|
||||
@file: py_lint_serializer.py
|
||||
@date:2024/9/30 15:38
|
||||
@desc:
|
||||
"""
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from pylint.lint import Run
|
||||
from pylint.reporters import JSON2Reporter
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.util.field_message import ErrMessage
|
||||
from smartdoc.const import PROJECT_DIR
|
||||
|
||||
|
||||
class PyLintInstance(serializers.Serializer):
|
||||
code = serializers.CharField(required=True, error_messages=ErrMessage.char("函数名称"))
|
||||
|
||||
|
||||
def to_dict(message, file_name):
|
||||
return {'line': message.line,
|
||||
'column': message.column,
|
||||
'endLine': message.end_line,
|
||||
'endColumn': message.end_column,
|
||||
'message': (message.msg or "").replace(file_name, 'code'),
|
||||
'type': message.category}
|
||||
|
||||
|
||||
def get_file_name():
|
||||
file_name = f"{uuid.uuid1()}"
|
||||
py_lint_dir = os.path.join(PROJECT_DIR, 'data', 'py_lint')
|
||||
if not os.path.exists(py_lint_dir):
|
||||
os.makedirs(py_lint_dir)
|
||||
return os.path.join(py_lint_dir, file_name)
|
||||
|
||||
|
||||
class PyLintSerializer(serializers.Serializer):
|
||||
|
||||
def pylint(self, instance, is_valid=True):
|
||||
if is_valid:
|
||||
self.is_valid(raise_exception=True)
|
||||
PyLintInstance(data=instance).is_valid(raise_exception=True)
|
||||
code = instance.get('code')
|
||||
file_name = get_file_name()
|
||||
with open(file_name, 'w') as file:
|
||||
file.write(code)
|
||||
reporter = JSON2Reporter()
|
||||
Run([file_name,
|
||||
"--disable=line-too-long",
|
||||
'--module-rgx=[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'],
|
||||
reporter=reporter, exit=False)
|
||||
os.remove(file_name)
|
||||
return [to_dict(m, os.path.basename(file_name)) for m in reporter.messages]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
@project: MaxKB
|
||||
@Author:虎
|
||||
@file: py_lint_api.py
|
||||
@date:2024/9/30 15:48
|
||||
@desc:
|
||||
"""
|
||||
from drf_yasg import openapi
|
||||
|
||||
from common.mixins.api_mixin import ApiMixin
|
||||
|
||||
|
||||
class PyLintApi(ApiMixin):
|
||||
@staticmethod
|
||||
def get_request_body_api():
|
||||
return openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
required=['code'],
|
||||
properties={
|
||||
'code': openapi.Schema(type=openapi.TYPE_STRING, title="函数内容", description="函数内容")
|
||||
}
|
||||
)
|
||||
|
|
@ -6,7 +6,8 @@ app_name = "function_lib"
|
|||
urlpatterns = [
|
||||
path('function_lib', views.FunctionLibView.as_view()),
|
||||
path('function_lib/debug', views.FunctionLibView.Debug.as_view()),
|
||||
path('function_lib/pylint', views.PyLintView.as_view()),
|
||||
path('function_lib/<str:function_lib_id>', views.FunctionLibView.Operate.as_view()),
|
||||
path("function_lib/<int:current_page>/<int:page_size>", views.FunctionLibView.Page.as_view(),
|
||||
name="function_lib_page"),
|
||||
name="function_lib_page")
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,3 +7,4 @@
|
|||
@desc:
|
||||
"""
|
||||
from .function_lib_views import *
|
||||
from .py_lint import *
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
@project: MaxKB
|
||||
@Author:虎
|
||||
@file: py_lint.py
|
||||
@date:2024/9/30 15:35
|
||||
@desc:
|
||||
"""
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from common.auth import TokenAuth, has_permissions
|
||||
from common.constants.permission_constants import RoleConstants
|
||||
from common.response import result
|
||||
from function_lib.serializers.py_lint_serializer import PyLintSerializer
|
||||
from function_lib.swagger_api.py_lint_api import PyLintApi
|
||||
|
||||
|
||||
class PyLintView(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@action(methods=['POST'], detail=False)
|
||||
@swagger_auto_schema(operation_summary="校验代码",
|
||||
operation_id="校验代码",
|
||||
request_body=PyLintApi.get_request_body_api(),
|
||||
tags=['函数库'])
|
||||
@has_permissions(RoleConstants.ADMIN, RoleConstants.USER)
|
||||
def post(self, request: Request):
|
||||
return result.success(PyLintSerializer(data={'user_id': request.user.id}).pylint(request.data))
|
||||
|
|
@ -53,6 +53,7 @@ celery = { extras = ["sqlalchemy"], version = "^5.4.0" }
|
|||
django-celery-beat = "^2.6.0"
|
||||
celery-once = "^3.0.1"
|
||||
anthropic = "^0.34.2"
|
||||
pylint = "3.1.0"
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"format": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
"@logicflow/core": "^1.2.27",
|
||||
"@logicflow/extension": "^1.2.27",
|
||||
|
|
@ -37,14 +38,14 @@
|
|||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.6",
|
||||
"pinyin-pro": "^3.18.2",
|
||||
"recorder-core": "^1.3.24040900",
|
||||
"screenfull": "^6.0.2",
|
||||
"use-element-plus-theme": "^0.0.5",
|
||||
"vue": "^3.3.4",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-codemirror": "^6.1.1",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"vue-router": "^4.2.4",
|
||||
"recorder-core": "^1.3.24040900"
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.3.2",
|
||||
|
|
|
|||
|
|
@ -95,6 +95,9 @@ const getFunctionLibById: (
|
|||
) => Promise<Result<any>> = (function_lib_id, loading) => {
|
||||
return get(`${prefix}/${function_lib_id}`, undefined, loading)
|
||||
}
|
||||
const pylint: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (code, loading) => {
|
||||
return post(`${prefix}/pylint`, { code }, {}, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
getFunctionLib,
|
||||
|
|
@ -103,5 +106,6 @@ export default {
|
|||
postFunctionLibDebug,
|
||||
getAllFunctionLib,
|
||||
delFunctionLib,
|
||||
getFunctionLibById
|
||||
getFunctionLibById,
|
||||
pylint
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,55 @@
|
|||
<template>
|
||||
<Codemirror v-bind="$attrs" border :options="cmOption" ref="cmRef" :style="codemirrorStyle" />
|
||||
<Codemirror
|
||||
v-bind="$attrs"
|
||||
ref="cmRef"
|
||||
:extensions="extensions"
|
||||
:style="codemirrorStyle"
|
||||
:tab-size="4"
|
||||
:autofocus="true"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { Codemirror } from 'vue-codemirror'
|
||||
import { python } from '@codemirror/lang-python'
|
||||
import { oneDark } from '@codemirror/theme-one-dark'
|
||||
import { linter, Diagnostic } from '@codemirror/lint'
|
||||
import FunctionApi from '@/api/function-lib'
|
||||
|
||||
defineOptions({ name: 'CodemirrorEditor' })
|
||||
const cmOption = {
|
||||
tabSize: 4,
|
||||
lineWrapping: true,
|
||||
lineNumbers: true,
|
||||
line: true
|
||||
|
||||
function getRangeFromLineAndColumn(state: any, line: number, column: number, end_column?: number) {
|
||||
const l = state.doc.line(line)
|
||||
return { form: l.from + column, to: end_column ? l.from + end_column : l.to }
|
||||
}
|
||||
|
||||
const regexpLinter = linter(async (view) => {
|
||||
let diagnostics: Diagnostic[] = []
|
||||
await FunctionApi.pylint(view.state.doc.toString()).then((ok) => {
|
||||
ok.data.forEach((element: any) => {
|
||||
const range = getRangeFromLineAndColumn(
|
||||
view.state,
|
||||
element.line,
|
||||
element.column,
|
||||
element.endColumn
|
||||
)
|
||||
|
||||
diagnostics.push({
|
||||
from: range.form,
|
||||
to: range.to,
|
||||
severity: element.type,
|
||||
message: element.message
|
||||
})
|
||||
})
|
||||
})
|
||||
return diagnostics
|
||||
})
|
||||
const extensions = [python(), regexpLinter, oneDark]
|
||||
const codemirrorStyle = {
|
||||
height: '210px!important'
|
||||
}
|
||||
const cmRef = ref()
|
||||
onMounted(() => {})
|
||||
|
||||
onUnmounted(() => {
|
||||
cmRef.value?.destroy()
|
||||
})
|
||||
const cmRef = ref<InstanceType<typeof Codemirror>>()
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
|
|
|
|||
|
|
@ -721,24 +721,6 @@ h5 {
|
|||
background: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
|
||||
// Codemirror 编辑器
|
||||
.function-CodemirrorEditor {
|
||||
border: 1px solid #bbbfc4;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
padding-bottom: 20px;
|
||||
&__footer {
|
||||
.magnify {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
}
|
||||
.cm-gutters {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-avatar {
|
||||
position: relative;
|
||||
.edit-mask {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
--app-view-padding: 24px;
|
||||
--app-view-bg-color: #ffffff;
|
||||
--app-border-color-dark: #bbbfc4;
|
||||
|
||||
--md-bk-hover-color:var(--el-border-color-hover);
|
||||
/** header 组件 */
|
||||
--app-header-height: 56px;
|
||||
--app-header-padding: 0 20px;
|
||||
|
|
|
|||
|
|
@ -305,4 +305,13 @@ defineExpose({
|
|||
open
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.function-CodemirrorEditor__footer {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
.function-CodemirrorEditor {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -221,4 +221,13 @@ onMounted(() => {
|
|||
}, 100)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
<style lang="scss">
|
||||
.function-CodemirrorEditor__footer {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
.function-CodemirrorEditor {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue