mirror of https://github.com/jumpserver/jumpserver
commit
52d528613e
|
@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
|
|||
from django import forms
|
||||
|
||||
from orgs.mixins import OrgModelForm
|
||||
from assets.models import SystemUser, Protocol
|
||||
from assets.models import SystemUser
|
||||
|
||||
from ..models import RemoteApp
|
||||
from .. import const
|
||||
|
@ -88,9 +88,7 @@ class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm):
|
|||
# 过滤RDP资产和系统用户
|
||||
super().__init__(*args, **kwargs)
|
||||
field_asset = self.fields['asset']
|
||||
field_asset.queryset = field_asset.queryset.filter(
|
||||
protocols__name=Protocol.PROTOCOL_RDP
|
||||
)
|
||||
field_asset.queryset = field_asset.queryset.has_protocol('rdp')
|
||||
field_system_user = self.fields['system_user']
|
||||
field_system_user.queryset = field_system_user.queryset.filter(
|
||||
protocol=SystemUser.PROTOCOL_RDP
|
||||
|
|
|
@ -20,6 +20,7 @@ from common.mixins import IDInCacheFilterMixin, ApiMessageMixin
|
|||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from orgs.mixins import OrgBulkModelViewSet
|
||||
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
|
||||
from ..models import Asset, AdminUser, Node
|
||||
from .. import serializers
|
||||
|
@ -36,7 +37,7 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class AssetViewSet(IDInCacheFilterMixin, LabelFilter, ApiMessageMixin, BulkModelViewSet):
|
||||
class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
|
||||
"""
|
||||
API endpoint that allows Asset to be viewed or edited.
|
||||
"""
|
||||
|
@ -100,11 +101,6 @@ class AssetViewSet(IDInCacheFilterMixin, LabelFilter, ApiMessageMixin, BulkModel
|
|||
queryset = self.filter_admin_user_id(queryset)
|
||||
return queryset
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().distinct()
|
||||
queryset = self.get_serializer_class().setup_eager_loading(queryset)
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
"""
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import time
|
||||
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import viewsets, status, generics
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
@ -10,7 +8,7 @@ from rest_framework import filters
|
|||
from rest_framework_bulk import BulkModelViewSet
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser
|
||||
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
from common.mixins import IDInCacheFilterMixin
|
||||
from ..backends import AssetUserManager
|
||||
|
@ -57,7 +55,7 @@ class AssetUserSearchBackend(filters.BaseFilterBackend):
|
|||
class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||
pagination_class = LimitOffsetPagination
|
||||
serializer_class = serializers.AssetUserSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser, )
|
||||
permission_classes = [IsOrgAdminOrAppUser]
|
||||
http_method_names = ['get', 'post']
|
||||
filter_fields = [
|
||||
"id", "ip", "hostname", "username", "asset_id", "node_id",
|
||||
|
@ -78,7 +76,7 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
|||
system_user_id = self.request.GET.get("system_user_id")
|
||||
|
||||
kwargs = {}
|
||||
assets = []
|
||||
assets = None
|
||||
|
||||
manager = AssetUserManager()
|
||||
if system_user_id:
|
||||
|
@ -92,7 +90,7 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
|||
manager.prefer('admin_user')
|
||||
|
||||
if asset_id:
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
assets = [asset]
|
||||
elif node_id:
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
|
@ -100,7 +98,7 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
|||
|
||||
if username:
|
||||
kwargs['username'] = username
|
||||
if assets:
|
||||
if assets is not None:
|
||||
kwargs['assets'] = assets
|
||||
|
||||
queryset = manager.filter(**kwargs)
|
||||
|
@ -110,23 +108,14 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
|||
class AssetUserExportViewSet(AssetUserViewSet):
|
||||
serializer_class = serializers.AssetUserExportSerializer
|
||||
http_method_names = ['get']
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
otp_last_verify = request.session.get("OTP_LAST_VERIFY_TIME")
|
||||
if not otp_last_verify or time.time() - int(otp_last_verify) > 600:
|
||||
return Response({"error": "Need MFA confirm mfa auth"}, status=403)
|
||||
return super().list(request, *args, **kwargs)
|
||||
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
|
||||
|
||||
|
||||
class AssetUserAuthInfoApi(generics.RetrieveAPIView):
|
||||
serializer_class = serializers.AssetUserAuthInfoSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
otp_last_verify = request.session.get("OTP_LAST_VERIFY_TIME")
|
||||
if not otp_last_verify or time.time() - int(otp_last_verify) > 600:
|
||||
return Response({"error": "Need MFA confirm mfa auth"}, status=403)
|
||||
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(instance)
|
||||
status_code = status.HTTP_200_OK
|
||||
|
@ -135,15 +124,14 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
|
|||
return Response(serializer.data, status=status_code)
|
||||
|
||||
def get_object(self):
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
prefer = self.request.GET.get("prefer")
|
||||
query_params = self.request.query_params
|
||||
username = query_params.get('username')
|
||||
asset_id = query_params.get('asset_id')
|
||||
prefer = query_params.get("prefer")
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
try:
|
||||
manger = AssetUserManager()
|
||||
if prefer:
|
||||
manger.prefer(prefer)
|
||||
instance = manger.get(username, asset)
|
||||
instance = manger.get(username, asset, prefer=prefer)
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
return None
|
||||
|
@ -156,13 +144,15 @@ class AssetUserTestConnectiveApi(generics.RetrieveAPIView):
|
|||
Test asset users connective
|
||||
"""
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
def get_asset_users(self):
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
prefer = self.request.GET.get("prefer")
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
manager = AssetUserManager()
|
||||
asset_users = manager.filter(username=username, assets=[asset])
|
||||
asset_users = manager.filter(username=username, assets=[asset], prefer=prefer)
|
||||
return asset_users
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
|
|
|
@ -26,6 +26,7 @@ from ..hands import IsOrgAdmin
|
|||
from ..models import Node
|
||||
from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util
|
||||
from .. import serializers
|
||||
from ..utils import NodeUtil
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
@ -79,12 +80,10 @@ class NodeListAsTreeApi(generics.ListAPIView):
|
|||
serializer_class = TreeNodeSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = [node.as_tree_node() for node in Node.objects.all()]
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
if self.request.query_params.get('refresh', '0') == '1':
|
||||
queryset = self.refresh_nodes(queryset)
|
||||
queryset = Node.objects.all()
|
||||
util = NodeUtil()
|
||||
nodes = util.get_nodes_by_queryset(queryset)
|
||||
queryset = [node.as_tree_node() for node in nodes]
|
||||
return queryset
|
||||
|
||||
@staticmethod
|
||||
|
@ -113,16 +112,16 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
|
|||
is_root = False
|
||||
|
||||
def get_queryset(self):
|
||||
self.check_need_refresh_nodes()
|
||||
node_key = self.request.query_params.get('key')
|
||||
if node_key:
|
||||
self.node = Node.objects.get(key=node_key)
|
||||
queryset = self.node.get_children(with_self=False)
|
||||
else:
|
||||
self.is_root = True
|
||||
self.node = Node.root()
|
||||
queryset = list(self.node.get_children(with_self=True))
|
||||
nodes_invalid = Node.objects.exclude(key__startswith=self.node.key)
|
||||
queryset.extend(list(nodes_invalid))
|
||||
util = NodeUtil()
|
||||
# 是否包含自己
|
||||
with_self = False
|
||||
if not node_key:
|
||||
node_key = Node.root().key
|
||||
with_self = True
|
||||
self.node = util.get_node_by_key(node_key)
|
||||
queryset = self.node.get_children(with_self=with_self)
|
||||
queryset = [node.as_tree_node() for node in queryset]
|
||||
queryset = sorted(queryset)
|
||||
return queryset
|
||||
|
@ -131,21 +130,20 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
|
|||
include_assets = self.request.query_params.get('assets', '0') == '1'
|
||||
if not include_assets:
|
||||
return queryset
|
||||
assets = self.node.get_assets()
|
||||
assets = self.node.get_assets().only(
|
||||
"id", "hostname", "ip", 'platform', "os", "org_id",
|
||||
)
|
||||
for asset in assets:
|
||||
queryset.append(asset.as_tree_node(self.node))
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = self.filter_assets(queryset)
|
||||
queryset = self.filter_refresh_nodes(queryset)
|
||||
return queryset
|
||||
|
||||
def filter_refresh_nodes(self, queryset):
|
||||
def check_need_refresh_nodes(self):
|
||||
if self.request.query_params.get('refresh', '0') == '1':
|
||||
Node.expire_nodes_assets_amount()
|
||||
Node.expire_nodes_full_value()
|
||||
return queryset
|
||||
Node.refresh_nodes()
|
||||
|
||||
|
||||
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||
|
|
|
@ -22,6 +22,7 @@ from rest_framework.pagination import LimitOffsetPagination
|
|||
from common.utils import get_logger
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from common.mixins import IDInCacheFilterMixin
|
||||
from orgs.mixins import OrgBulkModelViewSet
|
||||
from ..models import SystemUser, Asset
|
||||
from .. import serializers
|
||||
from ..tasks import push_system_user_to_assets_manual, \
|
||||
|
@ -39,7 +40,7 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class SystemUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||
class SystemUserViewSet(OrgBulkModelViewSet):
|
||||
"""
|
||||
System user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
|
|
|
@ -14,6 +14,11 @@ class AssetUserBackend(BaseBackend):
|
|||
@classmethod
|
||||
def filter(cls, username=None, assets=None, **kwargs):
|
||||
queryset = cls.model.objects.all()
|
||||
prefer_id = kwargs.get('prefer_id')
|
||||
if prefer_id:
|
||||
queryset = queryset.filter(id=prefer_id)
|
||||
instances = cls.construct_authbook_objects(queryset, assets)
|
||||
return instances
|
||||
if username:
|
||||
queryset = queryset.filter(username=username)
|
||||
if assets:
|
||||
|
|
|
@ -7,11 +7,13 @@ from abc import abstractmethod
|
|||
class BaseBackend:
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def filter(cls, username=None, assets=None, latest=True):
|
||||
def filter(cls, username=None, assets=None, latest=True, prefer=None, prefer_id=None):
|
||||
"""
|
||||
:param username: 用户名
|
||||
:param assets: <Asset>对象
|
||||
:param latest: 是否是最新记录
|
||||
:param prefer: 优先使用
|
||||
:param prefer_id: 使用id
|
||||
:return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>)
|
||||
"""
|
||||
pass
|
||||
|
|
|
@ -7,7 +7,7 @@ from .base import BaseBackend
|
|||
|
||||
class AuthBookBackend(BaseBackend):
|
||||
@classmethod
|
||||
def filter(cls, username=None, assets=None, latest=True):
|
||||
def filter(cls, username=None, assets=None, latest=True, **kwargs):
|
||||
queryset = AuthBook.objects.all()
|
||||
if username is not None:
|
||||
queryset = queryset.filter(username=username)
|
||||
|
|
|
@ -30,21 +30,22 @@ class AssetUserManager:
|
|||
)
|
||||
|
||||
_prefer = "system_user"
|
||||
_using = None
|
||||
|
||||
def filter(self, username=None, assets=None, latest=True):
|
||||
if self._using:
|
||||
backend = dict(self.backends).get(self._using)
|
||||
if not backend:
|
||||
return self.none()
|
||||
instances = backend.filter(username=username, assets=assets, latest=latest)
|
||||
return AssetUserQuerySet(instances)
|
||||
def filter(self, username=None, assets=None, latest=True, prefer=None, prefer_id=None):
|
||||
if assets is not None and not assets:
|
||||
return AssetUserQuerySet([])
|
||||
|
||||
if prefer:
|
||||
self._prefer = prefer
|
||||
|
||||
instances_map = {}
|
||||
instances = []
|
||||
for name, backend in self.backends:
|
||||
if name != "db" and self._prefer != name:
|
||||
continue
|
||||
_instances = backend.filter(
|
||||
username=username, assets=assets, latest=latest
|
||||
username=username, assets=assets, latest=latest,
|
||||
prefer=self._prefer, prefer_id=prefer_id,
|
||||
)
|
||||
instances_map[name] = _instances
|
||||
|
||||
|
@ -61,12 +62,12 @@ class AssetUserManager:
|
|||
else:
|
||||
ordering.extend(["admin_user", "system_user"])
|
||||
# 根据prefer决定优先使用系统用户或管理用户谁的
|
||||
ordering_instances = [instances_map.get(i) for i in ordering]
|
||||
ordering_instances = [instances_map.get(i, []) for i in ordering]
|
||||
instances = self._merge_instances(*ordering_instances)
|
||||
return AssetUserQuerySet(instances)
|
||||
|
||||
def get(self, username, asset):
|
||||
instances = self.filter(username, assets=[asset])
|
||||
def get(self, username, asset, **kwargs):
|
||||
instances = self.filter(username, assets=[asset], **kwargs)
|
||||
if len(instances) == 1:
|
||||
return instances[0]
|
||||
elif len(instances) == 0:
|
||||
|
@ -92,10 +93,6 @@ class AssetUserManager:
|
|||
self._prefer = s
|
||||
return self
|
||||
|
||||
def using(self, s):
|
||||
self._using = s
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def none():
|
||||
return AssetUserQuerySet()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
UPDATE_ASSETS_HARDWARE_TASKS = [
|
||||
{
|
||||
|
@ -11,7 +11,6 @@ UPDATE_ASSETS_HARDWARE_TASKS = [
|
|||
}
|
||||
]
|
||||
|
||||
ADMIN_USER_CONN_CACHE_KEY = "ADMIN_USER_CONN_{}"
|
||||
TEST_ADMIN_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
|
@ -49,7 +48,6 @@ TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [
|
|||
}
|
||||
]
|
||||
|
||||
ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}'
|
||||
TEST_ASSET_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
|
@ -74,5 +72,10 @@ TASK_OPTIONS = {
|
|||
}
|
||||
|
||||
CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}'
|
||||
|
||||
CONN_UNREACHABLE, CONN_REACHABLE, CONN_UNKNOWN = range(0, 3)
|
||||
CONNECTIVITY_CHOICES = (
|
||||
(CONN_UNREACHABLE, _("Unreachable")),
|
||||
(CONN_REACHABLE, _('Reachable')),
|
||||
(CONN_UNKNOWN, _("Unknown")),
|
||||
)
|
||||
|
||||
|
|
|
@ -6,32 +6,32 @@ from django.utils.translation import gettext_lazy as _
|
|||
from common.utils import get_logger
|
||||
from orgs.mixins import OrgModelForm
|
||||
|
||||
from ..models import Asset, Protocol
|
||||
from ..models import Asset, Node
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm',
|
||||
'ProtocolForm'
|
||||
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm',
|
||||
]
|
||||
|
||||
|
||||
class ProtocolForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Protocol
|
||||
fields = ['name', 'port']
|
||||
widgets = {
|
||||
'name': forms.Select(attrs={
|
||||
'class': 'form-control protocol-name'
|
||||
}),
|
||||
'port': forms.TextInput(attrs={
|
||||
'class': 'form-control protocol-port'
|
||||
}),
|
||||
}
|
||||
class ProtocolForm(forms.Form):
|
||||
name = forms.ChoiceField(
|
||||
choices=Asset.PROTOCOL_CHOICES, label=_("Name"), initial='ssh',
|
||||
widget=forms.Select(attrs={'class': 'form-control protocol-name'})
|
||||
)
|
||||
port = forms.IntegerField(
|
||||
max_value=65534, min_value=1, label=_("Port"), initial=22,
|
||||
widget=forms.TextInput(attrs={'class': 'form-control protocol-port'})
|
||||
)
|
||||
|
||||
|
||||
class AssetCreateForm(OrgModelForm):
|
||||
PROTOCOL_CHOICES = Protocol.PROTOCOL_CHOICES
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.data:
|
||||
nodes_field = self.fields['nodes']
|
||||
nodes_field._queryset = Node.get_queryset()
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
import re
|
||||
|
||||
from orgs.mixins import OrgModelForm
|
||||
from ..models import CommandFilter, CommandFilterRule
|
||||
|
@ -15,6 +17,8 @@ class CommandFilterForm(OrgModelForm):
|
|||
|
||||
|
||||
class CommandFilterRuleForm(OrgModelForm):
|
||||
invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]')
|
||||
|
||||
class Meta:
|
||||
model = CommandFilterRule
|
||||
fields = [
|
||||
|
@ -25,3 +29,11 @@ class CommandFilterRuleForm(OrgModelForm):
|
|||
'placeholder': 'eg:\r\nreboot\r\nrm -rf'
|
||||
}),
|
||||
}
|
||||
|
||||
def clean_content(self):
|
||||
content = self.cleaned_data.get("content")
|
||||
if self.invalid_pattern.search(content):
|
||||
invalid_char = self.invalid_pattern.pattern.replace('\\', '')
|
||||
msg = _("Content should not be contain: {}").format(invalid_char)
|
||||
raise ValidationError(msg)
|
||||
return content
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-05 10:07
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='adminuser',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Admin user'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='asset',
|
||||
options={'verbose_name': 'Asset'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='assetgroup',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Asset group'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='cluster',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Cluster'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='systemuser',
|
||||
options={'ordering': ['name'], 'verbose_name': 'System user'},
|
||||
),
|
||||
]
|
|
@ -1,22 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-09 15:31
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import assets.models.asset
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0002_auto_20180105_1807'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
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'),
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-25 04:18
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0003_auto_20180109_2331'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='assetgroup',
|
||||
name='created_by',
|
||||
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
|
||||
),
|
||||
]
|
|
@ -1,40 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-26 08:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0004_auto_20180125_1218'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Label',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('value', models.CharField(max_length=128, verbose_name='Value')),
|
||||
('category', models.CharField(choices=[('S', 'System'), ('U', 'User')], default='U', max_length=128, verbose_name='Category')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'assets_label',
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='label',
|
||||
unique_together=set([('name', 'value')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='labels',
|
||||
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'),
|
||||
),
|
||||
]
|
|
@ -1,39 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-30 07:02
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0005_auto_20180126_1637'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cabinet_no',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cabinet_pos',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='env',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='remote_card_ip',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='status',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='type',
|
||||
),
|
||||
]
|
|
@ -1,60 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-02-25 10:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import assets.models.asset
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0006_auto_20180130_1502'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Node',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('key', models.CharField(max_length=64, unique=True, verbose_name='Key')),
|
||||
('value', models.CharField(max_length=128, unique=True, verbose_name='Value')),
|
||||
('child_mark', models.IntegerField(default=0)),
|
||||
('date_create', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cluster',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='groups',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='systemuser',
|
||||
name='cluster',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='admin_user',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.AdminUser', verbose_name='Admin user'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
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'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes'),
|
||||
),
|
||||
]
|
|
@ -1,40 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-03-06 10:04
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0007_auto_20180225_1815'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='created_by',
|
||||
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='created_by',
|
||||
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-03-07 04:12
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0008_auto_20180306_1804'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, verbose_name='Value'),
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-03-07 09:49
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0009_auto_20180307_1212'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, unique=True, verbose_name='Value'),
|
||||
),
|
||||
]
|
|
@ -1,55 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-03-26 01:57
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import assets.models.utils
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0010_auto_20180307_1749'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Domain',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Gateway',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('username', models.CharField(max_length=128, verbose_name='Username')),
|
||||
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
|
||||
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
|
||||
('port', models.IntegerField(default=22, verbose_name='Port')),
|
||||
('protocol', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol')),
|
||||
('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Domain', verbose_name='Domain')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='domain',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assets', to='assets.Domain', verbose_name='Domain'),
|
||||
),
|
||||
]
|
|
@ -1,21 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-04 05:02
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0011_auto_20180326_0957'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='domain',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'),
|
||||
),
|
||||
]
|
|
@ -1,25 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-11 03:35
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0012_auto_20180404_1302'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='assets',
|
||||
field=models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='sudo',
|
||||
field=models.TextField(default='/bin/whoami', verbose_name='Sudo'),
|
||||
),
|
||||
]
|
|
@ -1,31 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-27 04:45
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0013_auto_20180411_1135'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
]
|
|
@ -1,31 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-05-10 04:35
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0014_auto_20180427_1245'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-05-11 04:03
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0015_auto_20180510_1235'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, verbose_name='Value'),
|
||||
),
|
||||
]
|
|
@ -1,58 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-07-02 06:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate_win_to_ssh_protocol(apps, schema_editor):
|
||||
asset_model = apps.get_model("assets", "Asset")
|
||||
db_alias = schema_editor.connection.alias
|
||||
asset_model.objects.using(db_alias).filter(platform__startswith='Win').update(protocol='rdp')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0016_auto_20180511_1203'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=128, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='login_mode',
|
||||
field=models.CharField(choices=[('auto', 'Automatic login'), ('manual', 'Manually login')], default='auto', max_length=10, verbose_name='Login mode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Windows2016', 'Windows(2016)'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.RunPython(migrate_win_to_ssh_protocol),
|
||||
]
|
|
@ -1,84 +0,0 @@
|
|||
# Generated by Django 2.0.7 on 2018-08-07 03:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0017_auto_20180702_1415'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='adminuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='domain',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='gateway',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='label',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='node',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='hostname',
|
||||
field=models.CharField(max_length=128, verbose_name='Hostname'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='adminuser',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='asset',
|
||||
unique_together={('org_id', 'hostname')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='gateway',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='systemuser',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
]
|
|
@ -1,22 +0,0 @@
|
|||
# Generated by Django 2.0.7 on 2018-08-16 05:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0018_auto_20180807_1116'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='cpu_vcpus',
|
||||
field=models.IntegerField(null=True, verbose_name='CPU vcpus'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='label',
|
||||
unique_together={('name', 'value', 'org_id')},
|
||||
),
|
||||
]
|
|
@ -3,14 +3,6 @@
|
|||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_assets_protocol(apps, schema_editor):
|
||||
asset_model = apps.get_model("assets", "Asset")
|
||||
db_alias = schema_editor.connection.alias
|
||||
assets = asset_model.objects.using(db_alias).all()
|
||||
for asset in assets:
|
||||
asset.protocols.create(name=asset.protocol, port=asset.port)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
|
@ -18,5 +10,4 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_assets_protocol),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
# Generated by Django 2.1.7 on 2019-06-24 13:08
|
||||
|
||||
import assets.models.utils
|
||||
import common.fields.model
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0031_auto_20190621_1332'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='_password',
|
||||
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='_private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='_public_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='_password',
|
||||
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='_private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='_public_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='_password',
|
||||
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='_private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='_public_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='_password',
|
||||
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='_private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='_public_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,73 @@
|
|||
# Generated by Django 2.1.7 on 2019-06-24 13:08
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0032_auto_20190624_2108'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='adminuser',
|
||||
old_name='_private_key',
|
||||
new_name='private_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='adminuser',
|
||||
old_name='_public_key',
|
||||
new_name='public_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='authbook',
|
||||
old_name='_private_key',
|
||||
new_name='private_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='authbook',
|
||||
old_name='_public_key',
|
||||
new_name='public_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='gateway',
|
||||
old_name='_private_key',
|
||||
new_name='private_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='gateway',
|
||||
old_name='_public_key',
|
||||
new_name='public_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='systemuser',
|
||||
old_name='_private_key',
|
||||
new_name='private_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='systemuser',
|
||||
old_name='_public_key',
|
||||
new_name='public_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='adminuser',
|
||||
old_name='_password',
|
||||
new_name='password',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='authbook',
|
||||
old_name='_password',
|
||||
new_name='password',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='gateway',
|
||||
old_name='_password',
|
||||
new_name='password',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='systemuser',
|
||||
old_name='_password',
|
||||
new_name='password',
|
||||
),
|
||||
]
|
|
@ -0,0 +1,39 @@
|
|||
# Generated by Django 2.1.7 on 2019-07-05 05:48
|
||||
|
||||
from django.db import migrations
|
||||
from django.db.models import F
|
||||
from django.db.models import CharField, Value as V
|
||||
from django.db.models.functions import Concat
|
||||
|
||||
|
||||
def migrate_assets_protocol(apps, schema_editor):
|
||||
asset_model = apps.get_model("assets", "Asset")
|
||||
db_alias = schema_editor.connection.alias
|
||||
assets = asset_model.objects.using(db_alias).all().annotate(
|
||||
protocols_new=Concat(
|
||||
'protocol', V('/'), 'port',
|
||||
output_field=CharField(),
|
||||
),
|
||||
)
|
||||
assets.update(protocols=F('protocols_new'))
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0033_auto_20190624_2108'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='protocols',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='protocols',
|
||||
field=CharField(blank=True, default='ssh/22', max_length=128, verbose_name='Protocols'),
|
||||
),
|
||||
migrations.RunPython(migrate_assets_protocol),
|
||||
migrations.DeleteModel(name='Protocol'),
|
||||
]
|
|
@ -1,10 +1,11 @@
|
|||
from .user import *
|
||||
from .asset import *
|
||||
from .label import Label
|
||||
from .user import *
|
||||
from .cluster import *
|
||||
from .group import *
|
||||
from .domain import *
|
||||
from .node import *
|
||||
from .asset import *
|
||||
from .cmd_filter import *
|
||||
from .authbook import *
|
||||
from .utils import *
|
||||
from .authbook import *
|
||||
|
|
|
@ -6,18 +6,16 @@ import uuid
|
|||
import logging
|
||||
import random
|
||||
from functools import reduce
|
||||
from collections import defaultdict
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.cache import cache
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
|
||||
from .user import AdminUser, SystemUser
|
||||
from .utils import Connectivity
|
||||
from orgs.mixins import OrgModelMixin, OrgManager
|
||||
|
||||
__all__ = ['Asset', 'Protocol']
|
||||
__all__ = ['Asset', 'ProtocolsMixin']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -47,14 +45,12 @@ class AssetQuerySet(models.QuerySet):
|
|||
def valid(self):
|
||||
return self.active()
|
||||
|
||||
|
||||
class AssetManager(OrgManager):
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().prefetch_related("nodes", "protocols")
|
||||
return queryset
|
||||
def has_protocol(self, name):
|
||||
return self.filter(protocols__contains=name)
|
||||
|
||||
|
||||
class Protocol(models.Model):
|
||||
class ProtocolsMixin:
|
||||
protocols = ''
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
PROTOCOL_TELNET = 'telnet'
|
||||
|
@ -65,19 +61,42 @@ class Protocol(models.Model):
|
|||
(PROTOCOL_TELNET, 'telnet (beta)'),
|
||||
(PROTOCOL_VNC, 'vnc'),
|
||||
)
|
||||
PORT_VALIDATORS = [MaxValueValidator(65535), MinValueValidator(1)]
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=16, choices=PROTOCOL_CHOICES,
|
||||
default=PROTOCOL_SSH, verbose_name=_("Name"))
|
||||
port = models.IntegerField(default=22, verbose_name=_("Port"),
|
||||
validators=PORT_VALIDATORS)
|
||||
@property
|
||||
def protocols_as_list(self):
|
||||
if not self.protocols:
|
||||
return []
|
||||
return self.protocols.split(' ')
|
||||
|
||||
def __str__(self):
|
||||
return "{}/{}".format(self.name, self.port)
|
||||
@property
|
||||
def protocols_as_dict(self):
|
||||
d = OrderedDict()
|
||||
protocols = self.protocols_as_list
|
||||
for i in protocols:
|
||||
if '/' not in i:
|
||||
continue
|
||||
name, port = i.split('/')[:2]
|
||||
if not all([name, port]):
|
||||
continue
|
||||
d[name] = int(port)
|
||||
return d
|
||||
|
||||
@property
|
||||
def protocols_as_json(self):
|
||||
return [
|
||||
{"name": name, "port": port}
|
||||
for name, port in self.protocols_as_dict.items()
|
||||
]
|
||||
|
||||
def has_protocol(self, name):
|
||||
return name in self.protocols_as_dict
|
||||
|
||||
@property
|
||||
def ssh_port(self):
|
||||
return self.protocols_as_dict.get("ssh", 22)
|
||||
|
||||
|
||||
class Asset(OrgModelMixin):
|
||||
class Asset(ProtocolsMixin, OrgModelMixin):
|
||||
# Important
|
||||
PLATFORM_CHOICES = (
|
||||
('Linux', 'Linux'),
|
||||
|
@ -92,12 +111,12 @@ class Asset(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'))
|
||||
protocol = models.CharField(max_length=128, default=Protocol.PROTOCOL_SSH,
|
||||
choices=Protocol.PROTOCOL_CHOICES,
|
||||
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.ManyToManyField('Protocol', verbose_name=_("Protocol"))
|
||||
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
|
||||
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
|
||||
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"))
|
||||
|
@ -133,14 +152,8 @@ class Asset(OrgModelMixin):
|
|||
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||
|
||||
objects = AssetManager.from_queryset(AssetQuerySet)()
|
||||
CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}'
|
||||
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
|
||||
CONNECTIVITY_CHOICES = (
|
||||
(UNREACHABLE, _("Unreachable")),
|
||||
(REACHABLE, _('Reachable')),
|
||||
(UNKNOWN, _("Unknown")),
|
||||
)
|
||||
objects = OrgManager.from_queryset(AssetQuerySet)()
|
||||
_connectivity = None
|
||||
|
||||
def __str__(self):
|
||||
return '{0.hostname}({0.ip})'.format(self)
|
||||
|
@ -150,41 +163,9 @@ class Asset(OrgModelMixin):
|
|||
warning = ''
|
||||
if not self.is_active:
|
||||
warning += ' inactive'
|
||||
else:
|
||||
return True, ''
|
||||
return False, warning
|
||||
|
||||
@property
|
||||
def protocols_name(self):
|
||||
names = []
|
||||
for protocol in self.protocols.all():
|
||||
names.append(protocol.name)
|
||||
return names
|
||||
|
||||
def has_protocol(self, name):
|
||||
return name in self.protocols_name
|
||||
|
||||
def get_protocol_by_name(self, name):
|
||||
for i in self.protocols.all():
|
||||
if i.name.lower() == name.lower():
|
||||
return i
|
||||
return None
|
||||
|
||||
@property
|
||||
def protocol_ssh(self):
|
||||
return self.get_protocol_by_name("ssh")
|
||||
|
||||
@property
|
||||
def protocol_rdp(self):
|
||||
return self.get_protocol_by_name("rdp")
|
||||
|
||||
@property
|
||||
def ssh_port(self):
|
||||
if self.protocol_ssh:
|
||||
port = self.protocol_ssh.port
|
||||
else:
|
||||
port = 22
|
||||
return port
|
||||
if warning:
|
||||
return False, warning
|
||||
return True, warning
|
||||
|
||||
def is_windows(self):
|
||||
if self.platform in ("Windows", "Windows2016"):
|
||||
|
@ -215,20 +196,6 @@ class Asset(OrgModelMixin):
|
|||
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
|
||||
return nodes
|
||||
|
||||
@classmethod
|
||||
def get_queryset_by_fullname_list(cls, fullname_list):
|
||||
org_fullname_map = defaultdict(list)
|
||||
for fullname in fullname_list:
|
||||
hostname, org = cls.split_fullname(fullname)
|
||||
org_fullname_map[org].append(hostname)
|
||||
filter_arg = Q()
|
||||
for org, hosts in org_fullname_map.items():
|
||||
if org.is_real():
|
||||
filter_arg |= Q(hostname__in=hosts, org_id=org.id)
|
||||
else:
|
||||
filter_arg |= Q(Q(org_id__isnull=True) | Q(org_id=''), hostname__in=hosts)
|
||||
return Asset.objects.filter(filter_arg)
|
||||
|
||||
@property
|
||||
def cpu_info(self):
|
||||
info = ""
|
||||
|
@ -250,15 +217,18 @@ class Asset(OrgModelMixin):
|
|||
|
||||
@property
|
||||
def connectivity(self):
|
||||
if self._connectivity:
|
||||
return self._connectivity
|
||||
if not self.admin_user:
|
||||
return self.UNKNOWN
|
||||
return self.admin_user.get_connectivity_of(self)
|
||||
return Connectivity.unknown()
|
||||
connectivity = self.admin_user.get_asset_connectivity(self)
|
||||
return connectivity
|
||||
|
||||
@connectivity.setter
|
||||
def connectivity(self, value):
|
||||
if not self.admin_user:
|
||||
return
|
||||
self.admin_user.set_connectivity_of(self, value)
|
||||
self.admin_user.set_asset_connectivity(self, value)
|
||||
|
||||
def get_auth_info(self):
|
||||
if not self.admin_user:
|
||||
|
@ -303,10 +273,7 @@ class Asset(OrgModelMixin):
|
|||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'protocols': [
|
||||
{"name": p.name, "port": p.port}
|
||||
for p in self.protocols.all()
|
||||
],
|
||||
'protocols': self.protocols_as_list,
|
||||
'platform': self.platform,
|
||||
}
|
||||
}
|
||||
|
@ -321,20 +288,25 @@ class Asset(OrgModelMixin):
|
|||
@classmethod
|
||||
def generate_fake(cls, count=100):
|
||||
from random import seed, choice
|
||||
import forgery_py
|
||||
from django.db import IntegrityError
|
||||
from .node import Node
|
||||
from orgs.utils import get_current_org
|
||||
from orgs.models import Organization
|
||||
org = get_current_org()
|
||||
if not org or not org.is_real():
|
||||
Organization.default().change_to()
|
||||
|
||||
nodes = list(Node.objects.all())
|
||||
seed()
|
||||
for i in range(count):
|
||||
ip = [str(i) for i in random.sample(range(255), 4)]
|
||||
asset = cls(ip='.'.join(ip),
|
||||
hostname=forgery_py.internet.user_name(True),
|
||||
hostname='.'.join(ip),
|
||||
admin_user=choice(AdminUser.objects.all()),
|
||||
created_by='Fake')
|
||||
try:
|
||||
asset.save()
|
||||
asset.protocols.create(name="ssh", port=22)
|
||||
asset.protocols = 'ssh/22'
|
||||
if nodes and len(nodes) > 3:
|
||||
_nodes = random.sample(nodes, 3)
|
||||
else:
|
||||
|
|
|
@ -3,12 +3,9 @@
|
|||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.cache import cache
|
||||
|
||||
from orgs.mixins import OrgManager
|
||||
|
||||
from .base import AssetUser
|
||||
from ..const import ASSET_USER_CONN_CACHE_KEY
|
||||
|
||||
__all__ = ['AuthBook']
|
||||
|
||||
|
@ -32,6 +29,7 @@ class AuthBook(AssetUser):
|
|||
backend = "db"
|
||||
# 用于system user和admin_user的动态设置
|
||||
_connectivity = None
|
||||
CONN_CACHE_KEY = "ASSET_USER_CONN_{}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('AuthBook')
|
||||
|
@ -65,20 +63,15 @@ class AuthBook(AssetUser):
|
|||
self._set_version()
|
||||
self._set_latest()
|
||||
|
||||
@property
|
||||
def _conn_cache_key(self):
|
||||
return ASSET_USER_CONN_CACHE_KEY.format(self.id)
|
||||
def get_related_assets(self):
|
||||
return [self.asset]
|
||||
|
||||
def generate_id_with_asset(self, asset):
|
||||
return self.id
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
if self._connectivity:
|
||||
return self._connectivity
|
||||
value = cache.get(self._conn_cache_key, self.UNKNOWN)
|
||||
return value
|
||||
|
||||
@connectivity.setter
|
||||
def connectivity(self, value):
|
||||
cache.set(self._conn_cache_key, value, 3600)
|
||||
return self.get_asset_connectivity(self.asset)
|
||||
|
||||
@property
|
||||
def keyword(self):
|
||||
|
|
|
@ -5,8 +5,8 @@ import uuid
|
|||
from hashlib import md5
|
||||
|
||||
import sshpubkeys
|
||||
from django.db import models
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
|
@ -14,8 +14,9 @@ from common.utils import (
|
|||
get_signer, ssh_key_string_to_obj, ssh_key_gen, get_logger
|
||||
)
|
||||
from common.validators import alphanumeric
|
||||
from common import fields
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from .utils import private_key_validator
|
||||
from .utils import private_key_validator, Connectivity
|
||||
|
||||
signer = get_signer()
|
||||
|
||||
|
@ -26,50 +27,25 @@ class AssetUser(OrgModelMixin):
|
|||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
|
||||
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
||||
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
||||
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
|
||||
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
|
||||
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
|
||||
|
||||
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
|
||||
CONNECTIVITY_CHOICES = (
|
||||
(UNREACHABLE, _("Unreachable")),
|
||||
(REACHABLE, _('Reachable')),
|
||||
(UNKNOWN, _("Unknown")),
|
||||
)
|
||||
CONNECTIVITY_CACHE_KEY = "CONNECTIVITY_{}"
|
||||
CONNECTIVITY_ASSET_CACHE_KEY = "ASSET_USER_{}_{}_ASSET_CONNECTIVITY"
|
||||
CONNECTIVITY_AMOUNT_CACHE_KEY = "ASSET_USER_{}_{}_CONNECTIVITY_AMOUNT"
|
||||
ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
|
||||
ASSET_USER_CACHE_TIME = 3600 * 24
|
||||
|
||||
_prefer = "system_user"
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
if self._password:
|
||||
return signer.unsign(self._password)
|
||||
else:
|
||||
return None
|
||||
|
||||
@password.setter
|
||||
def password(self, password_raw):
|
||||
# raise AttributeError("Using set_auth do that")
|
||||
self._password = signer.sign(password_raw)
|
||||
|
||||
@property
|
||||
def private_key(self):
|
||||
if self._private_key:
|
||||
return signer.unsign(self._private_key)
|
||||
|
||||
@private_key.setter
|
||||
def private_key(self, private_key_raw):
|
||||
# raise AttributeError("Using set_auth do that")
|
||||
self._private_key = signer.sign(private_key_raw)
|
||||
|
||||
@property
|
||||
def private_key_obj(self):
|
||||
if self._private_key:
|
||||
key_str = signer.unsign(self._private_key)
|
||||
return ssh_key_string_to_obj(key_str, password=self.password)
|
||||
if self.private_key:
|
||||
return ssh_key_string_to_obj(self.private_key, password=self.password)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
@ -79,27 +55,13 @@ class AssetUser(OrgModelMixin):
|
|||
return None
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
key_str = signer.unsign(self._private_key)
|
||||
key_name = '.' + md5(key_str.encode('utf-8')).hexdigest()
|
||||
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
|
||||
key_path = os.path.join(tmp_dir, key_name)
|
||||
if not os.path.exists(key_path):
|
||||
self.private_key_obj.write_private_key_file(key_path)
|
||||
os.chmod(key_path, 0o400)
|
||||
return key_path
|
||||
|
||||
@property
|
||||
def public_key(self):
|
||||
key = signer.unsign(self._public_key)
|
||||
if key:
|
||||
return key
|
||||
else:
|
||||
return None
|
||||
|
||||
@public_key.setter
|
||||
def public_key(self, public_key_raw):
|
||||
# raise AttributeError("Using set_auth do that")
|
||||
self._public_key = signer.sign(public_key_raw)
|
||||
|
||||
@property
|
||||
def public_key_obj(self):
|
||||
if self.public_key:
|
||||
|
@ -109,47 +71,119 @@ class AssetUser(OrgModelMixin):
|
|||
pass
|
||||
return None
|
||||
|
||||
@property
|
||||
def part_id(self):
|
||||
i = '-'.join(str(self.id).split('-')[:3])
|
||||
return i
|
||||
|
||||
def get_related_assets(self):
|
||||
assets = self.assets.all()
|
||||
return assets
|
||||
|
||||
def set_auth(self, password=None, private_key=None, public_key=None):
|
||||
update_fields = []
|
||||
if password:
|
||||
self._password = signer.sign(password)
|
||||
update_fields.append('_password')
|
||||
self.password = password
|
||||
update_fields.append('password')
|
||||
if private_key:
|
||||
self._private_key = signer.sign(private_key)
|
||||
update_fields.append('_private_key')
|
||||
self.private_key = private_key
|
||||
update_fields.append('private_key')
|
||||
if public_key:
|
||||
self._public_key = signer.sign(public_key)
|
||||
update_fields.append('_public_key')
|
||||
self.public_key = public_key
|
||||
update_fields.append('public_key')
|
||||
|
||||
if update_fields:
|
||||
self.save(update_fields=update_fields)
|
||||
|
||||
def get_auth(self, asset=None):
|
||||
pass
|
||||
def set_connectivity(self, summary):
|
||||
unreachable = summary.get('dark', {}).keys()
|
||||
reachable = summary.get('contacted', {}).keys()
|
||||
|
||||
def get_connectivity_of(self, asset):
|
||||
i = self.generate_id_with_asset(asset)
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(i)
|
||||
return cache.get(key)
|
||||
assets = self.get_related_assets()
|
||||
if not isinstance(assets, list):
|
||||
assets = assets.only('id', 'hostname', 'admin_user__id')
|
||||
for asset in assets:
|
||||
if asset.hostname in unreachable:
|
||||
self.set_asset_connectivity(asset, Connectivity.unreachable())
|
||||
elif asset.hostname in reachable:
|
||||
self.set_asset_connectivity(asset, Connectivity.reachable())
|
||||
else:
|
||||
self.set_asset_connectivity(asset, Connectivity.unknown())
|
||||
cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.username, self.part_id)
|
||||
cache.delete(cache_key)
|
||||
|
||||
def set_connectivity_of(self, asset, c):
|
||||
i = self.generate_id_with_asset(asset)
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(i)
|
||||
cache.set(key, c, 3600)
|
||||
@property
|
||||
def connectivity(self):
|
||||
assets = self.get_related_assets()
|
||||
if not isinstance(assets, list):
|
||||
assets = assets.only('id', 'hostname', 'admin_user__id')
|
||||
data = {
|
||||
'unreachable': [],
|
||||
'reachable': [],
|
||||
'unknown': [],
|
||||
}
|
||||
for asset in assets:
|
||||
connectivity = self.get_asset_connectivity(asset)
|
||||
if connectivity.is_reachable():
|
||||
data["reachable"].append(asset.hostname)
|
||||
elif connectivity.is_unreachable():
|
||||
data["unreachable"].append(asset.hostname)
|
||||
else:
|
||||
data["unknown"].append(asset.hostname)
|
||||
return data
|
||||
|
||||
def load_specific_asset_auth(self, asset):
|
||||
@property
|
||||
def connectivity_amount(self):
|
||||
cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.username, self.part_id)
|
||||
amount = cache.get(cache_key)
|
||||
if not amount:
|
||||
amount = {k: len(v) for k, v in self.connectivity.items()}
|
||||
cache.set(cache_key, amount, self.ASSET_USER_CACHE_TIME)
|
||||
return amount
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
|
||||
cached = cache.get(cache_key)
|
||||
if not cached:
|
||||
cached = self.get_related_assets().count()
|
||||
cache.set(cache_key, cached, self.ASSET_USER_CACHE_TIME)
|
||||
return cached
|
||||
|
||||
def expire_assets_amount(self):
|
||||
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
|
||||
cache.delete(cache_key)
|
||||
|
||||
def get_asset_connectivity(self, asset):
|
||||
key = self.get_asset_connectivity_key(asset)
|
||||
return Connectivity.get(key)
|
||||
|
||||
def get_asset_connectivity_key(self, asset):
|
||||
return self.CONNECTIVITY_ASSET_CACHE_KEY.format(self.username, asset.id)
|
||||
|
||||
def set_asset_connectivity(self, asset, c):
|
||||
key = self.get_asset_connectivity_key(asset)
|
||||
Connectivity.set(key, c)
|
||||
# 当为某个系统用户或管理用户设置的的时候,失效掉他们的连接数量
|
||||
amount_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.username, '*')
|
||||
cache.delete_pattern(amount_key)
|
||||
|
||||
def get_asset_user(self, asset):
|
||||
from ..backends import AssetUserManager
|
||||
try:
|
||||
manager = AssetUserManager().prefer(self._prefer)
|
||||
other = manager.get(username=self.username, asset=asset)
|
||||
other = manager.get(username=self.username, asset=asset, prefer_id=self.id)
|
||||
return other
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
else:
|
||||
self._merge_auth(other)
|
||||
return None
|
||||
|
||||
def load_specific_asset_auth(self, asset):
|
||||
instance = self.get_asset_user(asset)
|
||||
if instance:
|
||||
self._merge_auth(instance)
|
||||
|
||||
def _merge_auth(self, other):
|
||||
if not other:
|
||||
return
|
||||
if other.password:
|
||||
self.password = other.password
|
||||
if other.public_key:
|
||||
|
@ -158,9 +192,9 @@ class AssetUser(OrgModelMixin):
|
|||
self.private_key = other.private_key
|
||||
|
||||
def clear_auth(self):
|
||||
self._password = ''
|
||||
self._private_key = ''
|
||||
self._public_key = ''
|
||||
self.password = ''
|
||||
self.private_key = ''
|
||||
self.public_key = ''
|
||||
self.save()
|
||||
|
||||
def auto_gen_auth(self):
|
||||
|
@ -168,9 +202,10 @@ class AssetUser(OrgModelMixin):
|
|||
private_key, public_key = ssh_key_gen(
|
||||
username=self.username
|
||||
)
|
||||
self.set_auth(password=password,
|
||||
private_key=private_key,
|
||||
public_key=public_key)
|
||||
self.set_auth(
|
||||
password=password, private_key=private_key,
|
||||
public_key=public_key
|
||||
)
|
||||
|
||||
def auto_gen_auth_password(self):
|
||||
password = str(uuid.uuid4())
|
||||
|
@ -187,20 +222,20 @@ class AssetUser(OrgModelMixin):
|
|||
}
|
||||
|
||||
def generate_id_with_asset(self, asset):
|
||||
id_ = '{}_{}'.format(asset.id, self.id)
|
||||
id_ = uuid.UUID(md5(id_.encode()).hexdigest())
|
||||
return id_
|
||||
user_id = [self.part_id]
|
||||
asset_id = str(asset.id).split('-')[3:]
|
||||
ids = user_id + asset_id
|
||||
return '-'.join(ids)
|
||||
|
||||
def construct_to_authbook(self, asset):
|
||||
from . import AuthBook
|
||||
fields = [
|
||||
'name', 'username', 'comment', 'org_id',
|
||||
'_password', '_private_key', '_public_key',
|
||||
'password', 'private_key', 'public_key',
|
||||
'date_created', 'date_updated', 'created_by'
|
||||
]
|
||||
id_ = self.generate_id_with_asset(asset)
|
||||
obj = AuthBook(id=id_, asset=asset, version=0, is_latest=True)
|
||||
obj._connectivity = self.get_connectivity_of(asset)
|
||||
i = self.generate_id_with_asset(asset)
|
||||
obj = AuthBook(id=i, asset=asset, version=0, is_latest=True)
|
||||
for field in fields:
|
||||
value = getattr(self, field)
|
||||
setattr(obj, field, value)
|
||||
|
@ -208,3 +243,4 @@ class AssetUser(OrgModelMixin):
|
|||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
import re
|
||||
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Q
|
||||
|
@ -8,61 +9,195 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.utils.translation import ugettext
|
||||
from django.core.cache import cache
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from orgs.mixins import OrgModelMixin, OrgManager
|
||||
from orgs.utils import set_current_org, get_current_org
|
||||
from orgs.models import Organization
|
||||
|
||||
__all__ = ['Node']
|
||||
|
||||
|
||||
class Node(OrgModelMixin):
|
||||
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"))
|
||||
child_mark = models.IntegerField(default=0)
|
||||
date_create = models.DateTimeField(auto_now_add=True)
|
||||
class NodeQuerySet(models.QuerySet):
|
||||
def delete(self):
|
||||
raise PermissionError("Bulk delete node deny")
|
||||
|
||||
|
||||
class FamilyMixin:
|
||||
_parents = None
|
||||
_children = None
|
||||
_all_children = None
|
||||
is_node = True
|
||||
_assets_amount = None
|
||||
_full_value_cache_key = '_NODE_VALUE_{}'
|
||||
_assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Node")
|
||||
ordering = ['key']
|
||||
|
||||
def __str__(self):
|
||||
return self.full_value
|
||||
|
||||
def __eq__(self, other):
|
||||
if not other:
|
||||
return False
|
||||
return self.id == other.id
|
||||
|
||||
def __gt__(self, other):
|
||||
if self.is_root() and not other.is_root():
|
||||
return True
|
||||
elif not self.is_root() and other.is_root():
|
||||
return False
|
||||
self_key = [int(k) for k in self.key.split(':')]
|
||||
other_key = [int(k) for k in other.key.split(':')]
|
||||
self_parent_key = self_key[:-1]
|
||||
other_parent_key = other_key[:-1]
|
||||
|
||||
if self_parent_key == other_parent_key:
|
||||
return self.name > other.name
|
||||
if len(self_parent_key) < len(other_parent_key):
|
||||
return True
|
||||
elif len(self_parent_key) > len(other_parent_key):
|
||||
return False
|
||||
return self_key > other_key
|
||||
|
||||
def __lt__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.value
|
||||
def children(self):
|
||||
if self._children:
|
||||
return self._children
|
||||
pattern = r'^{0}:[0-9]+$'.format(self.key)
|
||||
return Node.objects.filter(key__regex=pattern)
|
||||
|
||||
@children.setter
|
||||
def children(self, value):
|
||||
self._children = value
|
||||
|
||||
@property
|
||||
def all_children(self):
|
||||
if self._all_children:
|
||||
return self._all_children
|
||||
pattern = r'^{0}:'.format(self.key)
|
||||
return Node.objects.filter(
|
||||
key__regex=pattern
|
||||
)
|
||||
|
||||
def get_children(self, with_self=False):
|
||||
children = list(self.children)
|
||||
if with_self:
|
||||
children.append(self)
|
||||
return children
|
||||
|
||||
def get_all_children(self, with_self=False):
|
||||
children = self.all_children
|
||||
if with_self:
|
||||
children = list(children)
|
||||
children.append(self)
|
||||
return children
|
||||
|
||||
@property
|
||||
def parents(self):
|
||||
if self._parents:
|
||||
return self._parents
|
||||
ancestor_keys = self.get_ancestor_keys()
|
||||
ancestor = Node.objects.filter(
|
||||
key__in=ancestor_keys
|
||||
).order_by('key')
|
||||
return ancestor
|
||||
|
||||
@parents.setter
|
||||
def parents(self, value):
|
||||
self._parents = value
|
||||
|
||||
def get_ancestor(self, with_self=False):
|
||||
parents = self.parents
|
||||
if with_self:
|
||||
parents = list(parents)
|
||||
parents.append(self)
|
||||
return parents
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if self._parents:
|
||||
return self._parents[0]
|
||||
if self.is_root():
|
||||
return self
|
||||
try:
|
||||
parent = Node.objects.get(key=self.parent_key)
|
||||
return parent
|
||||
except Node.DoesNotExist:
|
||||
return Node.root()
|
||||
|
||||
@parent.setter
|
||||
def parent(self, parent):
|
||||
if not self.is_node:
|
||||
self.key = parent.key + ':fake'
|
||||
return
|
||||
children = self.get_all_children()
|
||||
old_key = self.key
|
||||
with transaction.atomic():
|
||||
self.key = parent.get_next_child_key()
|
||||
for child in children:
|
||||
child.key = child.key.replace(old_key, self.key, 1)
|
||||
child.save()
|
||||
self.save()
|
||||
|
||||
def get_sibling(self, with_self=False):
|
||||
key = ':'.join(self.key.split(':')[:-1])
|
||||
pattern = r'^{}:[0-9]+$'.format(key)
|
||||
sibling = Node.objects.filter(
|
||||
key__regex=pattern.format(self.key)
|
||||
)
|
||||
if not with_self:
|
||||
sibling = sibling.exclude(key=self.key)
|
||||
return sibling
|
||||
|
||||
def get_family(self):
|
||||
ancestor = self.get_ancestor()
|
||||
children = self.get_all_children()
|
||||
return [*tuple(ancestor), self, *tuple(children)]
|
||||
|
||||
def get_ancestor_keys(self, with_self=False):
|
||||
parent_keys = []
|
||||
key_list = self.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 is_children(self, other):
|
||||
pattern = re.compile(r'^{0}:[0-9]+$'.format(self.key))
|
||||
return pattern.match(other.key)
|
||||
|
||||
def is_parent(self, other):
|
||||
pattern = re.compile(r'^{0}:[0-9]+$'.format(other.key))
|
||||
return pattern.match(self.key)
|
||||
|
||||
@property
|
||||
def parent_key(self):
|
||||
parent_key = ":".join(self.key.split(":")[:-1])
|
||||
return parent_key
|
||||
|
||||
@property
|
||||
def parents_keys(self, with_self=False):
|
||||
keys = []
|
||||
key_list = self.key.split(":")
|
||||
if not with_self:
|
||||
key_list.pop()
|
||||
for i in range(len(key_list)):
|
||||
keys.append(':'.join(key_list))
|
||||
key_list.pop()
|
||||
return keys
|
||||
|
||||
|
||||
class FullValueMixin:
|
||||
_full_value_cache_key = '_NODE_VALUE_{}'
|
||||
_full_value = ''
|
||||
key = ''
|
||||
|
||||
@property
|
||||
def full_value(self):
|
||||
if self._full_value:
|
||||
return self._full_value
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cached = cache.get(key)
|
||||
if cached:
|
||||
return cached
|
||||
if self.is_root():
|
||||
return self.value
|
||||
parent_full_value = self.parent.full_value
|
||||
value = parent_full_value + ' / ' + self.value
|
||||
self.full_value = value
|
||||
return value
|
||||
|
||||
@full_value.setter
|
||||
def full_value(self, value):
|
||||
self._full_value = value
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cache.set(key, value, 3600*24)
|
||||
|
||||
def expire_full_value(self):
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cache.delete_pattern(key+'*')
|
||||
|
||||
@classmethod
|
||||
def expire_nodes_full_value(cls, nodes=None):
|
||||
key = cls._full_value_cache_key.format('*')
|
||||
cache.delete_pattern(key+'*')
|
||||
|
||||
|
||||
class AssetsAmountMixin:
|
||||
_assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
|
||||
_assets_amount = None
|
||||
key = ''
|
||||
cache_time = 3600 * 24 * 7
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
|
@ -77,53 +212,82 @@ class Node(OrgModelMixin):
|
|||
if cached is not None:
|
||||
return cached
|
||||
assets_amount = self.get_all_assets().count()
|
||||
cache.set(cache_key, assets_amount, 3600)
|
||||
self.assets_amount = assets_amount
|
||||
return assets_amount
|
||||
|
||||
@assets_amount.setter
|
||||
def assets_amount(self, value):
|
||||
self._assets_amount = value
|
||||
cache_key = self._assets_amount_cache_key.format(self.key)
|
||||
cache.set(cache_key, value, self.cache_time)
|
||||
|
||||
def expire_assets_amount(self):
|
||||
ancestor_keys = self.get_ancestor_keys(with_self=True)
|
||||
cache_keys = [self._assets_amount_cache_key.format(k) for k in ancestor_keys]
|
||||
cache_keys = [self._assets_amount_cache_key.format(k) for k in
|
||||
ancestor_keys]
|
||||
cache.delete_many(cache_keys)
|
||||
|
||||
@classmethod
|
||||
def expire_nodes_assets_amount(cls, nodes=None):
|
||||
if nodes:
|
||||
for node in nodes:
|
||||
node.expire_assets_amount()
|
||||
return
|
||||
key = cls._assets_amount_cache_key.format('*')
|
||||
cache.delete_pattern(key)
|
||||
|
||||
@property
|
||||
def full_value(self):
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cached = cache.get(key)
|
||||
if cached:
|
||||
return cached
|
||||
if self.is_root():
|
||||
return self.value
|
||||
parent_full_value = self.parent.full_value
|
||||
value = parent_full_value + ' / ' + self.value
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cache.set(key, value, 3600)
|
||||
return value
|
||||
|
||||
def expire_full_value(self):
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cache.delete_pattern(key+'*')
|
||||
|
||||
@classmethod
|
||||
def expire_nodes_full_value(cls, nodes=None):
|
||||
if nodes:
|
||||
for node in nodes:
|
||||
node.expire_full_value()
|
||||
return
|
||||
key = cls._full_value_cache_key.format('*')
|
||||
cache.delete_pattern(key+'*')
|
||||
def refresh_nodes(cls):
|
||||
from ..utils import NodeUtil
|
||||
util = NodeUtil(with_assets_amount=True)
|
||||
util.set_assets_amount()
|
||||
util.set_full_value()
|
||||
|
||||
|
||||
class Node(OrgModelMixin, FamilyMixin, FullValueMixin, AssetsAmountMixin):
|
||||
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"))
|
||||
child_mark = models.IntegerField(default=0)
|
||||
date_create = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
objects = OrgManager.from_queryset(NodeQuerySet)()
|
||||
is_node = True
|
||||
_parents = None
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Node")
|
||||
ordering = ['key']
|
||||
|
||||
def __str__(self):
|
||||
return self.full_value
|
||||
|
||||
def __eq__(self, other):
|
||||
if not other:
|
||||
return False
|
||||
return self.id == other.id
|
||||
|
||||
def __gt__(self, other):
|
||||
# if self.is_root() and not other.is_root():
|
||||
# return False
|
||||
# elif not self.is_root() and other.is_root():
|
||||
# return True
|
||||
self_key = [int(k) for k in self.key.split(':')]
|
||||
other_key = [int(k) for k in other.key.split(':')]
|
||||
self_parent_key = self_key[:-1]
|
||||
other_parent_key = other_key[:-1]
|
||||
|
||||
if self_parent_key and other_parent_key and \
|
||||
self_parent_key == other_parent_key:
|
||||
return self.value > other.value
|
||||
# if len(self_parent_key) < len(other_parent_key):
|
||||
# return True
|
||||
# elif len(self_parent_key) > len(other_parent_key):
|
||||
# return False
|
||||
return self_key > other_key
|
||||
|
||||
def __lt__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.value
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
|
@ -152,33 +316,6 @@ class Node(OrgModelMixin):
|
|||
child = self.__class__.objects.create(id=_id, key=child_key, value=value)
|
||||
return child
|
||||
|
||||
def get_children(self, with_self=False):
|
||||
pattern = r'^{0}$|^{0}:[0-9]+$' if with_self else r'^{0}:[0-9]+$'
|
||||
return self.__class__.objects.filter(
|
||||
key__regex=pattern.format(self.key)
|
||||
)
|
||||
|
||||
def get_all_children(self, with_self=False):
|
||||
pattern = r'^{0}$|^{0}:' if with_self else r'^{0}:'
|
||||
return self.__class__.objects.filter(
|
||||
key__regex=pattern.format(self.key)
|
||||
)
|
||||
|
||||
def get_sibling(self, with_self=False):
|
||||
key = ':'.join(self.key.split(':')[:-1])
|
||||
pattern = r'^{}:[0-9]+$'.format(key)
|
||||
sibling = self.__class__.objects.filter(
|
||||
key__regex=pattern.format(self.key)
|
||||
)
|
||||
if not with_self:
|
||||
sibling = sibling.exclude(key=self.key)
|
||||
return sibling
|
||||
|
||||
def get_family(self):
|
||||
ancestor = self.get_ancestor()
|
||||
children = self.get_all_children()
|
||||
return [*tuple(ancestor), self, *tuple(children)]
|
||||
|
||||
def get_assets(self):
|
||||
from .asset import Asset
|
||||
if self.is_default_node():
|
||||
|
@ -206,7 +343,7 @@ class Node(OrgModelMixin):
|
|||
return self.get_all_assets().valid()
|
||||
|
||||
def is_default_node(self):
|
||||
return self.is_root() and self.key == '0'
|
||||
return self.is_root() and self.key == '1'
|
||||
|
||||
def is_root(self):
|
||||
if self.key.isdigit():
|
||||
|
@ -214,52 +351,6 @@ class Node(OrgModelMixin):
|
|||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def parent_key(self):
|
||||
parent_key = ":".join(self.key.split(":")[:-1])
|
||||
return parent_key
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if self.is_root():
|
||||
return self
|
||||
try:
|
||||
parent = self.__class__.objects.get(key=self.parent_key)
|
||||
return parent
|
||||
except Node.DoesNotExist:
|
||||
return self.__class__.root()
|
||||
|
||||
@parent.setter
|
||||
def parent(self, parent):
|
||||
if not self.is_node:
|
||||
self.key = parent.key + ':fake'
|
||||
return
|
||||
children = self.get_all_children()
|
||||
old_key = self.key
|
||||
with transaction.atomic():
|
||||
self.key = parent.get_next_child_key()
|
||||
for child in children:
|
||||
child.key = child.key.replace(old_key, self.key, 1)
|
||||
child.save()
|
||||
self.save()
|
||||
|
||||
def get_ancestor_keys(self, with_self=False):
|
||||
parent_keys = []
|
||||
key_list = self.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 get_ancestor(self, with_self=False):
|
||||
ancestor_keys = self.get_ancestor_keys(with_self=with_self)
|
||||
ancestor = self.__class__.objects.filter(
|
||||
key__in=ancestor_keys
|
||||
).order_by('key')
|
||||
return ancestor
|
||||
|
||||
@classmethod
|
||||
def create_root_node(cls):
|
||||
# 如果使用current_org 在set_current_org时会死循环
|
||||
|
@ -292,9 +383,7 @@ class Node(OrgModelMixin):
|
|||
|
||||
def as_tree_node(self):
|
||||
from common.tree import TreeNode
|
||||
from ..serializers import NodeSerializer
|
||||
name = '{} ({})'.format(self.value, self.assets_amount)
|
||||
node_serializer = NodeSerializer(instance=self)
|
||||
data = {
|
||||
'id': self.key,
|
||||
'name': name,
|
||||
|
@ -303,16 +392,37 @@ class Node(OrgModelMixin):
|
|||
'isParent': True,
|
||||
'open': self.is_root(),
|
||||
'meta': {
|
||||
'node': node_serializer.data,
|
||||
'node': {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"value": self.value,
|
||||
"key": self.key,
|
||||
"assets_amount": self.assets_amount,
|
||||
},
|
||||
'type': 'node'
|
||||
}
|
||||
}
|
||||
tree_node = TreeNode(**data)
|
||||
return tree_node
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.children or self.get_assets():
|
||||
return
|
||||
return super().delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls):
|
||||
from ..utils import NodeUtil
|
||||
util = NodeUtil()
|
||||
return sorted(util.nodes)
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=100):
|
||||
import random
|
||||
org = get_current_org()
|
||||
if not org or not org.is_real():
|
||||
Organization.default().change_to()
|
||||
|
||||
for i in range(count):
|
||||
node = random.choice(cls.objects.all())
|
||||
node.create_child('Node {}'.format(i))
|
||||
|
|
|
@ -4,13 +4,11 @@
|
|||
|
||||
import logging
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
|
||||
from common.utils import get_signer
|
||||
from ..const import SYSTEM_USER_CONN_CACHE_KEY
|
||||
from .base import AssetUser
|
||||
|
||||
|
||||
|
@ -31,7 +29,7 @@ class AdminUser(AssetUser):
|
|||
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
|
||||
become_user = models.CharField(default='root', max_length=64)
|
||||
_become_pass = models.CharField(default='', max_length=128)
|
||||
CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}'
|
||||
CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}'
|
||||
_prefer = "admin_user"
|
||||
|
||||
def __str__(self):
|
||||
|
@ -61,31 +59,6 @@ class AdminUser(AssetUser):
|
|||
info = None
|
||||
return info
|
||||
|
||||
def get_related_assets(self):
|
||||
assets = self.assets.all()
|
||||
return assets
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
return self.get_related_assets().count()
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
from .asset import Asset
|
||||
assets = self.get_related_assets().values_list('id', 'hostname', flat=True)
|
||||
data = {
|
||||
'unreachable': [],
|
||||
'reachable': [],
|
||||
}
|
||||
for asset_id, hostname in assets:
|
||||
key = Asset.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
value = cache.get(key, Asset.UNKNOWN)
|
||||
if value == Asset.REACHABLE:
|
||||
data['reachable'].append(hostname)
|
||||
elif value == Asset.UNREACHABLE:
|
||||
data['unreachable'].append(hostname)
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('name', 'org_id')]
|
||||
|
@ -141,9 +114,6 @@ class SystemUser(AssetUser):
|
|||
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
|
||||
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
|
||||
|
||||
SYSTEM_USER_CACHE_KEY = "__SYSTEM_USER_CACHED_{}"
|
||||
CONNECTIVE_CACHE_KEY = '_JMS_SYSTEM_USER_CONNECTIVE_{}'
|
||||
|
||||
def __str__(self):
|
||||
return '{0.name}({0.username})'.format(self)
|
||||
|
||||
|
@ -157,49 +127,6 @@ class SystemUser(AssetUser):
|
|||
'auto_push': self.auto_push,
|
||||
}
|
||||
|
||||
def get_related_assets(self):
|
||||
assets = set(self.assets.all())
|
||||
return assets
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
|
||||
value = cache.get(cache_key, None)
|
||||
if not value or 'unreachable' not in value:
|
||||
return {'unreachable': [], 'reachable': []}
|
||||
else:
|
||||
return value
|
||||
|
||||
@connectivity.setter
|
||||
def connectivity(self, value):
|
||||
data = self.connectivity
|
||||
unreachable = data['unreachable']
|
||||
reachable = data['reachable']
|
||||
assets = {asset.hostname: asset for asset in self.assets.all()}
|
||||
|
||||
for host in value.get('dark', {}).keys():
|
||||
if host not in unreachable:
|
||||
unreachable.append(host)
|
||||
if host in reachable:
|
||||
reachable.remove(host)
|
||||
self.set_connectivity_of(assets.get(host), self.UNREACHABLE)
|
||||
for host in value.get('contacted'):
|
||||
if host not in reachable:
|
||||
reachable.append(host)
|
||||
if host in unreachable:
|
||||
unreachable.remove(host)
|
||||
self.set_connectivity_of(assets.get(host), self.REACHABLE)
|
||||
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
|
||||
cache.set(cache_key, data, 3600)
|
||||
|
||||
@property
|
||||
def assets_unreachable(self):
|
||||
return self.connectivity.get('unreachable')
|
||||
|
||||
@property
|
||||
def assets_reachable(self):
|
||||
return self.connectivity.get('reachable')
|
||||
|
||||
@property
|
||||
def login_mode_display(self):
|
||||
return self.get_login_mode_display()
|
||||
|
@ -210,12 +137,6 @@ class SystemUser(AssetUser):
|
|||
else:
|
||||
return False
|
||||
|
||||
def set_cache(self):
|
||||
cache.set(self.SYSTEM_USER_CACHE_KEY.format(self.id), self, 3600)
|
||||
|
||||
def expire_cache(self):
|
||||
cache.delete(self.SYSTEM_USER_CACHE_KEY.format(self.id))
|
||||
|
||||
@property
|
||||
def cmd_filter_rules(self):
|
||||
from .cmd_filter import CommandFilterRule
|
||||
|
@ -233,18 +154,6 @@ class SystemUser(AssetUser):
|
|||
return False, matched_cmd
|
||||
return True, None
|
||||
|
||||
@classmethod
|
||||
def get_system_user_by_id_or_cached(cls, sid):
|
||||
cached = cache.get(cls.SYSTEM_USER_CACHE_KEY.format(sid))
|
||||
if cached:
|
||||
return cached
|
||||
try:
|
||||
system_user = cls.objects.get(id=sid)
|
||||
system_user.set_cache()
|
||||
return system_user
|
||||
except cls.DoesNotExist:
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('name', 'org_id')]
|
||||
|
|
|
@ -2,11 +2,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.utils import timezone
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import validate_ssh_private_key
|
||||
|
||||
|
||||
__all__ = ['init_model', 'generate_fake']
|
||||
__all__ = [
|
||||
'init_model', 'generate_fake', 'private_key_validator', 'Connectivity',
|
||||
]
|
||||
|
||||
|
||||
def init_model():
|
||||
|
@ -31,5 +37,72 @@ def private_key_validator(value):
|
|||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
class Connectivity:
|
||||
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
|
||||
CONNECTIVITY_CHOICES = (
|
||||
(UNREACHABLE, _("Unreachable")),
|
||||
(REACHABLE, _('Reachable')),
|
||||
(UNKNOWN, _("Unknown")),
|
||||
)
|
||||
|
||||
status = UNKNOWN
|
||||
datetime = timezone.now()
|
||||
|
||||
def __init__(self, status, datetime):
|
||||
self.status = status
|
||||
self.datetime = datetime
|
||||
|
||||
def display(self):
|
||||
return dict(self.__class__.CONNECTIVITY_CHOICES).get(self.status)
|
||||
|
||||
def is_reachable(self):
|
||||
return self.status == self.REACHABLE
|
||||
|
||||
def is_unreachable(self):
|
||||
return self.status == self.UNREACHABLE
|
||||
|
||||
def is_unknown(self):
|
||||
return self.status == self.UNKNOWN
|
||||
|
||||
@classmethod
|
||||
def unreachable(cls):
|
||||
return cls(cls.UNREACHABLE, timezone.now())
|
||||
|
||||
@classmethod
|
||||
def reachable(cls):
|
||||
return cls(cls.REACHABLE, timezone.now())
|
||||
|
||||
@classmethod
|
||||
def unknown(cls):
|
||||
return cls(cls.UNKNOWN, timezone.now())
|
||||
|
||||
@classmethod
|
||||
def set(cls, key, value, ttl=0):
|
||||
cache.set(key, value, ttl)
|
||||
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
value = cache.get(key, cls.unknown())
|
||||
if not isinstance(value, cls):
|
||||
value = cls.unknown()
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def set_unreachable(cls, key, ttl=0):
|
||||
cls.set(key, cls.unreachable(), ttl)
|
||||
|
||||
@classmethod
|
||||
def set_reachable(cls, key, ttl=0):
|
||||
cls.set(key, cls.reachable(), ttl)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.status == other.status
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.status > other.status
|
||||
|
||||
def __lt__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
def __str__(self):
|
||||
return self.display()
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
|
||||
from ..models import Node, AdminUser
|
||||
from ..const import ADMIN_USER_CONN_CACHE_KEY
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
|
||||
from .base import AuthSerializer
|
||||
|
@ -17,54 +15,27 @@ class AdminUserSerializer(BulkOrgResourceModelSerializer):
|
|||
"""
|
||||
管理用户
|
||||
"""
|
||||
password = serializers.CharField(
|
||||
required=False, write_only=True, label=_('Password')
|
||||
)
|
||||
unreachable_amount = serializers.SerializerMethodField(label=_('Unreachable'))
|
||||
assets_amount = serializers.SerializerMethodField(label=_('Asset'))
|
||||
reachable_amount = serializers.SerializerMethodField(label=_('Reachable'))
|
||||
|
||||
class Meta:
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
model = AdminUser
|
||||
fields = [
|
||||
'id', 'name', 'username', 'assets_amount',
|
||||
'reachable_amount', 'unreachable_amount', 'password', 'comment',
|
||||
'date_created', 'date_updated', 'become', 'become_method',
|
||||
'become_user', 'created_by',
|
||||
'id', 'name', 'username', 'password', 'private_key', 'public_key',
|
||||
'comment', 'connectivity_amount', 'assets_amount',
|
||||
'date_created', 'date_updated', 'created_by',
|
||||
]
|
||||
|
||||
extra_kwargs = {
|
||||
'date_created': {'label': _('Date created')},
|
||||
'date_updated': {'label': _('Date updated')},
|
||||
'become': {'read_only': True}, 'become_method': {'read_only': True},
|
||||
'become_user': {'read_only': True}, 'created_by': {'read_only': True}
|
||||
'password': {"write_only": True},
|
||||
'private_key': {"write_only": True},
|
||||
'public_key': {"write_only": True},
|
||||
'date_created': {'read_only': True},
|
||||
'date_updated': {'read_only': True},
|
||||
'created_by': {'read_only': True},
|
||||
'assets_amount': {'label': _('Asset')},
|
||||
'connectivity_amount': {'label': _('Connectivity')},
|
||||
}
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
return [f for f in fields if not f.startswith('_')]
|
||||
|
||||
@staticmethod
|
||||
def get_unreachable_amount(obj):
|
||||
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
|
||||
if data:
|
||||
return len(data.get('dark'))
|
||||
else:
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def get_reachable_amount(obj):
|
||||
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
|
||||
if data:
|
||||
return len(data.get('contacted'))
|
||||
else:
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.assets_amount
|
||||
|
||||
|
||||
class AdminUserAuthSerializer(AuthSerializer):
|
||||
|
||||
|
|
|
@ -1,48 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
from rest_framework.validators import ValidationError
|
||||
|
||||
from django.db.models import Prefetch
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from ..models import Asset, Protocol
|
||||
from .system_user import AssetSystemUserSerializer
|
||||
from ..models import Asset, Node, Label
|
||||
from .base import ConnectivitySerializer
|
||||
|
||||
__all__ = [
|
||||
'AssetSerializer', 'AssetGrantedSerializer', 'AssetSimpleSerializer',
|
||||
'ProtocolSerializer',
|
||||
'AssetSerializer', 'AssetSimpleSerializer',
|
||||
'ProtocolsField',
|
||||
]
|
||||
|
||||
|
||||
class ProtocolSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Protocol
|
||||
fields = ["name", "port"]
|
||||
class ProtocolField(serializers.RegexField):
|
||||
protocols = '|'.join(dict(Asset.PROTOCOL_CHOICES).keys())
|
||||
default_error_messages = {
|
||||
'invalid': _('Protocol format should {}/{}'.format(protocols, '1-65535'))
|
||||
}
|
||||
regex = r'^(%s)/(\d{1,5})$' % protocols
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(self.regex, **kwargs)
|
||||
|
||||
|
||||
class ProtocolsRelatedField(serializers.RelatedField):
|
||||
def validate_duplicate_protocols(values):
|
||||
errors = []
|
||||
names = []
|
||||
|
||||
for value in values:
|
||||
if not value or '/' not in value:
|
||||
continue
|
||||
name = value.split('/')[0]
|
||||
if name in names:
|
||||
errors.append(_("Protocol duplicate: {}").format(name))
|
||||
names.append(name)
|
||||
errors.append('')
|
||||
if any(errors):
|
||||
raise serializers.ValidationError(errors)
|
||||
|
||||
|
||||
class ProtocolsField(serializers.ListField):
|
||||
default_validators = [validate_duplicate_protocols]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['child'] = ProtocolField()
|
||||
kwargs['allow_null'] = True
|
||||
kwargs['allow_empty'] = True
|
||||
kwargs['min_length'] = 1
|
||||
kwargs['max_length'] = 4
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_representation(self, value):
|
||||
return str(value)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
if '/' not in data:
|
||||
raise ValidationError("protocol not contain /: {}".format(data))
|
||||
v = data.split("/")
|
||||
if len(v) != 2:
|
||||
raise ValidationError("protocol format should be name/port: {}".format(data))
|
||||
name, port = v
|
||||
cleaned_data = {"name": name, "port": port}
|
||||
return cleaned_data
|
||||
if not value:
|
||||
return []
|
||||
return value.split(' ')
|
||||
|
||||
|
||||
class AssetSerializer(BulkOrgResourceModelSerializer):
|
||||
protocols = ProtocolsRelatedField(
|
||||
many=True, queryset=Protocol.objects.all(), label=_("Protocols")
|
||||
)
|
||||
protocols = ProtocolsField(label=_('Protocols'), required=False)
|
||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
||||
|
||||
"""
|
||||
资产的数据结构
|
||||
|
@ -57,7 +76,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory',
|
||||
'disk_total', 'disk_info', 'os', 'os_version', 'os_arch',
|
||||
'hostname_raw', 'comment', 'created_by', 'date_created',
|
||||
'hardware_info', 'connectivity'
|
||||
'hardware_info', 'connectivity',
|
||||
]
|
||||
read_only_fields = (
|
||||
'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
||||
|
@ -69,121 +88,41 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
'protocol': {'write_only': True},
|
||||
'port': {'write_only': True},
|
||||
'hardware_info': {'label': _('Hardware info')},
|
||||
'connectivity': {'label': _('Connectivity')},
|
||||
'org_name': {'label': _('Org name')}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.prefetch_related('labels', 'nodes')\
|
||||
.select_related('admin_user')
|
||||
queryset = queryset.prefetch_related(
|
||||
Prefetch('nodes', queryset=Node.objects.all().only('id')),
|
||||
Prefetch('labels', queryset=Label.objects.all().only('id')),
|
||||
).select_related('admin_user', 'domain')
|
||||
return queryset
|
||||
|
||||
@staticmethod
|
||||
def validate_protocols(attr):
|
||||
protocols_serializer = ProtocolSerializer(data=attr, many=True)
|
||||
protocols_serializer.is_valid(raise_exception=True)
|
||||
protocols_name = [i.get("name", "ssh") for i in attr]
|
||||
errors = [{} for i in protocols_name]
|
||||
for i, name in enumerate(protocols_name):
|
||||
if name in protocols_name[:i]:
|
||||
errors[i] = {"name": _("Protocol duplicate: {}").format(name)}
|
||||
if any(errors):
|
||||
raise ValidationError(errors)
|
||||
return attr
|
||||
|
||||
def create(self, validated_data):
|
||||
def compatible_with_old_protocol(self, validated_data):
|
||||
protocols_data = validated_data.pop("protocols", [])
|
||||
|
||||
# 兼容老的api
|
||||
protocol = validated_data.get("protocol")
|
||||
name = validated_data.get("protocol")
|
||||
port = validated_data.get("port")
|
||||
if not protocols_data and protocol and port:
|
||||
protocols_data = [{"name": protocol, "port": port}]
|
||||
if not protocols_data and name and port:
|
||||
protocols_data.insert(0, '/'.join([name, str(port)]))
|
||||
elif not name and not port and protocols_data:
|
||||
protocol = protocols_data[0].split('/')
|
||||
validated_data["protocol"] = protocol[0]
|
||||
validated_data["port"] = int(protocol[1])
|
||||
if validated_data:
|
||||
validated_data["protocols"] = ' '.join(protocols_data)
|
||||
|
||||
if not protocol and not port and protocols_data:
|
||||
validated_data["protocol"] = protocols_data[0]["name"]
|
||||
validated_data["port"] = protocols_data[0]["port"]
|
||||
|
||||
protocols_serializer = ProtocolSerializer(data=protocols_data, many=True)
|
||||
protocols_serializer.is_valid(raise_exception=True)
|
||||
protocols = protocols_serializer.save()
|
||||
def create(self, validated_data):
|
||||
self.compatible_with_old_protocol(validated_data)
|
||||
instance = super().create(validated_data)
|
||||
instance.protocols.set(protocols)
|
||||
return instance
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
protocols_data = validated_data.pop("protocols", [])
|
||||
|
||||
# 兼容老的api
|
||||
protocol = validated_data.get("protocol")
|
||||
port = validated_data.get("port")
|
||||
if not protocols_data and protocol and port:
|
||||
protocols_data = [{"name": protocol, "port": port}]
|
||||
|
||||
if not protocol and not port and protocols_data:
|
||||
validated_data["protocol"] = protocols_data[0]["name"]
|
||||
validated_data["port"] = protocols_data[0]["port"]
|
||||
protocols = None
|
||||
if protocols_data:
|
||||
protocols_serializer = ProtocolSerializer(data=protocols_data, many=True)
|
||||
protocols_serializer.is_valid(raise_exception=True)
|
||||
protocols = protocols_serializer.save()
|
||||
|
||||
instance = super().update(instance, validated_data)
|
||||
if protocols:
|
||||
instance.protocols.all().delete()
|
||||
instance.protocols.set(protocols)
|
||||
return instance
|
||||
|
||||
|
||||
# class AssetAsNodeSerializer(serializers.ModelSerializer):
|
||||
# protocols = ProtocolSerializer(many=True)
|
||||
#
|
||||
# class Meta:
|
||||
# model = Asset
|
||||
# fields = ['id', 'hostname', 'ip', 'platform', 'protocols']
|
||||
|
||||
|
||||
class AssetGrantedSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
被授权资产的数据结构
|
||||
"""
|
||||
protocols = ProtocolsRelatedField(
|
||||
many=True, queryset=Protocol.objects.all(), label=_("Protocols")
|
||||
)
|
||||
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
|
||||
system_users_join = serializers.SerializerMethodField()
|
||||
# nodes = NodeTMPSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "ip", "protocol", "port", "protocols",
|
||||
"system_users_granted", "is_active", "system_users_join", "os",
|
||||
'domain', "platform", "comment", "org_id", "org_name",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_system_users_join(obj):
|
||||
system_users = [s.username for s in obj.system_users_granted]
|
||||
return ', '.join(system_users)
|
||||
|
||||
|
||||
# class MyAssetGrantedSerializer(AssetGrantedSerializer):
|
||||
# """
|
||||
# 普通用户获取授权的资产定义的数据结构
|
||||
# """
|
||||
# protocols = ProtocolSerializer(many=True)
|
||||
#
|
||||
# class Meta:
|
||||
# model = Asset
|
||||
# fields = (
|
||||
# "id", "hostname", "system_users_granted",
|
||||
# "is_active", "system_users_join", "org_name",
|
||||
# "os", "platform", "comment", "org_id", "protocols"
|
||||
# )
|
||||
self.compatible_with_old_protocol(validated_data)
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class AssetSimpleSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import AuthBook, Asset
|
||||
from ..backends import AssetUserManager
|
||||
from common.utils import validate_ssh_private_key
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
from ..models import AuthBook, Asset
|
||||
from ..backends import AssetUserManager
|
||||
from .base import ConnectivitySerializer
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
@ -26,20 +27,8 @@ class BasicAssetSerializer(serializers.ModelSerializer):
|
|||
class AssetUserSerializer(BulkOrgResourceModelSerializer):
|
||||
hostname = serializers.CharField(read_only=True, label=_("Hostname"))
|
||||
ip = serializers.CharField(read_only=True, label=_("IP"))
|
||||
connectivity = serializers.CharField(read_only=True, label=_("Connectivity"))
|
||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
||||
|
||||
password = serializers.CharField(
|
||||
max_length=256, allow_blank=True, allow_null=True, write_only=True,
|
||||
required=False, label=_('Password')
|
||||
)
|
||||
public_key = serializers.CharField(
|
||||
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
|
||||
required=False, label=_('Public key')
|
||||
)
|
||||
private_key = serializers.CharField(
|
||||
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
|
||||
required=False, label=_('Private key')
|
||||
)
|
||||
backend = serializers.CharField(read_only=True, label=_("Backend"))
|
||||
|
||||
class Meta:
|
||||
|
@ -56,6 +45,9 @@ class AssetUserSerializer(BulkOrgResourceModelSerializer):
|
|||
]
|
||||
extra_kwargs = {
|
||||
'username': {'required': True},
|
||||
'password': {'write_only': True},
|
||||
'private_key': {'write_only': True},
|
||||
'public_key': {'write_only': True},
|
||||
}
|
||||
|
||||
def validate_private_key(self, key):
|
||||
|
@ -66,17 +58,9 @@ class AssetUserSerializer(BulkOrgResourceModelSerializer):
|
|||
return key
|
||||
|
||||
def create(self, validated_data):
|
||||
kwargs = {
|
||||
'name': validated_data.get('username'),
|
||||
'username': validated_data.get('username'),
|
||||
'asset': validated_data.get('asset'),
|
||||
'comment': validated_data.get('comment', ''),
|
||||
'org_id': validated_data.get('org_id', ''),
|
||||
'password': validated_data.get('password'),
|
||||
'public_key': validated_data.get('public_key'),
|
||||
'private_key': validated_data.get('private_key')
|
||||
}
|
||||
instance = AssetUserManager.create(**kwargs)
|
||||
if not validated_data.get("name") and validated_data.get("username"):
|
||||
validated_data["name"] = validated_data["username"]
|
||||
instance = AssetUserManager.create(**validated_data)
|
||||
return instance
|
||||
|
||||
|
||||
|
|
|
@ -24,3 +24,8 @@ class AuthSerializer(serializers.ModelSerializer):
|
|||
self.instance.set_auth(password=password, private_key=private_key,
|
||||
public_key=public_key)
|
||||
return self.instance
|
||||
|
||||
|
||||
class ConnectivitySerializer(serializers.Serializer):
|
||||
status = serializers.IntegerField()
|
||||
datetime = serializers.DateTimeField()
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import re
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.fields import ChoiceDisplayField
|
||||
|
@ -20,8 +21,16 @@ class CommandFilterSerializer(BulkOrgResourceModelSerializer):
|
|||
|
||||
class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
|
||||
serializer_choice_field = ChoiceDisplayField
|
||||
invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]')
|
||||
|
||||
class Meta:
|
||||
model = CommandFilterRule
|
||||
fields = '__all__'
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
|
||||
def validate_content(self, content):
|
||||
if self.invalid_pattern.search(content):
|
||||
invalid_char = self.invalid_pattern.pattern.replace('\\', '')
|
||||
msg = _("Content should not be contain: {}").format(invalid_char)
|
||||
raise serializers.ValidationError(msg)
|
||||
return content
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
from ..models import Asset, Node
|
||||
|
@ -25,11 +26,11 @@ class NodeSerializer(BulkOrgResourceModelSerializer):
|
|||
|
||||
def validate_value(self, data):
|
||||
instance = self.instance if self.instance else Node.root()
|
||||
children = instance.parent.get_children().exclude(key=instance.key)
|
||||
values = [child.value for child in children]
|
||||
if data in values:
|
||||
children = instance.parent.get_children()
|
||||
children_values = [node.value for node in children if node != instance]
|
||||
if data in children_values:
|
||||
raise serializers.ValidationError(
|
||||
'The same level node name cannot be the same'
|
||||
_('The same level node name cannot be the same')
|
||||
)
|
||||
return data
|
||||
|
||||
|
|
|
@ -12,53 +12,31 @@ class SystemUserSerializer(BulkOrgResourceModelSerializer):
|
|||
"""
|
||||
系统用户
|
||||
"""
|
||||
password = serializers.CharField(
|
||||
required=False, write_only=True, label=_('Password')
|
||||
)
|
||||
unreachable_amount = serializers.SerializerMethodField(
|
||||
label=_('Unreachable')
|
||||
)
|
||||
unreachable_assets = serializers.SerializerMethodField(
|
||||
label=_('Unreachable assets')
|
||||
)
|
||||
reachable_assets = serializers.SerializerMethodField(
|
||||
label=_('Reachable assets')
|
||||
)
|
||||
reachable_amount = serializers.SerializerMethodField(label=_('Reachable'))
|
||||
assets_amount = serializers.SerializerMethodField(label=_('Asset'))
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'name', 'username', 'login_mode', 'login_mode_display',
|
||||
'login_mode_display', 'priority', 'protocol', 'auto_push',
|
||||
'password', 'assets_amount', 'reachable_amount', 'reachable_assets',
|
||||
'unreachable_amount', 'unreachable_assets', 'cmd_filters', 'sudo',
|
||||
'shell', 'comment', 'nodes', 'assets'
|
||||
'id', 'name', 'username', 'password', 'public_key', 'private_key',
|
||||
'login_mode', 'login_mode_display', 'priority', 'protocol',
|
||||
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes',
|
||||
'assets_amount', 'connectivity_amount'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'password': {"write_only": True},
|
||||
'public_key': {"write_only": True},
|
||||
'private_key': {"write_only": True},
|
||||
'assets_amount': {'label': _('Asset')},
|
||||
'connectivity_amount': {'label': _('Connectivity')},
|
||||
'login_mode_display': {'label': _('Login mode display')},
|
||||
'created_by': {'read_only': True},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_unreachable_assets(obj):
|
||||
return obj.assets_unreachable
|
||||
|
||||
@staticmethod
|
||||
def get_reachable_assets(obj):
|
||||
return obj.assets_reachable
|
||||
|
||||
def get_unreachable_amount(self, obj):
|
||||
return len(self.get_unreachable_assets(obj))
|
||||
|
||||
def get_reachable_amount(self, obj):
|
||||
return len(self.get_reachable_assets(obj))
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return len(obj.get_related_assets())
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.prefetch_related('cmd_filters', 'nodes')
|
||||
return queryset
|
||||
|
||||
|
||||
class SystemUserAuthSerializer(AuthSerializer):
|
||||
|
@ -74,23 +52,6 @@ class SystemUserAuthSerializer(AuthSerializer):
|
|||
]
|
||||
|
||||
|
||||
class AssetSystemUserSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
查看授权的资产系统用户的数据结构,这个和AssetSerializer不同,字段少
|
||||
"""
|
||||
actions = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = (
|
||||
'id', 'name', 'username', 'priority',
|
||||
'protocol', 'comment', 'login_mode', 'actions',
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_actions(obj):
|
||||
return [action.name for action in obj.actions]
|
||||
|
||||
|
||||
class SystemUserSimpleSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
|
|
|
@ -27,11 +27,6 @@ def test_asset_conn_on_created(asset):
|
|||
test_asset_connectivity_util.delay([asset])
|
||||
|
||||
|
||||
def set_asset_root_node(asset):
|
||||
logger.debug("Set asset default node: {}".format(Node.root()))
|
||||
asset.nodes.add(Node.root())
|
||||
|
||||
|
||||
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
|
||||
@on_transaction_commit
|
||||
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
||||
|
|
|
@ -15,7 +15,8 @@ from ops.celery.decorator import (
|
|||
register_as_period_task, after_app_shutdown_clean_periodic
|
||||
)
|
||||
|
||||
from .models import SystemUser, AdminUser, Asset
|
||||
from .models import SystemUser, AdminUser
|
||||
from .models.utils import Connectivity
|
||||
from . import const
|
||||
|
||||
|
||||
|
@ -207,8 +208,7 @@ def test_asset_connectivity_util(assets, task_name=None):
|
|||
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
|
||||
created_by=created_by,
|
||||
)
|
||||
result = task.run()
|
||||
summary = result[1]
|
||||
raw, summary = task.run()
|
||||
success = summary.get('success', False)
|
||||
contacted = summary.get('contacted', {})
|
||||
dark = summary.get('dark', {})
|
||||
|
@ -218,13 +218,12 @@ def test_asset_connectivity_util(assets, task_name=None):
|
|||
results_summary['dark'].update(dark)
|
||||
|
||||
for asset in assets:
|
||||
if asset.hostname in results_summary.get('dark', {}):
|
||||
asset.connectivity = asset.UNREACHABLE
|
||||
elif asset.hostname in results_summary.get('contacted', []):
|
||||
asset.connectivity = asset.REACHABLE
|
||||
if asset.hostname in results_summary.get('dark', {}).keys():
|
||||
asset.connectivity = Connectivity.unreachable()
|
||||
elif asset.hostname in results_summary.get('contacted', {}).keys():
|
||||
asset.connectivity = Connectivity.reachable()
|
||||
else:
|
||||
asset.connectivity = asset.UNKNOWN
|
||||
|
||||
asset.connectivity = Connectivity.unknown()
|
||||
return results_summary
|
||||
|
||||
|
||||
|
@ -286,10 +285,6 @@ def test_admin_user_connectivity_manual(admin_user):
|
|||
|
||||
## System user connective ##
|
||||
|
||||
@shared_task
|
||||
def set_system_user_connectivity_info(system_user, summary):
|
||||
system_user.connectivity = summary
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_system_user_connectivity_util(system_user, assets, task_name):
|
||||
|
@ -336,8 +331,7 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
|
|||
pattern='all', options=const.TASK_OPTIONS,
|
||||
run_as=system_user.username, created_by=system_user.org_id,
|
||||
)
|
||||
result = task.run()
|
||||
summary = result[1]
|
||||
raw, summary = task.run()
|
||||
success = summary.get('success', False)
|
||||
contacted = summary.get('contacted', {})
|
||||
dark = summary.get('dark', {})
|
||||
|
@ -346,7 +340,7 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
|
|||
results_summary['contacted'].update(contacted)
|
||||
results_summary['dark'].update(dark)
|
||||
|
||||
set_system_user_connectivity_info(system_user, results_summary)
|
||||
system_user.set_connectivity(results_summary)
|
||||
return results_summary
|
||||
|
||||
|
||||
|
@ -567,23 +561,12 @@ def get_test_asset_user_connectivity_tasks(asset):
|
|||
return tasks
|
||||
|
||||
|
||||
@shared_task
|
||||
def set_asset_user_connectivity_info(asset_user, result):
|
||||
summary = result[1]
|
||||
if summary.get('contacted'):
|
||||
connectivity = 1
|
||||
elif summary.get("dark"):
|
||||
connectivity = 0
|
||||
else:
|
||||
connectivity = 3
|
||||
asset_user.connectivity = connectivity
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False):
|
||||
"""
|
||||
:param asset_user: <AuthBook>对象
|
||||
:param task_name:
|
||||
:param run_as_admin:
|
||||
:return:
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
|
@ -593,6 +576,7 @@ def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False)
|
|||
|
||||
tasks = get_test_asset_user_connectivity_tasks(asset_user.asset)
|
||||
if not tasks:
|
||||
logger.debug("No tasks ")
|
||||
return
|
||||
|
||||
args = (task_name,)
|
||||
|
@ -606,8 +590,8 @@ def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False)
|
|||
else:
|
||||
kwargs["run_as"] = asset_user.username
|
||||
task, created = update_or_create_ansible_task(*args, **kwargs)
|
||||
result = task.run()
|
||||
set_asset_user_connectivity_info(asset_user, result)
|
||||
raw, summary = task.run()
|
||||
asset_user.set_connectivity(summary)
|
||||
|
||||
|
||||
@shared_task
|
||||
|
|
|
@ -67,6 +67,7 @@ function initTable2() {
|
|||
columns: [
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" }
|
||||
],
|
||||
lengthMenu: [[10, 25, 50], [10, 25, 50]],
|
||||
pageLength: 10
|
||||
};
|
||||
asset_table2 = jumpserver.initServerSideDataTable(options);
|
||||
|
|
|
@ -32,7 +32,9 @@ var assetUserListUrl = "{% url "api-assets:asset-user-list" %}";
|
|||
var assetUserTable;
|
||||
var needPush = false;
|
||||
var prefer = null;
|
||||
var lastMFATime = "{{ request.session.OTP_LAST_VERIFY_TIME }}";
|
||||
var lastMFATime = "{{ request.session.MFA_VERIFY_TIME }}";
|
||||
var testDatetime = "{% trans 'Test datetime: ' %}";
|
||||
var mfaVerifyTTL = "{{ SECURITY_MFA_VERIFY_TTL }}";
|
||||
|
||||
function initAssetUserTable() {
|
||||
var options = {
|
||||
|
@ -41,19 +43,25 @@ function initAssetUserTable() {
|
|||
columnDefs: [
|
||||
{
|
||||
targets: 5, createdCell: function (td, cellData) {
|
||||
if (cellData == 1) {
|
||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
} else if (cellData == 0) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
var innerHtml = "";
|
||||
if (cellData.status == 1) {
|
||||
innerHtml = '<i class="fa fa-circle text-navy"></i>'
|
||||
} else if (cellData.status == 0) {
|
||||
innerHtml = '<i class="fa fa-circle text-danger"></i>'
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
innerHtml = '<i class="fa fa-circle text-warning"></i>'
|
||||
}
|
||||
var date = new Date(cellData.datetime);
|
||||
var dateManual = date.toLocaleString();
|
||||
var dataContent = testDatetime + dateManual;
|
||||
innerHtml = "<a data-toggle='popover' data-content='" + dataContent + "'" + 'data-placement="auto bottom"' + ">" + innerHtml + "</a>";
|
||||
$(td).html(innerHtml);
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: 6, createdCell: function (td, cellData) {
|
||||
var date = new Date(cellData);
|
||||
$(td).html(date.toLocaleString());
|
||||
var data = formatDateAsCN(cellData);
|
||||
$(td).html(data);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -84,8 +92,8 @@ function initAssetUserTable() {
|
|||
ajax_url: assetUserListUrl,
|
||||
columns: [
|
||||
{data: "id"}, {data: "hostname"}, {data: "ip"},
|
||||
{data: "username", orderable: false}, {data: "version", orderable: false},
|
||||
{data: "connectivity", orderable: false},
|
||||
{data: "username"}, {data: "version", orderable: false},
|
||||
{data: "connectivity"},
|
||||
{data: "date_created", orderable: false},
|
||||
{data: "asset", orderable: false}
|
||||
],
|
||||
|
@ -102,7 +110,7 @@ $(document).ready(function(){
|
|||
authUsername = $(this).data('user');
|
||||
var now = new Date();
|
||||
var nowTime = now.getTime() / 1000;
|
||||
if (nowTime - lastMFATime > 60*10 ) {
|
||||
if ( !lastMFATime || nowTime - lastMFATime > mfaVerifyTTL ) {
|
||||
mfaFor = "viewAuth";
|
||||
$("#mfa_auth_confirm").modal("show");
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,358 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
|
||||
{# <link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet">#}
|
||||
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
|
||||
<style type="text/css">
|
||||
div#rMenu {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
text-align: left;
|
||||
{#top: 100%;#}
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
{#float: left;#}
|
||||
padding: 0 0;
|
||||
margin: 2px 0 0;
|
||||
list-style: none;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_processing {
|
||||
opacity: .9;
|
||||
border: none;
|
||||
}
|
||||
div#rMenu li{
|
||||
margin: 1px 0;
|
||||
cursor: pointer;
|
||||
list-style: none outside none;
|
||||
}
|
||||
.dropdown a:hover {
|
||||
background-color: #f1f1f1
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
|
||||
<div class="file-manager" id="tree-node-id">
|
||||
<div id="{% block treeID %}nodeTree{% endblock %}" class="ztree">
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rMenu">
|
||||
<ul class="dropdown-menu menu-actions">
|
||||
<li class="divider"></li>
|
||||
<li id="m_create" tabindex="-1" onclick="addTreeNode();"><a><i class="fa fa-plus-square-o"></i> {% trans 'Add node' %}</a></li>
|
||||
<li id="m_del" tabindex="-1" onclick="editTreeNode();"><a><i class="fa fa-pencil-square-o"></i> {% trans 'Rename node' %}</a></li>
|
||||
<li id="m_del" tabindex="-1" onclick="removeTreeNode();"><a><i class="fa fa-minus-square"></i> {% trans 'Delete node' %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<script>
|
||||
var zTree, rMenu = null;
|
||||
var current_node_id = null;
|
||||
var current_node = null;
|
||||
var showMenu = false;
|
||||
|
||||
|
||||
var treeUrl = '{% url 'api-assets:node-children-tree' %}?assets=0';
|
||||
// options:
|
||||
// {
|
||||
// "onSelected": func,
|
||||
// "showAssets": false,
|
||||
// "beforeAsync": func()
|
||||
// "showMenu": false,
|
||||
// "otherMenu": "",
|
||||
// "showAssets": false,
|
||||
// }
|
||||
function initNodeTree(options) {
|
||||
var setting = {
|
||||
view: {
|
||||
dblClickExpand: false,
|
||||
showLine: true
|
||||
},
|
||||
data: {
|
||||
simpleData: {
|
||||
enable: true
|
||||
}
|
||||
},
|
||||
async: {
|
||||
enable: true,
|
||||
url: treeUrl,
|
||||
autoParam: ["id=key", "name=n", "level=lv"],
|
||||
type: 'get'
|
||||
},
|
||||
edit: {
|
||||
enable: true,
|
||||
showRemoveBtn: false,
|
||||
showRenameBtn: false,
|
||||
drag: {
|
||||
isCopy: true,
|
||||
isMove: true
|
||||
}
|
||||
},
|
||||
callback: {
|
||||
onRightClick: OnRightClick,
|
||||
beforeClick: beforeClick,
|
||||
onRename: onRename,
|
||||
onSelected: options.onSelected || defaultCallback("On selected"),
|
||||
beforeDrag: beforeDrag,
|
||||
onDrag: onDrag,
|
||||
beforeDrop: beforeDrop,
|
||||
onDrop: onDrop,
|
||||
beforeAsync: options.beforeAsync || defaultCallback("Before async")
|
||||
}
|
||||
};
|
||||
if (options.showAssets) {
|
||||
treeUrl = setUrlParam(treeUrl, 'assets', '1')
|
||||
}
|
||||
|
||||
$.get(treeUrl, function(data, status){
|
||||
zNodes = data;
|
||||
zTree = $.fn.zTree.init($("#nodeTree"), setting, zNodes);
|
||||
rootNodeAddDom(zTree, function () {
|
||||
treeUrl = setUrlParam(treeUrl, 'refresh', '1');
|
||||
initTree();
|
||||
treeUrl = setUrlParam(treeUrl, 'refresh', '0')
|
||||
});
|
||||
});
|
||||
|
||||
if (options.showMenu) {
|
||||
showMenu = true;
|
||||
rMenu = $("#rMenu");
|
||||
}
|
||||
if (options.otherMenu) {
|
||||
$(".menu-actions").append(options.otherMenu)
|
||||
}
|
||||
return zTree
|
||||
}
|
||||
|
||||
function addTreeNode() {
|
||||
hideRMenu();
|
||||
var parentNode = zTree.getSelectedNodes()[0];
|
||||
if (!parentNode){
|
||||
return
|
||||
}
|
||||
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.meta.node.id);
|
||||
$.post(url, {}, function (data, status){
|
||||
if (status === "success") {
|
||||
var newNode = {
|
||||
id: data["key"],
|
||||
name: data["value"],
|
||||
pId: parentNode.id,
|
||||
meta: {
|
||||
"node": data
|
||||
}
|
||||
};
|
||||
newNode.checked = zTree.getSelectedNodes()[0].checked;
|
||||
zTree.addNodes(parentNode, 0, newNode);
|
||||
var node = zTree.getNodeByParam('id', newNode.id, parentNode);
|
||||
zTree.editName(node);
|
||||
} else {
|
||||
alert("{% trans 'Create node failed' %}")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeTreeNode() {
|
||||
hideRMenu();
|
||||
var current_node = zTree.getSelectedNodes()[0];
|
||||
if (!current_node){
|
||||
return
|
||||
}
|
||||
if (current_node.children && current_node.children.length > 0) {
|
||||
toastr.error("{% trans 'Have child node, cancel' %}");
|
||||
} else if (current_node.meta.node.assets_amount !== 0) {
|
||||
toastr.error("{% trans 'Have assets, cancel' %}");
|
||||
} else {
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: "DELETE",
|
||||
success: function () {
|
||||
zTree.removeNode(current_node);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function editTreeNode() {
|
||||
hideRMenu();
|
||||
var current_node = zTree.getSelectedNodes()[0];
|
||||
if (!current_node){
|
||||
return
|
||||
}
|
||||
if (current_node) {
|
||||
current_node.name = current_node.meta.node.value;
|
||||
}
|
||||
zTree.editName(current_node);
|
||||
}
|
||||
|
||||
function OnRightClick(event, treeId, treeNode) {
|
||||
if (!showMenu) {
|
||||
return
|
||||
}
|
||||
if (!treeNode && event.target.tagName.toLowerCase() !== "button" && $(event.target).parents("a").length === 0) {
|
||||
zTree.cancelSelectedNode();
|
||||
showRMenu("root", event.clientX, event.clientY);
|
||||
} else if (treeNode && !treeNode.noR) {
|
||||
zTree.selectNode(treeNode);
|
||||
showRMenu("node", event.clientX, event.clientY);
|
||||
}
|
||||
}
|
||||
|
||||
function showRMenu(type, x, y) {
|
||||
var offset = $("#tree-node-id").offset();
|
||||
x -= offset.left;
|
||||
y -= offset.top;
|
||||
x += document.body.scrollLeft;
|
||||
y += document.body.scrollTop + document.documentElement.scrollTop;
|
||||
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
|
||||
$("#rMenu ul").show();
|
||||
$("body").bind("mousedown", onBodyMouseDown);
|
||||
}
|
||||
|
||||
function beforeClick(treeId, treeNode, clickFlag) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function hideRMenu() {
|
||||
if (rMenu) rMenu.css({"visibility": "hidden"});
|
||||
$("body").unbind("mousedown", onBodyMouseDown);
|
||||
}
|
||||
|
||||
function onBodyMouseDown(event){
|
||||
if (!(event.target.id === "rMenu" || $(event.target).parents("#rMenu").length>0)) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
}
|
||||
}
|
||||
|
||||
function onRename(event, treeId, treeNode, isCancel){
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
var data = {"value": treeNode.name};
|
||||
if (isCancel){
|
||||
return
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
body: JSON.stringify(data),
|
||||
method: "PATCH",
|
||||
success_message: "{% trans 'Rename success' %}",
|
||||
success: function () {
|
||||
treeNode.name = treeNode.name + ' (' + treeNode.meta.node.assets_amount + ')';
|
||||
zTree.updateNode(treeNode);
|
||||
console.log("Success: " + treeNode.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function beforeDrag() {
|
||||
return true
|
||||
}
|
||||
|
||||
function beforeDrop(treeId, treeNodes, targetNode, moveType) {
|
||||
var treeNodesNames = [];
|
||||
$.each(treeNodes, function (index, value) {
|
||||
treeNodesNames.push(value.name);
|
||||
});
|
||||
|
||||
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.name + "` 下吗?";
|
||||
return confirm(msg);
|
||||
}
|
||||
|
||||
function onDrag(event, treeId, treeNodes) {
|
||||
}
|
||||
|
||||
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
|
||||
var treeNodesIds = [];
|
||||
$.each(treeNodes, function (index, value) {
|
||||
treeNodesIds.push(value.meta.node.id);
|
||||
});
|
||||
|
||||
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.meta.node.id);
|
||||
var body = {nodes: treeNodesIds};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: "PUT",
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
}
|
||||
|
||||
function defaultCallback(action) {
|
||||
function logging() {
|
||||
console.log(action)
|
||||
}
|
||||
return logging
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
})
|
||||
.on('click', '.btn-refresh-hardware', function () {
|
||||
var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}";
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
function success(data) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600')
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: "GET",
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}";
|
||||
if (!current_node_id) {
|
||||
return null;
|
||||
}
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
function success(data) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600')
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: "GET",
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-show-current-asset', function(){
|
||||
hideRMenu();
|
||||
$(this).css('display', 'none');
|
||||
$('#show_all_asset').css('display', 'inline-block');
|
||||
setCookie('show_current_asset', '1');
|
||||
location.reload()
|
||||
})
|
||||
.on('click', '.btn-show-all-asset', function(){
|
||||
hideRMenu();
|
||||
$(this).css('display', 'none');
|
||||
$('#show_current_asset').css('display', 'inline-block');
|
||||
setCookie('show_current_asset', '');
|
||||
location.reload();
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
hideRMenu();
|
||||
|
||||
})
|
||||
.on('click', '#menu_refresh_assets_amount', function () {
|
||||
hideRMenu();
|
||||
var url = "{% url 'api-assets:refresh-assets-amount' %}";
|
||||
APIUpdateAttr({
|
||||
'url': url,
|
||||
'method': 'GET'
|
||||
});
|
||||
window.location.reload();
|
||||
})
|
||||
</script>
|
|
@ -46,7 +46,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3" style="padding-left: 0;padding-right: 0">
|
||||
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
|
||||
|
@ -81,21 +81,6 @@ $(document).ready(function () {
|
|||
prefer = "admin_user";
|
||||
initAssetUserTable();
|
||||
})
|
||||
.on('click', '.btn-test-asset', function () {
|
||||
var asset_id = $(this).data('uid');
|
||||
var the_url = "{% url 'api-assets:asset-alive-test' pk=DEFAULT_PK %}".replace('{{ DEFAULT_PK }}', asset_id);
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}";
|
||||
var success = function (data) {
|
||||
|
@ -110,17 +95,5 @@ $(document).ready(function () {
|
|||
flash_message: false
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-update-asset-user-auth', function() {
|
||||
asset_id = $(this).data('aid');
|
||||
hostname = $(this).data('hostname');
|
||||
username = '{{ admin_user.username }}';
|
||||
$("#asset_user_auth_update_modal").modal();
|
||||
})
|
||||
.on("click", ".btn-view-auth", function (evt) {
|
||||
asset_id = $(this).data("aid") ;
|
||||
host = $(this).data("hostname");
|
||||
username = "{{ admin_user.username }}";
|
||||
$("#asset_user_auth_view").modal();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -11,27 +11,27 @@
|
|||
{% endblock %}
|
||||
{% block table_search %}
|
||||
<div class="" style="float: right">
|
||||
<div class=" btn-group">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class=" btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
||||
<span>{% trans "Update" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class=" btn-group">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class=" btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
||||
<span>{% trans "Update" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
|
@ -75,27 +75,29 @@ function initTable() {
|
|||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
var innerHtml = "";
|
||||
if (cellData !== 0) {
|
||||
innerHtml = "<span class='text-navy'>" + cellData + "</span>";
|
||||
var data = cellData.reachable;
|
||||
if (data !== 0) {
|
||||
innerHtml = "<span class='text-navy'>" + data + "</span>";
|
||||
} else {
|
||||
innerHtml = "<span>" + cellData + "</span>";
|
||||
innerHtml = "<span>" + data + "</span>";
|
||||
}
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData +'">' + innerHtml + '</span>');
|
||||
$(td).html(innerHtml)
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
var data = cellData.unreachable;
|
||||
var innerHtml = "";
|
||||
if (cellData !== 0) {
|
||||
innerHtml = "<span class='text-danger'>" + cellData + "</span>";
|
||||
if (data !== 0) {
|
||||
innerHtml = "<span class='text-danger'>" + data + "</span>";
|
||||
} else {
|
||||
innerHtml = "<span>" + cellData + "</span>";
|
||||
innerHtml = "<span>" + data + "</span>";
|
||||
}
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
var val = 0;
|
||||
var innerHtml = "";
|
||||
var total = rowData.assets_amount;
|
||||
var reachable = rowData.reachable_amount;
|
||||
var reachable = cellData.reachable;
|
||||
if (total !== 0) {
|
||||
val = reachable/total * 100;
|
||||
}
|
||||
|
@ -114,15 +116,18 @@ function initTable() {
|
|||
$(td).html(update_btn + del_btn)
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:admin-user-list" %}',
|
||||
columns: [{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" },
|
||||
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment"}, {data: "id"}]
|
||||
columns: [
|
||||
{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" },
|
||||
{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"},
|
||||
{data: "comment"}, {data: "id"}
|
||||
]
|
||||
};
|
||||
admin_user_table = jumpserver.initServerSideDataTable(options);
|
||||
return admin_user_table
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
initTable()
|
||||
initTable();
|
||||
})
|
||||
|
||||
.on('click', '.btn_admin_user_delete', function () {
|
||||
|
|
|
@ -70,11 +70,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Protocol' %}</td>
|
||||
<td>
|
||||
{% for protocol in asset.protocols.all %}
|
||||
<b>{{ protocol }}</b>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ asset.protocols }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Admin user' %}:</td>
|
||||
|
|
|
@ -49,15 +49,7 @@
|
|||
<div class="wrapper wrapper-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-3" id="split-left" style="padding-left: 3px">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
|
||||
<div class="file-manager ">
|
||||
<div id="assetTree" class="ztree">
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'assets/_node_tree.html' %}
|
||||
</div>
|
||||
<div class="col-lg-9 animated fadeInRight" id="split-right">
|
||||
<div class="tree-toggle">
|
||||
|
@ -132,26 +124,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="rMenu">
|
||||
<ul class="dropdown-menu">
|
||||
<li class="divider"></li>
|
||||
<li id="m_create" tabindex="-1" onclick="addTreeNode();"><a><i class="fa fa-plus-square-o"></i> {% trans 'Add node' %}</a></li>
|
||||
<li id="m_del" tabindex="-1" onclick="editTreeNode();"><a><i class="fa fa-pencil-square-o"></i> {% trans 'Rename node' %}</a></li>
|
||||
<li id="m_del" tabindex="-1" onclick="removeTreeNode();"><a><i class="fa fa-minus-square"></i> {% trans 'Delete node' %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="menu_asset_add" class="btn-add-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a><i class="fa fa-copy"></i> {% trans 'Add assets to node' %}</a></li>
|
||||
<li id="menu_asset_move" class="btn-move-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a><i class="fa fa-cut"></i> {% trans 'Move assets to node' %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="menu_refresh_hardware_info" class="btn-refresh-hardware" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh node hardware info' %}</a></li>
|
||||
<li id="menu_test_connective" class="btn-test-connective" tabindex="-1"><a><i class="fa fa-chain"></i> {% trans 'Test node connective' %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="menu_refresh_assets_amount" class="btn-refresh-assets-amount" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh all node assets amount' %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="show_current_asset" class="btn-show-current-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-hand-o-up"></i> {% trans 'Display only current node assets' %}</a></li>
|
||||
<li id="show_all_asset" class="btn-show-all-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-th"></i> {% trans 'Displays all child node assets' %}</a></li>
|
||||
{# <li id="fresh_tree" class="btn-refresh-tree" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh' %}</a></li>#}
|
||||
</ul>
|
||||
</div>
|
||||
{% include 'assets/_node_tree.html' %}
|
||||
{% include 'assets/_asset_update_modal.html' %}
|
||||
{% include 'assets/_asset_import_modal.html' %}
|
||||
{% include 'assets/_asset_list_modal.html' %}
|
||||
|
@ -159,10 +132,11 @@
|
|||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var zTree, rMenu, asset_table, show = 0;
|
||||
var asset_table, show = 0;
|
||||
var update_node_action = "";
|
||||
var current_node_id = null;
|
||||
var current_node = null;
|
||||
var testDatetime = "{% trans 'Test datetime: ' %}";
|
||||
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
|
@ -176,16 +150,21 @@ function initTable() {
|
|||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||
$(td).html(rowData.hardware_info)
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
if (cellData === 1){
|
||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
} else if (cellData === 0) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
}
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var innerHtml = "";
|
||||
if (cellData.status == 1) {
|
||||
innerHtml = '<i class="fa fa-circle text-navy"></i>'
|
||||
} else if (cellData.status == 0) {
|
||||
innerHtml = '<i class="fa fa-circle text-danger"></i>'
|
||||
} else {
|
||||
innerHtml = '<i class="fa fa-circle text-warning"></i>'
|
||||
}
|
||||
var date = new Date(cellData.datetime);
|
||||
var dateManual = date.toLocaleString();
|
||||
var dataContent = testDatetime + dateManual;
|
||||
innerHtml = "<a data-toggle='popover' data-content='" + dataContent + "'" + 'data-placement="auto bottom"' + ">" + innerHtml + "</a>";
|
||||
$(td).html(innerHtml);
|
||||
}},
|
||||
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
|
@ -203,240 +182,25 @@ function initTable() {
|
|||
asset_table = jumpserver.initServerSideDataTable(options);
|
||||
return asset_table
|
||||
}
|
||||
|
||||
function addTreeNode() {
|
||||
hideRMenu();
|
||||
var parentNode = zTree.getSelectedNodes()[0];
|
||||
if (!parentNode){
|
||||
return
|
||||
}
|
||||
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.meta.node.id);
|
||||
$.post(url, {}, function (data, status){
|
||||
if (status === "success") {
|
||||
var newNode = {
|
||||
id: data["key"],
|
||||
name: data["value"],
|
||||
pId: parentNode.id,
|
||||
meta: {
|
||||
"node": data
|
||||
}
|
||||
};
|
||||
newNode.checked = zTree.getSelectedNodes()[0].checked;
|
||||
zTree.addNodes(parentNode, 0, newNode);
|
||||
var node = zTree.getNodeByParam('id', newNode.id, parentNode);
|
||||
zTree.editName(node);
|
||||
} else {
|
||||
alert("{% trans 'Create node failed' %}")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeTreeNode() {
|
||||
hideRMenu();
|
||||
var current_node = zTree.getSelectedNodes()[0];
|
||||
if (!current_node){
|
||||
return
|
||||
}
|
||||
if (current_node.children && current_node.children.length > 0) {
|
||||
toastr.error("{% trans 'Have child node, cancel' %}");
|
||||
} else if (current_node.meta.node.assets_amount !== 0) {
|
||||
toastr.error("{% trans 'Have assets, cancel' %}");
|
||||
} else {
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: "DELETE",
|
||||
success: function () {
|
||||
zTree.removeNode(current_node);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function editTreeNode() {
|
||||
hideRMenu();
|
||||
var current_node = zTree.getSelectedNodes()[0];
|
||||
if (!current_node){
|
||||
return
|
||||
}
|
||||
if (current_node) {
|
||||
current_node.name = current_node.meta.node.value;
|
||||
}
|
||||
zTree.editName(current_node);
|
||||
}
|
||||
|
||||
function OnRightClick(event, treeId, treeNode) {
|
||||
if (!treeNode && event.target.tagName.toLowerCase() !== "button" && $(event.target).parents("a").length === 0) {
|
||||
zTree.cancelSelectedNode();
|
||||
showRMenu("root", event.clientX, event.clientY);
|
||||
} else if (treeNode && !treeNode.noR) {
|
||||
zTree.selectNode(treeNode);
|
||||
showRMenu("node", event.clientX, event.clientY);
|
||||
}
|
||||
}
|
||||
|
||||
function showRMenu(type, x, y) {
|
||||
$("#rMenu ul").show();
|
||||
x -= 220;
|
||||
x += document.body.scrollLeft;
|
||||
y += document.body.scrollTop+document.documentElement.scrollTop;
|
||||
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
|
||||
|
||||
$("body").bind("mousedown", onBodyMouseDown);
|
||||
}
|
||||
|
||||
function beforeClick(treeId, treeNode, clickFlag) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function hideRMenu() {
|
||||
if (rMenu) rMenu.css({"visibility": "hidden"});
|
||||
$("body").unbind("mousedown", onBodyMouseDown);
|
||||
}
|
||||
|
||||
function onBodyMouseDown(event){
|
||||
if (!(event.target.id === "rMenu" || $(event.target).parents("#rMenu").length>0)) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onRename(event, treeId, treeNode, isCancel){
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
var data = {"value": treeNode.name};
|
||||
if (isCancel){
|
||||
return
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
body: JSON.stringify(data),
|
||||
method: "PATCH",
|
||||
success_message: "{% trans 'Rename success' %}",
|
||||
fail_message: "{% trans 'Rename failed, do not change the root node name' %}",
|
||||
success: function () {
|
||||
treeNode.name = treeNode.name + ' (' + treeNode.meta.node.assets_amount + ')'
|
||||
zTree.updateNode(treeNode);
|
||||
console.log("Success: " + treeNode.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onSelected(event, treeNode) {
|
||||
current_node = treeNode;
|
||||
current_node_id = treeNode.meta.node.id;
|
||||
zTree.expandNode(current_node, true);
|
||||
var url = asset_table.ajax.url();
|
||||
url = setUrlParam(url, "node_id", current_node_id);
|
||||
url = setUrlParam(url, "show_current_asset", getCookie('show_current_asset'));
|
||||
setCookie('node_selected', treeNode.node_id);
|
||||
asset_table.ajax.url(url);
|
||||
asset_table.ajax.reload();
|
||||
}
|
||||
|
||||
function selectQueryNode() {
|
||||
// TODO: 是否应该添加
|
||||
// 暂时忽略之前选中的内容
|
||||
return
|
||||
var query_node_id = $.getUrlParam("node");
|
||||
var cookie_node_id = getCookie('node_selected');
|
||||
var node;
|
||||
var node_id;
|
||||
|
||||
if (query_node_id !== null) {
|
||||
node_id = query_node_id
|
||||
} else if (cookie_node_id !== null) {
|
||||
node_id = cookie_node_id;
|
||||
}
|
||||
|
||||
node = zTree.getNodesByParam("node_id", node_id, null);
|
||||
if (node){
|
||||
zTree.selectNode(node[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function beforeDrag() {
|
||||
return true
|
||||
}
|
||||
|
||||
function beforeDrop(treeId, treeNodes, targetNode, moveType) {
|
||||
var treeNodesNames = [];
|
||||
$.each(treeNodes, function (index, value) {
|
||||
treeNodesNames.push(value.name);
|
||||
});
|
||||
|
||||
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.name + "` 下吗?";
|
||||
return confirm(msg);
|
||||
}
|
||||
|
||||
function onDrag(event, treeId, treeNodes) {
|
||||
}
|
||||
|
||||
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
|
||||
var treeNodesIds = [];
|
||||
$.each(treeNodes, function (index, value) {
|
||||
treeNodesIds.push(value.meta.node.id);
|
||||
});
|
||||
|
||||
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.meta.node.id);
|
||||
var body = {nodes: treeNodesIds};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: "PUT",
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
}
|
||||
|
||||
function initTree() {
|
||||
if (zTree) {
|
||||
return
|
||||
}
|
||||
var url = '{% url 'api-assets:node-children-tree' %}?assets=0&all=';
|
||||
var showCurrentAsset = getCookie('show_current_asset');
|
||||
if (!showCurrentAsset) {
|
||||
url += '1'
|
||||
}
|
||||
var setting = {
|
||||
view: {
|
||||
dblClickExpand: false,
|
||||
showLine: true
|
||||
},
|
||||
data: {
|
||||
simpleData: {
|
||||
enable: true
|
||||
}
|
||||
},
|
||||
async: {
|
||||
enable: true,
|
||||
url: url,
|
||||
autoParam: ["id=key", "name=n", "level=lv"],
|
||||
type: 'get'
|
||||
},
|
||||
edit: {
|
||||
enable: true,
|
||||
showRemoveBtn: false,
|
||||
showRenameBtn: false,
|
||||
drag: {
|
||||
isCopy: true,
|
||||
isMove: true
|
||||
}
|
||||
},
|
||||
callback: {
|
||||
onRightClick: OnRightClick,
|
||||
beforeClick: beforeClick,
|
||||
onRename: onRename,
|
||||
onSelected: onSelected,
|
||||
beforeDrag: beforeDrag,
|
||||
onDrag: onDrag,
|
||||
beforeDrop: beforeDrop,
|
||||
onDrop: onDrop
|
||||
}
|
||||
};
|
||||
|
||||
var zNodes = [];
|
||||
zTree = $.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
rMenu = $("#rMenu");
|
||||
initNodeTree({
|
||||
onSelected: onNodeSelected,
|
||||
showMenu: true,
|
||||
otherMenu: `
|
||||
<li class="divider"></li>
|
||||
<li id="menu_asset_add" class="btn-add-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a><i class="fa fa-copy"></i> {% trans 'Add assets to node' %}</a></li>
|
||||
<li id="menu_asset_move" class="btn-move-asset" data-toggle="modal" data-target="#asset_list_modal" tabindex="0"><a><i class="fa fa-cut"></i> {% trans 'Move assets to node' %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="menu_refresh_hardware_info" class="btn-refresh-hardware" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh node hardware info' %}</a></li>
|
||||
<li id="menu_test_connective" class="btn-test-connective" tabindex="-1"><a><i class="fa fa-chain"></i> {% trans 'Test node connective' %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="show_current_asset" class="btn-show-current-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-hand-o-up"></i> {% trans 'Display only current node assets' %}</a></li>
|
||||
<li id="show_all_asset" class="btn-show-all-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-th"></i> {% trans 'Displays all child node assets' %}</a></li>
|
||||
`
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function toggle() {
|
||||
if (show === 0) {
|
||||
$("#split-left").hide(500, function () {
|
||||
|
@ -452,6 +216,18 @@ function toggle() {
|
|||
}
|
||||
}
|
||||
|
||||
function onNodeSelected(event, treeNode) {
|
||||
current_node = treeNode;
|
||||
current_node_id = treeNode.meta.node.id;
|
||||
zTree.expandNode(current_node, true);
|
||||
var url = asset_table.ajax.url();
|
||||
url = setUrlParam(url, "node_id", current_node_id);
|
||||
url = setUrlParam(url, "show_current_asset", getCookie('show_current_asset'));
|
||||
setCookie('node_selected', treeNode.node_id);
|
||||
asset_table.ajax.url(url);
|
||||
asset_table.ajax.reload();
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
initTree();
|
||||
|
@ -549,69 +325,7 @@ $(document).ready(function(){
|
|||
}
|
||||
window.open(url, '_self');
|
||||
})
|
||||
.on('click', '.btn-refresh-hardware', function () {
|
||||
var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}";
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
function success(data) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600')
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: "GET",
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}";
|
||||
if (!current_node_id) {
|
||||
return null;
|
||||
}
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
function success(data) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600')
|
||||
}
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: "GET",
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-show-current-asset', function(){
|
||||
hideRMenu();
|
||||
$(this).css('display', 'none');
|
||||
$('#show_all_asset').css('display', 'inline-block');
|
||||
setCookie('show_current_asset', '1');
|
||||
location.reload();
|
||||
})
|
||||
.on('click', '.btn-show-all-asset', function(){
|
||||
hideRMenu();
|
||||
$(this).css('display', 'none');
|
||||
$('#show_current_asset').css('display', 'inline-block');
|
||||
setCookie('show_current_asset', '');
|
||||
location.reload();
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
hideRMenu();
|
||||
|
||||
})
|
||||
.on('click', '#menu_refresh_assets_amount', function () {
|
||||
hideRMenu();
|
||||
var url = "{% url 'api-assets:refresh-assets-amount' %}";
|
||||
APIUpdateAttr({
|
||||
'url': url,
|
||||
'method': 'GET'
|
||||
});
|
||||
window.location.reload();
|
||||
})
|
||||
.on('click', '.btn_asset_delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $("#asset_list_table").DataTable();
|
||||
|
|
|
@ -169,40 +169,6 @@ $(document).ready(function () {
|
|||
initAssetUserTable();
|
||||
|
||||
})
|
||||
.on('click', '.btn-push', function () {
|
||||
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
error: error,
|
||||
method: 'GET',
|
||||
success: success
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
error: error,
|
||||
method: 'GET',
|
||||
success: success
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-remove-from-node', function() {
|
||||
var $this = $(this);
|
||||
var $tr = $this.closest('tr');
|
||||
|
@ -230,6 +196,23 @@ $(document).ready(function () {
|
|||
});
|
||||
updateSystemUserNode(nodes);
|
||||
})
|
||||
.on('click', '.btn-push', function () {
|
||||
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
error: error,
|
||||
method: 'GET',
|
||||
success: success
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-push-auth', function () {
|
||||
var $this = $(this);
|
||||
var asset_id = $this.data('asset');
|
||||
|
@ -250,6 +233,23 @@ $(document).ready(function () {
|
|||
error: error
|
||||
})
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
error: error,
|
||||
method: 'GET',
|
||||
success: success
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
</script>
|
|
@ -15,27 +15,27 @@
|
|||
|
||||
{% block table_search %}
|
||||
<div class="" style="float: right">
|
||||
<div class=" btn-group">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class=" btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
||||
<span>{% trans "Update" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class=" btn-group">
|
||||
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class=" btn_export" tabindex="0">
|
||||
<span>{% trans "Export" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
|
||||
<span>{% trans "Import" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
|
||||
<span>{% trans "Update" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block table_container %}
|
||||
|
@ -46,7 +46,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all">
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Username' %}</th>
|
||||
|
@ -80,28 +80,30 @@ function initTable() {
|
|||
}},
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
var innerHtml = "";
|
||||
if (cellData !== 0) {
|
||||
innerHtml = "<span class='text-navy'>" + cellData + "</span>";
|
||||
var data = cellData.reachable;
|
||||
if (data !== 0) {
|
||||
innerHtml = "<span class='text-navy'>" + data + "</span>";
|
||||
} else {
|
||||
innerHtml = "<span>" + cellData + "</span>";
|
||||
innerHtml = "<span>" + data + "</span>";
|
||||
}
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData +'">' + innerHtml + '</span>');
|
||||
$(td).html(innerHtml)
|
||||
}},
|
||||
{targets: 7, createdCell: function (td, cellData) {
|
||||
var data = cellData.unreachable;
|
||||
var innerHtml = "";
|
||||
if (cellData !== 0) {
|
||||
innerHtml = "<span class='text-danger'>" + cellData + "</span>";
|
||||
if (data !== 0) {
|
||||
innerHtml = "<span class='text-danger'>" + data + "</span>";
|
||||
} else {
|
||||
innerHtml = "<span>" + cellData + "</span>";
|
||||
innerHtml = "<span>" + data + "</span>";
|
||||
}
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');
|
||||
}},
|
||||
{targets: 8, createdCell: function (td, cellData, rowData) {
|
||||
var val = 0;
|
||||
var innerHtml = "";
|
||||
var total = rowData.assets_amount;
|
||||
var reachable = rowData.reachable_amount;
|
||||
if (total !== 0) {
|
||||
var reachable = cellData.reachable;
|
||||
if (total && total !== 0) {
|
||||
val = reachable/total * 100;
|
||||
}
|
||||
|
||||
|
@ -112,20 +114,20 @@ function initTable() {
|
|||
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";
|
||||
}
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
|
||||
}},
|
||||
{targets: 10, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}],
|
||||
}},
|
||||
],
|
||||
ajax_url: '{% url "api-assets:system-user-list" %}',
|
||||
columns: [
|
||||
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "login_mode_display"}, {data: "assets_amount" },
|
||||
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }
|
||||
{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "comment" }, {data: "id" }
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
};
|
||||
system_user_table = jumpserver.initServerSideDataTable(options);
|
||||
return system_user_table
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@ urlpatterns = [
|
|||
path('', views.AssetListView.as_view(), name='asset-index'),
|
||||
path('asset/', views.AssetListView.as_view(), name='asset-list'),
|
||||
path('asset/create/', views.AssetCreateView.as_view(), name='asset-create'),
|
||||
path('asset/export/', views.AssetExportView.as_view(), name='asset-export'),
|
||||
path('asset/import/', views.BulkImportAssetView.as_view(), name='asset-import'),
|
||||
path('asset/<uuid:pk>/', views.AssetDetailView.as_view(), name='asset-detail'),
|
||||
path('asset/<uuid:pk>/update/', views.AssetUpdateView.as_view(), name='asset-update'),
|
||||
path('asset/<uuid:pk>/delete/', views.AssetDeleteView.as_view(), name='asset-delete'),
|
||||
|
|
|
@ -1,23 +1,14 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
import os
|
||||
import paramiko
|
||||
from paramiko.ssh_exception import SSHException
|
||||
import time
|
||||
from django.db.models import Prefetch
|
||||
|
||||
from common.utils import get_object_or_none
|
||||
from .models import Asset, SystemUser, Label
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
from common.struct import Stack
|
||||
from .models import SystemUser, Label, Node, Asset
|
||||
|
||||
|
||||
def get_assets_by_id_list(id_list):
|
||||
return Asset.objects.filter(id__in=id_list).filter(is_active=True)
|
||||
|
||||
|
||||
def get_system_users_by_id_list(id_list):
|
||||
return SystemUser.objects.filter(id__in=id_list)
|
||||
|
||||
|
||||
def get_assets_by_fullname_list(hostname_list):
|
||||
return Asset.get_queryset_by_fullname_list(hostname_list)
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
def get_system_user_by_name(name):
|
||||
|
@ -49,3 +40,166 @@ class LabelFilter:
|
|||
for kwargs in conditions:
|
||||
queryset = queryset.filter(**kwargs)
|
||||
return queryset
|
||||
|
||||
|
||||
class NodeUtil:
|
||||
def __init__(self, with_assets_amount=False, debug=False):
|
||||
self.stack = Stack()
|
||||
self._nodes = {}
|
||||
self.with_assets_amount = with_assets_amount
|
||||
self._debug = debug
|
||||
self.init()
|
||||
|
||||
@staticmethod
|
||||
def sorted_by(node):
|
||||
return [int(i) for i in node.key.split(':')]
|
||||
|
||||
def get_queryset(self):
|
||||
all_nodes = Node.objects.all()
|
||||
if self.with_assets_amount:
|
||||
all_nodes = all_nodes.prefetch_related(
|
||||
Prefetch('assets', queryset=Asset.objects.all().only('id'))
|
||||
)
|
||||
all_nodes = list(all_nodes)
|
||||
for node in all_nodes:
|
||||
node._assets = set(node.assets.all())
|
||||
return all_nodes
|
||||
|
||||
def get_all_nodes(self):
|
||||
all_nodes = sorted(self.get_queryset(), key=self.sorted_by)
|
||||
|
||||
guarder = Node(key='', value='Guarder')
|
||||
guarder._assets = []
|
||||
all_nodes.append(guarder)
|
||||
return all_nodes
|
||||
|
||||
def push_to_stack(self, node):
|
||||
# 入栈之前检查
|
||||
# 如果栈是空的,证明是一颗树的根部
|
||||
if self.stack.is_empty():
|
||||
node._full_value = node.value
|
||||
node._parents = []
|
||||
else:
|
||||
# 如果不是根节点,
|
||||
# 该节点的祖先应该是父节点的祖先加上父节点
|
||||
# 该节点的名字是父节点的名字+自己的名字
|
||||
node._parents = [self.stack.top] + self.stack.top._parents
|
||||
node._full_value = ' / '.join(
|
||||
[self.stack.top._full_value, node.value]
|
||||
)
|
||||
node._children = []
|
||||
node._all_children = []
|
||||
self.debug("入栈: {}".format(node.key))
|
||||
self.stack.push(node)
|
||||
|
||||
# 出栈
|
||||
def pop_from_stack(self):
|
||||
_node = self.stack.pop()
|
||||
self.debug("出栈: {} 栈顶: {}".format(_node.key, self.stack.top.key if self.stack.top else None))
|
||||
self._nodes[_node.key] = _node
|
||||
if not self.stack.top:
|
||||
return
|
||||
if self.with_assets_amount:
|
||||
self.stack.top._assets.update(_node._assets)
|
||||
_node._assets_amount = len(_node._assets)
|
||||
delattr(_node, '_assets')
|
||||
self.stack.top._children.append(_node)
|
||||
self.stack.top._all_children.extend([_node] + _node._children)
|
||||
|
||||
def init(self):
|
||||
all_nodes = self.get_all_nodes()
|
||||
for node in all_nodes:
|
||||
self.debug("准备: {} 栈顶: {}".format(node.key, self.stack.top.key if self.stack.top else None))
|
||||
# 入栈之前检查,该节点是不是栈顶节点的子节点
|
||||
# 如果不是,则栈顶出栈
|
||||
while self.stack.top and not self.stack.top.is_children(node):
|
||||
self.pop_from_stack()
|
||||
self.push_to_stack(node)
|
||||
# 出栈最后一个
|
||||
self.debug("剩余: {}".format(', '.join([n.key for n in self.stack])))
|
||||
|
||||
def get_nodes_by_queryset(self, queryset):
|
||||
nodes = []
|
||||
for n in queryset:
|
||||
node = self.get_node_by_key(n.key)
|
||||
if not node:
|
||||
continue
|
||||
nodes.append(node)
|
||||
return nodes
|
||||
|
||||
def get_node_by_key(self, key):
|
||||
return self._nodes.get(key)
|
||||
|
||||
def debug(self, msg):
|
||||
self._debug and logger.debug(msg)
|
||||
|
||||
def set_assets_amount(self):
|
||||
for node in self._nodes.values():
|
||||
node.assets_amount = node._assets_amount
|
||||
|
||||
def set_full_value(self):
|
||||
for node in self._nodes.values():
|
||||
node.full_value = node._full_value
|
||||
|
||||
@property
|
||||
def nodes(self):
|
||||
return list(self._nodes.values())
|
||||
|
||||
# 使用给定节点生成一颗树
|
||||
# 找到他们的祖先节点
|
||||
# 可选找到他们的子孙节点
|
||||
def get_family(self, nodes, with_children=False):
|
||||
tree_nodes = set()
|
||||
for n in nodes:
|
||||
node = self.get_node_by_key(n.key)
|
||||
if not node:
|
||||
continue
|
||||
tree_nodes.update(node._parents)
|
||||
tree_nodes.add(node)
|
||||
if with_children:
|
||||
tree_nodes.update(node._children)
|
||||
return list(tree_nodes)
|
||||
|
||||
def get_nodes_parents(self, nodes, with_self=True):
|
||||
parents = set()
|
||||
for n in nodes:
|
||||
node = self.get_node_by_key(n.key)
|
||||
parents.update(set(node._parents))
|
||||
if with_self:
|
||||
parents.add(node)
|
||||
return parents
|
||||
|
||||
|
||||
def test_node_tree():
|
||||
tree = NodeUtil()
|
||||
for node in tree._nodes.values():
|
||||
print("Check {}".format(node.key))
|
||||
children_wanted = node.get_all_children().count()
|
||||
children = len(node._children)
|
||||
if children != children_wanted:
|
||||
print("{} children not equal: {} != {}".format(node.key, children, children_wanted))
|
||||
|
||||
assets_amount_wanted = node.get_all_assets().count()
|
||||
if node._assets_amount != assets_amount_wanted:
|
||||
print("{} assets amount not equal: {} != {}".format(
|
||||
node.key, node._assets_amount, assets_amount_wanted)
|
||||
)
|
||||
|
||||
full_value_wanted = node.full_value
|
||||
if node._full_value != full_value_wanted:
|
||||
print("{} full value not equal: {} != {}".format(
|
||||
node.key, node._full_value, full_value_wanted)
|
||||
)
|
||||
|
||||
parents_wanted = node.get_ancestor().count()
|
||||
parents = len(node._parents)
|
||||
if parents != parents_wanted:
|
||||
print("{} parents count not equal: {} != {}".format(
|
||||
node.key, parents, parents_wanted)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ class AdminUserDetailView(PermissionsMixin, DetailView):
|
|||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Admin user detail'),
|
||||
'nodes': Node.objects.all()
|
||||
'nodes': Node.get_queryset(),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
@ -106,8 +106,6 @@ class AdminUserAssetsView(PermissionsMixin, SingleObjectMixin, ListView):
|
|||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Admin user detail'),
|
||||
"total_amount": len(self.queryset),
|
||||
'unreachable_amount': len([asset for asset in self.queryset if asset.connectivity is False])
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
|
|
@ -1,43 +1,31 @@
|
|||
# coding:utf-8
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import csv
|
||||
import json
|
||||
import uuid
|
||||
import codecs
|
||||
import chardet
|
||||
from io import StringIO
|
||||
|
||||
from django.db import transaction
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import TemplateView, ListView, View
|
||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||
from django.views.generic import TemplateView, ListView
|
||||
from django.views.generic.edit import FormMixin
|
||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.core.cache import cache
|
||||
from django.utils import timezone
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.forms.formsets import formset_factory
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser
|
||||
from common.const import (
|
||||
create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID
|
||||
)
|
||||
from .. import forms
|
||||
from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
|
||||
from ..models import Asset, SystemUser, Label, Node
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetListView', 'AssetCreateView', 'AssetUpdateView', 'AssetUserListView',
|
||||
'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView',
|
||||
'AssetDeleteView', 'AssetExportView', 'BulkImportAssetView',
|
||||
'AssetDeleteView',
|
||||
]
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
@ -87,7 +75,7 @@ class UserAssetListView(PermissionsMixin, TemplateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
|
||||
class AssetCreateView(PermissionsMixin, FormMixin, TemplateView):
|
||||
model = Asset
|
||||
form_class = forms.AssetCreateForm
|
||||
template_name = 'assets/asset_create.html'
|
||||
|
@ -112,16 +100,6 @@ class AssetCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
|
|||
formset = ProtocolFormset()
|
||||
return formset
|
||||
|
||||
def form_valid(self, form):
|
||||
formset = self.get_protocol_formset()
|
||||
valid = formset.is_valid()
|
||||
if not valid:
|
||||
return self.form_invalid(form)
|
||||
protocols = formset.save()
|
||||
instance = super().form_valid(form)
|
||||
instance.protocols.set(protocols)
|
||||
return instance
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
formset = self.get_protocol_formset()
|
||||
context = {
|
||||
|
@ -132,8 +110,32 @@ class AssetCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
|
|||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
return create_success_msg % ({"name": cleaned_data["hostname"]})
|
||||
|
||||
class AssetUpdateView(PermissionsMixin, UpdateView):
|
||||
model = Asset
|
||||
form_class = forms.AssetUpdateForm
|
||||
template_name = 'assets/asset_update.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_protocol_formset(self):
|
||||
ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5)
|
||||
if self.request.method == "POST":
|
||||
formset = ProtocolFormset(self.request.POST)
|
||||
else:
|
||||
initial_data = self.object.protocols_as_json
|
||||
formset = ProtocolFormset(initial=initial_data)
|
||||
return formset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
formset = self.get_protocol_formset()
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Update asset'),
|
||||
'formset': formset,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetBulkUpdateView(PermissionsMixin, ListView):
|
||||
|
@ -177,36 +179,6 @@ class AssetBulkUpdateView(PermissionsMixin, ListView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
|
||||
model = Asset
|
||||
form_class = forms.AssetUpdateForm
|
||||
template_name = 'assets/asset_update.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_protocol_formset(self):
|
||||
ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5)
|
||||
if self.request.method == "POST":
|
||||
formset = ProtocolFormset(self.request.POST)
|
||||
else:
|
||||
initial_data = [{"name": p.name, "port": p.port} for p in self.object.protocols.all()]
|
||||
formset = ProtocolFormset(initial=initial_data)
|
||||
return formset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
formset = self.get_protocol_formset()
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Update asset'),
|
||||
'formset': formset,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
return update_success_msg % ({"name": cleaned_data["hostname"]})
|
||||
|
||||
|
||||
class AssetDeleteView(PermissionsMixin, DeleteView):
|
||||
model = Asset
|
||||
template_name = 'delete_confirm.html'
|
||||
|
@ -220,6 +192,11 @@ class AssetDetailView(PermissionsMixin, DetailView):
|
|||
template_name = 'assets/asset_detail.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().prefetch_related(
|
||||
"nodes", "labels",
|
||||
).select_related('admin_user', 'domain')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
nodes_remain = Node.objects.exclude(assets=self.object)
|
||||
context = {
|
||||
|
@ -229,150 +206,3 @@ class AssetDetailView(PermissionsMixin, DetailView):
|
|||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class AssetExportView(PermissionsMixin, View):
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get(self, request):
|
||||
spm = request.GET.get('spm', '')
|
||||
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else []
|
||||
assets_id = cache.get(spm, assets_id_default)
|
||||
fields = [
|
||||
field for field in Asset._meta.fields
|
||||
if field.name not in [
|
||||
'date_created', 'org_id'
|
||||
]
|
||||
]
|
||||
filename = 'assets-{}.csv'.format(
|
||||
timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S')
|
||||
)
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
|
||||
response.write(codecs.BOM_UTF8)
|
||||
assets = Asset.objects.filter(id__in=assets_id)
|
||||
writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL)
|
||||
|
||||
header = [field.verbose_name for field in fields]
|
||||
writer.writerow(header)
|
||||
|
||||
for asset in assets:
|
||||
data = [getattr(asset, field.name) for field in fields]
|
||||
writer.writerow(data)
|
||||
return response
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
try:
|
||||
assets_id = json.loads(request.body).get('assets_id', [])
|
||||
node_id = json.loads(request.body).get('node_id', None)
|
||||
except ValueError:
|
||||
return HttpResponse('Json object not valid', status=400)
|
||||
|
||||
if not assets_id:
|
||||
node = get_object_or_none(Node, id=node_id) if node_id else Node.root()
|
||||
assets = node.get_all_assets()
|
||||
for asset in assets:
|
||||
assets_id.append(asset.id)
|
||||
|
||||
spm = uuid.uuid4().hex
|
||||
cache.set(spm, assets_id, 300)
|
||||
url = reverse_lazy('assets:asset-export') + '?spm=%s' % spm
|
||||
return JsonResponse({'redirect': url})
|
||||
|
||||
|
||||
class BulkImportAssetView(PermissionsMixin, JSONResponseMixin, FormView):
|
||||
form_class = forms.FileForm
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def form_valid(self, form):
|
||||
node_id = self.request.GET.get("node_id")
|
||||
node = get_object_or_none(Node, id=node_id) if node_id else Node.root()
|
||||
f = form.cleaned_data['file']
|
||||
det_result = chardet.detect(f.read())
|
||||
f.seek(0) # reset file seek index
|
||||
|
||||
file_data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode())
|
||||
csv_file = StringIO(file_data)
|
||||
reader = csv.reader(csv_file)
|
||||
csv_data = [row for row in reader]
|
||||
fields = [
|
||||
field for field in Asset._meta.fields
|
||||
if field.name not in [
|
||||
'date_created'
|
||||
]
|
||||
]
|
||||
header_ = csv_data[0]
|
||||
mapping_reverse = {field.verbose_name: field.name for field in fields}
|
||||
attr = [mapping_reverse.get(n, None) for n in header_]
|
||||
if None in attr:
|
||||
data = {'valid': False,
|
||||
'msg': 'Must be same format as '
|
||||
'template or export file'}
|
||||
return self.render_json_response(data)
|
||||
|
||||
created, updated, failed = [], [], []
|
||||
assets = []
|
||||
for row in csv_data[1:]:
|
||||
if set(row) == {''}:
|
||||
continue
|
||||
|
||||
asset_dict_raw = dict(zip(attr, row))
|
||||
asset_dict = dict()
|
||||
for k, v in asset_dict_raw.items():
|
||||
v = v.strip()
|
||||
if k == 'is_active':
|
||||
v = False if v in ['False', 0, 'false'] else True
|
||||
elif k == 'admin_user':
|
||||
v = get_object_or_none(AdminUser, name=v)
|
||||
elif k in ['port', 'cpu_count', 'cpu_cores']:
|
||||
try:
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
v = ''
|
||||
elif k == 'domain':
|
||||
v = get_object_or_none(Domain, name=v)
|
||||
elif k == 'platform':
|
||||
v = v.lower().capitalize()
|
||||
if v != '':
|
||||
asset_dict[k] = v
|
||||
|
||||
asset = None
|
||||
asset_id = asset_dict.pop('id', None)
|
||||
if asset_id:
|
||||
asset = get_object_or_none(Asset, id=asset_id)
|
||||
if not asset:
|
||||
try:
|
||||
if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))):
|
||||
raise Exception(_('already exists'))
|
||||
with transaction.atomic():
|
||||
asset = Asset.objects.create(**asset_dict)
|
||||
if node:
|
||||
asset.nodes.set([node])
|
||||
created.append(asset_dict['hostname'])
|
||||
assets.append(asset)
|
||||
except Exception as e:
|
||||
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
|
||||
else:
|
||||
for k, v in asset_dict.items():
|
||||
if v != '':
|
||||
setattr(asset, k, v)
|
||||
try:
|
||||
asset.save()
|
||||
updated.append(asset_dict['hostname'])
|
||||
except Exception as e:
|
||||
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
|
||||
|
||||
data = {
|
||||
'created': created,
|
||||
'created_info': 'Created {}'.format(len(created)),
|
||||
'updated': updated,
|
||||
'updated_info': 'Updated {}'.format(len(updated)),
|
||||
'failed': failed,
|
||||
'failed_info': 'Failed {}'.format(len(failed)),
|
||||
'valid': True,
|
||||
'msg': 'Created: {}. Updated: {}, Error: {}'.format(
|
||||
len(created), len(updated), len(failed))
|
||||
}
|
||||
return self.render_json_response(data)
|
||||
|
||||
|
|
|
@ -74,10 +74,11 @@ class SystemUserDetailView(PermissionsMixin, DetailView):
|
|||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
cmd_filters_remain = CommandFilter.objects.exclude(system_users=self.object)
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('System user detail'),
|
||||
'cmd_filters_remain': CommandFilter.objects.exclude(system_users=self.object)
|
||||
'cmd_filters_remain': cmd_filters_remain,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
@ -92,12 +93,15 @@ class SystemUserDeleteView(PermissionsMixin, DeleteView):
|
|||
|
||||
class SystemUserAssetView(PermissionsMixin, DetailView):
|
||||
model = SystemUser
|
||||
template_name = 'assets/system_user_asset.html'
|
||||
template_name = 'assets/system_user_assets.html'
|
||||
context_object_name = 'system_user'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
nodes_remain = sorted(Node.objects.exclude(systemuser=self.object), reverse=True)
|
||||
from ..utils import NodeUtil
|
||||
nodes_remain = Node.objects.exclude(systemuser=self.object)
|
||||
util = NodeUtil()
|
||||
nodes_remain = util.get_nodes_by_queryset(nodes_remain)
|
||||
context = {
|
||||
'app': _('assets'),
|
||||
'action': _('System user asset'),
|
||||
|
|
|
@ -194,7 +194,7 @@ class UserOtpVerifyApi(CreateAPIView):
|
|||
code = serializer.validated_data["code"]
|
||||
|
||||
if request.user.check_otp(code):
|
||||
request.session["OTP_LAST_VERIFY_TIME"] = int(time.time())
|
||||
request.session["MFA_VERIFY_TIME"] = int(time.time())
|
||||
return Response({"ok": "1"})
|
||||
else:
|
||||
return Response({"error": "Code not valid"}, status=400)
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
from .backends import *
|
||||
from .middleware import *
|
||||
from .utils import *
|
||||
from .decorator import *
|
||||
|
|
|
@ -20,7 +20,6 @@ __all__ = [
|
|||
|
||||
|
||||
class BaseOpenIDAuthorizationBackend(object):
|
||||
|
||||
@staticmethod
|
||||
def user_can_authenticate(user):
|
||||
"""
|
||||
|
@ -40,25 +39,20 @@ class BaseOpenIDAuthorizationBackend(object):
|
|||
|
||||
|
||||
class OpenIDAuthorizationCodeBackend(BaseOpenIDAuthorizationBackend):
|
||||
|
||||
def authenticate(self, request, **kwargs):
|
||||
logger.info('Authentication OpenID code backend')
|
||||
|
||||
code = kwargs.get('code')
|
||||
redirect_uri = kwargs.get('redirect_uri')
|
||||
|
||||
if not code or not redirect_uri:
|
||||
logger.info('Authenticate failed: No code or No redirect uri')
|
||||
return None
|
||||
|
||||
try:
|
||||
oidt_profile = client.update_or_create_from_code(
|
||||
code=code, redirect_uri=redirect_uri
|
||||
code=code, redirect_uri=redirect_uri
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.info('Authenticate failed: get oidt_profile: {}'.format(e))
|
||||
|
||||
return None
|
||||
else:
|
||||
# Check openid user single logout or not with access_token
|
||||
request.session[OIDT_ACCESS_TOKEN] = oidt_profile.access_token
|
||||
|
@ -68,25 +62,19 @@ class OpenIDAuthorizationCodeBackend(BaseOpenIDAuthorizationBackend):
|
|||
|
||||
|
||||
class OpenIDAuthorizationPasswordBackend(BaseOpenIDAuthorizationBackend):
|
||||
|
||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||
logger.info('Authentication OpenID password backend')
|
||||
|
||||
if not settings.AUTH_OPENID:
|
||||
logger.info('Authenticate failed: AUTH_OPENID is False')
|
||||
return None
|
||||
elif not username:
|
||||
if not username:
|
||||
logger.info('Authenticate failed: Not username')
|
||||
return None
|
||||
|
||||
try:
|
||||
oidt_profile = client.update_or_create_from_password(
|
||||
username=username, password=password
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
logger.info('Authenticate failed: get oidt_profile: {}'.format(e))
|
||||
|
||||
return None
|
||||
else:
|
||||
user = oidt_profile.user
|
||||
logger.info('Authenticate success: user -> {}'.format(user))
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
|
||||
import warnings
|
||||
import contextlib
|
||||
|
||||
import requests
|
||||
from urllib3.exceptions import InsecureRequestWarning
|
||||
from django.conf import settings
|
||||
|
||||
__all__ = [
|
||||
'ssl_verification',
|
||||
]
|
||||
|
||||
old_merge_environment_settings = requests.Session.merge_environment_settings
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def no_ssl_verification():
|
||||
"""
|
||||
https://stackoverflow.com/questions/15445981/
|
||||
how-do-i-disable-the-security-certificate-check-in-python-requests
|
||||
"""
|
||||
opened_adapters = set()
|
||||
|
||||
def merge_environment_settings(self, url, proxies, stream, verify, cert):
|
||||
# Verification happens only once per connection so we need to close
|
||||
# all the opened adapters once we're done. Otherwise, the effects of
|
||||
# verify=False persist beyond the end of this context manager.
|
||||
opened_adapters.add(self.get_adapter(url))
|
||||
_settings = old_merge_environment_settings(
|
||||
self, url, proxies, stream, verify, cert
|
||||
)
|
||||
_settings['verify'] = False
|
||||
return _settings
|
||||
|
||||
requests.Session.merge_environment_settings = merge_environment_settings
|
||||
try:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore', InsecureRequestWarning)
|
||||
yield
|
||||
finally:
|
||||
requests.Session.merge_environment_settings = old_merge_environment_settings
|
||||
for adapter in opened_adapters:
|
||||
try:
|
||||
adapter.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def ssl_verification(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
if not settings.AUTH_OPENID_IGNORE_SSL_VERIFICATION:
|
||||
return func(*args, **kwargs)
|
||||
with no_ssl_verification():
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
|
@ -19,24 +19,23 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin):
|
|||
"""
|
||||
Check openid user single logout (with access_token)
|
||||
"""
|
||||
|
||||
def process_request(self, request):
|
||||
# Don't need openid auth if AUTH_OPENID is False
|
||||
if not settings.AUTH_OPENID:
|
||||
return
|
||||
# Don't need openid auth if no shared session enabled
|
||||
if not settings.AUTH_OPENID_SHARE_SESSION:
|
||||
return
|
||||
# Don't need check single logout if user not authenticated
|
||||
if not request.user.is_authenticated:
|
||||
return
|
||||
elif not request.session[BACKEND_SESSION_KEY].endswith(
|
||||
BACKEND_OPENID_AUTH_CODE):
|
||||
return
|
||||
|
||||
# Check openid user single logout or not with access_token
|
||||
client = new_client()
|
||||
try:
|
||||
client.openid_connect_client.userinfo(
|
||||
token=request.session.get(OIDT_ACCESS_TOKEN)
|
||||
)
|
||||
client = new_client()
|
||||
client.get_userinfo(token=request.session.get(OIDT_ACCESS_TOKEN))
|
||||
except Exception as e:
|
||||
logout(request)
|
||||
logger.error(e)
|
||||
|
|
|
@ -7,12 +7,24 @@ from keycloak.realm import KeycloakRealm
|
|||
from keycloak.keycloak_openid import KeycloakOpenID
|
||||
|
||||
from .signals import post_create_openid_user
|
||||
from .decorator import ssl_verification
|
||||
|
||||
OIDT_ACCESS_TOKEN = 'oidt_access_token'
|
||||
|
||||
|
||||
class OpenIDTokenProfile(object):
|
||||
class Nonce(object):
|
||||
"""
|
||||
The openid-login is stored in cache as a temporary object, recording the
|
||||
user's redirect_uri and next_pat
|
||||
"""
|
||||
def __init__(self, redirect_uri, next_path):
|
||||
import uuid
|
||||
self.state = uuid.uuid4()
|
||||
self.redirect_uri = redirect_uri
|
||||
self.next_path = next_path
|
||||
|
||||
|
||||
class OpenIDTokenProfile(object):
|
||||
def __init__(self, user, access_token, refresh_token):
|
||||
"""
|
||||
:param user: User object
|
||||
|
@ -28,80 +40,109 @@ class OpenIDTokenProfile(object):
|
|||
|
||||
|
||||
class Client(object):
|
||||
|
||||
def __init__(self, server_url, realm_name, client_id, client_secret):
|
||||
self.server_url = server_url
|
||||
self.realm_name = realm_name
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.realm = self.new_realm()
|
||||
self.openid_client = self.new_openid_client()
|
||||
self.openid_connect_client = self.new_openid_connect_client()
|
||||
self._openid_client = None
|
||||
self._realm = None
|
||||
self._openid_connect_client = None
|
||||
|
||||
def new_realm(self):
|
||||
return KeycloakRealm(
|
||||
server_url=self.server_url,
|
||||
realm_name=self.realm_name,
|
||||
headers={}
|
||||
)
|
||||
@property
|
||||
def realm(self):
|
||||
if self._realm is None:
|
||||
self._realm = KeycloakRealm(
|
||||
server_url=self.server_url,
|
||||
realm_name=self.realm_name,
|
||||
headers={}
|
||||
)
|
||||
return self._realm
|
||||
|
||||
def new_openid_connect_client(self):
|
||||
@property
|
||||
def openid_connect_client(self):
|
||||
"""
|
||||
:rtype: keycloak.openid_connect.KeycloakOpenidConnect
|
||||
"""
|
||||
openid_connect = self.realm.open_id_connect(
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret
|
||||
)
|
||||
return openid_connect
|
||||
if self._openid_connect_client is None:
|
||||
self._openid_connect_client = self.realm.open_id_connect(
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret
|
||||
)
|
||||
return self._openid_connect_client
|
||||
|
||||
def new_openid_client(self):
|
||||
@property
|
||||
def openid_client(self):
|
||||
"""
|
||||
:rtype: keycloak.keycloak_openid.KeycloakOpenID
|
||||
"""
|
||||
if self._openid_client is None:
|
||||
self._openid_client = KeycloakOpenID(
|
||||
server_url='%sauth/' % self.server_url,
|
||||
realm_name=self.realm_name,
|
||||
client_id=self.client_id,
|
||||
client_secret_key=self.client_secret,
|
||||
)
|
||||
return self._openid_client
|
||||
|
||||
return KeycloakOpenID(
|
||||
server_url='%sauth/' % self.server_url,
|
||||
realm_name=self.realm_name,
|
||||
client_id=self.client_id,
|
||||
client_secret_key=self.client_secret,
|
||||
@ssl_verification
|
||||
def get_url(self, name):
|
||||
return self.openid_connect_client.get_url(name=name)
|
||||
|
||||
def get_url_end_session_endpoint(self):
|
||||
return self.get_url(name='end_session_endpoint')
|
||||
|
||||
@ssl_verification
|
||||
def get_authorization_url(self, redirect_uri, scope, state):
|
||||
url = self.openid_connect_client.authorization_url(
|
||||
redirect_uri=redirect_uri, scope=scope, state=state
|
||||
)
|
||||
return url
|
||||
|
||||
def update_or_create_from_password(self, username, password):
|
||||
"""
|
||||
Update or create an user based on an authentication username and password.
|
||||
@ssl_verification
|
||||
def get_userinfo(self, token):
|
||||
user_info = self.openid_connect_client.userinfo(token=token)
|
||||
return user_info
|
||||
|
||||
:param str username: authentication username
|
||||
:param str password: authentication password
|
||||
:return: OpenIDTokenProfile
|
||||
"""
|
||||
@ssl_verification
|
||||
def authorization_code(self, code, redirect_uri):
|
||||
token_response = self.openid_connect_client.authorization_code(
|
||||
code=code, redirect_uri=redirect_uri
|
||||
)
|
||||
return token_response
|
||||
|
||||
@ssl_verification
|
||||
def authorization_password(self, username, password):
|
||||
token_response = self.openid_client.token(
|
||||
username=username, password=password
|
||||
)
|
||||
|
||||
return self._update_or_create(token_response=token_response)
|
||||
return token_response
|
||||
|
||||
def update_or_create_from_code(self, code, redirect_uri):
|
||||
"""
|
||||
Update or create an user based on an authentication code.
|
||||
Response as specified in:
|
||||
|
||||
https://tools.ietf.org/html/rfc6749#section-4.1.4
|
||||
|
||||
:param str code: authentication code
|
||||
:param str redirect_uri:
|
||||
:rtype: OpenIDTokenProfile
|
||||
"""
|
||||
token_response = self.authorization_code(code, redirect_uri)
|
||||
return self._update_or_create(token_response=token_response)
|
||||
|
||||
token_response = self.openid_connect_client.authorization_code(
|
||||
code=code, redirect_uri=redirect_uri)
|
||||
|
||||
def update_or_create_from_password(self, username, password):
|
||||
"""
|
||||
Update or create an user based on an authentication username and password.
|
||||
:param str username: authentication username
|
||||
:param str password: authentication password
|
||||
:return: OpenIDTokenProfile
|
||||
"""
|
||||
token_response = self.authorization_password(username, password)
|
||||
return self._update_or_create(token_response=token_response)
|
||||
|
||||
def _update_or_create(self, token_response):
|
||||
"""
|
||||
Update or create an user based on a token response.
|
||||
|
||||
`token_response` contains the items returned by the OpenIDConnect Token API
|
||||
end-point:
|
||||
- id_token
|
||||
|
@ -109,14 +150,10 @@ class Client(object):
|
|||
- expires_in
|
||||
- refresh_token
|
||||
- refresh_expires_in
|
||||
|
||||
:param dict token_response:
|
||||
:rtype: OpenIDTokenProfile
|
||||
"""
|
||||
|
||||
userinfo = self.openid_connect_client.userinfo(
|
||||
token=token_response['access_token'])
|
||||
|
||||
userinfo = self.get_userinfo(token=token_response['access_token'])
|
||||
with transaction.atomic():
|
||||
user, _ = get_user_model().objects.update_or_create(
|
||||
username=userinfo.get('preferred_username', ''),
|
||||
|
@ -126,13 +163,11 @@ class Client(object):
|
|||
'last_name': userinfo.get('family_name', '')
|
||||
}
|
||||
)
|
||||
|
||||
oidt_profile = OpenIDTokenProfile(
|
||||
user=user,
|
||||
access_token=token_response['access_token'],
|
||||
refresh_token=token_response['refresh_token'],
|
||||
)
|
||||
|
||||
if user:
|
||||
post_create_openid_user.send(sender=user.__class__, user=user)
|
||||
|
||||
|
@ -140,17 +175,3 @@ class Client(object):
|
|||
|
||||
def __str__(self):
|
||||
return self.client_id
|
||||
|
||||
|
||||
class Nonce(object):
|
||||
"""
|
||||
The openid-login is stored in cache as a temporary object, recording the
|
||||
user's redirect_uri and next_pat
|
||||
"""
|
||||
|
||||
def __init__(self, redirect_uri, next_path):
|
||||
import uuid
|
||||
self.state = uuid.uuid4()
|
||||
self.redirect_uri = redirect_uri
|
||||
self.next_path = next_path
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ __all__ = ['OpenIDLoginView', 'OpenIDLoginCompleteView']
|
|||
|
||||
|
||||
class OpenIDLoginView(RedirectView):
|
||||
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
redirect_uri = settings.BASE_SITE_URL + str(settings.LOGIN_COMPLETE_URL)
|
||||
nonce = Nonce(
|
||||
|
@ -32,42 +31,36 @@ class OpenIDLoginView(RedirectView):
|
|||
next_path=self.request.GET.get('next')
|
||||
)
|
||||
cache.set(str(nonce.state), nonce, 24*3600)
|
||||
|
||||
self.request.session['openid_state'] = str(nonce.state)
|
||||
authorization_url = client.openid_connect_client.\
|
||||
authorization_url(
|
||||
redirect_uri=nonce.redirect_uri, scope='code',
|
||||
state=str(nonce.state)
|
||||
)
|
||||
authorization_url = client.get_authorization_url(
|
||||
redirect_uri=nonce.redirect_uri,
|
||||
scope='code',
|
||||
state=str(nonce.state)
|
||||
)
|
||||
return authorization_url
|
||||
|
||||
|
||||
class OpenIDLoginCompleteView(RedirectView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if 'error' in request.GET:
|
||||
return HttpResponseServerError(self.request.GET['error'])
|
||||
|
||||
if 'code' not in self.request.GET and 'state' not in self.request.GET:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
return HttpResponseBadRequest(content='Code or State is empty')
|
||||
if self.request.GET['state'] != self.request.session['openid_state']:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
return HttpResponseBadRequest(content='State invalid')
|
||||
nonce = cache.get(self.request.GET['state'])
|
||||
|
||||
if not nonce:
|
||||
return HttpResponseBadRequest()
|
||||
return HttpResponseBadRequest(content='State failure')
|
||||
|
||||
user = authenticate(
|
||||
request=self.request,
|
||||
code=self.request.GET['code'],
|
||||
redirect_uri=nonce.redirect_uri
|
||||
)
|
||||
|
||||
cache.delete(str(nonce.state))
|
||||
|
||||
if not user:
|
||||
return HttpResponseBadRequest()
|
||||
return HttpResponseBadRequest(content='Authenticate user failed')
|
||||
|
||||
login(self.request, user)
|
||||
post_openid_login_success.send(
|
||||
|
|
|
@ -18,19 +18,17 @@ from .signals import post_auth_success, post_auth_failed
|
|||
def on_user_logged_out(sender, request, user, **kwargs):
|
||||
if not settings.AUTH_OPENID:
|
||||
return
|
||||
|
||||
if not settings.AUTH_OPENID_SHARE_SESSION:
|
||||
return
|
||||
query = QueryDict('', mutable=True)
|
||||
query.update({
|
||||
'redirect_uri': settings.BASE_SITE_URL
|
||||
})
|
||||
|
||||
client = new_client()
|
||||
openid_logout_url = "%s?%s" % (
|
||||
client.openid_connect_client.get_url(
|
||||
name='end_session_endpoint'),
|
||||
client.get_url_end_session_endpoint(),
|
||||
query.urlencode()
|
||||
)
|
||||
|
||||
request.COOKIES['next'] = openid_logout_url
|
||||
|
||||
|
||||
|
|
|
@ -6,12 +6,16 @@ from django.dispatch import receiver
|
|||
from django.db.backends.signals import connection_created
|
||||
|
||||
|
||||
@receiver(connection_created, dispatch_uid="my_unique_identifier")
|
||||
@receiver(connection_created)
|
||||
def on_db_connection_ready(sender, **kwargs):
|
||||
from .signals import django_ready
|
||||
if 'migrate' not in sys.argv:
|
||||
django_ready.send(CommonConfig)
|
||||
connection_created.disconnect(on_db_connection_ready)
|
||||
|
||||
|
||||
class CommonConfig(AppConfig):
|
||||
name = 'common'
|
||||
|
||||
def ready(self):
|
||||
from . import signals_handlers
|
||||
|
|
|
@ -124,10 +124,27 @@ class EncryptTextField(EncryptMixin, models.TextField):
|
|||
|
||||
|
||||
class EncryptCharField(EncryptMixin, models.CharField):
|
||||
@staticmethod
|
||||
def change_max_length(kwargs):
|
||||
kwargs.setdefault('max_length', 1024)
|
||||
max_length = kwargs.get('max_length')
|
||||
if max_length < 129:
|
||||
max_length = 128
|
||||
max_length = max_length * 2
|
||||
kwargs['max_length'] = max_length
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['max_length'] = 2048
|
||||
self.change_max_length(kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def deconstruct(self):
|
||||
name, path, args, kwargs = super().deconstruct()
|
||||
max_length = kwargs.pop('max_length')
|
||||
if max_length > 255:
|
||||
max_length = max_length // 2
|
||||
kwargs['max_length'] = max_length
|
||||
return name, path, args, kwargs
|
||||
|
||||
|
||||
class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField):
|
||||
pass
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import filters
|
||||
from rest_framework.fields import DateTimeField
|
||||
from rest_framework.serializers import ValidationError
|
||||
import logging
|
||||
|
||||
__all__ = ["DatetimeRangeFilter"]
|
||||
|
||||
|
||||
class DatetimeRangeFilter(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
if not hasattr(view, 'date_range_filter_fields'):
|
||||
return queryset
|
||||
try:
|
||||
fields = dict(view.date_range_filter_fields)
|
||||
except ValueError:
|
||||
msg = "View {} datetime_filter_fields set is error".format(view.name)
|
||||
logging.error(msg)
|
||||
return queryset
|
||||
kwargs = {}
|
||||
for attr, date_range_keyword in fields.items():
|
||||
if len(date_range_keyword) != 2:
|
||||
continue
|
||||
for i, v in enumerate(date_range_keyword):
|
||||
value = request.query_params.get(v)
|
||||
if not value:
|
||||
continue
|
||||
try:
|
||||
field = DateTimeField()
|
||||
value = field.to_internal_value(value)
|
||||
if i == 0:
|
||||
lookup = "__gte"
|
||||
else:
|
||||
lookup = "__lte"
|
||||
kwargs[attr+lookup] = value
|
||||
except ValidationError as e:
|
||||
print(e)
|
||||
continue
|
||||
if kwargs:
|
||||
queryset = queryset.filter(**kwargs)
|
||||
return queryset
|
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from werkzeug.local import Local
|
||||
|
||||
thread_local = Local()
|
||||
|
||||
|
||||
def _find(attr):
|
||||
return getattr(thread_local, attr, None)
|
|
@ -1,234 +0,0 @@
|
|||
# coding: utf-8
|
||||
|
||||
from django.db import models
|
||||
from django.http import JsonResponse
|
||||
from django.utils import timezone
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib import messages
|
||||
from rest_framework.utils import html
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import SkipField
|
||||
|
||||
from .const import KEY_CACHE_RESOURCES_ID
|
||||
|
||||
|
||||
class NoDeleteQuerySet(models.query.QuerySet):
|
||||
|
||||
def delete(self):
|
||||
return self.update(is_discard=True, discard_time=timezone.now())
|
||||
|
||||
|
||||
class NoDeleteManager(models.Manager):
|
||||
|
||||
def get_all(self):
|
||||
return NoDeleteQuerySet(self.model, using=self._db)
|
||||
|
||||
def get_queryset(self):
|
||||
return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=False)
|
||||
|
||||
def get_deleted(self):
|
||||
return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=True)
|
||||
|
||||
|
||||
class NoDeleteModelMixin(models.Model):
|
||||
is_discard = models.BooleanField(verbose_name=_("is discard"), default=False)
|
||||
discard_time = models.DateTimeField(verbose_name=_("discard time"), null=True, blank=True)
|
||||
|
||||
objects = NoDeleteManager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def delete(self):
|
||||
self.is_discard = True
|
||||
self.discard_time = timezone.now()
|
||||
return self.save()
|
||||
|
||||
|
||||
class JSONResponseMixin(object):
|
||||
"""JSON mixin"""
|
||||
@staticmethod
|
||||
def render_json_response(context):
|
||||
return JsonResponse(context)
|
||||
|
||||
|
||||
class IDInFilterMixin(object):
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super(IDInFilterMixin, self).filter_queryset(queryset)
|
||||
id_list = self.request.query_params.get('id__in')
|
||||
if id_list:
|
||||
import json
|
||||
try:
|
||||
ids = json.loads(id_list)
|
||||
except Exception as e:
|
||||
return queryset
|
||||
if isinstance(ids, list):
|
||||
queryset = queryset.filter(id__in=ids)
|
||||
return queryset
|
||||
|
||||
|
||||
class IDInCacheFilterMixin(object):
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super(IDInCacheFilterMixin, self).filter_queryset(queryset)
|
||||
spm = self.request.query_params.get('spm')
|
||||
cache_key = KEY_CACHE_RESOURCES_ID.format(spm)
|
||||
resources_id = cache.get(cache_key)
|
||||
if resources_id and isinstance(resources_id, list):
|
||||
queryset = queryset.filter(id__in=resources_id)
|
||||
return queryset
|
||||
|
||||
|
||||
class IDExportFilterMixin(object):
|
||||
def filter_queryset(self, queryset):
|
||||
# 下载导入模版
|
||||
if self.request.query_params.get('template') == 'import':
|
||||
return []
|
||||
else:
|
||||
return super(IDExportFilterMixin, self).filter_queryset(queryset)
|
||||
|
||||
|
||||
class BulkSerializerMixin(object):
|
||||
"""
|
||||
Become rest_framework_bulk not support uuid as a primary key
|
||||
so rewrite it. https://github.com/miki725/django-rest-framework-bulk/issues/66
|
||||
"""
|
||||
def to_internal_value(self, data):
|
||||
from rest_framework_bulk import BulkListSerializer
|
||||
ret = super(BulkSerializerMixin, self).to_internal_value(data)
|
||||
|
||||
id_attr = getattr(self.Meta, 'update_lookup_field', 'id')
|
||||
if self.context.get('view'):
|
||||
request_method = getattr(getattr(self.context.get('view'), 'request'), 'method', '')
|
||||
# add update_lookup_field field back to validated data
|
||||
# since super by default strips out read-only fields
|
||||
# hence id will no longer be present in validated_data
|
||||
if all((isinstance(self.root, BulkListSerializer),
|
||||
id_attr,
|
||||
request_method in ('PUT', 'PATCH'))):
|
||||
id_field = self.fields[id_attr]
|
||||
if data.get("id"):
|
||||
id_value = id_field.to_internal_value(data.get("id"))
|
||||
else:
|
||||
id_value = id_field.to_internal_value(data.get("pk"))
|
||||
ret[id_attr] = id_value
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
class BulkListSerializerMixin(object):
|
||||
"""
|
||||
Become rest_framework_bulk doing bulk update raise Exception:
|
||||
'QuerySet' object has no attribute 'pk' when doing bulk update
|
||||
so rewrite it .
|
||||
https://github.com/miki725/django-rest-framework-bulk/issues/68
|
||||
"""
|
||||
|
||||
def to_internal_value(self, data):
|
||||
"""
|
||||
List of dicts of native values <- List of dicts of primitive datatypes.
|
||||
"""
|
||||
if html.is_html_input(data):
|
||||
data = html.parse_html_list(data)
|
||||
|
||||
if not isinstance(data, list):
|
||||
message = self.error_messages['not_a_list'].format(
|
||||
input_type=type(data).__name__
|
||||
)
|
||||
raise ValidationError({
|
||||
api_settings.NON_FIELD_ERRORS_KEY: [message]
|
||||
}, code='not_a_list')
|
||||
|
||||
if not self.allow_empty and len(data) == 0:
|
||||
if self.parent and self.partial:
|
||||
raise SkipField()
|
||||
|
||||
message = self.error_messages['empty']
|
||||
raise ValidationError({
|
||||
api_settings.NON_FIELD_ERRORS_KEY: [message]
|
||||
}, code='empty')
|
||||
|
||||
ret = []
|
||||
errors = []
|
||||
|
||||
for item in data:
|
||||
try:
|
||||
# prepare child serializer to only handle one instance
|
||||
if 'id' in item.keys():
|
||||
self.child.instance = self.instance.get(id=item['id']) if self.instance else None
|
||||
if 'pk' in item.keys():
|
||||
self.child.instance = self.instance.get(id=item['pk']) if self.instance else None
|
||||
|
||||
self.child.initial_data = item
|
||||
# raw
|
||||
validated = self.child.run_validation(item)
|
||||
except ValidationError as exc:
|
||||
errors.append(exc.detail)
|
||||
else:
|
||||
ret.append(validated)
|
||||
errors.append({})
|
||||
|
||||
if any(errors):
|
||||
raise ValidationError(errors)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
class DatetimeSearchMixin:
|
||||
date_format = '%Y-%m-%d'
|
||||
date_from = date_to = None
|
||||
|
||||
def get_date_range(self):
|
||||
date_from_s = self.request.GET.get('date_from')
|
||||
date_to_s = self.request.GET.get('date_to')
|
||||
|
||||
if date_from_s:
|
||||
date_from = timezone.datetime.strptime(date_from_s, self.date_format)
|
||||
tz = timezone.get_current_timezone()
|
||||
self.date_from = tz.localize(date_from)
|
||||
else:
|
||||
self.date_from = timezone.now() - timezone.timedelta(7)
|
||||
|
||||
if date_to_s:
|
||||
date_to = timezone.datetime.strptime(
|
||||
date_to_s + ' 23:59:59', self.date_format + ' %H:%M:%S'
|
||||
)
|
||||
self.date_to = date_to.replace(
|
||||
tzinfo=timezone.get_current_timezone()
|
||||
)
|
||||
else:
|
||||
self.date_to = timezone.now()
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.get_date_range()
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class ApiMessageMixin:
|
||||
success_message = _("%(name)s was %(action)s successfully")
|
||||
_action_map = {"create": _("create"), "update": _("update")}
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
if not isinstance(cleaned_data, dict):
|
||||
return ''
|
||||
data = {k: v for k, v in cleaned_data.items()}
|
||||
action = getattr(self, "action", "create")
|
||||
data["action"] = self._action_map.get(action)
|
||||
try:
|
||||
message = self.success_message % data
|
||||
except:
|
||||
message = ''
|
||||
return message
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
resp = super().dispatch(request, *args, **kwargs)
|
||||
if request.method.lower() in ("get", "delete", "patch"):
|
||||
return resp
|
||||
if resp.status_code >= 400:
|
||||
return resp
|
||||
message = self.get_success_message(resp.data)
|
||||
if message:
|
||||
messages.success(request, message)
|
||||
return resp
|
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .models import *
|
||||
from .serializers import *
|
||||
from .api import *
|
||||
from .views import *
|
|
@ -0,0 +1,86 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.http import JsonResponse
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib import messages
|
||||
|
||||
from ..const import KEY_CACHE_RESOURCES_ID
|
||||
|
||||
__all__ = [
|
||||
"JSONResponseMixin", "IDInCacheFilterMixin", "IDExportFilterMixin",
|
||||
"IDInFilterMixin", "ApiMessageMixin"
|
||||
]
|
||||
|
||||
|
||||
class JSONResponseMixin(object):
|
||||
"""JSON mixin"""
|
||||
@staticmethod
|
||||
def render_json_response(context):
|
||||
return JsonResponse(context)
|
||||
|
||||
|
||||
class IDInFilterMixin(object):
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super(IDInFilterMixin, self).filter_queryset(queryset)
|
||||
id_list = self.request.query_params.get('id__in')
|
||||
if id_list:
|
||||
import json
|
||||
try:
|
||||
ids = json.loads(id_list)
|
||||
except Exception as e:
|
||||
return queryset
|
||||
if isinstance(ids, list):
|
||||
queryset = queryset.filter(id__in=ids)
|
||||
return queryset
|
||||
|
||||
|
||||
class IDInCacheFilterMixin(object):
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
spm = self.request.query_params.get('spm')
|
||||
if not spm:
|
||||
return queryset
|
||||
cache_key = KEY_CACHE_RESOURCES_ID.format(spm)
|
||||
resources_id = cache.get(cache_key)
|
||||
if resources_id and isinstance(resources_id, list):
|
||||
queryset = queryset.filter(id__in=resources_id)
|
||||
return queryset
|
||||
|
||||
|
||||
class IDExportFilterMixin(object):
|
||||
def filter_queryset(self, queryset):
|
||||
# 下载导入模版
|
||||
if self.request.query_params.get('template') == 'import':
|
||||
return []
|
||||
else:
|
||||
return super(IDExportFilterMixin, self).filter_queryset(queryset)
|
||||
|
||||
|
||||
class ApiMessageMixin:
|
||||
success_message = _("%(name)s was %(action)s successfully")
|
||||
_action_map = {"create": _("create"), "update": _("update")}
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
if not isinstance(cleaned_data, dict):
|
||||
return ''
|
||||
data = {k: v for k, v in cleaned_data.items()}
|
||||
action = getattr(self, "action", "create")
|
||||
data["action"] = self._action_map.get(action)
|
||||
try:
|
||||
message = self.success_message % data
|
||||
except:
|
||||
message = ''
|
||||
return message
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
resp = super().dispatch(request, *args, **kwargs)
|
||||
if request.method.lower() in ("get", "delete", "patch"):
|
||||
return resp
|
||||
if resp.status_code >= 400:
|
||||
return resp
|
||||
message = self.get_success_message(resp.data)
|
||||
if message:
|
||||
messages.success(request, message)
|
||||
return resp
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
__all__ = ["NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet"]
|
||||
|
||||
|
||||
class NoDeleteQuerySet(models.query.QuerySet):
|
||||
|
||||
def delete(self):
|
||||
return self.update(is_discard=True, discard_time=timezone.now())
|
||||
|
||||
|
||||
class NoDeleteManager(models.Manager):
|
||||
|
||||
def get_all(self):
|
||||
return NoDeleteQuerySet(self.model, using=self._db)
|
||||
|
||||
def get_queryset(self):
|
||||
return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=False)
|
||||
|
||||
def get_deleted(self):
|
||||
return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=True)
|
||||
|
||||
|
||||
class NoDeleteModelMixin(models.Model):
|
||||
is_discard = models.BooleanField(verbose_name=_("is discard"), default=False)
|
||||
discard_time = models.DateTimeField(verbose_name=_("discard time"), null=True, blank=True)
|
||||
|
||||
objects = NoDeleteManager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def delete(self):
|
||||
self.is_discard = True
|
||||
self.discard_time = timezone.now()
|
||||
return self.save()
|
|
@ -0,0 +1,94 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework.utils import html
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import SkipField
|
||||
|
||||
__all__ = ['BulkSerializerMixin', 'BulkListSerializerMixin']
|
||||
|
||||
|
||||
class BulkSerializerMixin(object):
|
||||
"""
|
||||
Become rest_framework_bulk not support uuid as a primary key
|
||||
so rewrite it. https://github.com/miki725/django-rest-framework-bulk/issues/66
|
||||
"""
|
||||
def to_internal_value(self, data):
|
||||
from rest_framework_bulk import BulkListSerializer
|
||||
ret = super(BulkSerializerMixin, self).to_internal_value(data)
|
||||
|
||||
id_attr = getattr(self.Meta, 'update_lookup_field', 'id')
|
||||
if self.context.get('view'):
|
||||
request_method = getattr(getattr(self.context.get('view'), 'request'), 'method', '')
|
||||
# add update_lookup_field field back to validated data
|
||||
# since super by default strips out read-only fields
|
||||
# hence id will no longer be present in validated_data
|
||||
if all((isinstance(self.root, BulkListSerializer),
|
||||
id_attr,
|
||||
request_method in ('PUT', 'PATCH'))):
|
||||
id_field = self.fields.get("id") or self.fields.get('pk')
|
||||
if data.get("id"):
|
||||
id_value = id_field.to_internal_value(data.get("id"))
|
||||
else:
|
||||
id_value = id_field.to_internal_value(data.get("pk"))
|
||||
ret[id_attr] = id_value
|
||||
return ret
|
||||
|
||||
|
||||
class BulkListSerializerMixin(object):
|
||||
"""
|
||||
Become rest_framework_bulk doing bulk update raise Exception:
|
||||
'QuerySet' object has no attribute 'pk' when doing bulk update
|
||||
so rewrite it .
|
||||
https://github.com/miki725/django-rest-framework-bulk/issues/68
|
||||
"""
|
||||
|
||||
def to_internal_value(self, data):
|
||||
"""
|
||||
List of dicts of native values <- List of dicts of primitive datatypes.
|
||||
"""
|
||||
if html.is_html_input(data):
|
||||
data = html.parse_html_list(data)
|
||||
|
||||
if not isinstance(data, list):
|
||||
message = self.error_messages['not_a_list'].format(
|
||||
input_type=type(data).__name__
|
||||
)
|
||||
raise ValidationError({
|
||||
api_settings.NON_FIELD_ERRORS_KEY: [message]
|
||||
}, code='not_a_list')
|
||||
|
||||
if not self.allow_empty and len(data) == 0:
|
||||
if self.parent and self.partial:
|
||||
raise SkipField()
|
||||
|
||||
message = self.error_messages['empty']
|
||||
raise ValidationError({
|
||||
api_settings.NON_FIELD_ERRORS_KEY: [message]
|
||||
}, code='empty')
|
||||
|
||||
ret = []
|
||||
errors = []
|
||||
|
||||
for item in data:
|
||||
try:
|
||||
# prepare child serializer to only handle one instance
|
||||
if 'id' in item.keys():
|
||||
self.child.instance = self.instance.get(id=item['id']) if self.instance else None
|
||||
if 'pk' in item.keys():
|
||||
self.child.instance = self.instance.get(id=item['pk']) if self.instance else None
|
||||
|
||||
self.child.initial_data = item
|
||||
# raw
|
||||
validated = self.child.run_validation(item)
|
||||
except ValidationError as exc:
|
||||
errors.append(exc.detail)
|
||||
else:
|
||||
ret.append(validated)
|
||||
errors.append({})
|
||||
|
||||
if any(errors):
|
||||
raise ValidationError(errors)
|
||||
|
||||
return ret
|
|
@ -0,0 +1,40 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# coding: utf-8
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
__all__ = ["DatetimeSearchMixin"]
|
||||
|
||||
|
||||
class DatetimeSearchMixin:
|
||||
date_format = '%Y-%m-%d'
|
||||
date_from = date_to = None
|
||||
|
||||
def get_date_range(self):
|
||||
date_from_s = self.request.GET.get('date_from')
|
||||
date_to_s = self.request.GET.get('date_to')
|
||||
|
||||
if date_from_s:
|
||||
date_from = timezone.datetime.strptime(date_from_s, self.date_format)
|
||||
tz = timezone.get_current_timezone()
|
||||
self.date_from = tz.localize(date_from)
|
||||
else:
|
||||
self.date_from = timezone.now() - timezone.timedelta(7)
|
||||
|
||||
if date_to_s:
|
||||
date_to = timezone.datetime.strptime(
|
||||
date_to_s + ' 23:59:59', self.date_format + ' %H:%M:%S'
|
||||
)
|
||||
self.date_to = date_to.replace(
|
||||
tzinfo=timezone.get_current_timezone()
|
||||
)
|
||||
else:
|
||||
self.date_to = timezone.now()
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.get_date_range()
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
|
@ -126,9 +126,33 @@ class WithBootstrapToken(permissions.BasePermission):
|
|||
class PermissionsMixin(UserPassesTestMixin):
|
||||
permission_classes = []
|
||||
|
||||
def get_permissions(self):
|
||||
return self.permission_classes
|
||||
|
||||
def test_func(self):
|
||||
permission_classes = self.permission_classes
|
||||
permission_classes = self.get_permissions()
|
||||
for permission_class in permission_classes:
|
||||
if not permission_class().has_permission(self.request, self):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class NeedMFAVerify(permissions.BasePermission):
|
||||
def has_permission(self, request, view):
|
||||
mfa_verify_time = request.session.get('MFA_VERIFY_TIME', 0)
|
||||
if time.time() - mfa_verify_time < settings.SECURITY_MFA_VERIFY_TTL:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class CanUpdateSuperUser(permissions.BasePermission):
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.method in ['GET', 'OPTIONS']:
|
||||
return True
|
||||
if str(request.user.id) == str(obj.id):
|
||||
return False
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
if hasattr(obj, 'is_superuser') and obj.is_superuser:
|
||||
return False
|
||||
return True
|
||||
|
|
|
@ -57,10 +57,15 @@ class JMSCSVRender(BaseRenderer):
|
|||
request = renderer_context['request']
|
||||
template = request.query_params.get('template', 'export')
|
||||
view = renderer_context['view']
|
||||
data = json.loads(json.dumps(data, cls=encoders.JSONEncoder))
|
||||
|
||||
if isinstance(data, dict) and data.get("count"):
|
||||
data = data["results"]
|
||||
|
||||
if template == 'import':
|
||||
data = [data[0]] if data else data
|
||||
|
||||
data = json.loads(json.dumps(data, cls=encoders.JSONEncoder))
|
||||
|
||||
try:
|
||||
serializer = view.get_serializer()
|
||||
self.set_response_disposition(serializer, renderer_context)
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from django.conf import settings
|
||||
from django.dispatch import receiver
|
||||
from django.core.signals import request_finished
|
||||
from django.db import connection
|
||||
|
||||
|
||||
from common.utils import get_logger
|
||||
from .local import thread_local
|
||||
|
||||
pattern = re.compile(r'FROM `(\w+)`')
|
||||
# logger = logging.getLogger('jmsdb')
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class Counter:
|
||||
def __init__(self):
|
||||
self.counter = 0
|
||||
self.time = 0
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.counter > other.counter
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.counter < other.counter
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.counter == other.counter
|
||||
|
||||
|
||||
def on_request_finished_logging_db_query(sender, **kwargs):
|
||||
queries = connection.queries
|
||||
counters = defaultdict(Counter)
|
||||
for query in queries:
|
||||
if not query['sql'].startswith('SELECT'):
|
||||
continue
|
||||
tables = pattern.findall(query['sql'])
|
||||
table_name = ''.join(tables)
|
||||
time = query['time']
|
||||
counters[table_name].counter += 1
|
||||
counters[table_name].time += float(time)
|
||||
counters['total'].counter += 1
|
||||
counters['total'].time += float(time)
|
||||
|
||||
counters = sorted(counters.items(), key=lambda x: x[1])
|
||||
for name, counter in counters:
|
||||
logger.debug("Query {:3} times using {:.2f}s {}".format(
|
||||
counter.counter, counter.time, name)
|
||||
)
|
||||
|
||||
|
||||
@receiver(request_finished)
|
||||
def on_request_finished_release_local(sender, **kwargs):
|
||||
thread_local.__release_local__()
|
||||
|
||||
|
||||
if settings.DEBUG:
|
||||
request_finished.connect(on_request_finished_logging_db_query)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
|
||||
class Stack(list):
|
||||
def is_empty(self):
|
||||
return len(self) == 0
|
||||
|
||||
@property
|
||||
def top(self):
|
||||
if self.is_empty():
|
||||
return None
|
||||
return self[-1]
|
||||
|
||||
@property
|
||||
def bottom(self):
|
||||
if self.is_empty():
|
||||
return None
|
||||
return self[0]
|
||||
|
||||
def size(self):
|
||||
return len(self)
|
||||
|
||||
def push(self, item):
|
||||
self.append(item)
|
|
@ -112,7 +112,6 @@ def to_dict(data):
|
|||
|
||||
@register.filter
|
||||
def sort(data):
|
||||
print(data)
|
||||
return sorted(data)
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
from .utils import random_string, get_signer
|
||||
|
||||
|
||||
def test_signer_len():
|
||||
signer = get_signer()
|
||||
results = {}
|
||||
for i in range(1, 4096):
|
||||
s = random_string(i)
|
||||
encs = signer.sign(s)
|
||||
results[i] = (len(encs)/len(s))
|
||||
results = sorted(results.items(), key=lambda x: x[1], reverse=True)
|
||||
print(results)
|
||||
|
|
|
@ -130,16 +130,13 @@ def get_short_uuid_str():
|
|||
|
||||
|
||||
def is_uuid(seq):
|
||||
if isinstance(seq, str):
|
||||
if UUID_PATTERN.match(seq):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
for s in seq:
|
||||
if not is_uuid(s):
|
||||
return False
|
||||
if isinstance(seq, uuid.UUID):
|
||||
return True
|
||||
elif isinstance(seq, str) and UUID_PATTERN.match(seq):
|
||||
return True
|
||||
elif isinstance(seq, (list, tuple)):
|
||||
all([is_uuid(x) for x in seq])
|
||||
return False
|
||||
|
||||
|
||||
def get_request_ip(request):
|
||||
|
@ -176,120 +173,9 @@ def with_cache(func):
|
|||
return wrapper
|
||||
|
||||
|
||||
class LocalProxy(object):
|
||||
|
||||
"""
|
||||
Copy from werkzeug.local.LocalProxy
|
||||
"""
|
||||
__slots__ = ('__local', '__dict__', '__name__', '__wrapped__')
|
||||
|
||||
def __init__(self, local, name=None):
|
||||
object.__setattr__(self, '_LocalProxy__local', local)
|
||||
object.__setattr__(self, '__name__', name)
|
||||
if callable(local) and not hasattr(local, '__release_local__'):
|
||||
# "local" is a callable that is not an instance of Local or
|
||||
# LocalManager: mark it as a wrapped function.
|
||||
object.__setattr__(self, '__wrapped__', local)
|
||||
|
||||
def _get_current_object(self):
|
||||
"""Return the current object. This is useful if you want the real
|
||||
object behind the proxy at a time for performance reasons or because
|
||||
you want to pass the object into a different context.
|
||||
"""
|
||||
if not hasattr(self.__local, '__release_local__'):
|
||||
return self.__local()
|
||||
try:
|
||||
return getattr(self.__local, self.__name__)
|
||||
except AttributeError:
|
||||
raise RuntimeError('no object bound to %s' % self.__name__)
|
||||
|
||||
@property
|
||||
def __dict__(self):
|
||||
try:
|
||||
return self._get_current_object().__dict__
|
||||
except RuntimeError:
|
||||
raise AttributeError('__dict__')
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
obj = self._get_current_object()
|
||||
except RuntimeError:
|
||||
return '<%s unbound>' % self.__class__.__name__
|
||||
return repr(obj)
|
||||
|
||||
def __bool__(self):
|
||||
try:
|
||||
return bool(self._get_current_object())
|
||||
except RuntimeError:
|
||||
return False
|
||||
|
||||
def __dir__(self):
|
||||
try:
|
||||
return dir(self._get_current_object())
|
||||
except RuntimeError:
|
||||
return []
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == '__members__':
|
||||
return dir(self._get_current_object())
|
||||
return getattr(self._get_current_object(), name)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._get_current_object()[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._get_current_object()[key]
|
||||
|
||||
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
|
||||
__delattr__ = lambda x, n: delattr(x._get_current_object(), n)
|
||||
__str__ = lambda x: str(x._get_current_object())
|
||||
__lt__ = lambda x, o: x._get_current_object() < o
|
||||
__le__ = lambda x, o: x._get_current_object() <= o
|
||||
__eq__ = lambda x, o: x._get_current_object() == o
|
||||
__ne__ = lambda x, o: x._get_current_object() != o
|
||||
__gt__ = lambda x, o: x._get_current_object() > o
|
||||
__ge__ = lambda x, o: x._get_current_object() >= o
|
||||
__cmp__ = lambda x, o: cmp(x._get_current_object(), o) # noqa
|
||||
__hash__ = lambda x: hash(x._get_current_object())
|
||||
__call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
|
||||
__len__ = lambda x: len(x._get_current_object())
|
||||
__getitem__ = lambda x, i: x._get_current_object()[i]
|
||||
__iter__ = lambda x: iter(x._get_current_object())
|
||||
__contains__ = lambda x, i: i in x._get_current_object()
|
||||
__add__ = lambda x, o: x._get_current_object() + o
|
||||
__sub__ = lambda x, o: x._get_current_object() - o
|
||||
__mul__ = lambda x, o: x._get_current_object() * o
|
||||
__floordiv__ = lambda x, o: x._get_current_object() // o
|
||||
__mod__ = lambda x, o: x._get_current_object() % o
|
||||
__divmod__ = lambda x, o: x._get_current_object().__divmod__(o)
|
||||
__pow__ = lambda x, o: x._get_current_object() ** o
|
||||
__lshift__ = lambda x, o: x._get_current_object() << o
|
||||
__rshift__ = lambda x, o: x._get_current_object() >> o
|
||||
__and__ = lambda x, o: x._get_current_object() & o
|
||||
__xor__ = lambda x, o: x._get_current_object() ^ o
|
||||
__or__ = lambda x, o: x._get_current_object() | o
|
||||
__div__ = lambda x, o: x._get_current_object().__div__(o)
|
||||
__truediv__ = lambda x, o: x._get_current_object().__truediv__(o)
|
||||
__neg__ = lambda x: -(x._get_current_object())
|
||||
__pos__ = lambda x: +(x._get_current_object())
|
||||
__abs__ = lambda x: abs(x._get_current_object())
|
||||
__invert__ = lambda x: ~(x._get_current_object())
|
||||
__complex__ = lambda x: complex(x._get_current_object())
|
||||
__int__ = lambda x: int(x._get_current_object())
|
||||
__float__ = lambda x: float(x._get_current_object())
|
||||
__oct__ = lambda x: oct(x._get_current_object())
|
||||
__hex__ = lambda x: hex(x._get_current_object())
|
||||
__index__ = lambda x: x._get_current_object().__index__()
|
||||
__coerce__ = lambda x, o: x._get_current_object().__coerce__(x, o)
|
||||
__enter__ = lambda x: x._get_current_object().__enter__()
|
||||
__exit__ = lambda x, *a, **kw: x._get_current_object().__exit__(*a, **kw)
|
||||
__radd__ = lambda x, o: o + x._get_current_object()
|
||||
__rsub__ = lambda x, o: o - x._get_current_object()
|
||||
__rmul__ = lambda x, o: o * x._get_current_object()
|
||||
__rdiv__ = lambda x, o: o / x._get_current_object()
|
||||
__rtruediv__ = __rdiv__
|
||||
__rfloordiv__ = lambda x, o: o // x._get_current_object()
|
||||
__rmod__ = lambda x, o: o % x._get_current_object()
|
||||
__rdivmod__ = lambda x, o: x._get_current_object().__rdivmod__(o)
|
||||
__copy__ = lambda x: copy.copy(x._get_current_object())
|
||||
__deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo)
|
||||
def random_string(length):
|
||||
import string
|
||||
import random
|
||||
charset = string.ascii_letters + string.digits
|
||||
s = [random.choice(charset) for i in range(length)]
|
||||
return ''.join(s)
|
||||
|
|
|
@ -51,7 +51,7 @@ class Signer(metaclass=Singleton):
|
|||
try:
|
||||
return s.loads(value)
|
||||
except BadSignature:
|
||||
return {}
|
||||
return None
|
||||
|
||||
def sign_t(self, value, expires_in=3600):
|
||||
s = TimedJSONWebSignatureSerializer(self.secret_key, expires_in=expires_in)
|
||||
|
@ -62,7 +62,7 @@ class Signer(metaclass=Singleton):
|
|||
try:
|
||||
return s.loads(value)
|
||||
except (BadSignature, SignatureExpired):
|
||||
return {}
|
||||
return None
|
||||
|
||||
|
||||
def ssh_key_string_to_obj(text, password=None):
|
||||
|
|
|
@ -344,7 +344,9 @@ defaults = {
|
|||
'SESSION_COOKIE_AGE': 3600 * 24,
|
||||
'SESSION_EXPIRE_AT_BROWSER_CLOSE': False,
|
||||
'AUTH_OPENID': False,
|
||||
'OTP_VALID_WINDOW': 0,
|
||||
'AUTH_OPENID_IGNORE_SSL_VERIFICATION': True,
|
||||
'AUTH_OPENID_SHARE_SESSION': False,
|
||||
'OTP_VALID_WINDOW': 2,
|
||||
'OTP_ISSUER_NAME': 'Jumpserver',
|
||||
'EMAIL_SUFFIX': 'jumpserver.org',
|
||||
'TERMINAL_PASSWORD_AUTH': True,
|
||||
|
@ -373,8 +375,8 @@ defaults = {
|
|||
'HTTP_BIND_HOST': '0.0.0.0',
|
||||
'HTTP_LISTEN_PORT': 8080,
|
||||
'LOGIN_LOG_KEEP_DAYS': 90,
|
||||
'ASSETS_PERM_CACHE_TIME': 3600,
|
||||
|
||||
'ASSETS_PERM_CACHE_TIME': 3600*24,
|
||||
'SECURITY_MFA_VERIFY_TTL': 3600,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
VERSION = '1.5.0'
|
||||
VERSION = '1.5.1'
|
||||
|
|
|
@ -17,6 +17,7 @@ def jumpserver_processor(request):
|
|||
'VERSION': settings.VERSION,
|
||||
'COPYRIGHT': 'FIT2CLOUD 飞致云' + ' © 2014-2019',
|
||||
'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION,
|
||||
'SECURITY_MFA_VERIFY_TTL': settings.SECURITY_MFA_VERIFY_TTL,
|
||||
}
|
||||
return context
|
||||
|
||||
|
|
|
@ -111,6 +111,7 @@ MIDDLEWARE = [
|
|||
'orgs.middleware.OrgMiddleware',
|
||||
]
|
||||
|
||||
|
||||
ROOT_URLCONF = 'jumpserver.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
|
@ -398,7 +399,7 @@ REST_FRAMEWORK = {
|
|||
'ORDERING_PARAM': "order",
|
||||
'SEARCH_PARAM': "search",
|
||||
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z',
|
||||
'DATETIME_INPUT_FORMATS': ['%Y-%m-%d %H:%M:%S %z'],
|
||||
'DATETIME_INPUT_FORMATS': ['iso-8601', '%Y-%m-%d %H:%M:%S %z'],
|
||||
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
||||
# 'PAGE_SIZE': 15
|
||||
}
|
||||
|
@ -456,6 +457,8 @@ AUTH_OPENID_SERVER_URL = CONFIG.AUTH_OPENID_SERVER_URL
|
|||
AUTH_OPENID_REALM_NAME = CONFIG.AUTH_OPENID_REALM_NAME
|
||||
AUTH_OPENID_CLIENT_ID = CONFIG.AUTH_OPENID_CLIENT_ID
|
||||
AUTH_OPENID_CLIENT_SECRET = CONFIG.AUTH_OPENID_CLIENT_SECRET
|
||||
AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION
|
||||
AUTH_OPENID_SHARE_SESSION = CONFIG.AUTH_OPENID_SHARE_SESSION
|
||||
AUTH_OPENID_BACKENDS = [
|
||||
'authentication.backends.openid.backends.OpenIDAuthorizationPasswordBackend',
|
||||
'authentication.backends.openid.backends.OpenIDAuthorizationCodeBackend',
|
||||
|
@ -564,6 +567,7 @@ SECURITY_PASSWORD_RULES = [
|
|||
'SECURITY_PASSWORD_NUMBER',
|
||||
'SECURITY_PASSWORD_SPECIAL_CHAR'
|
||||
]
|
||||
SECURITY_MFA_VERIFY_TTL = CONFIG.SECURITY_MFA_VERIFY_TTL
|
||||
|
||||
TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH
|
||||
TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.conf.urls.static import static
|
|||
from django.conf.urls.i18n import i18n_patterns
|
||||
from django.views.i18n import JavaScriptCatalog
|
||||
|
||||
from .views import IndexView, LunaView, I18NView
|
||||
from .views import IndexView, LunaView, I18NView, HealthCheckView
|
||||
from .swagger import get_swagger_view
|
||||
|
||||
api_v1 = [
|
||||
|
@ -44,8 +44,12 @@ app_view_patterns = [
|
|||
|
||||
|
||||
if settings.XPACK_ENABLED:
|
||||
app_view_patterns.append(path('xpack/', include('xpack.urls.view_urls', namespace='xpack')))
|
||||
api_v1.append(path('xpack/v1/', include('xpack.urls.api_urls', namespace='api-xpack')))
|
||||
app_view_patterns.append(
|
||||
path('xpack/', include('xpack.urls.view_urls', namespace='xpack'))
|
||||
)
|
||||
api_v1.append(
|
||||
path('xpack/v1/', include('xpack.urls.api_urls', namespace='api-xpack'))
|
||||
)
|
||||
|
||||
js_i18n_patterns = i18n_patterns(
|
||||
path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
|
||||
|
@ -63,6 +67,7 @@ urlpatterns = [
|
|||
path('', IndexView.as_view(), name='index'),
|
||||
path('', include(api_v2_patterns)),
|
||||
path('', include(api_v1_patterns)),
|
||||
path('api/health/', HealthCheckView.as_view(), name="health"),
|
||||
path('luna/', LunaView.as_view(), name='luna-view'),
|
||||
path('i18n/<str:lang>/', I18NView.as_view(), name='i18n-switch'),
|
||||
path('settings/', include('settings.urls.view_urls', namespace='settings')),
|
||||
|
@ -92,3 +97,4 @@ if settings.DEBUG:
|
|||
path('docs/v2/', get_swagger_view("v2").with_ui('swagger', cache_timeout=1), name="docs"),
|
||||
path('redoc/v2/', get_swagger_view("v2").with_ui('redoc', cache_timeout=1), name='redoc'),
|
||||
]
|
||||
|
||||
|
|
|
@ -1,24 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from functools import partial
|
||||
|
||||
from common.utils import LocalProxy
|
||||
|
||||
try:
|
||||
from threading import local
|
||||
except ImportError:
|
||||
from django.utils._threading_local import local
|
||||
|
||||
_thread_locals = local()
|
||||
from werkzeug.local import LocalProxy
|
||||
from common.local import thread_local
|
||||
|
||||
|
||||
def set_current_request(request):
|
||||
setattr(_thread_locals, 'current_request', request)
|
||||
setattr(thread_local, 'current_request', request)
|
||||
|
||||
|
||||
def _find(attr):
|
||||
return getattr(_thread_locals, attr, None)
|
||||
return getattr(thread_local, attr, None)
|
||||
|
||||
|
||||
def get_current_request():
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import datetime
|
||||
import re
|
||||
import time
|
||||
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.conf import settings
|
||||
|
@ -9,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.db.models import Count
|
||||
from django.shortcuts import redirect
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.http import HttpResponse
|
||||
from django.utils.encoding import iri_to_uri
|
||||
|
@ -222,3 +224,10 @@ def redirect_format_api(request, *args, **kwargs):
|
|||
return HttpResponseTemporaryRedirect(_path)
|
||||
else:
|
||||
return Response({"msg": "Redirect url failed: {}".format(_path)}, status=404)
|
||||
|
||||
|
||||
class HealthCheckView(APIView):
|
||||
permission_classes = ()
|
||||
|
||||
def get(self, request):
|
||||
return Response({"status": 1, "time": int(time.time())})
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -20,7 +20,7 @@ class CommandExecutionViewSet(viewsets.ModelViewSet):
|
|||
)
|
||||
|
||||
def check_permissions(self, request):
|
||||
if not settings.SECURITY_COMMAND_EXECUTION:
|
||||
if not settings.SECURITY_COMMAND_EXECUTION and request.user.is_common_user:
|
||||
return self.permission_denied(request, "Command execution disabled")
|
||||
return super().check_permissions(request)
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue