feat: 添加资产导入时可以直接写节点 (#4868)

* feat: 优化资产导入, 可以添加节点全称,并自动创建

* feat: 添加资产导入时可以直接写节点

* fix: 修改错误

* fix: 添加node value校验,不能包含/

* chore: merge migrations

* perf: 去掉full value replace

Co-authored-by: ibuler <ibuler@qq.com>
pull/4888/head
fit2bot 2020-10-30 10:16:49 +08:00 committed by GitHub
parent 4ebb4d1b6d
commit c02f8e499b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 147 additions and 35 deletions

View File

@ -20,4 +20,9 @@ class Migration(migrations.Migration):
name='ad_domain',
field=models.CharField(default='', max_length=256),
),
migrations.AlterField(
model_name='gateway',
name='ip',
field=models.CharField(db_index=True, max_length=128, verbose_name='IP'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 2.2.13 on 2020-10-28 08:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0059_auto_20201027_1905'),
]
operations = [
migrations.AlterField(
model_name='gateway',
name='ip',
field=models.CharField(db_index=True, max_length=128, verbose_name='IP'),
),
]

View File

@ -0,0 +1,58 @@
# Generated by Django 2.2.13 on 2020-10-26 11:31
from django.db import migrations, models
def get_node_ancestor_keys(key, with_self=False):
parent_keys = []
key_list = key.split(":")
if not with_self:
key_list.pop()
for i in range(len(key_list)):
parent_keys.append(":".join(key_list))
key_list.pop()
return parent_keys
def migrate_nodes_value_with_slash(apps, schema_editor):
model = apps.get_model("assets", "Node")
db_alias = schema_editor.connection.alias
nodes = model.objects.using(db_alias).filter(value__contains='/')
print('')
print("- Start migrate node value if has /")
for i, node in enumerate(list(nodes)):
new_value = node.value.replace('/', '|')
print("{} start migrate node value: {} => {}".format(i, node.value, new_value))
node.value = new_value
node.save()
def migrate_nodes_full_value(apps, schema_editor):
model = apps.get_model("assets", "Node")
db_alias = schema_editor.connection.alias
nodes = model.objects.using(db_alias).all()
print("- Start migrate node full value")
for i, node in enumerate(list(nodes)):
print("{} start migrate {} node full value".format(i, node.value))
ancestor_keys = get_node_ancestor_keys(node.key, True)
values = model.objects.filter(key__in=ancestor_keys).values_list('key', 'value')
values = [v for k, v in sorted(values, key=lambda x: len(x[0]))]
node.full_value = '/'.join(values)
node.save()
class Migration(migrations.Migration):
dependencies = [
('assets', '0059_auto_20201027_1905'),
]
operations = [
migrations.AddField(
model_name='node',
name='full_value',
field=models.CharField(default='', max_length=4096, verbose_name='Full value'),
),
migrations.RunPython(migrate_nodes_value_with_slash),
migrations.RunPython(migrate_nodes_full_value)
]

View File

@ -313,6 +313,12 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
}
return info
def nodes_display(self):
names = []
for n in self.nodes.all():
names.append(self.full_value)
return names
def as_node(self):
from .node import Node
fake_node = Node()

View File

@ -13,7 +13,7 @@ from django.db.transaction import atomic
from common.utils import get_logger
from common.utils.common import lazyproperty
from orgs.mixins.models import OrgModelMixin, OrgManager
from orgs.utils import get_current_org, tmp_to_org, current_org
from orgs.utils import get_current_org, tmp_to_org
from orgs.models import Organization
@ -205,6 +205,28 @@ class FamilyMixin:
sibling = sibling.exclude(key=self.key)
return sibling
@classmethod
def create_node_by_full_value(cls, full_value):
if not full_value:
return []
nodes_family = full_value.split('/')
org_root = cls.org_root()
if nodes_family[0] == org_root.value:
nodes_family = nodes_family[1:]
return cls.create_nodes_recurse(nodes_family, org_root)
@classmethod
def create_nodes_recurse(cls, values, parent=None):
if not values:
return None
if parent is None:
parent = cls.org_root()
value = values[0]
child, created = parent.get_or_create_child(value=value)
if len(values) == 1:
return child
return cls.create_nodes_recurse(values[1:], child)
def get_family(self):
ancestors = self.get_ancestors()
children = self.get_all_children()
@ -372,6 +394,7 @@ class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
value = models.CharField(max_length=128, verbose_name=_("Value"))
full_value = models.CharField(max_length=4096, verbose_name=_('Full value'), default='')
child_mark = models.IntegerField(default=0)
date_create = models.DateTimeField(auto_now_add=True)
parent_key = models.CharField(max_length=64, verbose_name=_("Parent key"),
@ -412,14 +435,14 @@ class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin):
return self.value
@lazyproperty
def full_value(self):
def computed_full_value(self):
# 不要在列表中调用该属性
values = self.__class__.objects.filter(
key__in=self.get_ancestor_keys()
).values_list('key', 'value')
values = [v for k, v in sorted(values, key=lambda x: len(x[0]))]
values.append(self.value)
return ' / '.join(values)
return '/'.join(values)
@property
def level(self):

View File

@ -67,8 +67,9 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
slug_field='name', queryset=Platform.objects.all(), label=_("Platform")
)
protocols = ProtocolsField(label=_('Protocols'), required=False)
domain_display = serializers.ReadOnlyField(source='domain.name')
admin_user_display = serializers.ReadOnlyField(source='admin_user.name')
domain_display = serializers.ReadOnlyField(source='domain.name', label=_('Domain name'))
admin_user_display = serializers.ReadOnlyField(source='admin_user.name', label=_('Admin user name'))
nodes_display = serializers.ListField(child=serializers.CharField(), label=_('Nodes name'), required=False)
"""
资产的数据结构
@ -90,7 +91,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
'platform': ['name']
}
fields_m2m = [
'nodes', 'labels',
'nodes', 'nodes_display', 'labels',
]
annotates_fields = {
# 'admin_user_display': 'admin_user__name'
@ -133,14 +134,32 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
if protocols_data:
validated_data["protocols"] = ' '.join(protocols_data)
def perform_nodes_display_create(self, instance, nodes_display):
if not nodes_display:
return
nodes_to_set = []
for full_value in nodes_display:
node = Node.objects.filter(full_value=full_value).first()
if node:
nodes_to_set.append(node)
else:
node = Node.create_node_by_full_value(full_value)
nodes_to_set.append(node)
instance.nodes.set(nodes_to_set)
def create(self, validated_data):
self.compatible_with_old_protocol(validated_data)
nodes_display = validated_data.pop('nodes_display')
instance = super().create(validated_data)
self.perform_nodes_display_create(instance, nodes_display)
return instance
def update(self, instance, validated_data):
nodes_display = validated_data.pop('nodes_display')
self.compatible_with_old_protocol(validated_data)
return super().update(instance, validated_data)
instance = super().update(instance, validated_data)
self.perform_nodes_display_create(instance, nodes_display)
return instance
class AssetDisplaySerializer(AssetSerializer):

View File

@ -25,6 +25,9 @@ class NodeSerializer(BulkOrgResourceModelSerializer):
read_only_fields = ['key', 'org_id']
def validate_value(self, data):
if '/' in data:
error = _("Can't contains: " + "/")
raise serializers.ValidationError(error)
if self.instance:
instance = self.instance
siblings = instance.get_siblings()

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-27 20:00+0800\n"
"POT-Creation-Date: 2020-10-27 10:29+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -114,7 +114,7 @@ msgstr "集群"
msgid "KubernetesApp"
msgstr "Kubernetes应用"
#: applications/models/remote_app.py:23 assets/models/asset.py:357
#: applications/models/remote_app.py:23 assets/models/asset.py:364
#: assets/models/authbook.py:26 assets/models/gathered_user.py:14
#: assets/serializers/admin_user.py:32 assets/serializers/asset_user.py:47
#: assets/serializers/asset_user.py:84 assets/serializers/system_user.py:46
@ -563,7 +563,7 @@ msgstr "默认资产组"
msgid "User"
msgstr "用户"
#: assets/models/label.py:19 assets/models/node.py:374 settings/models.py:28
#: assets/models/label.py:19 assets/models/node.py:397 settings/models.py:28
msgid "Value"
msgstr "值"
@ -575,19 +575,23 @@ msgstr "分类"
msgid "New node"
msgstr "新节点"
#: assets/models/node.py:280 users/templates/users/_granted_assets.html:130
#: assets/models/node.py:303 users/templates/users/_granted_assets.html:130
msgid "empty"
msgstr "空"
#: assets/models/node.py:373 perms/models/asset_permission.py:144
#: assets/models/node.py:396 perms/models/asset_permission.py:144
msgid "Key"
msgstr "键"
#: assets/models/node.py:377 perms/models/asset_permission.py:148
#: assets/models/node.py:398
msgid "Full value"
msgstr "全称"
#: assets/models/node.py:401 perms/models/asset_permission.py:148
msgid "Parent key"
msgstr "ssh私钥"
#: assets/models/node.py:386 assets/serializers/system_user.py:45
#: assets/models/node.py:410 assets/serializers/system_user.py:45
#: assets/serializers/system_user.py:185 perms/forms/asset_permission.py:92
#: perms/forms/asset_permission.py:99
#: users/templates/users/user_asset_permission.html:41
@ -701,15 +705,27 @@ msgstr "协议格式 {}/{}"
msgid "Protocol duplicate: {}"
msgstr "协议重复: {}"
#: assets/serializers/asset.py:110
#: assets/serializers/asset.py:70
msgid "Domain name"
msgstr "网域名称"
#: assets/serializers/asset.py:71
msgid "Admin user name"
msgstr "管理用户名称"
#: assets/serializers/asset.py:72
msgid "Nodes name"
msgstr "节点名称"
#: assets/serializers/asset.py:111
msgid "Hardware info"
msgstr "硬件信息"
#: assets/serializers/asset.py:111 orgs/mixins/serializers.py:26
#: assets/serializers/asset.py:112 orgs/mixins/serializers.py:26
msgid "Org name"
msgstr "组织名称"
#: assets/serializers/asset.py:147 assets/serializers/asset.py:178
#: assets/serializers/asset.py:168 assets/serializers/asset.py:199
msgid "Connectivity"
msgstr "连接"