From 060ef3816944b0b9e55e6f6d13f5a41535b6db07 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Tue, 29 Jul 2025 16:16:55 +0800 Subject: [PATCH] perf: add charts_map --- apps/reports/api/users/user.py | 35 +++++++++++++++++++++++++------ apps/reports/views.py | 21 ++++++++++++++----- apps/users/models/user/_source.py | 4 ++++ 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/apps/reports/api/users/user.py b/apps/reports/api/users/user.py index 6ca02104d..b4cb21c3c 100644 --- a/apps/reports/api/users/user.py +++ b/apps/reports/api/users/user.py @@ -2,7 +2,7 @@ # from collections import defaultdict -from django.db.models import Count +from django.db.models import Count, Q from django.http.response import JsonResponse from rest_framework.views import APIView @@ -15,6 +15,9 @@ from reports.mixins import DateRangeMixin __all__ = ['UserReportApi'] +from users.models import User +from users.models.user import Source + class UserReportApi(DateRangeMixin, APIView): http_method_names = ['get'] @@ -87,16 +90,36 @@ class UserReportApi(DateRangeMixin, APIView): queryset = UserLoginLog.objects.filter(status=LoginStatusChoices.failed) return UserLoginLog.filter_queryset_by_org(queryset) + @lazyproperty + def user_qs(self): + return User.get_org_users() + def get(self, request, *args, **kwargs): data = {} + user_stats = self.user_qs.aggregate( + total=Count(1), + first_login=Count(1, filter=Q(is_first_login=True)), + need_update_password=Count(1, filter=Q(need_update_password=True)), + face_vector=Count(1, filter=Q(face_vector__isnull=False)), + not_enabled_mfa=Count(1, filter=Q(mfa_level=0)), + ) + + user_stats['valid'] = sum(1 for u in self.user_qs if u.is_valid) + data['user_stats'] = user_stats + + source_map = Source.as_dict() + user_by_source = defaultdict(int) + for source in self.user_qs.values_list('source', flat=True): + k = source_map.get(source, source) + user_by_source[str(k)] += 1 + + data['user_by_source'] = [{'name': k, 'value': v} for k, v in user_by_source.items()] + data['user_login_log_metrics'] = { 'dates_metrics_date': self.dates_metrics_date, - 'dates_metrics_total': self.get_user_login_metrics(self.user_login_log_queryset), - } - data['user_login_failed_metrics'] = { - 'dates_metrics_date': self.dates_metrics_date, - 'dates_metrics_total': self.get_user_login_metrics(self.user_login_failed_queryset), + 'dates_metrics_success_total': self.get_user_login_metrics(self.user_login_log_queryset), + 'dates_metrics_failure_total': self.get_user_login_metrics(self.user_login_failed_queryset), } data['user_login_method_metrics'] = { 'dates_metrics_date': self.dates_metrics_date, diff --git a/apps/reports/views.py b/apps/reports/views.py index b4bf7cfec..5a46ecc56 100644 --- a/apps/reports/views.py +++ b/apps/reports/views.py @@ -6,8 +6,7 @@ from urllib.parse import urlparse from django.conf import settings from django.core.mail import EmailMultiAlternatives -from django.http import FileResponse, HttpResponseBadRequest -from django.http import JsonResponse +from django.http import FileResponse, HttpResponseBadRequest, JsonResponse from django.utils import timezone from django.utils.decorators import method_decorator from django.views import View @@ -16,9 +15,21 @@ from pdf2image import convert_from_bytes from playwright.sync_api import sync_playwright charts_map = { - "UserActivity": { - "title": "用户活动", + "UserLoginActivity": { + "title": "用户登录活动", "path": "/ui/#/reports/users/user-activity" + }, + "UserPasswordChange": { + "title": "用户改密记录", + "path": "/ui/#/reports/users/change-password" + }, + "AssetStatistics": { + "title": "资产统计概览", + "path": "/ui/#/reports/assets/asset-statistics" + }, + "AssetAccessActivity": { + "title": "资产访问活动", + "path": "/ui/#/reports/assets/asset-activity" } } @@ -81,7 +92,7 @@ class ExportPdfView(View): sessionid = request.COOKIES.get(settings.SESSION_COOKIE_NAME) if not sessionid: return HttpResponseBadRequest('No sessionid found in cookies') - + pdf_bytes, title = export_chart_to_pdf(chart_name, sessionid, request=request) if not pdf_bytes: return HttpResponseBadRequest('Failed to generate PDF') diff --git a/apps/users/models/user/_source.py b/apps/users/models/user/_source.py index e7e0dd21f..1c5ece956 100644 --- a/apps/users/models/user/_source.py +++ b/apps/users/models/user/_source.py @@ -23,6 +23,10 @@ class Source(models.TextChoices): slack = "slack", _("Slack") custom = "custom", "Custom" + @classmethod + def as_dict(cls): + return {choice.value: choice.label for choice in cls} + class SourceMixin: source: str