perf: some swagger api (#15203)

* perf: some swagger api

* perf: update deps

* perf: Update Dockerfile with new base image tag

---------

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
fit2bot 2025-04-15 11:43:36 +08:00 committed by GitHub
parent 8b9fe3c72b
commit 5390fbacec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1287 additions and 1158 deletions

View File

@ -1,4 +1,4 @@
FROM jumpserver/core-base:20250224_065619 AS stage-build
FROM jumpserver/core-base:20250415_032719 AS stage-build
ARG VERSION

View File

@ -10,6 +10,7 @@ class VirtualAccountViewSet(OrgBulkModelViewSet):
serializer_class = VirtualAccountSerializer
search_fields = ('alias',)
filterset_fields = ('alias',)
http_method_names = ['get']
def get_queryset(self):
return VirtualAccount.get_or_init_queryset()

View File

@ -39,9 +39,10 @@ class AutomationAssetsListApi(generics.ListAPIView):
return assets
class AutomationRemoveAssetApi(generics.RetrieveUpdateAPIView):
class AutomationRemoveAssetApi(generics.UpdateAPIView):
model = BaseAutomation
serializer_class = serializers.UpdateAssetSerializer
http_method_names = ['put']
def update(self, request, *args, **kwargs):
instance = self.get_object()
@ -56,9 +57,10 @@ class AutomationRemoveAssetApi(generics.RetrieveUpdateAPIView):
return Response({'msg': 'ok'})
class AutomationAddAssetApi(generics.RetrieveUpdateAPIView):
class AutomationAddAssetApi(generics.UpdateAPIView):
model = BaseAutomation
serializer_class = serializers.UpdateAssetSerializer
http_method_names = ['put']
def update(self, request, *args, **kwargs):
instance = self.get_object()
@ -72,9 +74,10 @@ class AutomationAddAssetApi(generics.RetrieveUpdateAPIView):
return Response({"error": serializer.errors})
class AutomationNodeAddRemoveApi(generics.RetrieveUpdateAPIView):
class AutomationNodeAddRemoveApi(generics.UpdateAPIView):
model = BaseAutomation
serializer_class = serializers.UpdateNodeSerializer
http_method_names = ['put']
def update(self, request, *args, **kwargs):
action_params = ['add', 'remove']

View File

@ -147,6 +147,7 @@ class CheckAccountEngineViewSet(JMSModelViewSet):
serializer_class = serializers.CheckAccountEngineSerializer
permission_classes = [RBACPermission, IsValidLicense]
perm_model = CheckAccountEngine
http_method_names = ['get']
def get_queryset(self):
return CheckAccountEngine.get_default_engines()

View File

@ -97,7 +97,7 @@ class AssetFilterSet(BaseFilterSet):
return queryset.filter(protocols__name__in=value).distinct()
class AssetViewSet(SuggestionMixin, OrgBulkModelViewSet):
class BaseAssetViewSet(OrgBulkModelViewSet):
"""
API endpoint that allows Asset to be viewed or edited.
"""
@ -143,6 +143,19 @@ class AssetViewSet(SuggestionMixin, OrgBulkModelViewSet):
return retrieve_cls
return cls
def create(self, request, *args, **kwargs):
if request.path.find('/api/v1/assets/assets/') > -1:
error = _('Cannot create asset directly, you should create a host or other')
return Response({'error': error}, status=400)
if not settings.XPACK_LICENSE_IS_VALID and self.model.objects.order_by().count() >= 5000:
error = _('The number of assets exceeds the limit of 5000')
return Response({'error': error}, status=400)
return super().create(request, *args, **kwargs)
class AssetViewSet(SuggestionMixin, BaseAssetViewSet):
@action(methods=["GET"], detail=True, url_path="platform")
def platform(self, *args, **kwargs):
asset = super().get_object()
@ -197,17 +210,6 @@ class AssetViewSet(SuggestionMixin, OrgBulkModelViewSet):
Protocol.objects.bulk_create(objs)
return Response(status=status.HTTP_200_OK)
def create(self, request, *args, **kwargs):
if request.path.find('/api/v1/assets/assets/') > -1:
error = _('Cannot create asset directly, you should create a host or other')
return Response({'error': error}, status=400)
if not settings.XPACK_LICENSE_IS_VALID and self.model.objects.order_by().count() >= 5000:
error = _('The number of assets exceeds the limit of 5000')
return Response({'error': error}, status=400)
return super().create(request, *args, **kwargs)
def filter_bulk_update_data(self):
bulk_data = []
skip_assets = []

View File

@ -1,12 +1,12 @@
from assets.models import Cloud, Asset
from assets.serializers import CloudSerializer
from .asset import AssetViewSet
from .asset import BaseAssetViewSet
__all__ = ['CloudViewSet']
class CloudViewSet(AssetViewSet):
class CloudViewSet(BaseAssetViewSet):
model = Cloud
perm_model = Asset

View File

@ -1,12 +1,12 @@
from assets.models import Custom, Asset
from assets.serializers import CustomSerializer
from .asset import AssetViewSet
from .asset import BaseAssetViewSet
__all__ = ['CustomViewSet']
class CustomViewSet(AssetViewSet):
class CustomViewSet(BaseAssetViewSet):
model = Custom
perm_model = Asset

View File

@ -1,12 +1,12 @@
from assets.models import Database, Asset
from assets.serializers import DatabaseSerializer
from .asset import AssetViewSet
from .asset import BaseAssetViewSet
__all__ = ['DatabaseViewSet']
class DatabaseViewSet(AssetViewSet):
class DatabaseViewSet(BaseAssetViewSet):
model = Database
perm_model = Asset

View File

@ -1,11 +1,11 @@
from assets.serializers import DeviceSerializer
from assets.models import Device, Asset
from .asset import AssetViewSet
from assets.serializers import DeviceSerializer
from .asset import BaseAssetViewSet
__all__ = ['DeviceViewSet']
class DeviceViewSet(AssetViewSet):
class DeviceViewSet(BaseAssetViewSet):
model = Device
perm_model = Asset

View File

@ -1,12 +1,12 @@
from assets.models import DirectoryService, Asset
from assets.serializers import DSSerializer
from .asset import AssetViewSet
from .asset import BaseAssetViewSet
__all__ = ['DSViewSet']
class DSViewSet(AssetViewSet):
class DSViewSet(BaseAssetViewSet):
model = DirectoryService
perm_model = Asset

View File

@ -1,12 +1,12 @@
from assets.models import GPT, Asset
from assets.serializers import GPTSerializer
from .asset import AssetViewSet
from .asset import BaseAssetViewSet
__all__ = ['GPTViewSet']
class GPTViewSet(AssetViewSet):
class GPTViewSet(BaseAssetViewSet):
model = GPT
perm_model = Asset

View File

@ -1,11 +1,11 @@
from assets.models import Host, Asset
from assets.serializers import HostSerializer
from .asset import AssetViewSet
from .asset import BaseAssetViewSet
__all__ = ['HostViewSet']
class HostViewSet(AssetViewSet):
class HostViewSet(BaseAssetViewSet):
model = Host
perm_model = Asset

View File

@ -1,12 +1,12 @@
from assets.models import Web, Asset
from assets.serializers import WebSerializer
from .asset import AssetViewSet
from .asset import BaseAssetViewSet
__all__ = ['WebViewSet']
class WebViewSet(AssetViewSet):
class WebViewSet(BaseAssetViewSet):
model = Web
perm_model = Asset

View File

@ -188,6 +188,9 @@ class ActivityUnionLogSerializer(serializers.Serializer):
class FileSerializer(serializers.Serializer):
file = serializers.FileField(allow_empty_file=True)
class Meta:
ref_name = 'AuditFileSerializer'
class UserSessionSerializer(serializers.ModelSerializer):
type = LabeledChoiceField(choices=LoginTypeChoices.choices, label=_("Type"))

View File

@ -2,6 +2,7 @@
from __future__ import unicode_literals
import os
import uuid
import private_storage.urls
from django.conf import settings
@ -74,12 +75,19 @@ urlpatterns += [
path('core/jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
]
DOC_TTL = 60 * 60
DOC_VERSION = uuid.uuid4().hex
cache_kwargs = {
'cache_timeout': DOC_TTL,
'cache_kwargs': {
'key_prefix': 'swagger-cache-' + DOC_VERSION,
},
}
# docs 路由
urlpatterns += [
re_path('^api/swagger(?P<format>\.json|\.yaml)$',
views.get_swagger_view().without_ui(cache_timeout=1), name='schema-json'),
re_path('api/docs/?', views.get_swagger_view().with_ui('swagger', cache_timeout=1), name="docs"),
re_path('api/redoc/?', views.get_swagger_view().with_ui('redoc', cache_timeout=1), name='redoc'),
path('api/swagger.<format>', views.get_swagger_view(False).without_ui(**cache_kwargs), name='schema-json'),
re_path('api/docs/?', views.get_swagger_view().with_ui('swagger', **cache_kwargs), name="docs"),
re_path('api/redoc/?', views.get_swagger_view().with_ui('redoc', **cache_kwargs), name='redoc'),
]
if os.environ.get('DEBUG_TOOLBAR', False):

View File

@ -1,8 +1,54 @@
from drf_yasg.inspectors import SwaggerAutoSchema
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from drf_yasg.generators import OpenAPISchemaGenerator
from drf_yasg.inspectors import SwaggerAutoSchema
from drf_yasg.views import get_schema_view
from rest_framework import permissions
class CustomSchemaGenerator(OpenAPISchemaGenerator):
from_mcp = False
def get_schema(self, request=None, public=False):
self.from_mcp = request.query_params.get('mcp') or request.path.endswith('swagger.json')
return super().get_schema(request, public)
@staticmethod
def exclude_some_paths(path):
# 这里可以对 paths 进行处理
excludes = ['/report/', '/render-to-json/', '/suggestions/', 'executions', 'automations']
for p in excludes:
if path.find(p) >= 0:
return True
return False
def exclude_some_app(self, path):
parts = path.split('/')
if len(parts) < 4:
return False
apps = []
if self.from_mcp:
apps = [
'ops', 'tickets', 'common', 'authentication',
'settings', 'xpack', 'terminal', 'rbac'
]
app_name = parts[3]
if app_name in apps:
return True
return False
def get_operation(self, view, path, prefix, method, components, request):
# 这里可以对 path 进行处理
if self.exclude_some_paths(path):
return None
if self.exclude_some_app(path):
return None
operation = super().get_operation(view, path, prefix, method, components, request)
operation_id = operation.get('operationId')
if 'bulk' in operation_id:
return None
return operation
class CustomSwaggerAutoSchema(SwaggerAutoSchema):
@ -59,17 +105,25 @@ api_info = openapi.Info(
)
def get_swagger_view(version='v1'):
def get_swagger_view(with_auth=True):
from ..urls import api_v1
from django.urls import path, include
api_v1_patterns = [
patterns = [
path('api/v1/', include(api_v1))
]
patterns = api_v1_patterns
if with_auth:
permission_classes = (permissions.IsAuthenticated,)
public = False
else:
permission_classes = []
public = True
schema_view = get_schema_view(
api_info,
public=public,
patterns=patterns,
permission_classes=(permissions.IsAuthenticated,),
generator_class=CustomSchemaGenerator,
permission_classes=permission_classes
)
return schema_view

View File

@ -2,7 +2,6 @@
#
import logging
from django.conf import settings
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from django_filters import rest_framework as filters
@ -10,7 +9,7 @@ from rest_framework import generics
from rest_framework import status
from rest_framework.views import APIView, Response
from common.api import JMSBulkModelViewSet
from common.api import JMSModelViewSet
from common.drf.filters import BaseFilterSet
from common.exceptions import JMSException
from common.permissions import WithBootstrapToken, IsServiceAccount
@ -43,7 +42,7 @@ class TerminalFilterSet(BaseFilterSet):
return queryset
class TerminalViewSet(JMSBulkModelViewSet):
class TerminalViewSet(JMSModelViewSet):
queryset = Terminal.objects.filter(is_deleted=False)
serializer_class = serializers.TerminalSerializer
filterset_class = TerminalFilterSet

View File

@ -12,7 +12,6 @@ app_name = 'terminal'
router = BulkRouter()
router.register(r'sessions', api.SessionViewSet, 'session')
router.register(r'terminals/((?P<terminal>[^/.]{36})/)?status', api.StatusViewSet, 'terminal-status')
router.register(r'terminals/((?P<terminal>[^/.]{36})/)?sessions', api.SessionViewSet, 'terminal-sessions')
router.register(r'terminals', api.TerminalViewSet, 'terminal')
router.register(r'tasks', api.TaskViewSet, 'tasks')
router.register(r'commands', api.CommandViewSet, 'command')

2265
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -99,7 +99,7 @@ django-private-storage = "3.1"
drf-nested-routers = "0.93.4"
drf-writable-nested = "0.7.0"
rest-condition = "1.0.3"
drf-yasg = "1.21.7"
drf-yasg = "1.21.10"
coreapi = "2.3.3"
coreschema = "0.0.4"
openapi-codec = "1.3.2"
@ -148,7 +148,7 @@ mistune = "2.0.3"
openai = "^1.29.0"
xlsxwriter = "^3.1.9"
exchangelib = "^5.1.0"
xmlsec = "^1.3.13"
xmlsec = "1.3.14"
lxml = "5.2.1"
pydantic = "^2.7.4"
annotated-types = "^0.6.0"