mirror of https://github.com/jumpserver/jumpserver
perf: 打算重构 asset application
parent
54d1996507
commit
3de881fa19
|
@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from common.utils import lazyproperty
|
||||
from assets.models.base import BaseUser
|
||||
from assets.models import SystemUser
|
||||
|
||||
|
||||
class Account(BaseUser):
|
||||
|
@ -108,3 +109,9 @@ class Account(BaseUser):
|
|||
|
||||
def __str__(self):
|
||||
return self.smart_name
|
||||
|
||||
|
||||
class ApplicationUser(SystemUser):
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = _('Application user')
|
||||
|
|
|
@ -1,216 +1,13 @@
|
|||
from collections import defaultdict
|
||||
from urllib.parse import urlencode, parse_qsl
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from common.mixins import CommonModelMixin
|
||||
from common.tree import TreeNode
|
||||
from common.utils import is_uuid
|
||||
from assets.models import Asset, SystemUser
|
||||
from assets.models import Asset
|
||||
|
||||
from ..utils import KubernetesTree
|
||||
from .. import const
|
||||
|
||||
|
||||
class ApplicationTreeNodeMixin:
|
||||
id: str
|
||||
name: str
|
||||
type: str
|
||||
category: str
|
||||
attrs: dict
|
||||
|
||||
@staticmethod
|
||||
def create_tree_id(pid, type, v):
|
||||
i = dict(parse_qsl(pid))
|
||||
i[type] = v
|
||||
tree_id = urlencode(i)
|
||||
return tree_id
|
||||
|
||||
@classmethod
|
||||
def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None,
|
||||
show_empty=True, show_count=True):
|
||||
count = counts.get(c.value, 0)
|
||||
if count == 0 and not show_empty:
|
||||
return None
|
||||
label = c.label
|
||||
if count is not None and show_count:
|
||||
label = '{} ({})'.format(label, count)
|
||||
data = {
|
||||
'id': id_,
|
||||
'name': label,
|
||||
'title': label,
|
||||
'pId': pid,
|
||||
'isParent': bool(count),
|
||||
'open': opened,
|
||||
'iconSkin': '',
|
||||
'meta': {
|
||||
'type': tp,
|
||||
'data': {
|
||||
'name': c.name,
|
||||
'value': c.value
|
||||
}
|
||||
}
|
||||
}
|
||||
return TreeNode(**data)
|
||||
|
||||
@classmethod
|
||||
def create_root_tree_node(cls, queryset, show_count=True):
|
||||
count = queryset.count() if show_count else None
|
||||
root_id = 'applications'
|
||||
root_name = _('Applications')
|
||||
if count is not None and show_count:
|
||||
root_name = '{} ({})'.format(root_name, count)
|
||||
node = TreeNode(**{
|
||||
'id': root_id,
|
||||
'name': root_name,
|
||||
'title': root_name,
|
||||
'pId': '',
|
||||
'isParent': True,
|
||||
'open': True,
|
||||
'iconSkin': '',
|
||||
'meta': {
|
||||
'type': 'applications_root',
|
||||
}
|
||||
})
|
||||
return node
|
||||
|
||||
@classmethod
|
||||
def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True):
|
||||
nodes = []
|
||||
categories = const.AppType.category_types_mapper().keys()
|
||||
for category in categories:
|
||||
if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category):
|
||||
continue
|
||||
i = cls.create_tree_id(pid, 'category', category.value)
|
||||
node = cls.create_choice_node(
|
||||
category, i, pid=pid, tp='category',
|
||||
counts=counts, opened=False, show_empty=show_empty,
|
||||
show_count=show_count
|
||||
)
|
||||
if not node:
|
||||
continue
|
||||
nodes.append(node)
|
||||
return nodes
|
||||
|
||||
@classmethod
|
||||
def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True):
|
||||
nodes = []
|
||||
temp_pid = pid
|
||||
type_category_mapper = const.AppType.type_category_mapper()
|
||||
types = const.AppType.type_category_mapper().keys()
|
||||
|
||||
for tp in types:
|
||||
if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp):
|
||||
continue
|
||||
category = type_category_mapper.get(tp)
|
||||
pid = cls.create_tree_id(pid, 'category', category.value)
|
||||
i = cls.create_tree_id(pid, 'type', tp.value)
|
||||
node = cls.create_choice_node(
|
||||
tp, i, pid, tp='type', counts=counts, opened=False,
|
||||
show_empty=show_empty, show_count=show_count
|
||||
)
|
||||
pid = temp_pid
|
||||
if not node:
|
||||
continue
|
||||
nodes.append(node)
|
||||
return nodes
|
||||
|
||||
@staticmethod
|
||||
def get_tree_node_counts(queryset):
|
||||
counts = defaultdict(int)
|
||||
values = queryset.values_list('type', 'category')
|
||||
for i in values:
|
||||
tp = i[0]
|
||||
category = i[1]
|
||||
counts[tp] += 1
|
||||
counts[category] += 1
|
||||
return counts
|
||||
|
||||
@classmethod
|
||||
def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True):
|
||||
counts = cls.get_tree_node_counts(queryset)
|
||||
tree_nodes = []
|
||||
|
||||
# 类别的节点
|
||||
tree_nodes += cls.create_category_tree_nodes(
|
||||
pid, counts, show_empty=show_empty,
|
||||
show_count=show_count
|
||||
)
|
||||
|
||||
# 类型的节点
|
||||
tree_nodes += cls.create_types_tree_nodes(
|
||||
pid, counts, show_empty=show_empty,
|
||||
show_count=show_count
|
||||
)
|
||||
return tree_nodes
|
||||
|
||||
@classmethod
|
||||
def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True):
|
||||
tree_nodes = []
|
||||
|
||||
# 根节点有可能是组织名称
|
||||
if root_node is None:
|
||||
root_node = cls.create_root_tree_node(queryset, show_count=show_count)
|
||||
tree_nodes.append(root_node)
|
||||
|
||||
tree_nodes += cls.create_category_type_tree_nodes(
|
||||
queryset, root_node.id, show_empty=show_empty, show_count=show_count
|
||||
)
|
||||
|
||||
# 应用的节点
|
||||
for app in queryset:
|
||||
if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type):
|
||||
continue
|
||||
node = app.as_tree_node(root_node.id)
|
||||
tree_nodes.append(node)
|
||||
return tree_nodes
|
||||
|
||||
def create_app_tree_pid(self, root_id):
|
||||
pid = self.create_tree_id(root_id, 'category', self.category)
|
||||
pid = self.create_tree_id(pid, 'type', self.type)
|
||||
return pid
|
||||
|
||||
def as_tree_node(self, pid, k8s_as_tree=False):
|
||||
if self.type == const.AppType.k8s and k8s_as_tree:
|
||||
node = KubernetesTree(pid).as_tree_node(self)
|
||||
else:
|
||||
node = self._as_tree_node(pid)
|
||||
return node
|
||||
|
||||
def _attrs_to_tree(self):
|
||||
if self.category == const.AppCategory.db:
|
||||
return self.attrs
|
||||
return {}
|
||||
|
||||
def _as_tree_node(self, pid):
|
||||
icon_skin_category_mapper = {
|
||||
'remote_app': 'chrome',
|
||||
'db': 'database',
|
||||
'cloud': 'cloud'
|
||||
}
|
||||
icon_skin = icon_skin_category_mapper.get(self.category, 'file')
|
||||
pid = self.create_app_tree_pid(pid)
|
||||
node = TreeNode(**{
|
||||
'id': str(self.id),
|
||||
'name': self.name,
|
||||
'title': self.name,
|
||||
'pId': pid,
|
||||
'isParent': False,
|
||||
'open': False,
|
||||
'iconSkin': icon_skin,
|
||||
'meta': {
|
||||
'type': 'application',
|
||||
'data': {
|
||||
'category': self.category,
|
||||
'type': self.type,
|
||||
'attrs': self._attrs_to_tree()
|
||||
}
|
||||
}
|
||||
})
|
||||
return node
|
||||
from .tree import ApplicationTreeNodeMixin
|
||||
|
||||
|
||||
class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
|
||||
|
@ -279,8 +76,3 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
|
|||
if raise_exception:
|
||||
raise ValueError("Remote App not has asset attr")
|
||||
|
||||
|
||||
class ApplicationUser(SystemUser):
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = _('Application user')
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .application import Application
|
||||
|
||||
|
||||
class Database(Application):
|
||||
host = models.CharField(max_length=1024, verbose_name=_('Host'))
|
||||
port = models.IntegerField(verbose_name=_("Port"))
|
||||
database = models.CharField(max_length=1024, blank=True, null=True, verbose_name=_("Database"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Database")
|
|
@ -0,0 +1,208 @@
|
|||
from collections import defaultdict
|
||||
from urllib.parse import urlencode, parse_qsl
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from common.tree import TreeNode
|
||||
from ..utils import KubernetesTree
|
||||
from .. import const
|
||||
|
||||
|
||||
class ApplicationTreeNodeMixin:
|
||||
id: str
|
||||
name: str
|
||||
type: str
|
||||
category: str
|
||||
attrs: dict
|
||||
|
||||
@staticmethod
|
||||
def create_tree_id(pid, type, v):
|
||||
i = dict(parse_qsl(pid))
|
||||
i[type] = v
|
||||
tree_id = urlencode(i)
|
||||
return tree_id
|
||||
|
||||
@classmethod
|
||||
def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None,
|
||||
show_empty=True, show_count=True):
|
||||
count = counts.get(c.value, 0)
|
||||
if count == 0 and not show_empty:
|
||||
return None
|
||||
label = c.label
|
||||
if count is not None and show_count:
|
||||
label = '{} ({})'.format(label, count)
|
||||
data = {
|
||||
'id': id_,
|
||||
'name': label,
|
||||
'title': label,
|
||||
'pId': pid,
|
||||
'isParent': bool(count),
|
||||
'open': opened,
|
||||
'iconSkin': '',
|
||||
'meta': {
|
||||
'type': tp,
|
||||
'data': {
|
||||
'name': c.name,
|
||||
'value': c.value
|
||||
}
|
||||
}
|
||||
}
|
||||
return TreeNode(**data)
|
||||
|
||||
@classmethod
|
||||
def create_root_tree_node(cls, queryset, show_count=True):
|
||||
count = queryset.count() if show_count else None
|
||||
root_id = 'applications'
|
||||
root_name = _('Applications')
|
||||
if count is not None and show_count:
|
||||
root_name = '{} ({})'.format(root_name, count)
|
||||
node = TreeNode(**{
|
||||
'id': root_id,
|
||||
'name': root_name,
|
||||
'title': root_name,
|
||||
'pId': '',
|
||||
'isParent': True,
|
||||
'open': True,
|
||||
'iconSkin': '',
|
||||
'meta': {
|
||||
'type': 'applications_root',
|
||||
}
|
||||
})
|
||||
return node
|
||||
|
||||
@classmethod
|
||||
def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True):
|
||||
nodes = []
|
||||
categories = const.AppType.category_types_mapper().keys()
|
||||
for category in categories:
|
||||
if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category):
|
||||
continue
|
||||
i = cls.create_tree_id(pid, 'category', category.value)
|
||||
node = cls.create_choice_node(
|
||||
category, i, pid=pid, tp='category',
|
||||
counts=counts, opened=False, show_empty=show_empty,
|
||||
show_count=show_count
|
||||
)
|
||||
if not node:
|
||||
continue
|
||||
nodes.append(node)
|
||||
return nodes
|
||||
|
||||
@classmethod
|
||||
def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True):
|
||||
nodes = []
|
||||
temp_pid = pid
|
||||
type_category_mapper = const.AppType.type_category_mapper()
|
||||
types = const.AppType.type_category_mapper().keys()
|
||||
|
||||
for tp in types:
|
||||
if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp):
|
||||
continue
|
||||
category = type_category_mapper.get(tp)
|
||||
pid = cls.create_tree_id(pid, 'category', category.value)
|
||||
i = cls.create_tree_id(pid, 'type', tp.value)
|
||||
node = cls.create_choice_node(
|
||||
tp, i, pid, tp='type', counts=counts, opened=False,
|
||||
show_empty=show_empty, show_count=show_count
|
||||
)
|
||||
pid = temp_pid
|
||||
if not node:
|
||||
continue
|
||||
nodes.append(node)
|
||||
return nodes
|
||||
|
||||
@staticmethod
|
||||
def get_tree_node_counts(queryset):
|
||||
counts = defaultdict(int)
|
||||
values = queryset.values_list('type', 'category')
|
||||
for i in values:
|
||||
tp = i[0]
|
||||
category = i[1]
|
||||
counts[tp] += 1
|
||||
counts[category] += 1
|
||||
return counts
|
||||
|
||||
@classmethod
|
||||
def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True):
|
||||
counts = cls.get_tree_node_counts(queryset)
|
||||
tree_nodes = []
|
||||
|
||||
# 类别的节点
|
||||
tree_nodes += cls.create_category_tree_nodes(
|
||||
pid, counts, show_empty=show_empty,
|
||||
show_count=show_count
|
||||
)
|
||||
|
||||
# 类型的节点
|
||||
tree_nodes += cls.create_types_tree_nodes(
|
||||
pid, counts, show_empty=show_empty,
|
||||
show_count=show_count
|
||||
)
|
||||
return tree_nodes
|
||||
|
||||
@classmethod
|
||||
def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True):
|
||||
tree_nodes = []
|
||||
|
||||
# 根节点有可能是组织名称
|
||||
if root_node is None:
|
||||
root_node = cls.create_root_tree_node(queryset, show_count=show_count)
|
||||
tree_nodes.append(root_node)
|
||||
|
||||
tree_nodes += cls.create_category_type_tree_nodes(
|
||||
queryset, root_node.id, show_empty=show_empty, show_count=show_count
|
||||
)
|
||||
|
||||
# 应用的节点
|
||||
for app in queryset:
|
||||
if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type):
|
||||
continue
|
||||
node = app.as_tree_node(root_node.id)
|
||||
tree_nodes.append(node)
|
||||
return tree_nodes
|
||||
|
||||
def create_app_tree_pid(self, root_id):
|
||||
pid = self.create_tree_id(root_id, 'category', self.category)
|
||||
pid = self.create_tree_id(pid, 'type', self.type)
|
||||
return pid
|
||||
|
||||
def as_tree_node(self, pid, k8s_as_tree=False):
|
||||
if self.type == const.AppType.k8s and k8s_as_tree:
|
||||
node = KubernetesTree(pid).as_tree_node(self)
|
||||
else:
|
||||
node = self._as_tree_node(pid)
|
||||
return node
|
||||
|
||||
def _attrs_to_tree(self):
|
||||
if self.category == const.AppCategory.db:
|
||||
return self.attrs
|
||||
return {}
|
||||
|
||||
def _as_tree_node(self, pid):
|
||||
icon_skin_category_mapper = {
|
||||
'remote_app': 'chrome',
|
||||
'db': 'database',
|
||||
'cloud': 'cloud'
|
||||
}
|
||||
icon_skin = icon_skin_category_mapper.get(self.category, 'file')
|
||||
pid = self.create_app_tree_pid(pid)
|
||||
node = TreeNode(**{
|
||||
'id': str(self.id),
|
||||
'name': self.name,
|
||||
'title': self.name,
|
||||
'pId': pid,
|
||||
'isParent': False,
|
||||
'open': False,
|
||||
'iconSkin': icon_skin,
|
||||
'meta': {
|
||||
'type': 'application',
|
||||
'data': {
|
||||
'category': self.category,
|
||||
'type': self.type,
|
||||
'attrs': self._attrs_to_tree()
|
||||
}
|
||||
}
|
||||
})
|
||||
return node
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
from .mixin import *
|
||||
from .platform import *
|
||||
from .admin_user import *
|
||||
from .asset import *
|
||||
from .label import *
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from rest_framework.generics import RetrieveAPIView, ListAPIView
|
||||
from rest_framework.decorators import action
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import Q
|
||||
|
||||
|
@ -27,8 +27,7 @@ from ..filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFi
|
|||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AssetViewSet', 'AssetPlatformRetrieveApi',
|
||||
'AssetGatewayListApi', 'AssetPlatformViewSet',
|
||||
'AssetTaskCreateApi', 'AssetsTaskCreateApi',
|
||||
'AssetGatewayListApi', 'AssetTaskCreateApi', 'AssetsTaskCreateApi',
|
||||
'AssetPermUserListApi', 'AssetPermUserPermissionsListApi',
|
||||
'AssetPermUserGroupListApi', 'AssetPermUserGroupPermissionsListApi',
|
||||
]
|
||||
|
@ -52,7 +51,8 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet)
|
|||
ordering = ('hostname', )
|
||||
serializer_classes = {
|
||||
'default': serializers.AssetSerializer,
|
||||
'suggestion': serializers.MiniAssetSerializer
|
||||
'suggestion': serializers.MiniAssetSerializer,
|
||||
'platform': serializers.PlatformSerializer
|
||||
}
|
||||
rbac_perms = {
|
||||
'match': 'assets.match_asset'
|
||||
|
@ -74,6 +74,10 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet)
|
|||
assets = serializer.save()
|
||||
self.set_assets_node(assets)
|
||||
|
||||
@action(methods='GET', detail=True, url_path='platform')
|
||||
def platform(self, request, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class AssetPlatformRetrieveApi(RetrieveAPIView):
|
||||
queryset = Platform.objects.all()
|
||||
|
@ -88,20 +92,6 @@ class AssetPlatformRetrieveApi(RetrieveAPIView):
|
|||
return asset.platform
|
||||
|
||||
|
||||
class AssetPlatformViewSet(ModelViewSet):
|
||||
queryset = Platform.objects.all()
|
||||
serializer_class = serializers.PlatformSerializer
|
||||
filterset_fields = ['name', 'base']
|
||||
search_fields = ['name']
|
||||
|
||||
def check_object_permissions(self, request, obj):
|
||||
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
|
||||
self.permission_denied(
|
||||
request, message={"detail": "Internal platform"}
|
||||
)
|
||||
return super().check_object_permissions(request, obj)
|
||||
|
||||
|
||||
class AssetsTaskMixin:
|
||||
|
||||
def perform_assets_task(self, serializer):
|
||||
|
@ -246,7 +236,7 @@ class AssetPermUserGroupListApi(BaseAssetPermUserOrUserGroupListApi):
|
|||
return user_groups
|
||||
|
||||
|
||||
class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView):
|
||||
class BasePermedAssetListApi(generics.ListAPIView):
|
||||
model = AssetPermission
|
||||
serializer_class = AssetPermissionSerializer
|
||||
filterset_class = AssetPermissionFilter
|
||||
|
@ -272,7 +262,7 @@ class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView):
|
|||
return queryset
|
||||
|
||||
|
||||
class AssetPermUserPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin):
|
||||
class AssetPermUserPermissionsListApi(BasePermedAssetListApi):
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
queryset = self.filter_user_related(queryset)
|
||||
|
@ -291,7 +281,7 @@ class AssetPermUserPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsLis
|
|||
return user
|
||||
|
||||
|
||||
class AssetPermUserGroupPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin):
|
||||
class AssetPermUserGroupPermissionsListApi(BasePermedAssetListApi):
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
queryset = self.filter_user_group_related(queryset)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from assets.models import Platform
|
||||
from assets.serializers import PlatformSerializer
|
||||
|
||||
|
||||
__all__ = ['AssetPlatformViewSet']
|
||||
|
||||
|
||||
class AssetPlatformViewSet(ModelViewSet):
|
||||
queryset = Platform.objects.all()
|
||||
serializer_class = PlatformSerializer
|
||||
filterset_fields = ['name', 'base']
|
||||
search_fields = ['name']
|
||||
|
||||
def check_object_permissions(self, request, obj):
|
||||
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
|
||||
self.permission_denied(
|
||||
request, message={"detail": "Internal platform"}
|
||||
)
|
||||
return super().check_object_permissions(request, obj)
|
|
@ -17,6 +17,6 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='cluster',
|
||||
field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
|
||||
field=models.ForeignKey(default=assets.models.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -50,7 +50,7 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
|
||||
field=models.ManyToManyField(default=assets.models.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
|
|
|
@ -34,7 +34,7 @@ class Migration(migrations.Migration):
|
|||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.ForeignKey(
|
||||
default=assets.models.asset.Platform.default,
|
||||
default=assets.models.Platform.default,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name='assets', to='assets.Platform',
|
||||
verbose_name='Platform'),
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 3.1.14 on 2022-03-30 10:35
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0089_auto_20220310_0616'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Host',
|
||||
fields=[
|
||||
('asset_ptr', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='assets.asset')),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,40 @@
|
|||
# Generated by Django 3.1.14 on 2022-04-01 07:58
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def migrate_to_host(apps, schema_editor):
|
||||
asset_model = apps.get_model("assets", "Asset")
|
||||
host_model = apps.get_model("assets", 'Host')
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
created = 0
|
||||
batch_size = 1000
|
||||
|
||||
while True:
|
||||
start = created
|
||||
end = created + batch_size
|
||||
assets = asset_model.objects.using(db_alias).all()[start:end]
|
||||
if not assets:
|
||||
break
|
||||
|
||||
hosts = [host_model(asset_ptr=asset) for asset in assets]
|
||||
host_model.objects.using(db_alias).bulk_create(hosts, ignore_conflicts=True)
|
||||
created += len(hosts)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0090_add_host'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='host',
|
||||
name='asset_ptr',
|
||||
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset'),
|
||||
),
|
||||
migrations.RunPython(migrate_to_host)
|
||||
]
|
|
@ -0,0 +1,42 @@
|
|||
# Generated by Django 3.1.14 on 2022-04-02 09:09
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0091_auto_20220401_1558'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='HostInfo',
|
||||
fields=[
|
||||
('vendor', models.CharField(blank=True, max_length=64, null=True, verbose_name='Vendor')),
|
||||
('model', models.CharField(blank=True, max_length=54, null=True, verbose_name='Model')),
|
||||
('sn', models.CharField(blank=True, max_length=128, null=True, verbose_name='Serial number')),
|
||||
('cpu_model', models.CharField(blank=True, max_length=64, null=True, verbose_name='CPU model')),
|
||||
('cpu_count', models.IntegerField(null=True, verbose_name='CPU count')),
|
||||
('cpu_cores', models.IntegerField(null=True, verbose_name='CPU cores')),
|
||||
('cpu_vcpus', models.IntegerField(null=True, verbose_name='CPU vcpus')),
|
||||
('memory', models.CharField(blank=True, max_length=64, null=True, verbose_name='Memory')),
|
||||
('disk_total', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk total')),
|
||||
('disk_info', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk info')),
|
||||
('os', models.CharField(blank=True, max_length=128, null=True, verbose_name='OS')),
|
||||
('os_version', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS version')),
|
||||
('os_arch', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS arch')),
|
||||
('hostname_raw', models.CharField(blank=True, max_length=128, null=True, verbose_name='Hostname raw')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('host', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='assets.host', related_name='info', verbose_name='Host')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'HostInfo',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -0,0 +1,51 @@
|
|||
# Generated by Django 3.1.14 on 2022-04-02 08:27
|
||||
|
||||
from django.utils import timezone
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_hardware(apps, *args):
|
||||
host_model = apps.get_model('assets', 'Host')
|
||||
asset_model = apps.get_model('assets', 'Asset')
|
||||
hardware_model = apps.get_model('assets', 'HostInfo')
|
||||
|
||||
created = 0
|
||||
batch_size = 1000
|
||||
|
||||
excludes = ['id', 'host', 'date_updated']
|
||||
fields = [f.name for f in hardware_model._meta.fields]
|
||||
fields = [name for name in fields if name not in excludes]
|
||||
|
||||
while True:
|
||||
start = created
|
||||
end = created + batch_size
|
||||
hosts = host_model.objects.all()[start:end]
|
||||
asset_ids = [h.asset_ptr_id for h in hosts]
|
||||
assets = asset_model.objects.filter(id__in=asset_ids)
|
||||
asset_mapper = {a.id: a for a in assets}
|
||||
if not hosts:
|
||||
break
|
||||
|
||||
hardware_list = []
|
||||
for host in hosts:
|
||||
hardware = hardware_model()
|
||||
asset = asset_mapper[host.asset_ptr_id]
|
||||
hardware.host = host
|
||||
hardware.date_updated = timezone.now()
|
||||
for name in fields:
|
||||
setattr(hardware, name, getattr(asset, name))
|
||||
hardware_list.append(hardware)
|
||||
|
||||
hardware_model.objects.bulk_create(hardware_list, ignore_conflicts=True)
|
||||
created += len(hardware_list)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0092_hardware'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_hardware)
|
||||
]
|
|
@ -0,0 +1,69 @@
|
|||
# Generated by Django 3.1.14 on 2022-04-02 09:36
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0093_auto_20220403_1627'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cpu_cores',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cpu_count',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cpu_model',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cpu_vcpus',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='disk_info',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='disk_total',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='hostname_raw',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='memory',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='model',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='os',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='os_arch',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='os_version',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='sn',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='vendor',
|
||||
),
|
||||
]
|
|
@ -1,4 +1,5 @@
|
|||
from .base import *
|
||||
from .platform import *
|
||||
from .asset import *
|
||||
from .label import Label
|
||||
from .user import *
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
from .common import *
|
||||
from .host import *
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
#
|
||||
|
||||
import uuid
|
||||
import logging
|
||||
|
@ -11,18 +11,17 @@ from django.db import models
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from common.fields.model import JsonDictTextField
|
||||
from common.utils import lazyproperty
|
||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||
from ..platform import Platform
|
||||
from ..base import AbsConnectivity
|
||||
|
||||
from .base import AbsConnectivity
|
||||
|
||||
__all__ = ['Asset', 'ProtocolsMixin', 'Platform', 'AssetQuerySet']
|
||||
__all__ = ['Asset', 'ProtocolsMixin', 'AssetQuerySet', 'default_node', 'default_cluster']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def default_cluster():
|
||||
from .cluster import Cluster
|
||||
from assets.models import Cluster
|
||||
name = "Default"
|
||||
defaults = {"name": name}
|
||||
cluster, created = Cluster.objects.get_or_create(
|
||||
|
@ -33,7 +32,7 @@ def default_cluster():
|
|||
|
||||
def default_node():
|
||||
try:
|
||||
from .node import Node
|
||||
from assets.models import Node
|
||||
root = Node.org_root()
|
||||
return Node.objects.filter(id=root.id)
|
||||
except:
|
||||
|
@ -106,7 +105,7 @@ class NodesRelationMixin:
|
|||
_all_nodes_keys = None
|
||||
|
||||
def get_nodes(self):
|
||||
from .node import Node
|
||||
from assets.models import Node
|
||||
nodes = self.nodes.all()
|
||||
if not nodes:
|
||||
nodes = Node.objects.filter(id=Node.org_root().id)
|
||||
|
@ -122,104 +121,25 @@ class NodesRelationMixin:
|
|||
return nodes
|
||||
|
||||
|
||||
class Platform(models.Model):
|
||||
CHARSET_CHOICES = (
|
||||
('utf8', 'UTF-8'),
|
||||
('gbk', 'GBK'),
|
||||
)
|
||||
BASE_CHOICES = (
|
||||
('Linux', 'Linux'),
|
||||
('Unix', 'Unix'),
|
||||
('MacOS', 'MacOS'),
|
||||
('BSD', 'BSD'),
|
||||
('Windows', 'Windows'),
|
||||
('Other', 'Other'),
|
||||
)
|
||||
name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True)
|
||||
base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base"))
|
||||
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
|
||||
meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta"))
|
||||
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
|
||||
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
linux, created = cls.objects.get_or_create(
|
||||
defaults={'name': 'Linux'}, name='Linux'
|
||||
)
|
||||
return linux.id
|
||||
|
||||
def is_windows(self):
|
||||
return self.base.lower() in ('windows',)
|
||||
|
||||
def is_unixlike(self):
|
||||
return self.base.lower() in ("linux", "unix", "macos", "bsd")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Platform")
|
||||
# ordering = ('name',)
|
||||
|
||||
|
||||
class AbsHardwareInfo(models.Model):
|
||||
# Collect
|
||||
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
|
||||
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
|
||||
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
|
||||
|
||||
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
|
||||
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
|
||||
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
|
||||
cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus'))
|
||||
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
|
||||
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
|
||||
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
|
||||
|
||||
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
|
||||
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
|
||||
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
||||
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@property
|
||||
def cpu_info(self):
|
||||
info = ""
|
||||
if self.cpu_model:
|
||||
info += self.cpu_model
|
||||
if self.cpu_count and self.cpu_cores:
|
||||
info += "{}*{}".format(self.cpu_count, self.cpu_cores)
|
||||
return info
|
||||
|
||||
@property
|
||||
def hardware_info(self):
|
||||
if self.cpu_count:
|
||||
return '{} Core {} {}'.format(
|
||||
self.cpu_vcpus or self.cpu_count * self.cpu_cores,
|
||||
self.memory, self.disk_total
|
||||
)
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||
class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
||||
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
||||
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
||||
protocol = models.CharField(max_length=128, default=ProtocolsMixin.Protocol.ssh,
|
||||
choices=ProtocolsMixin.Protocol.choices, verbose_name=_('Protocol'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
|
||||
platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
|
||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
||||
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
|
||||
platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT,
|
||||
verbose_name=_("Platform"), related_name='assets')
|
||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets',
|
||||
verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
||||
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets',
|
||||
verbose_name=_("Nodes"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
|
||||
# Auth
|
||||
admin_user = models.ForeignKey('assets.SystemUser', on_delete=models.SET_NULL, null=True, verbose_name=_("Admin user"), related_name='admin_assets')
|
||||
admin_user = models.ForeignKey('assets.SystemUser', on_delete=models.SET_NULL, null=True,
|
||||
verbose_name=_("Admin user"), related_name='admin_assets')
|
||||
|
||||
# Some information
|
||||
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
|
||||
|
@ -236,7 +156,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin
|
|||
return '{0.hostname}({0.ip})'.format(self)
|
||||
|
||||
def set_admin_user_relation(self):
|
||||
from .authbook import AuthBook
|
||||
from assets.models import AuthBook
|
||||
if not self.admin_user:
|
||||
return
|
||||
if self.admin_user.type != 'admin':
|
||||
|
@ -333,7 +253,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin
|
|||
return names
|
||||
|
||||
def as_node(self):
|
||||
from .node import Node
|
||||
from assets.models import Node
|
||||
fake_node = Node()
|
||||
fake_node.id = self.id
|
||||
fake_node.key = self.id
|
||||
|
@ -372,7 +292,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin
|
|||
return tree_node
|
||||
|
||||
def get_all_system_users(self):
|
||||
from .user import SystemUser
|
||||
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)
|
|
@ -0,0 +1,9 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .common import Asset
|
||||
|
||||
|
||||
class Database(Asset):
|
||||
database = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True)
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db import models
|
||||
|
||||
from common.mixins.models import CommonModelMixin
|
||||
from .common import Asset
|
||||
|
||||
|
||||
class Host(Asset):
|
||||
pass
|
||||
|
||||
|
||||
class HostInfo(CommonModelMixin):
|
||||
host = models.OneToOneField(Host, related_name='info', on_delete=models.CASCADE,
|
||||
verbose_name=_("Host"), unique=True)
|
||||
# Collect
|
||||
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
|
||||
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
|
||||
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
|
||||
|
||||
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
|
||||
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
|
||||
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
|
||||
cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus'))
|
||||
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
|
||||
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
|
||||
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
|
||||
|
||||
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
|
||||
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
|
||||
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
||||
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
|
||||
|
||||
@property
|
||||
def cpu_info(self):
|
||||
info = ""
|
||||
if self.cpu_model:
|
||||
info += self.cpu_model
|
||||
if self.cpu_count and self.cpu_cores:
|
||||
info += "{}*{}".format(self.cpu_count, self.cpu_cores)
|
||||
return info
|
||||
|
||||
@property
|
||||
def hardware_info(self):
|
||||
if self.cpu_count:
|
||||
return '{} Core {} {}'.format(
|
||||
self.cpu_vcpus or self.cpu_count * self.cpu_cores,
|
||||
self.memory, self.disk_total
|
||||
)
|
||||
else:
|
||||
return ''
|
||||
|
||||
def __str__(self):
|
||||
return '{} of {}'.format(self.hardware_info, self.host.hostname)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("HostInfo")
|
|
@ -0,0 +1,56 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.fields.model import JsonDictTextField
|
||||
|
||||
__all__ = ['Platform']
|
||||
|
||||
|
||||
class Category(models.TextChoices):
|
||||
Host = 'host', _('Host')
|
||||
Network = 'network', _('Network device')
|
||||
Database = 'database', _('Database')
|
||||
RemoteApp = 'remote_app', _('Microsoft remote app')
|
||||
Cloud = 'cloud', _("Cloud")
|
||||
|
||||
|
||||
class Platform(models.Model):
|
||||
CHARSET_CHOICES = (
|
||||
('utf8', 'UTF-8'),
|
||||
('gbk', 'GBK'),
|
||||
)
|
||||
BASE_CHOICES = (
|
||||
('Linux', 'Linux'),
|
||||
('Unix', 'Unix'),
|
||||
('MacOS', 'MacOS'),
|
||||
('BSD', 'BSD'),
|
||||
('Windows', 'Windows'),
|
||||
('Other', 'Other'),
|
||||
)
|
||||
name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True)
|
||||
base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base"))
|
||||
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
|
||||
meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta"))
|
||||
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
|
||||
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
linux, created = cls.objects.get_or_create(
|
||||
defaults={'name': 'Linux'}, name='Linux'
|
||||
)
|
||||
return linux.id
|
||||
|
||||
def is_windows(self):
|
||||
return self.base.lower() in ('windows',)
|
||||
|
||||
def is_unixlike(self):
|
||||
return self.base.lower() in ("linux", "unix", "macos", "bsd")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Platform")
|
||||
# ordering = ('name',)
|
||||
|
|
@ -0,0 +1 @@
|
|||
from .common import *
|
|
@ -5,7 +5,7 @@ from django.core.validators import RegexValidator
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from ..models import Asset, Node, Platform, SystemUser
|
||||
from ...models import Asset, Node, Platform, SystemUser
|
||||
|
||||
__all__ = [
|
||||
'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer',
|
||||
|
@ -82,12 +82,6 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
'protocol', 'port', 'protocols', 'is_active',
|
||||
'public_ip', 'number', 'comment',
|
||||
]
|
||||
fields_hardware = [
|
||||
'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
||||
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
|
||||
'os', 'os_version', 'os_arch', 'hostname_raw',
|
||||
'cpu_info', 'hardware_info',
|
||||
]
|
||||
fields_fk = [
|
||||
'domain', 'domain_display', 'platform', 'admin_user', 'admin_user_display'
|
||||
]
|
||||
|
@ -95,16 +89,13 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
'nodes', 'nodes_display', 'labels', 'labels_display',
|
||||
]
|
||||
read_only_fields = [
|
||||
'connectivity', 'date_verified', 'cpu_info', 'hardware_info',
|
||||
'created_by', 'date_created',
|
||||
'connectivity', 'date_verified', 'created_by', 'date_created',
|
||||
]
|
||||
fields = fields_small + fields_hardware + fields_fk + fields_m2m + read_only_fields
|
||||
fields = fields_small + fields_fk + fields_m2m + read_only_fields
|
||||
extra_kwargs = {
|
||||
'protocol': {'write_only': True},
|
||||
'port': {'write_only': True},
|
||||
'hardware_info': {'label': _('Hardware info'), 'read_only': True},
|
||||
'admin_user_display': {'label': _('Admin user display'), 'read_only': True},
|
||||
'cpu_info': {'label': _('CPU info')},
|
||||
}
|
||||
|
||||
def get_fields(self):
|
|
@ -0,0 +1,19 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from .common import AssetSerializer
|
||||
from assets.models import HostInfo
|
||||
|
||||
|
||||
class HardwareSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = HostInfo
|
||||
fields = [
|
||||
'id', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
||||
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
|
||||
'os', 'os_version', 'os_arch', 'hostname_raw',
|
||||
'cpu_info', 'hardware_info', 'date_updated'
|
||||
]
|
||||
|
||||
|
||||
class HostSerializer(AssetSerializer):
|
||||
hardware_info = HardwareSerializer(read_only=True)
|
|
@ -2,31 +2,31 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelV
|
|||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from ..mixins.api import (
|
||||
RelationMixin, AllowBulkDestroyMixin, CommonMixin
|
||||
RelationMixin, AllowBulkDestroyMixin, CommonApiMixin
|
||||
)
|
||||
|
||||
|
||||
class JMSGenericViewSet(CommonMixin, GenericViewSet):
|
||||
class JMSGenericViewSet(CommonApiMixin, GenericViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class JMSViewSet(CommonMixin, ViewSet):
|
||||
class JMSViewSet(CommonApiMixin, ViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class JMSModelViewSet(CommonMixin, ModelViewSet):
|
||||
class JMSModelViewSet(CommonApiMixin, ModelViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class JMSReadOnlyModelViewSet(CommonMixin, ReadOnlyModelViewSet):
|
||||
class JMSReadOnlyModelViewSet(CommonApiMixin, ReadOnlyModelViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class JMSBulkModelViewSet(CommonMixin, AllowBulkDestroyMixin, BulkModelViewSet):
|
||||
class JMSBulkModelViewSet(CommonApiMixin, AllowBulkDestroyMixin, BulkModelViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class JMSBulkRelationModelViewSet(CommonMixin,
|
||||
class JMSBulkRelationModelViewSet(CommonApiMixin,
|
||||
RelationMixin,
|
||||
AllowBulkDestroyMixin,
|
||||
BulkModelViewSet):
|
||||
|
|
|
@ -13,7 +13,7 @@ from .queryset import QuerySetMixin
|
|||
|
||||
|
||||
__all__ = [
|
||||
'CommonApiMixin', 'PaginatedResponseMixin', 'RelationMixin', 'CommonMixin'
|
||||
'CommonApiMixin', 'PaginatedResponseMixin', 'RelationMixin',
|
||||
]
|
||||
|
||||
|
||||
|
@ -82,15 +82,10 @@ class RelationMixin:
|
|||
self.send_m2m_changed_signal(instance, 'post_remove')
|
||||
|
||||
|
||||
class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin, RenderToJsonMixin):
|
||||
pass
|
||||
|
||||
|
||||
class CommonMixin(SerializerMixin,
|
||||
QuerySetMixin,
|
||||
ExtraFilterFieldsMixin,
|
||||
RenderToJsonMixin):
|
||||
class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin,
|
||||
QuerySetMixin, RenderToJsonMixin):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 244ace5a95503ffaf41b73037692e1121f7c066f
|
Loading…
Reference in New Issue