From 05f913ab1805e6a30d99a87b451c34b2bb6ea18a Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 18 Aug 2022 17:58:59 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20platform?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/common.py | 1 - .../migrations/0099_auto_20220426_1558.py | 3 - .../migrations/0100_auto_20220430_2126.py | 13 +- apps/assets/models/_authbook.py | 137 ------------------ apps/assets/models/asset/common.py | 7 - apps/assets/models/platform.py | 12 +- apps/assets/serializers/asset/common.py | 4 + apps/assets/serializers/platform.py | 35 ++--- apps/common/drf/fields.py | 2 +- apps/ops/api/command.py | 4 +- 10 files changed, 45 insertions(+), 173 deletions(-) delete mode 100644 apps/assets/models/_authbook.py diff --git a/apps/assets/api/asset/common.py b/apps/assets/api/asset/common.py index 6d266e1bd..756ce09ee 100644 --- a/apps/assets/api/asset/common.py +++ b/apps/assets/api/asset/common.py @@ -29,7 +29,6 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet) filterset_fields = { 'name': ['exact'], 'ip': ['exact'], - 'system_users__id': ['exact'], 'is_active': ['exact'], 'protocols': ['exact', 'icontains'] } diff --git a/apps/assets/migrations/0099_auto_20220426_1558.py b/apps/assets/migrations/0099_auto_20220426_1558.py index a3418aea8..8d888cf6b 100644 --- a/apps/assets/migrations/0099_auto_20220426_1558.py +++ b/apps/assets/migrations/0099_auto_20220426_1558.py @@ -18,9 +18,6 @@ def create_app_platform(apps, *args): {'name': 'MongoDB', 'category': 'database', 'type': 'mongodb'}, {'name': 'Redis', 'category': 'database', 'type': 'redis'}, {'name': 'Chrome', 'category': 'remote_app', 'type': 'chrome'}, - {'name': 'vSphereClient', 'category': 'remote_app', 'type': 'vmware_client'}, - {'name': 'MySQLWorkbench', 'category': 'remote_app', 'type': 'mysql_workbench'}, - {'name': 'GeneralRemoteApp', 'category': 'remote_app', 'type': 'general_remote_app'}, {'name': 'Kubernetes', 'category': 'cloud', 'type': 'k8s'}, ] diff --git a/apps/assets/migrations/0100_auto_20220430_2126.py b/apps/assets/migrations/0100_auto_20220430_2126.py index 9651a9e43..17f6d6da1 100644 --- a/apps/assets/migrations/0100_auto_20220430_2126.py +++ b/apps/assets/migrations/0100_auto_20220430_2126.py @@ -11,6 +11,15 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='PlatformProtocol', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=32, verbose_name='Name')), + ('port', models.IntegerField(verbose_name='Port')), + ('setting', models.JSONField(default=dict, verbose_name='Setting')), + ], + ), migrations.AddField( model_name='platform', name='domain_default', @@ -28,8 +37,8 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='platform', - name='protocols_enabled', - field=models.BooleanField(default=True, verbose_name='Protocols enabled'), + name='protocols', + field=models.ManyToManyField(blank=True, to='assets.PlatformProtocol', verbose_name='Protocols'), ), migrations.AddField( model_name='platform', diff --git a/apps/assets/models/_authbook.py b/apps/assets/models/_authbook.py deleted file mode 100644 index 74a81df61..000000000 --- a/apps/assets/models/_authbook.py +++ /dev/null @@ -1,137 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.db import models -from django.db.models import F -from django.utils.translation import ugettext_lazy as _ -from simple_history.models import HistoricalRecords - -from common.utils import lazyproperty, get_logger -from .base import BaseAccount, AbsConnectivity - -logger = get_logger(__name__) - - -__all__ = ['AuthBook'] - - -class AuthBook(BaseAccount, AbsConnectivity): - asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')) - systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")) - version = models.IntegerField(default=1, verbose_name=_('Version')) - history = HistoricalRecords() - - auth_attrs = ['username', 'password', 'private_key', 'public_key'] - - class Meta: - verbose_name = _('AuthBook') - unique_together = [('username', 'asset', 'systemuser')] - permissions = [ - ('test_authbook', _('Can test asset account connectivity')), - ('view_assetaccountsecret', _('Can view asset account secret')), - ('change_assetaccountsecret', _('Can change asset account secret')), - ('view_assethistoryaccount', _('Can view asset history account')), - ('view_assethistoryaccountsecret', _('Can view asset history account secret')), - ] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.auth_snapshot = {} - - def get_or_systemuser_attr(self, attr): - val = getattr(self, attr, None) - if val: - return val - if self.systemuser: - return getattr(self.systemuser, attr, '') - return '' - - def load_auth(self): - for attr in self.auth_attrs: - value = self.get_or_systemuser_attr(attr) - self.auth_snapshot[attr] = [getattr(self, attr), value] - setattr(self, attr, value) - - def unload_auth(self): - if not self.systemuser: - return - - for attr, values in self.auth_snapshot.items(): - origin_value, loaded_value = values - current_value = getattr(self, attr, '') - if current_value == loaded_value: - setattr(self, attr, origin_value) - - def save(self, *args, **kwargs): - self.unload_auth() - instance = super().save(*args, **kwargs) - self.load_auth() - return instance - - @property - def username_display(self): - return self.get_or_systemuser_attr('username') or '*' - - @lazyproperty - def systemuser_display(self): - if not self.systemuser: - return '' - return str(self.systemuser) - - @property - def smart_name(self): - username = self.username_display - - if self.asset: - asset = str(self.asset) - else: - asset = '*' - return '{}@{}'.format(username, asset) - - def sync_to_system_user_account(self): - if self.systemuser: - return - matched = AuthBook.objects.filter( - asset=self.asset, systemuser__username=self.username - ) - if not matched: - return - - for i in matched: - i.password = self.password - i.private_key = self.private_key - i.public_key = self.public_key - i.comment = 'Update triggered by account {}'.format(self.id) - - # 不触发post_save信号 - self.__class__.objects.bulk_update(matched, fields=['password', 'private_key', 'public_key']) - - def remove_asset_admin_user_if_need(self): - if not self.asset or not self.systemuser: - return - if not self.systemuser.is_admin_user or self.asset.admin_user != self.systemuser: - return - self.asset.admin_user = None - self.asset.save() - logger.debug('Remove asset admin user: {} {}'.format(self.asset, self.systemuser)) - - def update_asset_admin_user_if_need(self): - if not self.asset or not self.systemuser: - return - if not self.systemuser.is_admin_user or self.asset.admin_user == self.systemuser: - return - self.asset.admin_user = self.systemuser - self.asset.save() - logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser)) - - @classmethod - def get_queryset(cls): - queryset = cls.objects.all() \ - .annotate(ip=F('asset__ip')) \ - .annotate(hostname=F('asset__hostname')) \ - .annotate(platform=F('asset__platform__name')) \ - .annotate(protocols=F('asset__protocols')) - return queryset - - def __str__(self): - return self.smart_name diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index f04f1f959..dd8d07dd1 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -157,13 +157,6 @@ class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): tree_node = TreeNode(**data) return tree_node - def get_all_system_users(self): - from assets.models import SystemUser - system_user_ids = SystemUser.assets.through.objects.filter(asset=self) \ - .values_list('systemuser_id', flat=True) - system_users = SystemUser.objects.filter(id__in=system_user_ids) - return system_users - class Meta: unique_together = [('org_id', 'name')] verbose_name = _("Asset") diff --git a/apps/assets/models/platform.py b/apps/assets/models/platform.py index 7dd991c1d..7b6dc1889 100644 --- a/apps/assets/models/platform.py +++ b/apps/assets/models/platform.py @@ -5,7 +5,13 @@ from assets.const import Category, AllTypes from common.db.fields import JsonDictTextField -__all__ = ['Platform'] +__all__ = ['Platform', 'PlatformProtocol'] + + +class PlatformProtocol(models.Model): + name = models.CharField(max_length=32, verbose_name=_('Name')) + port = models.IntegerField(verbose_name=_('Port')) + setting = models.JSONField(verbose_name=_('Setting'), default=dict) class Platform(models.Model): @@ -30,9 +36,7 @@ class Platform(models.Model): verbose_name=_("Domain default") ) protocols_enabled = models.BooleanField(default=True, verbose_name=_("Protocols enabled")) - protocols_default = models.JSONField( - max_length=128, default=list, blank=True, verbose_name=_("Protocols default") - ) + protocols = models.ManyToManyField(PlatformProtocol, blank=True, verbose_name=_("Protocols")) # Accounts # 这应该和账号有关 su_enabled = models.BooleanField(default=False, verbose_name=_("Su enabled")) diff --git a/apps/assets/serializers/asset/common.py b/apps/assets/serializers/asset/common.py index 91f1871e9..9363cbe3c 100644 --- a/apps/assets/serializers/asset/common.py +++ b/apps/assets/serializers/asset/common.py @@ -4,8 +4,10 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from common.drf.serializers import JMSWritableNestedModelSerializer +from common.drf.fields import ChoiceDisplayField from ..account import AccountSerializer from ...models import Asset, Node, Platform, Protocol, Label, Domain, Account +from ...const import Category, AllTypes __all__ = [ 'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer', @@ -57,6 +59,8 @@ class AssetNodesSerializer(serializers.ModelSerializer): class AssetSerializer(JMSWritableNestedModelSerializer): + category = ChoiceDisplayField(choices=Category.choices, read_only=True, label=_('Category')) + type = ChoiceDisplayField(choices=AllTypes.choices, read_only=True, label=_('Type')) domain = AssetDomainSerializer(required=False) platform = AssetPlatformSerializer(required=False) labels = AssetLabelSerializer(many=True, required=False) diff --git a/apps/assets/serializers/platform.py b/apps/assets/serializers/platform.py index 999821abc..6cbd5b898 100644 --- a/apps/assets/serializers/platform.py +++ b/apps/assets/serializers/platform.py @@ -1,42 +1,43 @@ from rest_framework import serializers -from django.core.validators import RegexValidator from django.utils.translation import gettext_lazy as _ -from assets.models import Platform -from .mixin import CategoryDisplayMixin +from common.drf.fields import ChoiceDisplayField +from common.drf.serializers import JMSWritableNestedModelSerializer +from ..models import Platform, PlatformProtocol +from ..const import Category, AllTypes __all__ = ['PlatformSerializer'] -class PlatformProtocolsSerializer(serializers.Serializer): - name = serializers.CharField(max_length=255, required=True) - port = serializers.IntegerField(max_value=65535, min_value=1, required=True) +class PlatformProtocolsSerializer(serializers.ModelSerializer): + class Meta: + model = PlatformProtocol + fields = ['id', 'name', 'port', 'setting'] -class PlatformSerializer(CategoryDisplayMixin, serializers.ModelSerializer): - meta = serializers.DictField(required=False, allow_null=True, label=_('Meta')) - protocols_default = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) +class PlatformSerializer(JMSWritableNestedModelSerializer): + type = ChoiceDisplayField(choices=AllTypes.choices, label=_("Type")) + category = ChoiceDisplayField(choices=Category.choices, label=_("Category")) + protocols = PlatformProtocolsSerializer(label=_('Protocols'), many=True, required=False) type_constraints = serializers.ReadOnlyField(required=False, read_only=True) class Meta: model = Platform fields_mini = ['id', 'name', 'internal'] fields_small = fields_mini + [ - 'meta', 'comment', 'charset', - 'category', 'category_display', - 'type', 'type_display', + 'category', 'type', + ] + fields = fields_small + [ + 'domain_enabled', 'domain_default', 'su_enabled', 'su_method', + 'protocols_enabled', 'protocols', 'ping_enabled', 'ping_method', 'verify_account_enabled', 'verify_account_method', 'create_account_enabled', 'create_account_method', 'change_password_enabled', 'change_password_method', 'type_constraints', + 'comment', 'charset', ] - fields_fk = [ - 'domain_enabled', 'domain_default', - 'protocols_enabled', 'protocols_default', - ] - fields = fields_small + fields_fk read_only_fields = [ 'category_display', 'type_display', ] diff --git a/apps/common/drf/fields.py b/apps/common/drf/fields.py index c0c214c4f..d925164da 100644 --- a/apps/common/drf/fields.py +++ b/apps/common/drf/fields.py @@ -51,6 +51,6 @@ class ChoiceDisplayField(ChoiceField): if value in ('', None): return value return { - 'name': value, + 'value': value, 'label': self.choice_mapper.get(six.text_type(value), value), } diff --git a/apps/ops/api/command.py b/apps/ops/api/command.py index a129ec416..8c716fb03 100644 --- a/apps/ops/api/command.py +++ b/apps/ops/api/command.py @@ -28,7 +28,9 @@ class CommandExecutionViewSet(RootOrgViewMixin, viewsets.ModelViewSet): system_user = data["run_as"] user = self.request.user - q = Q(granted_by_permissions__system_users__id=system_user.id) & ( + # TOdo: + # Q(granted_by_permissions__system_users__id=system_user.id) & + q = ( Q(granted_by_permissions__users=user) | Q(granted_by_permissions__user_groups__users=user) )