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
.exclude(**{f'{key}__isnull': True})
.values(**{alias: F(key)})
.annotate(total=Count('id'))
.annotate(total=Count('id', distinct=True))
)
data = [

View File

@ -8,7 +8,7 @@ from rest_framework.views import APIView
from audits.models import PasswordChangeLog
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 reports.mixins import DateRangeMixin
@ -24,22 +24,6 @@ class UserChangeSecretApi(DateRangeMixin, APIView):
}
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):
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),
}
data['change_password_region_distribution'] = self.get_change_password_region_distribution(qs)
return JsonResponse(data, status=200)

View File

@ -1,79 +1,56 @@
{% load i18n %}
{% 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 %}
<style>
.markdown-footer {
position: absolute;
left: 50%;
bottom: 0;
display: flex;
flex-grow: 0;
flex-shrink: 0;
align-items: center;
justify-content: center;
transform: translateX(-50%);
width: 285px;
}
.markdown-footer p {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
gap: 3px;
}
<style>
.markdown-footer{
position: absolute;
left: 50%;
bottom: 0;
transform: translateX(-50%);
max-width: 520px;
width: calc(100% - 40px);
padding: 8px 0;
text-align: center;
line-height: 1.4;
color: inherit;
}
.markdown-footer a {
color: #428bca;
}
</style>
<div id="markdown-output" class="markdown-footer"></div>
.markdown-footer p{ margin: 0; }
.markdown-footer a{ color: #428bca; text-decoration: none; }
.markdown-footer a:hover{ text-decoration: underline; }
</style>
<div id="markdown-output" class="markdown-footer" role="contentinfo" aria-label="{% trans 'Page footer' %}"></div>
{% endif %}
<script src="{% static 'js/plugins/markdown-it.min.js' %}"></script>
<script type="text/markdown"> {{ INTERFACE.footer_content }} </script>
<script>
activeNav("{{ FORCE_SCRIPT_NAME }}");
$(document).ready(function () {
setAjaxCSRFToken();
$('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');
(function () {
var container = document.getElementById('markdown-output');
if (!container) return;
if (markdownRef && markdownContent) {
const renderedContent = md.render(markdownContent.trim());
markdownRef.innerHTML = renderedContent;
markdownRef.querySelectorAll('a').forEach(link => {
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
});
}
var src = `{{ INTERFACE.footer_content|default:''|escapejs }}`.replace(/\r\n?/g, '\n').trim();
if (!src) { container.remove(); return; }
var md = window.markdownit({
html: false,
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>