From 81daf33b16931e056e91b30c429307a5fd84f6d3 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Mon, 2 Dec 2024 11:36:20 +0800 Subject: [PATCH] perf: Change secret record table dashboard --- .../accounts/api/automations/change_secret.py | 47 +++++++++++++++++-- apps/accounts/filters.py | 14 +++++- ...changesecretrecord_ignore_fail_and_more.py | 23 +++++++++ .../models/automations/change_secret.py | 3 +- 4 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 apps/accounts/migrations/0018_changesecretrecord_ignore_fail_and_more.py diff --git a/apps/accounts/api/automations/change_secret.py b/apps/accounts/api/automations/change_secret.py index 186a49e4e..7f2ca1979 100644 --- a/apps/accounts/api/automations/change_secret.py +++ b/apps/accounts/api/automations/change_secret.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- # +from django.db.models import Max, Q, Subquery, OuterRef from rest_framework import status, mixins from rest_framework.decorators import action from rest_framework.response import Response from accounts import serializers -from accounts.const import AutomationTypes +from accounts.const import AutomationTypes, ChangeSecretRecordStatusChoice from accounts.filters import ChangeSecretRecordFilterSet from accounts.models import ChangeSecretAutomation, ChangeSecretRecord from accounts.tasks import execute_automation_record_task @@ -34,7 +35,8 @@ class ChangeSecretAutomationViewSet(OrgBulkModelViewSet): class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet): filterset_class = ChangeSecretRecordFilterSet - search_fields = ('asset__address',) + search_fields = ('asset__address', 'account_username') + ordering_fields = ('date_finished',) tp = AutomationTypes.change_secret serializer_classes = { 'default': serializers.ChangeSecretRecordSerializer, @@ -43,6 +45,8 @@ class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet): rbac_perms = { 'execute': 'accounts.add_changesecretexecution', 'secret': 'accounts.view_changesecretrecord', + 'dashboard': 'accounts.view_changesecretrecord', + 'ignore_fail': 'accounts.view_changesecretrecord', } def get_permissions(self): @@ -53,9 +57,35 @@ class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet): ] return super().get_permissions() + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + + if self.action == 'dashboard': + return self.get_dashboard_queryset(queryset) + return queryset + + @staticmethod + def get_dashboard_queryset(queryset): + recent_dates = queryset.values('account').annotate( + max_date_finished=Max('date_finished') + ) + + recent_success_accounts = queryset.filter( + account=OuterRef('account'), + date_finished=Subquery( + recent_dates.filter(account=OuterRef('account')).values('max_date_finished')[:1] + ) + ).filter(Q(status=ChangeSecretRecordStatusChoice.success) | Q(ignore_fail=True)) + + failed_records = queryset.filter( + ~Q(account__in=Subquery(recent_success_accounts.values('account'))), + status=ChangeSecretRecordStatusChoice.failed + ) + return failed_records + def get_queryset(self): qs = ChangeSecretRecord.get_valid_records() - return qs.objects.filter( + return qs.filter( execution__automation__type=self.tp ) @@ -78,6 +108,17 @@ class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet): serializer = self.get_serializer(instance) return Response(serializer.data) + @action(methods=['get'], detail=False, url_path='dashboard') + def dashboard(self, request, *args, **kwargs): + return super().list(request, *args, **kwargs) + + @action(methods=['patch'], detail=True, url_path='ignore-fail') + def ignore_fail(self, request, *args, **kwargs): + instance = self.get_object() + instance.ignore_fail = True + instance.save(update_fields=['ignore_fail']) + return Response(status=status.HTTP_200_OK) + class ChangSecretExecutionViewSet(AutomationExecutionViewSet): rbac_perms = ( diff --git a/apps/accounts/filters.py b/apps/accounts/filters.py index 75fe529cc..a27d2ba25 100644 --- a/apps/accounts/filters.py +++ b/apps/accounts/filters.py @@ -6,6 +6,7 @@ from django_filters import rest_framework as drf_filters from assets.models import Node from common.drf.filters import BaseFilterSet +from common.utils.timezone import local_zero_hour, local_now from .models import Account, GatheredAccount, ChangeSecretRecord @@ -28,7 +29,7 @@ class AccountFilterSet(BaseFilterSet): latest_updated = drf_filters.BooleanFilter(method='filter_latest') latest_secret_changed = drf_filters.BooleanFilter(method='filter_latest') latest_secret_change_failed = drf_filters.BooleanFilter(method='filter_latest') - risk = drf_filters.CharFilter(method='filter_risk',) + risk = drf_filters.CharFilter(method='filter_risk', ) long_time_no_change_secret = drf_filters.BooleanFilter(method='filter_long_time') long_time_no_verified = drf_filters.BooleanFilter(method='filter_long_time') @@ -127,6 +128,17 @@ class ChangeSecretRecordFilterSet(BaseFilterSet): account_username = drf_filters.CharFilter(field_name='account__username', lookup_expr='icontains') execution_id = drf_filters.CharFilter(field_name='execution_id', lookup_expr='exact') + days = drf_filters.NumberFilter(method='filter_days') + + @staticmethod + def filter_days(queryset, name, value): + value = int(value) + + dt = local_zero_hour() + if value != 1: + dt = local_now() - timezone.timedelta(days=value) + return queryset.filter(date_finished__gte=dt) + class Meta: model = ChangeSecretRecord fields = ['id', 'status', 'asset_id', 'execution'] diff --git a/apps/accounts/migrations/0018_changesecretrecord_ignore_fail_and_more.py b/apps/accounts/migrations/0018_changesecretrecord_ignore_fail_and_more.py new file mode 100644 index 000000000..a2805de64 --- /dev/null +++ b/apps/accounts/migrations/0018_changesecretrecord_ignore_fail_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.13 on 2024-12-02 03:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0017_serviceintegration'), + ] + + operations = [ + migrations.AddField( + model_name='changesecretrecord', + name='ignore_fail', + field=models.BooleanField(default=False, verbose_name='Ignore fail'), + ), + migrations.AlterField( + model_name='changesecretrecord', + name='date_finished', + field=models.DateTimeField(blank=True, db_index=True, null=True, verbose_name='Date finished'), + ), + ] diff --git a/apps/accounts/models/automations/change_secret.py b/apps/accounts/models/automations/change_secret.py index 4e42af544..00e7cad04 100644 --- a/apps/accounts/models/automations/change_secret.py +++ b/apps/accounts/models/automations/change_secret.py @@ -37,7 +37,8 @@ class ChangeSecretRecord(JMSBaseModel): old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret')) new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret')) date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started')) - date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished')) + date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'), db_index=True) + ignore_fail = models.BooleanField(default=False, verbose_name=_('Ignore fail')) status = models.CharField( max_length=16, verbose_name=_('Status'), default=ChangeSecretRecordStatusChoice.pending.value )