perf: 修改 platform

pull/8873/head
ibuler 2022-08-18 17:58:59 +08:00
parent a9bf4eddea
commit 05f913ab18
10 changed files with 45 additions and 173 deletions

View File

@ -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']
}

View File

@ -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'},
]

View File

@ -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',

View File

@ -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

View File

@ -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")

View File

@ -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"))

View File

@ -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)

View File

@ -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',
]

View File

@ -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),
}

View File

@ -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)
)