mirror of https://github.com/jumpserver/jumpserver
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
parent
4ebb4d1b6d
commit
c02f8e499b
|
@ -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'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
]
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 "连接"
|
||||
|
||||
|
|
Loading…
Reference in New Issue