mirror of https://github.com/jumpserver/jumpserver
perf: Add user report api
parent
f685ea457a
commit
ae881022dc
|
@ -167,10 +167,7 @@ class UserLoginLogViewSet(UserLoginCommonMixin, OrgReadonlyModelViewSet):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
if current_org.is_root() or not settings.XPACK_ENABLED:
|
queryset = queryset.model.filter_login_queryset_by_org(queryset)
|
||||||
return queryset
|
|
||||||
users = self.get_org_member_usernames()
|
|
||||||
queryset = queryset.filter(username__in=users)
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,15 @@ class UserLoginLog(models.Model):
|
||||||
reason = old_reason_choices.get(self.reason, self.reason)
|
reason = old_reason_choices.get(self.reason, self.reason)
|
||||||
return reason
|
return reason
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def filter_login_queryset_by_org(queryset):
|
||||||
|
from audits.utils import construct_userlogin_usernames
|
||||||
|
if current_org.is_root() or not settings.XPACK_ENABLED:
|
||||||
|
return queryset
|
||||||
|
user_queryset = current_org.get_members()
|
||||||
|
users = construct_userlogin_usernames(user_queryset)
|
||||||
|
return queryset.filter(username__in=users)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["-datetime", "username"]
|
ordering = ["-datetime", "username"]
|
||||||
verbose_name = _("User login log")
|
verbose_name = _("User login log")
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from django.db.models import Count
|
||||||
|
from django.http.response import JsonResponse
|
||||||
|
from django.utils import timezone
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
from audits.const import LoginStatusChoices
|
||||||
|
from audits.models import UserLoginLog
|
||||||
|
from common.permissions import IsValidLicense
|
||||||
|
from common.utils import lazyproperty
|
||||||
|
from common.utils.timezone import local_zero_hour, local_now
|
||||||
|
from rbac.permissions import RBACPermission
|
||||||
|
|
||||||
|
__all__ = ['UserReportApi']
|
||||||
|
|
||||||
|
|
||||||
|
class UserReportApi(APIView):
|
||||||
|
http_method_names = ['get']
|
||||||
|
# TODO: Define the required RBAC permissions for this API
|
||||||
|
rbac_perms = {
|
||||||
|
}
|
||||||
|
permission_classes = [RBACPermission, IsValidLicense]
|
||||||
|
|
||||||
|
@lazyproperty
|
||||||
|
def days(self):
|
||||||
|
count = self.request.query_params.get('days', 1)
|
||||||
|
return int(count)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def days_to_datetime(self):
|
||||||
|
if self.days == 1:
|
||||||
|
return local_zero_hour()
|
||||||
|
return local_now() - timezone.timedelta(days=self.days)
|
||||||
|
|
||||||
|
@lazyproperty
|
||||||
|
def date_range_list(self):
|
||||||
|
return [
|
||||||
|
(local_now() - timezone.timedelta(days=i)).date()
|
||||||
|
for i in range(self.days - 1, -1, -1)
|
||||||
|
]
|
||||||
|
|
||||||
|
def filter_by_date_range(self, queryset, field_name):
|
||||||
|
date_range_bounds = self.days_to_datetime.date(), (local_now() + timezone.timedelta(days=1)).date()
|
||||||
|
return queryset.filter(**{f'{field_name}__range': date_range_bounds})
|
||||||
|
|
||||||
|
def get_user_login_metrics(self):
|
||||||
|
filtered_queryset = self.filter_by_date_range(self.user_login_log_queryset, 'datetime')
|
||||||
|
|
||||||
|
data = defaultdict(set)
|
||||||
|
for t, username in filtered_queryset.values_list('datetime', 'username'):
|
||||||
|
date_str = str(t.date())
|
||||||
|
data[date_str].add(username)
|
||||||
|
|
||||||
|
metrics = [len(v) for __, v in data]
|
||||||
|
return metrics
|
||||||
|
|
||||||
|
def get_user_login_method_metrics(self):
|
||||||
|
filtered_queryset = self.filter_by_date_range(self.user_login_log_queryset, 'datetime')
|
||||||
|
|
||||||
|
backends = set()
|
||||||
|
data = defaultdict(lambda: defaultdict(set))
|
||||||
|
for t, username, backend in filtered_queryset.values_list('datetime', 'username', 'backend'):
|
||||||
|
backends.add(backend)
|
||||||
|
date_str = str(t.date())
|
||||||
|
data[date_str][backend].add(username)
|
||||||
|
|
||||||
|
metrics = defaultdict(list)
|
||||||
|
for backend in backends:
|
||||||
|
for date_str, usernames in data.items():
|
||||||
|
metrics[backend].append(len(usernames.get(backend, set())))
|
||||||
|
return metrics
|
||||||
|
|
||||||
|
def get_user_login_region_distribution(self):
|
||||||
|
filtered_queryset = self.filter_by_date_range(self.user_login_log_queryset, 'datetime')
|
||||||
|
|
||||||
|
data = filtered_queryset.values('city').annotate(
|
||||||
|
user_count=Count('username', distinct=True)
|
||||||
|
).order_by('-user_count')
|
||||||
|
return list(data)
|
||||||
|
|
||||||
|
@lazyproperty
|
||||||
|
def user_login_log_queryset(self):
|
||||||
|
queryset = UserLoginLog.objects.filter(status=LoginStatusChoices.success)
|
||||||
|
return UserLoginLog.filter_login_queryset_by_org(queryset)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
query_params = self.request.query_params
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
_all = query_params.get('all')
|
||||||
|
|
||||||
|
if _all or query_params.get('user_login_log_metrics'):
|
||||||
|
metrics = self.get_user_login_metrics()
|
||||||
|
data.update({
|
||||||
|
'dates_metrics_date': [date.strftime('%m-%d') for date in self.date_range_list] or ['0'],
|
||||||
|
'dates_metrics_total': metrics,
|
||||||
|
})
|
||||||
|
|
||||||
|
if _all or query_params.get('user_login_method_metrics'):
|
||||||
|
metrics = self.get_user_login_method_metrics()
|
||||||
|
data.update({
|
||||||
|
'dates_metrics_date': [date.strftime('%m-%d') for date in self.date_range_list] or ['0'],
|
||||||
|
'dates_metrics_total': metrics,
|
||||||
|
})
|
||||||
|
|
||||||
|
if _all or query_params.get('user_login_region_distribution'):
|
||||||
|
data.update({
|
||||||
|
'user_login_region_distribution': self.get_user_login_region_distribution(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return JsonResponse(data, status=200)
|
|
@ -1,19 +1,19 @@
|
||||||
|
import base64
|
||||||
import io
|
import io
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
from io import BytesIO
|
||||||
from urllib.parse import urlparse
|
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 JsonResponse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.conf import settings
|
|
||||||
from django.http import FileResponse, HttpResponseBadRequest
|
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from playwright.sync_api import sync_playwright
|
|
||||||
from django.core.mail import EmailMultiAlternatives
|
|
||||||
from pdf2image import convert_from_bytes
|
from pdf2image import convert_from_bytes
|
||||||
import base64
|
from playwright.sync_api import sync_playwright
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
charts_map = {
|
charts_map = {
|
||||||
"UserActivity": {
|
"UserActivity": {
|
||||||
|
|
Loading…
Reference in New Issue