* [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
老广 2019-07-04 21:23:19 +08:00 committed by GitHub
commit 58875d9a95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
164 changed files with 4943 additions and 4922 deletions

View File

@ -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):
"""

View File

@ -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):

View File

@ -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):

View File

@ -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
"""

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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")),
)

View File

@ -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 = [

View File

@ -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

View File

@ -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'},
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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',
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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),
]

View File

@ -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')},
),
]

View File

@ -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')},
),
]

View File

@ -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'),
),
]

View File

@ -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',
),
]

View File

@ -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 *

View File

@ -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:

View File

@ -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):

View File

@ -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

View File

@ -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))

View File

@ -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')]

View File

@ -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()

View File

@ -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):

View File

@ -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:

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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):
"""

View File

@ -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):

View File

@ -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

View File

@ -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);

View File

@ -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 {

View File

@ -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>

View File

@ -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 %}

View File

@ -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 () {

View File

@ -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();

View File

@ -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>

View File

@ -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
}

View File

@ -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'),

View File

@ -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)
)

View File

@ -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)

View File

@ -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)

View File

@ -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'),

View File

@ -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)

View File

@ -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

View File

@ -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

42
apps/common/filters.py Normal file
View File

@ -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

9
apps/common/local.py Normal file
View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
#
from werkzeug.local import Local
thread_local = Local()
def _find(attr):
return getattr(thread_local, attr, None)

View File

@ -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

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
#
from .models import *
from .serializers import *
from .api import *
from .views import *

86
apps/common/mixins/api.py Normal file
View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

25
apps/common/struct.py Normal file
View File

@ -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)

View File

@ -112,7 +112,6 @@ def to_dict(data):
@register.filter
def sort(data):
print(data)
return sorted(data)

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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,
}

View File

@ -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

View File

@ -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

View File

@ -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'),
]

View File

@ -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():

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
#
from .models import *
from .serializers import *
from .forms import *
from .api import *

56
apps/orgs/mixins/api.py Normal file
View File

@ -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

16
apps/orgs/mixins/forms.py Normal file
View File

@ -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()

116
apps/orgs/mixins/models.py Normal file
View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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