perf: user asset account report

This commit is contained in:
feng 2025-08-13 18:47:44 +08:00 committed by feng626
parent ab30bfb2d2
commit 0fb7e84678
3 changed files with 43 additions and 83 deletions

View File

@ -6,7 +6,7 @@ def group_stats(queryset, alias, key, label_map=None):
queryset queryset
.exclude(**{f'{key}__isnull': True}) .exclude(**{f'{key}__isnull': True})
.values(**{alias: F(key)}) .values(**{alias: F(key)})
.annotate(total=Count('id')) .annotate(total=Count('id', distinct=True))
) )
data = [ data = [

View File

@ -8,7 +8,7 @@ from rest_framework.views import APIView
from audits.models import PasswordChangeLog from audits.models import PasswordChangeLog
from common.permissions import IsValidLicense from common.permissions import IsValidLicense
from common.utils import lazyproperty, get_ip_city, get_logger from common.utils import lazyproperty, get_logger
from rbac.permissions import RBACPermission from rbac.permissions import RBACPermission
from reports.mixins import DateRangeMixin from reports.mixins import DateRangeMixin
@ -24,22 +24,6 @@ class UserChangeSecretApi(DateRangeMixin, APIView):
} }
permission_classes = [RBACPermission, IsValidLicense] permission_classes = [RBACPermission, IsValidLicense]
@staticmethod
def get_change_password_region_distribution(queryset):
unique_ips = queryset.values_list('remote_addr', flat=True).distinct()
data = defaultdict(int)
for ip in unique_ips:
try:
city = str(get_ip_city(ip))
if not city:
continue
data[city] += 1
except Exception:
logger.debug(f"Failed to get city for IP {ip}, skipping", exc_info=True)
continue
return [{'name': k, 'value': v} for k, v in data.items()]
def get_change_password_metrics(self, queryset): def get_change_password_metrics(self, queryset):
filtered_queryset = self.filter_by_date_range(queryset, 'datetime') filtered_queryset = self.filter_by_date_range(queryset, 'datetime')
@ -82,5 +66,4 @@ class UserChangeSecretApi(DateRangeMixin, APIView):
'dates_metrics_total': self.get_change_password_metrics(qs), 'dates_metrics_total': self.get_change_password_metrics(qs),
} }
data['change_password_region_distribution'] = self.get_change_password_region_distribution(qs)
return JsonResponse(data, status=200) return JsonResponse(data, status=200)

View File

@ -1,79 +1,56 @@
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
<!-- Mainly scripts -->
<script src="{% static "js/plugins/metisMenu/jquery.metisMenu.3.0.7.js" %}"></script>
<!-- Custom and plugin javascript -->
<script src="{% static "js/plugins/toastr/toastr.min.js" %}"></script>
<script src="{% static "js/inspinia.js" %}"></script>
<script src="{% static "js/jumpserver.js" %}?v=10"></script>
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<script src="{% static 'js/plugins/select2/i18n/zh-CN.js' %}"></script>
<script src="{% static 'js/plugins/markdown-it.min.js' %}"></script>
{% if INTERFACE.footer_content %} {% if INTERFACE.footer_content %}
<style> <style>
.markdown-footer { .markdown-footer{
position: absolute; position: absolute;
left: 50%; left: 50%;
bottom: 0; bottom: 0;
display: flex; transform: translateX(-50%);
flex-grow: 0; max-width: 520px;
flex-shrink: 0; width: calc(100% - 40px);
align-items: center; padding: 8px 0;
justify-content: center; text-align: center;
transform: translateX(-50%); line-height: 1.4;
width: 285px; color: inherit;
} }
.markdown-footer p {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
gap: 3px;
}
.markdown-footer a { .markdown-footer p{ margin: 0; }
color: #428bca; .markdown-footer a{ color: #428bca; text-decoration: none; }
} .markdown-footer a:hover{ text-decoration: underline; }
</style> </style>
<div id="markdown-output" class="markdown-footer"></div> <div id="markdown-output" class="markdown-footer" role="contentinfo" aria-label="{% trans 'Page footer' %}"></div>
{% endif %} {% endif %}
<script src="{% static 'js/plugins/markdown-it.min.js' %}"></script>
<script type="text/markdown"> {{ INTERFACE.footer_content }} </script>
<script> <script>
activeNav("{{ FORCE_SCRIPT_NAME }}"); (function () {
$(document).ready(function () { var container = document.getElementById('markdown-output');
setAjaxCSRFToken(); if (!container) return;
$('textarea').attr('rows', 5);
if ($('.tooltip')[0]) {
$('.tooltip').tooltip();
}
$.fn.select2.defaults.set('language', getUserLang());
const md = window.markdownit({
html: true,
linkify: true,
typographer: true,
breaks: false
});
const markdownContent = `{{ INTERFACE.footer_content|escapejs }}`;
const markdownRef = document.getElementById('markdown-output');
if (markdownRef && markdownContent) { var src = `{{ INTERFACE.footer_content|default:''|escapejs }}`.replace(/\r\n?/g, '\n').trim();
const renderedContent = md.render(markdownContent.trim()); if (!src) { container.remove(); return; }
markdownRef.innerHTML = renderedContent;
markdownRef.querySelectorAll('a').forEach(link => { var md = window.markdownit({
link.setAttribute('target', '_blank'); html: false,
link.setAttribute('rel', 'noopener noreferrer'); linkify: true,
}); typographer: true,
} breaks: true
}); });
</script>
var html = md.render(src);
if (window.DOMPurify) {
html = window.DOMPurify.sanitize(html);
}
container.innerHTML = html;
container.querySelectorAll('a').forEach(function (a) {
a.setAttribute('target', '_blank');
a.setAttribute('rel', 'noopener noreferrer');
});
})();
</script>