perf: add account status action

pull/14387/head
ibuler 2024-10-28 18:57:57 +08:00
parent 46962e035a
commit 4db4a6dce7
12 changed files with 186 additions and 52 deletions

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
#
from django.shortcuts import get_object_or_404
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
@ -7,8 +8,9 @@ from rest_framework.response import Response
from accounts import serializers
from accounts.const import AutomationTypes
from accounts.filters import GatheredAccountFilterSet
from accounts.models import GatherAccountsAutomation
from accounts.models import GatherAccountsAutomation, AutomationExecution
from accounts.models import GatheredAccount
from assets.models import Asset
from orgs.mixins.api import OrgBulkModelViewSet
from .base import AutomationExecutionViewSet
@ -49,8 +51,29 @@ class GatheredAccountViewSet(OrgBulkModelViewSet):
}
rbac_perms = {
'sync_accounts': 'assets.add_gatheredaccount',
'discover': 'assets.add_gatheredaccount',
}
@action(methods=['get'], detail=False, url_path='discover')
def discover(self, request, *args, **kwargs):
asset_id = request.query_params.get('asset_id')
if not asset_id:
return Response(status=status.HTTP_400_BAD_REQUEST, data={'asset_id': 'This field is required.'})
asset = get_object_or_404(Asset, pk=asset_id)
execution = AutomationExecution()
execution.snapshot = {
'assets': [asset_id],
'nodes': [],
'type': 'gather_accounts',
'is_sync_account': True,
'name': 'Adhoc gather accounts: {}'.format(asset_id),
}
execution.save()
execution.start()
accounts = self.model.objects.filter(asset=asset)
serializer = self.get_serializer(accounts, many=True)
return Response(status=status.HTTP_200_OK, data=serializer.data)
@action(methods=['post'], detail=False, url_path='sync-accounts')
def sync_accounts(self, request, *args, **kwargs):
gathered_account_ids = request.data.get('gathered_account_ids')

View File

@ -3,6 +3,7 @@ from collections import defaultdict
from accounts.const import AutomationTypes
from accounts.models import GatheredAccount
from assets.models import Asset
from common.const import ConfirmOrIgnore
from common.utils import get_logger
from orgs.utils import tmp_to_org
from users.models import User
@ -70,8 +71,9 @@ class GatherAccountsManager(AccountBasePlaybookManager):
def update_or_create_accounts(self):
for asset, data in self.asset_account_info.items():
with tmp_to_org(asset.org_id):
with (tmp_to_org(asset.org_id)):
gathered_accounts = []
# 把所有的设置为 present = False, 创建的时候如果有就会更新
GatheredAccount.objects.filter(asset=asset, present=True).update(present=False)
for d in data:
username = d['username']
@ -79,10 +81,16 @@ class GatherAccountsManager(AccountBasePlaybookManager):
defaults=d, asset=asset, username=username,
)
gathered_accounts.append(gathered_account)
# 不存在的标识为待处理
GatheredAccount.objects \
.filter(asset=asset, present=False) \
.exclude(status=ConfirmOrIgnore.ignored) \
.update(status='')
if not self.is_sync_account:
continue
GatheredAccount.sync_accounts(gathered_accounts)
def run(self, *args, **kwargs):
super().run(*args, **kwargs)
users, change_info = self.generate_send_users_and_change_info()

View File

@ -0,0 +1,41 @@
# Generated by Django 4.1.13 on 2024-10-28 08:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("accounts", "0009_remove_account_date_discovery_and_more"),
]
operations = [
migrations.AlterModelOptions(
name="accountrisk",
options={"verbose_name": "Account risk"},
),
migrations.RenameField(
model_name="account",
old_name="date_last_access",
new_name="date_last_login",
),
migrations.RenameField(
model_name="account",
old_name="access_by",
new_name="login_by",
),
migrations.AddField(
model_name="gatheredaccount",
name="action",
field=models.CharField(
choices=[
("pending", "Pending"),
("confirm", "Confirm"),
("ignore", "Ignore"),
],
default="pending",
max_length=32,
verbose_name="Action",
),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 4.1.13 on 2024-10-28 08:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("accounts", "0010_alter_accountrisk_options_and_more"),
]
operations = [
migrations.RemoveField(
model_name="gatheredaccount",
name="action",
),
migrations.AddField(
model_name="gatheredaccount",
name="status",
field=models.CharField(
blank=True,
choices=[("confirmed", "Confirmed"), ("ignored", "Ignored")],
default="",
max_length=32,
verbose_name="Action",
),
),
]

View File

@ -57,8 +57,8 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount):
history = AccountHistoricalRecords(included_fields=['id', '_secret', 'secret_type', 'version'])
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
source_id = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Source ID'))
date_last_access = models.DateTimeField(null=True, blank=True, verbose_name=_('Date last access'))
access_by = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Access by'))
date_last_login = models.DateTimeField(null=True, blank=True, verbose_name=_('Date last access'))
login_by = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Access by'))
date_change_secret = models.DateTimeField(null=True, blank=True, verbose_name=_('Date change secret'))
change_secret_status = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Change secret status'))

View File

@ -1,9 +1,11 @@
from django.db import models
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes, Source
from accounts.models import Account
from common.const import ConfirmOrIgnore
from orgs.mixins.models import JMSOrgBaseModel
from .base import AccountBaseAutomation
@ -11,19 +13,46 @@ __all__ = ['GatherAccountsAutomation', 'GatheredAccount']
class GatheredAccount(JMSOrgBaseModel):
present = models.BooleanField(default=True, verbose_name=_("Present"))
present = models.BooleanField(default=True, verbose_name=_("Present")) # 资产上是否还存在
date_last_login = models.DateTimeField(null=True, verbose_name=_("Date login"))
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_("Asset"))
username = models.CharField(max_length=32, blank=True, db_index=True, verbose_name=_('Username'))
address_last_login = models.CharField(max_length=39, default='', verbose_name=_("Address login"))
status = models.CharField(max_length=32, default='', blank=True, choices=ConfirmOrIgnore.choices, verbose_name=_("Action"))
@property
def address(self):
return self.asset.address
@staticmethod
def sync_accounts(gathered_accounts):
@classmethod
def update_exists_accounts(cls, gathered_account, accounts):
if not gathered_account.date_last_login:
return
for account in accounts:
if (not account.date_last_login or
account.date_last_login - gathered_account.date_last_login > timezone.timedelta(minutes=5)):
account.date_last_login = gathered_account.date_last_login
account.login_by = '{}({})'.format('unknown', gathered_account.address_last_login)
account.save(update_fields=['date_last_login', 'login_by'])
@classmethod
def create_accounts(cls, gathered_account, accounts):
account_objs = []
asset_id = gathered_account.asset_id
username = gathered_account.username
access_by = '{}({})'.format('unknown', gathered_account.address_last_login)
account = Account(
asset_id=asset_id, username=username,
name=username, source=Source.COLLECTED,
date_last_access=gathered_account.date_last_login,
access_by=access_by
)
account_objs.append(account)
Account.objects.bulk_create(account_objs)
@classmethod
def sync_accounts(cls, gathered_accounts):
for gathered_account in gathered_accounts:
asset_id = gathered_account.asset_id
username = gathered_account.username
@ -32,13 +61,12 @@ class GatheredAccount(JMSOrgBaseModel):
Q(asset_id=asset_id, name=username)
)
if accounts.exists():
continue
account = Account(
asset_id=asset_id, username=username,
name=username, source=Source.COLLECTED
)
account_objs.append(account)
Account.objects.bulk_create(account_objs)
cls.update_exists_accounts(gathered_account, accounts)
else:
cls.create_accounts(gathered_account, accounts)
gathered_account.status = ConfirmOrIgnore.confirmed
gathered_account.save(update_fields=['action'])
class Meta:
verbose_name = _("Gather asset accounts")

View File

@ -236,7 +236,7 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
class Meta(BaseAccountSerializer.Meta):
model = Account
automation_fields = [
'date_last_access', 'access_by', 'date_verified', 'connectivity',
'date_last_login', 'login_by', 'date_verified', 'connectivity',
'date_change_secret', 'change_secret_status'
]
fields = BaseAccountSerializer.Meta.fields + [

View File

@ -2,10 +2,15 @@ from django.utils.translation import gettext_lazy as _
from accounts.models import GatheredAccount
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .account import AccountAssetSerializer
from .account import AccountAssetSerializer as _AccountAssetSerializer
from .base import BaseAccountSerializer
class AccountAssetSerializer(_AccountAssetSerializer):
class Meta(_AccountAssetSerializer.Meta):
fields = [f for f in _AccountAssetSerializer.Meta.fields if f != 'auto_config']
class GatheredAccountSerializer(BulkOrgResourceModelSerializer):
asset = AccountAssetSerializer(label=_('Asset'))
@ -13,7 +18,8 @@ class GatheredAccountSerializer(BulkOrgResourceModelSerializer):
model = GatheredAccount
fields = [
'id', 'present', 'asset', 'username',
'date_updated', 'address_last_login', 'date_last_login'
'date_updated', 'address_last_login',
'date_last_login', 'status'
]
@classmethod

View File

@ -1,39 +1,33 @@
# ~*~ coding: utf-8 ~*~
from celery import shared_task
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_noop
from accounts.const import AutomationTypes
from accounts.tasks.common import quickstart_automation_by_snapshot
from assets.models import Node
from common.utils import get_logger
from orgs.utils import org_aware_func
__all__ = ['gather_asset_accounts_task']
# __all__ = ['gather_asset_accounts_task']
logger = get_logger(__name__)
@org_aware_func("nodes")
def gather_asset_accounts_util(nodes, task_name):
from accounts.models import GatherAccountsAutomation
task_name = GatherAccountsAutomation.generate_unique_name(task_name)
task_snapshot = {
'nodes': [str(node.id) for node in nodes],
}
tp = AutomationTypes.verify_account
quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
@shared_task(
queue="ansible",
verbose_name=_('Gather asset accounts'),
activity_callback=lambda self, node_ids, task_name=None, *args, **kwargs: (node_ids, None),
description=_("Unused")
)
def gather_asset_accounts_task(node_ids, task_name=None):
if task_name is None:
task_name = gettext_noop("Gather assets accounts")
nodes = Node.objects.filter(id__in=node_ids)
gather_asset_accounts_util(nodes=nodes, task_name=task_name)
#
# @org_aware_func("nodes")
# def gather_asset_accounts_util(nodes, task_name):
# from accounts.models import GatherAccountsAutomation
# task_name = GatherAccountsAutomation.generate_unique_name(task_name)
#
# task_snapshot = {
# 'nodes': [str(node.id) for node in nodes],
# }
# tp = AutomationTypes.verify_account
# quickstart_automation_by_snapshot(task_name, tp, task_snapshot)
#
#
# @shared_task(
# queue="ansible",
# verbose_name=_('Gather asset accounts'),
# activity_callback=lambda self, node_ids, task_name=None, *args, **kwargs: (node_ids, None),
# description=_("Unused")
# )
# def gather_asset_accounts_task(node_ids, task_name=None):
# if task_name is None:
# task_name = gettext_noop("Gather assets accounts")
#
# nodes = Node.objects.filter(id__in=node_ids)
# gather_asset_accounts_util(nodes=nodes, task_name=task_name)
#

View File

@ -131,8 +131,8 @@ class AutomationExecution(OrgModelMixin):
return self.snapshot['type']
def get_all_asset_ids(self):
node_ids = self.snapshot['nodes']
asset_ids = self.snapshot['assets']
node_ids = self.snapshot.get('nodes', [])
asset_ids = self.snapshot.get('assets', [])
nodes = Node.objects.filter(id__in=node_ids)
node_asset_ids = Node.get_nodes_all_assets(*nodes).values_list('id', flat=True)
asset_ids = set(list(asset_ids) + list(node_asset_ids))

View File

@ -75,4 +75,9 @@ class Language(models.TextChoices):
jp = 'ja', '日本語',
class ConfirmOrIgnore(models.TextChoices):
confirmed = 'confirmed', _('Confirmed')
ignored = 'ignored', _('Ignored')
COUNTRY_CALLING_CODES = get_country_phone_choices()

View File

@ -3,6 +3,7 @@
<!-- css file -->
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'css/font-awesome.min.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/toastr/toastr.min.css' %}" rel="stylesheet">
<link href="{% static 'css/style.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">