perf: account report

pull/15630/head
feng 2025-08-01 14:31:41 +08:00
parent 060ef38169
commit 5b349ae231
6 changed files with 126 additions and 1 deletions

View File

@ -14,6 +14,10 @@ class Connectivity(TextChoices):
NTLM_ERR = 'ntlm_err', _('NTLM credentials rejected error')
CREATE_TEMPORARY_ERR = 'create_temp_err', _('Create temporary error')
@classmethod
def as_dict(cls):
return {choice.value: choice.label for choice in cls}
class AutomationTypes(TextChoices):
ping = 'ping', _('Ping')

View File

@ -1,3 +1,4 @@
from .accouts import *
from .assets import *
from .report import *
from .users import *

View File

@ -0,0 +1 @@
from .account import *

View File

@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
#
from collections import defaultdict
from django.db.models import Count, Q, F, Value
from django.db.models.functions import Concat
from django.http import JsonResponse
from rest_framework.views import APIView
from accounts.models import Account, AccountTemplate
from assets.const import Connectivity
from common.permissions import IsValidLicense
from common.utils import lazyproperty
from rbac.permissions import RBACPermission
from reports.api.assets.base import group_stats
from reports.mixins import DateRangeMixin
__all__ = ['AccountStatisticApi']
class AccountStatisticApi(DateRangeMixin, APIView):
http_method_names = ['get']
# TODO: Define the required RBAC permissions for this API
rbac_perms = {
'GET': 'accounts.view_account',
}
permission_classes = [RBACPermission, IsValidLicense]
@lazyproperty
def base_qs(self):
return Account.objects.all()
@lazyproperty
def template_qs(self):
return AccountTemplate.objects.all()
def get_change_secret_account_metrics(self):
filtered_queryset = self.filter_by_date_range(self.base_qs, 'date_change_secret')
data = defaultdict(set)
for t, _id in filtered_queryset.values_list('date_change_secret', 'id'):
date_str = str(t.date())
data[date_str].add(_id)
metrics = [len(data.get(str(d), set())) for d in self.date_range_list]
return metrics
def get(self, request, *args, **kwargs):
qs = self.base_qs
stats = qs.aggregate(
total=Count(1),
active=Count(1, filter=Q(is_active=True)),
connected=Count(1, filter=Q(connectivity=Connectivity.OK)),
su_from=Count(1, filter=Q(su_from__isnull=False)),
date_change_secret=Count(1, filter=Q(secret_reset=True)),
)
stats['template_total'] = self.template_qs.count()
source_pie_data = [
{'name': str(source), 'value': total}
for source, total in
qs.values('source').annotate(
total=Count(1)
).values_list('source', 'total')
]
by_connectivity = group_stats(
qs, 'label', 'connectivity', Connectivity.as_dict(),
)
top_assets = qs.values('asset__name') \
.annotate(account_count=Count('id')) \
.order_by('-account_count')[:10]
top_version_accounts = qs.annotate(
display_key=Concat(
F('asset__name'),
Value(''),
F('username'),
Value('')
)
).values('display_key', 'version').order_by('-version')[:10]
payload = {
'account_stats': stats,
'top_assets': list(top_assets),
'top_version_accounts': list(top_version_accounts),
'source_pie': source_pie_data,
'by_connectivity': by_connectivity,
'change_secret_account_metrics': {
'dates_metrics_date': self.dates_metrics_date,
'dates_metrics_total': self.get_change_secret_account_metrics(),
}
}
return JsonResponse(payload, status=200)

View File

@ -0,0 +1,21 @@
from django.db.models import Count, F
def group_stats(queryset, alias, key, label_map=None):
grouped = (
queryset
.exclude(**{f'{key}__isnull': True})
.values(**{alias: F(key)})
.annotate(total=Count('id'))
)
data = [
{
alias: val,
'total': cnt,
**({'label': label_map.get(val, val)} if label_map else {})
}
for val, cnt in grouped.values_list(alias, 'total')
]
return data

View File

@ -9,5 +9,6 @@ urlpatterns = [
path('reports/users/', api.UserReportApi.as_view(), name='user-list'),
path('reports/user-change-password/', api.UserChangeSecretApi.as_view(), name='user-change-password'),
path('reports/asset-statistic/', api.AssetStatisticApi.as_view(), name='asset-statistic'),
path('reports/asset-activity/', api.AssetActivityApi.as_view(), name='asset-activity')
path('reports/asset-activity/', api.AssetActivityApi.as_view(), name='asset-activity'),
path('reports/account-statistic/', api.AccountStatisticApi.as_view(), name='account-statistic'),
]