mirror of https://github.com/jumpserver/jumpserver
Bugfix (#2831)
* [Update] 修改小问题 * [Update] 添加重传guacamole的脚本 * [Update] 添加debug * [Update] 优化可连接性 * [Update] 修改connectivity * [Update] 更改查看认证需要的MFA时间间隔 * [Update] 修改表结构 * [Update] 修改users public_key等字段 * [Update] 修改用户表结构 * [Update] 修改assets users api * [Update] 修改org mixin * [Update] 解决连接windows资产出现幽灵会话的问题 * [Update] 优化树结构 * [Update] 修改Permission * Stash * [Update] 修改serializer * [Update] 修改用户有权限的资产 * [Update] 修改upgrouped_node key的获取(解决操作日志中出现coco/gua的问题) * [Update] 修改一些bug * [Update] Debug cache * [Bugfix] 修复用户页面不走cache的bug * ipython * [Update] 修改action * [Bugfix] 修改校验系统用户资产动作权限的API逻辑 * [Update] 去掉原来批量的view * [Bugfix] 会话/命令列表中获取用户列表排除app用户 * [Update] 修改用户授权资产API返回的queryset * [Update] 修正migrations * [Bugfix] 解决进入授权详情页的资产管理页面bug * [Update] 修改Minxs * [Update] 修改migrations * [Update] 资产授权Model模块添加导入 * [Update] 优化命令记录列表 * [Update] 修改command列表 * [Update] 解决用户授权资产/节点为空时,前端构建资产授权树的bug (#2874) * [Update] 解决用户授权资产/节点为空时,前端构建资产授权树的bug * [Update] 如果用户授权节点为空,返回时添加空节点 * [Update] 修改command导出和搜索 * [Update] 修改session * [Update] 修改Permission响应层缓存key * [Update] 准备优化 asset user * [Update] 修改去掉一些print * [Bugfix] 修复initDataTable表格搜索栏位置错乱的问题,显示不友好问题 (#2880) * [Bugfix] 修复创建用户的View,使用密码创建用户时没有校验密码规则 (#2877) * [Bugfix] 修复创建用户的View,使用密码创建用户时没有校验密码规则 * [Bugfix]修复小问题 * [Update] 优化创建用户和更新用户密码的校验 * [Update] 优化用户表单校验password逻辑 * [Update] 小问题 * [Update] 修改command搜索 * [Update] 修改user group serialzier * [Update] 优化资产 * [Update] 优化节点 * [Update] 优化用户组列表用户显示问题 (#2882) * [Update] 解决select_for_update的错误 * [update] 修改Node无法被删除的bug * [Update] 添加翻译 * [update] 修改资产导出的permssions * [Bugfix] 修复删除节点bug (#2883) * [update] 修改一些性能问题pull/2885/head
commit
58875d9a95
|
@ -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, ApiMessageMixin, 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().prefetch_related("protocols").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,7 +6,7 @@ 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, Protocol, Node
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
@ -33,6 +33,12 @@ class ProtocolForm(forms.ModelForm):
|
|||
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
|
||||
fields = [
|
||||
|
|
|
@ -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')},
|
||||
),
|
||||
]
|
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -1,10 +1,10 @@
|
|||
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 .utils import *
|
||||
from .authbook import *
|
||||
from .utils import *
|
||||
|
|
|
@ -6,15 +6,13 @@ import uuid
|
|||
import logging
|
||||
import random
|
||||
from functools import reduce
|
||||
from collections import defaultdict
|
||||
|
||||
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']
|
||||
|
@ -48,12 +46,6 @@ class AssetQuerySet(models.QuerySet):
|
|||
return self.active()
|
||||
|
||||
|
||||
class AssetManager(OrgManager):
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().prefetch_related("nodes", "protocols")
|
||||
return queryset
|
||||
|
||||
|
||||
class Protocol(models.Model):
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
|
@ -133,14 +125,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)
|
||||
|
@ -215,20 +201,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 +222,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:
|
||||
|
@ -321,15 +296,20 @@ 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:
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -2,17 +2,17 @@
|
|||
#
|
||||
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, Protocol, Node, Label
|
||||
from .base import ConnectivitySerializer
|
||||
|
||||
__all__ = [
|
||||
'AssetSerializer', 'AssetGrantedSerializer', 'AssetSimpleSerializer',
|
||||
'ProtocolSerializer',
|
||||
'AssetSerializer', 'AssetSimpleSerializer',
|
||||
'ProtocolSerializer', 'ProtocolsRelatedField',
|
||||
]
|
||||
|
||||
|
||||
|
@ -43,6 +43,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
protocols = ProtocolsRelatedField(
|
||||
many=True, queryset=Protocol.objects.all(), label=_("Protocols")
|
||||
)
|
||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
||||
|
||||
"""
|
||||
资产的数据结构
|
||||
|
@ -57,7 +58,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,15 +70,17 @@ 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')),
|
||||
'protocols'
|
||||
).select_related('admin_user', 'domain')
|
||||
return queryset
|
||||
|
||||
@staticmethod
|
||||
|
@ -138,54 +141,6 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
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"
|
||||
# )
|
||||
|
||||
|
||||
class AssetSimpleSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -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>
|
|
@ -23,7 +23,7 @@
|
|||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-9" style="padding-left: 0;">
|
||||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Asset list of ' %} <b>{{ admin_user.name }}</b></span>
|
||||
|
@ -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 () {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -37,7 +37,7 @@ from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
|
|||
__all__ = [
|
||||
'AssetListView', 'AssetCreateView', 'AssetUpdateView', 'AssetUserListView',
|
||||
'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView',
|
||||
'AssetDeleteView', 'AssetExportView', 'BulkImportAssetView',
|
||||
'AssetDeleteView',
|
||||
]
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
@ -220,6 +220,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", "protocols"
|
||||
).select_related('admin_user', 'domain')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
nodes_remain = Node.objects.exclude(assets=self.object)
|
||||
context = {
|
||||
|
@ -229,150 +234,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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
@ -135,3 +135,24 @@ class PermissionsMixin(UserPassesTestMixin):
|
|||
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):
|
||||
|
|
|
@ -375,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,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -566,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
|
@ -2,7 +2,6 @@
|
|||
#
|
||||
|
||||
from .ansible.inventory import BaseInventory
|
||||
from assets.utils import get_assets_by_id_list, get_system_user_by_id
|
||||
|
||||
from common.utils import get_logger
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ def run_command_execution(cid, **kwargs):
|
|||
try:
|
||||
execution.run()
|
||||
except SoftTimeLimitExceeded:
|
||||
print("HLLL")
|
||||
logger.error("Run time out")
|
||||
else:
|
||||
logger.error("Not found the execution id: {}".format(cid))
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from assets.models import Node
|
||||
from orgs.utils import set_current_org, current_org
|
||||
from orgs.utils import set_current_org, current_org, get_current_org
|
||||
|
|
|
@ -1,247 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from werkzeug.local import Local
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.shortcuts import redirect, get_object_or_404
|
||||
from django.forms import ModelForm
|
||||
from django.http.response import HttpResponseForbidden
|
||||
from django.core.exceptions import ValidationError
|
||||
from rest_framework import serializers
|
||||
from rest_framework.validators import UniqueTogetherValidator
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.validators import ProjectUniqueValidator
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from .utils import (
|
||||
current_org, set_current_org, set_to_root_org, get_current_org_id
|
||||
)
|
||||
from .models import Organization
|
||||
|
||||
logger = get_logger(__file__)
|
||||
tl = Local()
|
||||
|
||||
__all__ = [
|
||||
'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm',
|
||||
'RootOrgViewMixin', 'OrgMembershipSerializerMixin',
|
||||
'OrgMembershipModelViewSetMixin', 'OrgResourceSerializerMixin',
|
||||
'BulkOrgResourceSerializerMixin', 'BulkOrgResourceModelSerializer',
|
||||
]
|
||||
|
||||
|
||||
class OrgManager(models.Manager):
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(OrgManager, self).get_queryset()
|
||||
kwargs = {}
|
||||
# if not hasattr(tl, 'times'):
|
||||
# tl.times = 0
|
||||
# logger.debug("[{}]>>>>>>>>>> Get query set".format(tl.times))
|
||||
if not current_org:
|
||||
kwargs['id'] = None
|
||||
elif current_org.is_real():
|
||||
kwargs['org_id'] = current_org.id
|
||||
elif current_org.is_default():
|
||||
queryset = queryset.filter(org_id="")
|
||||
queryset = queryset.filter(**kwargs)
|
||||
# tl.times += 1
|
||||
return queryset
|
||||
|
||||
def filter_by_fullname(self, fullname, field=None):
|
||||
ori_org = current_org
|
||||
value, org = self.model.split_fullname(fullname)
|
||||
set_current_org(org)
|
||||
if not field:
|
||||
if hasattr(self.model, 'name'):
|
||||
field = 'name'
|
||||
elif hasattr(self.model, 'hostname'):
|
||||
field = 'hostname'
|
||||
queryset = self.get_queryset().filter(**{field: value})
|
||||
set_current_org(ori_org)
|
||||
return queryset
|
||||
|
||||
def get_object_by_fullname(self, fullname, field=None):
|
||||
queryset = self.filter_by_fullname(fullname, field=field)
|
||||
if len(queryset) == 1:
|
||||
return queryset[0]
|
||||
return None
|
||||
|
||||
def all(self):
|
||||
if not current_org:
|
||||
msg = 'You can `objects.set_current_org(org).all()` then run it'
|
||||
return self
|
||||
else:
|
||||
return super(OrgManager, self).all()
|
||||
|
||||
def set_current_org(self, org):
|
||||
if isinstance(org, str):
|
||||
org = Organization.objects.get(name=org)
|
||||
set_current_org(org)
|
||||
return self
|
||||
|
||||
|
||||
class OrgModelMixin(models.Model):
|
||||
org_id = models.CharField(max_length=36, blank=True, default='', verbose_name=_("Organization"), db_index=True)
|
||||
objects = OrgManager()
|
||||
|
||||
sep = '@'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if current_org and current_org.is_real():
|
||||
self.org_id = current_org.id
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def split_fullname(cls, fullname, sep=None):
|
||||
if not sep:
|
||||
sep = cls.sep
|
||||
index = fullname.rfind(sep)
|
||||
if index == -1:
|
||||
value = fullname
|
||||
org = Organization.default()
|
||||
else:
|
||||
value = fullname[:index]
|
||||
org = Organization.get_instance(fullname[index + 1:])
|
||||
return value, org
|
||||
|
||||
@property
|
||||
def org(self):
|
||||
from orgs.models import Organization
|
||||
org = Organization.get_instance(self.org_id)
|
||||
return org
|
||||
|
||||
@property
|
||||
def org_name(self):
|
||||
return self.org.name
|
||||
|
||||
@property
|
||||
def fullname(self, attr=None):
|
||||
name = ''
|
||||
if attr and hasattr(self, attr):
|
||||
name = getattr(self, attr)
|
||||
elif hasattr(self, 'name'):
|
||||
name = self.name
|
||||
elif hasattr(self, 'hostname'):
|
||||
name = self.hostname
|
||||
if self.org.is_real():
|
||||
return name + self.sep + self.org_name
|
||||
else:
|
||||
return name
|
||||
|
||||
def validate_unique(self, exclude=None):
|
||||
"""
|
||||
Check unique constraints on the model and raise ValidationError if any
|
||||
failed.
|
||||
"""
|
||||
self.org_id = current_org.id if current_org.is_real() else ''
|
||||
if exclude and 'org_id' in exclude:
|
||||
exclude.remove('org_id')
|
||||
unique_checks, date_checks = self._get_unique_checks(exclude=exclude)
|
||||
|
||||
errors = self._perform_unique_checks(unique_checks)
|
||||
date_errors = self._perform_date_checks(date_checks)
|
||||
|
||||
for k, v in date_errors.items():
|
||||
errors.setdefault(k, []).extend(v)
|
||||
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class OrgViewGenericMixin:
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not current_org:
|
||||
return redirect('orgs:switch-a-org')
|
||||
|
||||
if not current_org.can_admin_by(request.user):
|
||||
print("{} cannot admin {}".format(request.user, current_org))
|
||||
if request.user.is_org_admin:
|
||||
return redirect('orgs:switch-a-org')
|
||||
return HttpResponseForbidden()
|
||||
else:
|
||||
print(current_org.can_admin_by(request.user))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class RootOrgViewMixin:
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
set_to_root_org()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class OrgModelForm(ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# if 'initial' not in kwargs:
|
||||
# return
|
||||
for name, field in self.fields.items():
|
||||
if not hasattr(field, 'queryset'):
|
||||
continue
|
||||
model = field.queryset.model
|
||||
field.queryset = model.objects.all()
|
||||
|
||||
|
||||
class OrgMembershipSerializerMixin:
|
||||
def run_validation(self, initial_data=None):
|
||||
initial_data['organization'] = str(self.context['org'].id)
|
||||
return super().run_validation(initial_data)
|
||||
|
||||
|
||||
class OrgMembershipModelViewSetMixin:
|
||||
org = None
|
||||
membership_class = None
|
||||
lookup_field = 'user'
|
||||
lookup_url_kwarg = 'user_id'
|
||||
http_method_names = ['get', 'post', 'delete', 'head', 'options']
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.org = get_object_or_404(Organization, pk=kwargs.get('org_id'))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context['org'] = self.org
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = self.membership_class.objects.filter(organization=self.org)
|
||||
return queryset
|
||||
|
||||
|
||||
class OrgResourceSerializerMixin(serializers.Serializer):
|
||||
"""
|
||||
通过API批量操作资源时, 自动给每个资源添加所需属性org_id的值为current_org_id
|
||||
(同时为serializer.is_valid()对Model的unique_together校验做准备)
|
||||
由于HiddenField字段不可读,API获取资产信息时获取不到org_id,
|
||||
但是coco需要资产的org_id字段,所以修改为CharField类型
|
||||
"""
|
||||
org_id = serializers.ReadOnlyField(default=get_current_org_id, label=_("Organization"))
|
||||
org_name = serializers.ReadOnlyField(label=_("Org name"))
|
||||
|
||||
def get_validators(self):
|
||||
_validators = super().get_validators()
|
||||
validators = []
|
||||
|
||||
for v in _validators:
|
||||
if isinstance(v, UniqueTogetherValidator) \
|
||||
and "org_id" in v.fields:
|
||||
v = ProjectUniqueValidator(v.queryset, v.fields)
|
||||
validators.append(v)
|
||||
return validators
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend(["org_id", "org_name"])
|
||||
return fields
|
||||
|
||||
|
||||
class BulkOrgResourceSerializerMixin(BulkSerializerMixin, OrgResourceSerializerMixin):
|
||||
pass
|
||||
|
||||
|
||||
class BulkOrgResourceModelSerializer(BulkOrgResourceSerializerMixin, serializers.ModelSerializer):
|
||||
pass
|
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .models import *
|
||||
from .serializers import *
|
||||
from .forms import *
|
||||
from .api import *
|
|
@ -0,0 +1,56 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from common.mixins import IDInCacheFilterMixin
|
||||
|
||||
from ..utils import set_to_root_org
|
||||
from ..models import Organization
|
||||
|
||||
__all__ = [
|
||||
'RootOrgViewMixin', 'OrgMembershipModelViewSetMixin', 'OrgModelViewSet',
|
||||
'OrgBulkModelViewSet',
|
||||
]
|
||||
|
||||
|
||||
class RootOrgViewMixin:
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
set_to_root_org()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class OrgModelViewSet(IDInCacheFilterMixin, ModelViewSet):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().all()
|
||||
|
||||
|
||||
class OrgBulkModelViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().all()
|
||||
if hasattr(self, 'action') and self.action == 'list' and \
|
||||
hasattr(self, 'serializer_class') and \
|
||||
hasattr(self.serializer_class, 'setup_eager_loading'):
|
||||
queryset = self.serializer_class.setup_eager_loading(queryset)
|
||||
return queryset
|
||||
|
||||
|
||||
class OrgMembershipModelViewSetMixin:
|
||||
org = None
|
||||
membership_class = None
|
||||
lookup_field = 'user'
|
||||
lookup_url_kwarg = 'user_id'
|
||||
http_method_names = ['get', 'post', 'delete', 'head', 'options']
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.org = get_object_or_404(Organization, pk=kwargs.get('org_id'))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
context['org'] = self.org
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = self.membership_class.objects.filter(organization=self.org)
|
||||
return queryset
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django import forms
|
||||
|
||||
__all__ = ['OrgModelForm']
|
||||
|
||||
|
||||
class OrgModelForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for name, field in self.fields.items():
|
||||
if not hasattr(field, 'queryset'):
|
||||
continue
|
||||
model = field.queryset.model
|
||||
field.queryset = model.objects.all()
|
|
@ -0,0 +1,116 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import traceback
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..utils import (
|
||||
set_current_org, get_current_org, current_org,
|
||||
)
|
||||
from ..models import Organization
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
__all__ = [
|
||||
'OrgManager', 'OrgModelMixin',
|
||||
]
|
||||
|
||||
|
||||
class OrgManager(models.Manager):
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(OrgManager, self).get_queryset()
|
||||
kwargs = {}
|
||||
|
||||
_current_org = get_current_org()
|
||||
if _current_org is None:
|
||||
kwargs['id'] = None
|
||||
elif _current_org.is_real():
|
||||
kwargs['org_id'] = _current_org.id
|
||||
elif _current_org.is_default():
|
||||
queryset = queryset.filter(org_id="")
|
||||
#
|
||||
# lines = traceback.format_stack()
|
||||
# print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>")
|
||||
# for line in lines[-10:-1]:
|
||||
# print(line)
|
||||
# print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
|
||||
|
||||
queryset = queryset.filter(**kwargs)
|
||||
return queryset
|
||||
|
||||
def all(self):
|
||||
if not current_org:
|
||||
msg = 'You can `objects.set_current_org(org).all()` then run it'
|
||||
return self
|
||||
else:
|
||||
return super(OrgManager, self).all()
|
||||
|
||||
def set_current_org(self, org):
|
||||
if isinstance(org, str):
|
||||
org = Organization.get_instance(org)
|
||||
set_current_org(org)
|
||||
return self
|
||||
|
||||
|
||||
class OrgModelMixin(models.Model):
|
||||
org_id = models.CharField(max_length=36, blank=True, default='',
|
||||
verbose_name=_("Organization"), db_index=True)
|
||||
objects = OrgManager()
|
||||
|
||||
sep = '@'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if current_org is not None and current_org.is_real():
|
||||
self.org_id = current_org.id
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def org(self):
|
||||
from orgs.models import Organization
|
||||
org = Organization.get_instance(self.org_id)
|
||||
return org
|
||||
|
||||
@property
|
||||
def org_name(self):
|
||||
return self.org.name
|
||||
|
||||
@property
|
||||
def fullname(self, attr=None):
|
||||
name = ''
|
||||
if attr and hasattr(self, attr):
|
||||
name = getattr(self, attr)
|
||||
elif hasattr(self, 'name'):
|
||||
name = self.name
|
||||
elif hasattr(self, 'hostname'):
|
||||
name = self.hostname
|
||||
if self.org.is_real():
|
||||
return name + self.sep + self.org_name
|
||||
else:
|
||||
return name
|
||||
|
||||
def validate_unique(self, exclude=None):
|
||||
"""
|
||||
Check unique constraints on the model and raise ValidationError if any
|
||||
failed.
|
||||
Form 提交时会使用这个检验
|
||||
"""
|
||||
self.org_id = current_org.id if current_org.is_real() else ''
|
||||
if exclude and 'org_id' in exclude:
|
||||
exclude.remove('org_id')
|
||||
unique_checks, date_checks = self._get_unique_checks(exclude=exclude)
|
||||
|
||||
errors = self._perform_unique_checks(unique_checks)
|
||||
date_errors = self._perform_date_checks(date_checks)
|
||||
|
||||
for k, v in date_errors.items():
|
||||
errors.setdefault(k, []).extend(v)
|
||||
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
|
@ -0,0 +1,56 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from rest_framework.validators import UniqueTogetherValidator
|
||||
|
||||
from common.validators import ProjectUniqueValidator
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from ..utils import get_current_org_id_for_serializer
|
||||
|
||||
|
||||
__all__ = [
|
||||
"OrgResourceSerializerMixin", "BulkOrgResourceSerializerMixin",
|
||||
"BulkOrgResourceModelSerializer", "OrgMembershipSerializerMixin"
|
||||
]
|
||||
|
||||
|
||||
class OrgResourceSerializerMixin(serializers.Serializer):
|
||||
"""
|
||||
通过API批量操作资源时, 自动给每个资源添加所需属性org_id的值为current_org_id
|
||||
(同时为serializer.is_valid()对Model的unique_together校验做准备)
|
||||
由于HiddenField字段不可读,API获取资产信息时获取不到org_id,
|
||||
但是coco需要资产的org_id字段,所以修改为CharField类型
|
||||
"""
|
||||
org_id = serializers.ReadOnlyField(default=get_current_org_id_for_serializer, label=_("Organization"))
|
||||
org_name = serializers.ReadOnlyField(label=_("Org name"))
|
||||
|
||||
def get_validators(self):
|
||||
_validators = super().get_validators()
|
||||
validators = []
|
||||
|
||||
for v in _validators:
|
||||
if isinstance(v, UniqueTogetherValidator) \
|
||||
and "org_id" in v.fields:
|
||||
v = ProjectUniqueValidator(v.queryset, v.fields)
|
||||
validators.append(v)
|
||||
return validators
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend(["org_id", "org_name"])
|
||||
return fields
|
||||
|
||||
|
||||
class BulkOrgResourceSerializerMixin(OrgResourceSerializerMixin, BulkSerializerMixin):
|
||||
pass
|
||||
|
||||
|
||||
class BulkOrgResourceModelSerializer(BulkOrgResourceSerializerMixin, serializers.ModelSerializer):
|
||||
pass
|
||||
|
||||
|
||||
class OrgMembershipSerializerMixin:
|
||||
def run_validation(self, initial_data=None):
|
||||
initial_data['organization'] = str(self.context['org'].id)
|
||||
return super().run_validation(initial_data)
|
|
@ -1,7 +1,6 @@
|
|||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import is_uuid
|
||||
|
@ -16,9 +15,13 @@ class Organization(models.Model):
|
|||
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'))
|
||||
|
||||
orgs = None
|
||||
CACHE_PREFIX = 'JMS_ORG_{}'
|
||||
ROOT_ID_NAME = 'ROOT'
|
||||
DEFAULT_ID_NAME = 'DEFAULT'
|
||||
ROOT_ID = '00000000-0000-0000-0000-000000000000'
|
||||
ROOT_NAME = 'ROOT'
|
||||
DEFAULT_ID = 'DEFAULT'
|
||||
DEFAULT_NAME = 'DEFAULT'
|
||||
_user_admin_orgs = None
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Organization")
|
||||
|
@ -27,33 +30,30 @@ class Organization(models.Model):
|
|||
return self.name
|
||||
|
||||
def set_to_cache(self):
|
||||
key_id = self.CACHE_PREFIX.format(self.id)
|
||||
key_name = self.CACHE_PREFIX.format(self.name)
|
||||
cache.set(key_id, self, 3600)
|
||||
cache.set(key_name, self, 3600)
|
||||
if self.__class__.orgs is None:
|
||||
self.__class__.orgs = {}
|
||||
self.__class__.orgs[str(self.id)] = self
|
||||
|
||||
def expire_cache(self):
|
||||
key_id = self.CACHE_PREFIX.format(self.id)
|
||||
key_name = self.CACHE_PREFIX.format(self.name)
|
||||
cache.delete(key_id)
|
||||
cache.delete(key_name)
|
||||
self.__class__.orgs.pop(str(self.id), None)
|
||||
|
||||
@classmethod
|
||||
def get_instance_from_cache(cls, oid):
|
||||
key = cls.CACHE_PREFIX.format(oid)
|
||||
return cache.get(key, None)
|
||||
if not cls.orgs or not isinstance(cls.orgs, dict):
|
||||
return None
|
||||
return cls.orgs.get(str(oid))
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, id_or_name, default=True):
|
||||
def get_instance(cls, id_or_name, default=False):
|
||||
cached = cls.get_instance_from_cache(id_or_name)
|
||||
if cached:
|
||||
return cached
|
||||
|
||||
if not id_or_name:
|
||||
if id_or_name is None:
|
||||
return cls.default() if default else None
|
||||
elif id_or_name == cls.DEFAULT_ID_NAME:
|
||||
elif id_or_name in [cls.DEFAULT_ID, cls.DEFAULT_NAME, '']:
|
||||
return cls.default()
|
||||
elif id_or_name == cls.ROOT_ID_NAME:
|
||||
elif id_or_name in [cls.ROOT_ID, cls.ROOT_NAME]:
|
||||
return cls.root()
|
||||
|
||||
try:
|
||||
|
@ -89,10 +89,12 @@ class Organization(models.Model):
|
|||
return False
|
||||
|
||||
def is_real(self):
|
||||
return len(str(self.id)) == 36
|
||||
return self.id not in (self.DEFAULT_NAME, self.ROOT_ID)
|
||||
|
||||
@classmethod
|
||||
def get_user_admin_orgs(cls, user):
|
||||
if cls._user_admin_orgs and user.id in cls._user_admin_orgs:
|
||||
return cls._user_admin_orgs[user.id]
|
||||
admin_orgs = []
|
||||
if user.is_anonymous:
|
||||
return admin_orgs
|
||||
|
@ -101,24 +103,29 @@ class Organization(models.Model):
|
|||
admin_orgs.append(cls.default())
|
||||
elif user.is_org_admin:
|
||||
admin_orgs = user.admin_orgs.all()
|
||||
|
||||
if cls._user_admin_orgs is None:
|
||||
cls._user_admin_orgs = {user.id: admin_orgs}
|
||||
else:
|
||||
cls._user_admin_orgs[user.id] = admin_orgs
|
||||
return admin_orgs
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return cls(id=cls.DEFAULT_ID_NAME, name=cls.DEFAULT_ID_NAME)
|
||||
return cls(id=cls.DEFAULT_ID, name=cls.DEFAULT_NAME)
|
||||
|
||||
@classmethod
|
||||
def root(cls):
|
||||
return cls(id=cls.ROOT_ID_NAME, name=cls.ROOT_ID_NAME)
|
||||
return cls(id=cls.ROOT_ID, name=cls.ROOT_NAME)
|
||||
|
||||
def is_root(self):
|
||||
if self.id is self.ROOT_ID_NAME:
|
||||
if self.id is self.ROOT_ID:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_default(self):
|
||||
if self.id is self.DEFAULT_ID_NAME:
|
||||
if self.id is self.DEFAULT_ID:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.db.models.signals import post_save
|
|||
from django.dispatch import receiver
|
||||
|
||||
from .models import Organization
|
||||
from .hands import set_current_org, current_org, Node
|
||||
from .hands import set_current_org, current_org, Node, get_current_org
|
||||
from perms.models import AssetPermission
|
||||
from users.models import UserGroup
|
||||
|
||||
|
@ -14,7 +14,7 @@ from users.models import UserGroup
|
|||
@receiver(post_save, sender=Organization)
|
||||
def on_org_create_or_update(sender, instance=None, created=False, **kwargs):
|
||||
if instance:
|
||||
old_org = current_org
|
||||
old_org = get_current_org()
|
||||
set_current_org(instance)
|
||||
node_root = Node.root()
|
||||
if node_root.value != instance.name:
|
||||
|
@ -41,3 +41,8 @@ def on_org_user_changed(sender, instance=None, **kwargs):
|
|||
for user_group in user_groups:
|
||||
user_group.users.remove(user)
|
||||
set_current_org(old_org)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Organization.admins.through)
|
||||
def on_org_admin_change(sender, **kwargs):
|
||||
Organization._user_admin_orgs = None
|
||||
|
|
|
@ -1,25 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from functools import partial
|
||||
from werkzeug.local import Local
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
from common.utils import LocalProxy
|
||||
from common.local import thread_local
|
||||
from .models import Organization
|
||||
|
||||
|
||||
_thread_locals = Local()
|
||||
|
||||
|
||||
def get_org_from_request(request):
|
||||
oid = request.session.get("oid")
|
||||
if not oid:
|
||||
oid = request.META.get("HTTP_X_JMS_ORG")
|
||||
if not oid:
|
||||
oid = Organization.DEFAULT_ID
|
||||
org = Organization.get_instance(oid)
|
||||
return org
|
||||
|
||||
|
||||
def set_current_org(org):
|
||||
setattr(_thread_locals, 'current_org', org)
|
||||
setattr(thread_local, 'current_org_id', org.id)
|
||||
|
||||
|
||||
def set_to_default_org():
|
||||
|
@ -31,17 +29,27 @@ def set_to_root_org():
|
|||
|
||||
|
||||
def _find(attr):
|
||||
return getattr(_thread_locals, attr, None)
|
||||
return getattr(thread_local, attr, None)
|
||||
|
||||
|
||||
def get_current_org():
|
||||
return _find('current_org')
|
||||
org_id = get_current_org_id()
|
||||
if org_id is None:
|
||||
return None
|
||||
org = Organization.get_instance(org_id)
|
||||
return org
|
||||
|
||||
|
||||
def get_current_org_id():
|
||||
org = get_current_org()
|
||||
org_id = str(org.id) if org.is_real() else ''
|
||||
org_id = _find('current_org_id')
|
||||
return org_id
|
||||
|
||||
|
||||
current_org = LocalProxy(partial(_find, 'current_org'))
|
||||
def get_current_org_id_for_serializer():
|
||||
org_id = get_current_org_id()
|
||||
if org_id == Organization.DEFAULT_ID:
|
||||
org_id = ''
|
||||
return org_id
|
||||
|
||||
|
||||
current_org = LocalProxy(get_current_org)
|
||||
|
|
|
@ -13,7 +13,8 @@ class SwitchOrgView(DetailView):
|
|||
def get(self, request, *args, **kwargs):
|
||||
pk = kwargs.get('pk')
|
||||
self.object = Organization.get_instance(pk)
|
||||
request.session['oid'] = self.object.id.__str__()
|
||||
oid = str(self.object.id)
|
||||
request.session['oid'] = oid
|
||||
host = request.get_host()
|
||||
referer = request.META.get('HTTP_REFERER')
|
||||
if referer.find(host) != -1:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue