mirror of https://github.com/jumpserver/jumpserver
commit
a5b9b4e1d2
|
@ -14,6 +14,7 @@
|
|||
# limitations under the License.
|
||||
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
@ -24,13 +25,14 @@ from common.utils import get_logger
|
|||
from ..hands import IsOrgAdmin
|
||||
from ..models import AdminUser, Asset
|
||||
from .. import serializers
|
||||
from ..tasks import test_admin_user_connectability_manual
|
||||
from ..tasks import test_admin_user_connectivity_manual
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AdminUserViewSet', 'ReplaceNodesAdminUserApi',
|
||||
'AdminUserTestConnectiveApi', 'AdminUserAuthApi',
|
||||
'AdminUserAssetsListView',
|
||||
]
|
||||
|
||||
|
||||
|
@ -81,12 +83,29 @@ class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
|
|||
|
||||
class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test asset admin user connectivity
|
||||
Test asset admin user assets_connectivity
|
||||
"""
|
||||
queryset = AdminUser.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
task = test_admin_user_connectability_manual.delay(admin_user)
|
||||
task = test_admin_user_connectivity_manual.delay(admin_user)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class AdminUserAssetsListView(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSimpleSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
filter_fields = ("hostname", "ip")
|
||||
http_method_names = ['get']
|
||||
search_fields = filter_fields
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
return get_object_or_404(AdminUser, pk=pk)
|
||||
|
||||
def get_queryset(self):
|
||||
admin_user = self.get_object()
|
||||
return admin_user.get_related_assets()
|
||||
|
|
|
@ -17,7 +17,7 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
|||
from ..models import Asset, AdminUser, Node
|
||||
from .. import serializers
|
||||
from ..tasks import update_asset_hardware_info_manual, \
|
||||
test_asset_connectability_manual
|
||||
test_asset_connectivity_manual
|
||||
from ..utils import LabelFilter
|
||||
|
||||
|
||||
|
@ -41,40 +41,46 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
|||
pagination_class = LimitOffsetPagination
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def filter_node(self):
|
||||
def filter_node(self, queryset):
|
||||
node_id = self.request.query_params.get("node_id")
|
||||
if not node_id:
|
||||
return
|
||||
return queryset
|
||||
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
show_current_asset = self.request.query_params.get("show_current_asset") in ('1', 'true')
|
||||
|
||||
if node.is_root():
|
||||
if show_current_asset:
|
||||
self.queryset = self.queryset.filter(
|
||||
Q(nodes=node_id) | Q(nodes__isnull=True)
|
||||
)
|
||||
return
|
||||
if show_current_asset:
|
||||
self.queryset = self.queryset.filter(nodes=node)
|
||||
if node.is_root() and show_current_asset:
|
||||
queryset = queryset.filter(
|
||||
Q(nodes=node_id) | Q(nodes__isnull=True)
|
||||
)
|
||||
elif node.is_root() and not show_current_asset:
|
||||
pass
|
||||
elif not node.is_root() and show_current_asset:
|
||||
queryset = queryset.filter(nodes=node)
|
||||
else:
|
||||
self.queryset = self.queryset.filter(
|
||||
queryset = queryset.filter(
|
||||
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_admin_user_id(self):
|
||||
def filter_admin_user_id(self, queryset):
|
||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||
if admin_user_id:
|
||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||
self.queryset = self.queryset.filter(admin_user=admin_user)
|
||||
if not admin_user_id:
|
||||
return queryset
|
||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||
queryset = queryset.filter(admin_user=admin_user)
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
queryset = self.filter_node(queryset)
|
||||
queryset = self.filter_admin_user_id(queryset)
|
||||
return queryset
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = super().get_queryset()\
|
||||
.prefetch_related('labels', 'nodes')\
|
||||
.select_related('admin_user')
|
||||
self.filter_admin_user_id()
|
||||
self.filter_node()
|
||||
return self.queryset.distinct()
|
||||
queryset = super().get_queryset().distinct()
|
||||
queryset = self.get_serializer_class().setup_eager_loading(queryset)
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
|
@ -103,7 +109,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
|||
|
||||
class AssetAdminUserTestApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test asset admin user connectivity
|
||||
Test asset admin user assets_connectivity
|
||||
"""
|
||||
queryset = Asset.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
@ -111,7 +117,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
|
|||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
task = test_asset_connectability_manual.delay(asset)
|
||||
task = test_asset_connectivity_manual.delay(asset)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
|
|
|
@ -17,15 +17,14 @@ from rest_framework import generics, mixins, viewsets
|
|||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import Count
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from common.tree import TreeNodeSerializer
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import Node
|
||||
from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util
|
||||
from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util
|
||||
from .. import serializers
|
||||
|
||||
|
||||
|
@ -34,7 +33,8 @@ __all__ = [
|
|||
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
|
||||
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
|
||||
'TestNodeConnectiveApi'
|
||||
'TestNodeConnectiveApi', 'NodeListAsTreeApi',
|
||||
'NodeChildrenAsTreeApi',
|
||||
]
|
||||
|
||||
|
||||
|
@ -43,22 +43,89 @@ class NodeViewSet(viewsets.ModelViewSet):
|
|||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
|
||||
def perform_create(self, serializer):
|
||||
child_key = Node.root().get_next_child_key()
|
||||
serializer.validated_data["key"] = child_key
|
||||
serializer.save()
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
node = self.get_object()
|
||||
if node.is_root():
|
||||
node_value = node.value
|
||||
post_value = request.data.get('value')
|
||||
if node_value != post_value:
|
||||
return Response(
|
||||
{"msg": _("You can't update the root node name")},
|
||||
status=400
|
||||
)
|
||||
return super().update(request, *args, **kwargs)
|
||||
class NodeListAsTreeApi(generics.ListAPIView):
|
||||
"""
|
||||
获取节点列表树
|
||||
[
|
||||
{
|
||||
"id": "",
|
||||
"name": "",
|
||||
"pId": "",
|
||||
"meta": ""
|
||||
}
|
||||
]
|
||||
"""
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
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)
|
||||
return queryset
|
||||
|
||||
@staticmethod
|
||||
def refresh_nodes(queryset):
|
||||
Node.expire_nodes_assets_amount()
|
||||
Node.expire_nodes_full_value()
|
||||
return queryset
|
||||
|
||||
|
||||
class NodeChildrenAsTreeApi(generics.ListAPIView):
|
||||
"""
|
||||
节点子节点作为树返回,
|
||||
[
|
||||
{
|
||||
"id": "",
|
||||
"name": "",
|
||||
"pId": "",
|
||||
"meta": ""
|
||||
}
|
||||
]
|
||||
|
||||
"""
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = TreeNodeSerializer
|
||||
node = None
|
||||
is_root = False
|
||||
|
||||
def get_queryset(self):
|
||||
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))
|
||||
queryset = [node.as_tree_node() for node in queryset]
|
||||
return queryset
|
||||
|
||||
def filter_assets(self, queryset):
|
||||
include_assets = self.request.query_params.get('assets', '0') == '1'
|
||||
if not include_assets:
|
||||
return queryset
|
||||
assets = self.node.get_assets()
|
||||
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):
|
||||
if self.request.query_params.get('refresh', '0') == '1':
|
||||
Node.expire_nodes_assets_amount()
|
||||
Node.expire_nodes_full_value()
|
||||
return queryset
|
||||
|
||||
|
||||
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||
|
@ -67,19 +134,10 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
|||
serializer_class = serializers.NodeSerializer
|
||||
instance = None
|
||||
|
||||
def counter(self):
|
||||
values = [
|
||||
child.value[child.value.rfind(' '):]
|
||||
for child in self.get_object().get_children()
|
||||
if child.value.startswith("新节点 ")
|
||||
]
|
||||
values = [int(value) for value in values if value.strip().isdigit()]
|
||||
count = max(values)+1 if values else 1
|
||||
return count
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
if not request.data.get("value"):
|
||||
request.data["value"] = _("New node {}").format(self.counter())
|
||||
request.data["value"] = instance.get_next_child_preset_name()
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
|
@ -91,10 +149,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
|||
'The same level node name cannot be the same'
|
||||
)
|
||||
node = instance.create_child(value=value)
|
||||
return Response(
|
||||
{"id": node.id, "key": node.key, "value": node.value},
|
||||
status=201,
|
||||
)
|
||||
return Response(self.serializer_class(instance=node).data, status=201)
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
|
||||
|
@ -107,7 +162,6 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
|||
def get_queryset(self):
|
||||
queryset = []
|
||||
query_all = self.request.query_params.get("all")
|
||||
query_assets = self.request.query_params.get('assets')
|
||||
node = self.get_object()
|
||||
|
||||
if node is None:
|
||||
|
@ -120,23 +174,8 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
|||
else:
|
||||
children = node.get_children()
|
||||
queryset.extend(list(children))
|
||||
|
||||
if query_assets:
|
||||
assets = node.get_assets()
|
||||
for asset in assets:
|
||||
node_fake = Node()
|
||||
node_fake.assets__count = 0
|
||||
node_fake.id = asset.id
|
||||
node_fake.is_node = False
|
||||
node_fake.key = node.key + ':0'
|
||||
node_fake.value = asset.hostname
|
||||
queryset.append(node_fake)
|
||||
queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True)
|
||||
return queryset
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
|
||||
class NodeAssetsApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
@ -234,5 +273,5 @@ class TestNodeConnectiveApi(APIView):
|
|||
assets = node.assets.all()
|
||||
# task_name = _("测试节点下资产是否可连接: {}".format(node.name))
|
||||
task_name = _("Test if the assets under the node are connectable: {}".format(node.name))
|
||||
task = test_asset_connectability_util.delay(assets, task_name=task_name)
|
||||
task = test_asset_connectivity_util.delay(assets, task_name=task_name)
|
||||
return Response({"task": task.id})
|
||||
|
|
|
@ -24,8 +24,8 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
|||
from ..models import SystemUser, Asset
|
||||
from .. import serializers
|
||||
from ..tasks import push_system_user_to_assets_manual, \
|
||||
test_system_user_connectability_manual, push_system_user_a_asset_manual, \
|
||||
test_system_user_connectability_a_asset
|
||||
test_system_user_connectivity_manual, push_system_user_a_asset_manual, \
|
||||
test_system_user_connectivity_a_asset
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
@ -33,7 +33,7 @@ __all__ = [
|
|||
'SystemUserViewSet', 'SystemUserAuthInfoApi',
|
||||
'SystemUserPushApi', 'SystemUserTestConnectiveApi',
|
||||
'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
|
||||
'SystemUserTestAssetConnectabilityApi', 'SystemUserCommandFilterRuleListApi',
|
||||
'SystemUserTestAssetConnectivityApi', 'SystemUserCommandFilterRuleListApi',
|
||||
|
||||
]
|
||||
|
||||
|
@ -93,15 +93,16 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
|
|||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
task = test_system_user_connectability_manual.delay(system_user)
|
||||
task = test_system_user_connectivity_manual.delay(system_user)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class SystemUserAssetsListView(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSerializer
|
||||
serializer_class = serializers.AssetSimpleSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
filter_fields = ("hostname", "ip")
|
||||
http_method_names = ['get']
|
||||
search_fields = filter_fields
|
||||
|
||||
def get_object(self):
|
||||
|
@ -125,7 +126,7 @@ class SystemUserPushToAssetApi(generics.RetrieveAPIView):
|
|||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class SystemUserTestAssetConnectabilityApi(generics.RetrieveAPIView):
|
||||
class SystemUserTestAssetConnectivityApi(generics.RetrieveAPIView):
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
@ -133,7 +134,7 @@ class SystemUserTestAssetConnectabilityApi(generics.RetrieveAPIView):
|
|||
system_user = self.get_object()
|
||||
asset_id = self.kwargs.get('aid')
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
task = test_system_user_connectability_a_asset.delay(system_user, asset)
|
||||
task = test_system_user_connectivity_a_asset.delay(system_user, asset)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
|
|
|
@ -99,8 +99,8 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
|
|||
auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
|
||||
private_key, public_key = super().gen_keys()
|
||||
|
||||
if login_mode == SystemUser.MANUAL_LOGIN or \
|
||||
protocol in [SystemUser.RDP_PROTOCOL, SystemUser.TELNET_PROTOCOL]:
|
||||
if login_mode == SystemUser.LOGIN_MANUAL or \
|
||||
protocol in [SystemUser.PROTOCOL_RDP, SystemUser.PROTOCOL_TELNET]:
|
||||
system_user.auto_push = 0
|
||||
auto_generate_key = False
|
||||
system_user.save()
|
||||
|
@ -124,7 +124,7 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
|
|||
validated = super().is_valid()
|
||||
username = self.cleaned_data.get('username')
|
||||
login_mode = self.cleaned_data.get('login_mode')
|
||||
if login_mode == SystemUser.AUTO_LOGIN and not username:
|
||||
if login_mode == SystemUser.LOGIN_AUTO and not username:
|
||||
self.add_error(
|
||||
"username", _('* Automatic login mode,'
|
||||
' must fill in the username.')
|
||||
|
|
|
@ -13,36 +13,36 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='domain',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='label',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -16,7 +16,7 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='CommandFilter',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=64, verbose_name='Name')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
|
@ -32,7 +32,7 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='CommandFilterRule',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('type', models.CharField(choices=[('regex', 'Regex'), ('command', 'Command')], default='command', max_length=16, verbose_name='Type')),
|
||||
('priority', models.IntegerField(default=50, help_text='1-100, the lower will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
|
||||
|
|
|
@ -13,7 +13,6 @@ from django.db.models import Q
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.cache import cache
|
||||
|
||||
from ..const import ASSET_ADMIN_CONN_CACHE_KEY
|
||||
from .user import AdminUser, SystemUser
|
||||
from orgs.mixins import OrgModelMixin, OrgManager
|
||||
|
||||
|
@ -75,63 +74,48 @@ class Asset(OrgModelMixin):
|
|||
protocol = models.CharField(max_length=128, default=SSH_PROTOCOL, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
|
||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True,
|
||||
related_name='assets', verbose_name=_("Domain"),
|
||||
on_delete=models.SET_NULL)
|
||||
nodes = models.ManyToManyField('assets.Node', default=default_node,
|
||||
related_name='assets',
|
||||
verbose_name=_("Nodes"))
|
||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
||||
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
|
||||
# Auth
|
||||
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT,
|
||||
null=True, verbose_name=_("Admin user"))
|
||||
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
|
||||
|
||||
# Some information
|
||||
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
|
||||
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
|
||||
|
||||
# Collect
|
||||
vendor = models.CharField(max_length=64, null=True, blank=True,
|
||||
verbose_name=_('Vendor'))
|
||||
model = models.CharField(max_length=54, null=True, blank=True,
|
||||
verbose_name=_('Model'))
|
||||
sn = models.CharField(max_length=128, null=True, blank=True,
|
||||
verbose_name=_('Serial number'))
|
||||
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
|
||||
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
|
||||
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
|
||||
|
||||
cpu_model = models.CharField(max_length=64, null=True, blank=True,
|
||||
verbose_name=_('CPU model'))
|
||||
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
|
||||
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
|
||||
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
|
||||
cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus'))
|
||||
memory = models.CharField(max_length=64, null=True, blank=True,
|
||||
verbose_name=_('Memory'))
|
||||
disk_total = models.CharField(max_length=1024, null=True, blank=True,
|
||||
verbose_name=_('Disk total'))
|
||||
disk_info = models.CharField(max_length=1024, null=True, blank=True,
|
||||
verbose_name=_('Disk info'))
|
||||
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
|
||||
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
|
||||
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
|
||||
|
||||
os = models.CharField(max_length=128, null=True, blank=True,
|
||||
verbose_name=_('OS'))
|
||||
os_version = models.CharField(max_length=16, null=True, blank=True,
|
||||
verbose_name=_('OS version'))
|
||||
os_arch = models.CharField(max_length=16, blank=True, null=True,
|
||||
verbose_name=_('OS arch'))
|
||||
hostname_raw = models.CharField(max_length=128, blank=True, null=True,
|
||||
verbose_name=_('Hostname raw'))
|
||||
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
|
||||
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
|
||||
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
||||
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
|
||||
|
||||
labels = models.ManyToManyField('assets.Label', blank=True,
|
||||
related_name='assets',
|
||||
verbose_name=_("Labels"))
|
||||
created_by = models.CharField(max_length=32, null=True, blank=True,
|
||||
verbose_name=_('Created by'))
|
||||
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'))
|
||||
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
|
||||
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
||||
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 = OrgManager.from_queryset(AssetQuerySet)()
|
||||
CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}'
|
||||
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
|
||||
CONNECTIVITY_CHOICES = (
|
||||
(UNREACHABLE, _("Unreachable")),
|
||||
(REACHABLE, _('Reachable')),
|
||||
(UNKNOWN, _("Unknown")),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return '{0.hostname}({0.ip})'.format(self)
|
||||
|
@ -197,25 +181,17 @@ class Asset(OrgModelMixin):
|
|||
return ''
|
||||
|
||||
@property
|
||||
def is_connective(self):
|
||||
def connectivity(self):
|
||||
if not self.is_unixlike():
|
||||
return True
|
||||
val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname))
|
||||
if val == 1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return self.UNKNOWN
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
cached = cache.get(key, None)
|
||||
return cached if cached is not None else self.UNKNOWN
|
||||
|
||||
def to_json(self):
|
||||
info = {
|
||||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'port': self.port,
|
||||
}
|
||||
if self.domain and self.domain.gateway_set.all():
|
||||
info["gateways"] = [d.id for d in self.domain.gateway_set.all()]
|
||||
return info
|
||||
@connectivity.setter
|
||||
def connectivity(self, value):
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
cache.set(key, value, 3600*2)
|
||||
|
||||
def get_auth_info(self):
|
||||
if self.admin_user:
|
||||
|
@ -236,11 +212,20 @@ class Asset(OrgModelMixin):
|
|||
fake_node.is_node = False
|
||||
return fake_node
|
||||
|
||||
def to_json(self):
|
||||
info = {
|
||||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'port': self.port,
|
||||
}
|
||||
if self.domain and self.domain.gateway_set.all():
|
||||
info["gateways"] = [d.id for d in self.domain.gateway_set.all()]
|
||||
return info
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""
|
||||
Ansible use it create inventory, First using asset user,
|
||||
otherwise using cluster admin user
|
||||
|
||||
Ansible use it create inventory
|
||||
Todo: May be move to ops implements it
|
||||
"""
|
||||
data = self.to_json()
|
||||
|
@ -255,6 +240,36 @@ class Asset(OrgModelMixin):
|
|||
})
|
||||
return data
|
||||
|
||||
def as_tree_node(self, parent_node):
|
||||
from common.tree import TreeNode
|
||||
icon_skin = 'file'
|
||||
if self.platform.lower() == 'windows':
|
||||
icon_skin = 'windows'
|
||||
elif self.platform.lower() == 'linux':
|
||||
icon_skin = 'linux'
|
||||
data = {
|
||||
'id': str(self.id),
|
||||
'name': self.hostname,
|
||||
'title': self.ip,
|
||||
'pId': parent_node.key,
|
||||
'isParent': False,
|
||||
'open': False,
|
||||
'iconSkin': icon_skin,
|
||||
'meta': {
|
||||
'type': 'asset',
|
||||
'asset': {
|
||||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'port': self.port,
|
||||
'platform': self.platform,
|
||||
'protocol': self.protocol,
|
||||
}
|
||||
}
|
||||
}
|
||||
tree_node = TreeNode(**data)
|
||||
return tree_node
|
||||
|
||||
class Meta:
|
||||
unique_together = [('org_id', 'hostname')]
|
||||
verbose_name = _("Asset")
|
||||
|
|
|
@ -29,6 +29,13 @@ class AssetUser(OrgModelMixin):
|
|||
date_updated = models.DateTimeField(auto_now=True)
|
||||
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")),
|
||||
)
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
if self._password:
|
||||
|
|
|
@ -5,6 +5,7 @@ import uuid
|
|||
from django.db import models, transaction
|
||||
from django.db.models import Q
|
||||
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
|
||||
|
@ -22,7 +23,9 @@ class Node(OrgModelMixin):
|
|||
date_create = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
is_node = True
|
||||
_full_value_cache_key_prefix = '_NODE_VALUE_{}'
|
||||
_assets_amount = None
|
||||
_full_value_cache_key = '_NODE_VALUE_{}'
|
||||
_assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Node")
|
||||
|
@ -49,30 +52,65 @@ class Node(OrgModelMixin):
|
|||
def name(self):
|
||||
return self.value
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
"""
|
||||
获取节点下所有资产数量速度太慢,所以需要重写,使用cache等方案
|
||||
:return:
|
||||
"""
|
||||
if self._assets_amount is not None:
|
||||
return self._assets_amount
|
||||
cache_key = self._assets_amount_cache_key.format(self.key)
|
||||
cached = cache.get(cache_key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
assets_amount = self.get_all_assets().count()
|
||||
cache.set(cache_key, assets_amount, 3600)
|
||||
return assets_amount
|
||||
|
||||
@assets_amount.setter
|
||||
def assets_amount(self, value):
|
||||
self._assets_amount = value
|
||||
|
||||
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.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_prefix.format(self.key)
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cached = cache.get(key)
|
||||
if cached:
|
||||
return cached
|
||||
value = self.get_full_value()
|
||||
self.cache_full_value(value)
|
||||
return value
|
||||
|
||||
def get_full_value(self):
|
||||
# ancestor = [a.value for a in self.get_ancestor(with_self=True)]
|
||||
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 cache_full_value(self, value):
|
||||
key = self._full_value_cache_key_prefix.format(self.key)
|
||||
cache.set(key, value, 3600)
|
||||
|
||||
def expire_full_value(self):
|
||||
key = self._full_value_cache_key_prefix.format(self.key)
|
||||
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+'*')
|
||||
|
||||
@property
|
||||
|
@ -85,6 +123,17 @@ class Node(OrgModelMixin):
|
|||
self.save()
|
||||
return "{}:{}".format(self.key, mark)
|
||||
|
||||
def get_next_child_preset_name(self):
|
||||
name = ugettext("New node")
|
||||
values = [
|
||||
child.value[child.value.rfind(' '):]
|
||||
for child in self.get_children()
|
||||
if child.value.startswith(name)
|
||||
]
|
||||
values = [int(value) for value in values if value.strip().isdigit()]
|
||||
count = max(values) + 1 if values else 1
|
||||
return '{} {}'.format(name, count)
|
||||
|
||||
def create_child(self, value):
|
||||
with transaction.atomic():
|
||||
child_key = self.get_next_child_key()
|
||||
|
@ -134,7 +183,7 @@ class Node(OrgModelMixin):
|
|||
pattern = r'^{0}$|^{0}:'.format(self.key)
|
||||
args = []
|
||||
kwargs = {}
|
||||
if self.is_default_node():
|
||||
if self.is_root():
|
||||
args.append(Q(nodes__key__regex=pattern) | Q(nodes=None))
|
||||
else:
|
||||
kwargs['nodes__key__regex'] = pattern
|
||||
|
@ -182,17 +231,18 @@ class Node(OrgModelMixin):
|
|||
child.save()
|
||||
self.save()
|
||||
|
||||
def get_ancestor(self, with_self=False):
|
||||
if self.is_root():
|
||||
root = self.__class__.root()
|
||||
return [root]
|
||||
_key = self.key.split(':')
|
||||
def get_ancestor_keys(self, with_self=False):
|
||||
parent_keys = []
|
||||
key_list = self.key.split(":")
|
||||
if not with_self:
|
||||
_key.pop()
|
||||
ancestor_keys = []
|
||||
for i in range(len(_key)):
|
||||
ancestor_keys.append(':'.join(_key))
|
||||
_key.pop()
|
||||
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')
|
||||
|
@ -227,9 +277,25 @@ class Node(OrgModelMixin):
|
|||
defaults = {'value': 'Default'}
|
||||
return cls.objects.get_or_create(defaults=defaults, key='1')
|
||||
|
||||
@classmethod
|
||||
def get_tree_name_ref(cls):
|
||||
pass
|
||||
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,
|
||||
'title': name,
|
||||
'pId': self.parent_key,
|
||||
'isParent': True,
|
||||
'open': self.is_root(),
|
||||
'meta': {
|
||||
'node': node_serializer.data,
|
||||
'type': 'node'
|
||||
}
|
||||
}
|
||||
tree_node = TreeNode(**data)
|
||||
return tree_node
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=100):
|
||||
|
|
|
@ -14,7 +14,7 @@ from ..const import SYSTEM_USER_CONN_CACHE_KEY
|
|||
from .base import AssetUser
|
||||
|
||||
|
||||
__all__ = ['AdminUser', 'SystemUser',]
|
||||
__all__ = ['AdminUser', 'SystemUser']
|
||||
logger = logging.getLogger(__name__)
|
||||
signer = get_signer()
|
||||
|
||||
|
@ -31,6 +31,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_{}'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -67,6 +68,23 @@ class AdminUser(AssetUser):
|
|||
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')]
|
||||
|
@ -94,34 +112,34 @@ class AdminUser(AssetUser):
|
|||
|
||||
|
||||
class SystemUser(AssetUser):
|
||||
SSH_PROTOCOL = 'ssh'
|
||||
RDP_PROTOCOL = 'rdp'
|
||||
TELNET_PROTOCOL = 'telnet'
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
PROTOCOL_TELNET = 'telnet'
|
||||
PROTOCOL_CHOICES = (
|
||||
(SSH_PROTOCOL, 'ssh'),
|
||||
(RDP_PROTOCOL, 'rdp'),
|
||||
(TELNET_PROTOCOL, 'telnet (beta)'),
|
||||
(PROTOCOL_SSH, 'ssh'),
|
||||
(PROTOCOL_RDP, 'rdp'),
|
||||
(PROTOCOL_TELNET, 'telnet (beta)'),
|
||||
)
|
||||
|
||||
AUTO_LOGIN = 'auto'
|
||||
MANUAL_LOGIN = 'manual'
|
||||
LOGIN_AUTO = 'auto'
|
||||
LOGIN_MANUAL = 'manual'
|
||||
LOGIN_MODE_CHOICES = (
|
||||
(AUTO_LOGIN, _('Automatic login')),
|
||||
(MANUAL_LOGIN, _('Manually login'))
|
||||
(LOGIN_AUTO, _('Automatic login')),
|
||||
(LOGIN_MANUAL, _('Manually login'))
|
||||
)
|
||||
|
||||
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
|
||||
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
|
||||
priority = models.IntegerField(default=20, verbose_name=_("Priority"),
|
||||
validators=[MinValueValidator(1), MaxValueValidator(100)])
|
||||
priority = models.IntegerField(default=20, verbose_name=_("Priority"), validators=[MinValueValidator(1), MaxValueValidator(100)])
|
||||
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
|
||||
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
||||
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
|
||||
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
|
||||
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=AUTO_LOGIN, max_length=10, verbose_name=_('Login mode'))
|
||||
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)
|
||||
|
||||
cache_key = "__SYSTEM_USER_CACHED_{}"
|
||||
SYSTEM_USER_CACHE_KEY = "__SYSTEM_USER_CACHED_{}"
|
||||
CONNECTIVE_CACHE_KEY = '_JMS_SYSTEM_USER_CONNECTIVE_{}'
|
||||
|
||||
def __str__(self):
|
||||
return '{0.name}({0.username})'.format(self)
|
||||
|
@ -136,34 +154,61 @@ class SystemUser(AssetUser):
|
|||
'auto_push': self.auto_push,
|
||||
}
|
||||
|
||||
def get_assets(self):
|
||||
def get_related_assets(self):
|
||||
assets = set(self.assets.all())
|
||||
return assets
|
||||
|
||||
@property
|
||||
def assets_connective(self):
|
||||
_result = cache.get(SYSTEM_USER_CONN_CACHE_KEY.format(self.name), {})
|
||||
return _result
|
||||
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']
|
||||
|
||||
for host in value.get('dark', {}).keys():
|
||||
if host not in unreachable:
|
||||
unreachable.append(host)
|
||||
if host in reachable:
|
||||
reachable.remove(host)
|
||||
for host in value.get('contacted'):
|
||||
if host not in reachable:
|
||||
reachable.append(host)
|
||||
if host in unreachable:
|
||||
unreachable.remove(host)
|
||||
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
|
||||
cache.set(cache_key, data, 3600)
|
||||
|
||||
@property
|
||||
def unreachable_assets(self):
|
||||
return list(self.assets_connective.get('dark', {}).keys())
|
||||
def assets_unreachable(self):
|
||||
return self.connectivity.get('unreachable')
|
||||
|
||||
@property
|
||||
def reachable_assets(self):
|
||||
return self.assets_connective.get('contacted', [])
|
||||
def assets_reachable(self):
|
||||
return self.connectivity.get('reachable')
|
||||
|
||||
@property
|
||||
def login_mode_display(self):
|
||||
return self.get_login_mode_display()
|
||||
|
||||
def is_need_push(self):
|
||||
if self.auto_push and self.protocol == self.__class__.SSH_PROTOCOL:
|
||||
if self.auto_push and self.protocol == self.PROTOCOL_SSH:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_cache(self):
|
||||
cache.set(self.cache_key.format(self.id), self, 3600)
|
||||
cache.set(self.SYSTEM_USER_CACHE_KEY.format(self.id), self, 3600)
|
||||
|
||||
def expire_cache(self):
|
||||
cache.delete(self.cache_key.format(self.id))
|
||||
cache.delete(self.SYSTEM_USER_CACHE_KEY.format(self.id))
|
||||
|
||||
@property
|
||||
def cmd_filter_rules(self):
|
||||
|
@ -184,7 +229,7 @@ class SystemUser(AssetUser):
|
|||
|
||||
@classmethod
|
||||
def get_system_user_by_id_or_cached(cls, sid):
|
||||
cached = cache.get(cls.cache_key.format(sid))
|
||||
cached = cache.get(cls.SYSTEM_USER_CACHE_KEY.format(sid))
|
||||
if cached:
|
||||
return cached
|
||||
try:
|
||||
|
|
|
@ -9,6 +9,7 @@ from .system_user import AssetSystemUserSerializer
|
|||
|
||||
__all__ = [
|
||||
'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer',
|
||||
'AssetAsNodeSerializer', 'AssetSimpleSerializer',
|
||||
]
|
||||
|
||||
|
||||
|
@ -22,14 +23,27 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
fields = '__all__'
|
||||
validators = []
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.prefetch_related('labels', 'nodes')\
|
||||
.select_related('admin_user')
|
||||
return queryset
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend([
|
||||
'hardware_info', 'is_connective', 'org_name'
|
||||
'hardware_info', 'connectivity', 'org_name'
|
||||
])
|
||||
return fields
|
||||
|
||||
|
||||
class AssetAsNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['id', 'hostname', 'ip', 'port', 'platform', 'protocol']
|
||||
|
||||
|
||||
class AssetGrantedSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
被授权资产的数据结构
|
||||
|
@ -64,3 +78,9 @@ class MyAssetGrantedSerializer(AssetGrantedSerializer):
|
|||
"is_active", "system_users_join", "org_name",
|
||||
"os", "platform", "comment", "org_id", "protocol"
|
||||
)
|
||||
|
||||
|
||||
class AssetSimpleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['id', 'hostname', 'port', 'ip', 'connectivity']
|
||||
|
|
|
@ -8,84 +8,33 @@ from .asset import AssetGrantedSerializer
|
|||
|
||||
|
||||
__all__ = [
|
||||
'NodeSerializer', "NodeGrantedSerializer", "NodeAddChildrenSerializer",
|
||||
'NodeSerializer', "NodeAddChildrenSerializer",
|
||||
"NodeAssetsSerializer",
|
||||
]
|
||||
|
||||
|
||||
class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
"""
|
||||
授权资产组
|
||||
"""
|
||||
assets_granted = AssetGrantedSerializer(many=True, read_only=True)
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
parent = serializers.SerializerMethodField()
|
||||
name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = [
|
||||
'id', 'key', 'name', 'value', 'parent',
|
||||
'assets_granted', 'assets_amount', 'org_id',
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return len(obj.assets_granted)
|
||||
|
||||
@staticmethod
|
||||
def get_name(obj):
|
||||
return obj.name
|
||||
|
||||
@staticmethod
|
||||
def get_parent(obj):
|
||||
return obj.parent.id
|
||||
|
||||
|
||||
class NodeSerializer(serializers.ModelSerializer):
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
tree_id = serializers.SerializerMethodField()
|
||||
tree_parent = serializers.SerializerMethodField()
|
||||
assets_amount = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = [
|
||||
'id', 'key', 'value', 'assets_amount',
|
||||
'is_node', 'org_id', 'tree_id', 'tree_parent',
|
||||
'id', 'key', 'value', 'assets_amount', 'org_id',
|
||||
]
|
||||
read_only_fields = [
|
||||
'id', 'key', 'assets_amount', 'org_id',
|
||||
]
|
||||
list_serializer_class = BulkListSerializer
|
||||
|
||||
def validate(self, data):
|
||||
value = data.get('value')
|
||||
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 value in values:
|
||||
if data in values:
|
||||
raise serializers.ValidationError(
|
||||
'The same level node name cannot be the same'
|
||||
)
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
if hasattr(obj, 'assets_amount'):
|
||||
return obj.assets_amount
|
||||
return obj.get_all_assets().count()
|
||||
|
||||
@staticmethod
|
||||
def get_tree_id(obj):
|
||||
return obj.key
|
||||
|
||||
@staticmethod
|
||||
def get_tree_parent(obj):
|
||||
return obj.parent_key
|
||||
|
||||
def get_fields(self):
|
||||
fields = super().get_fields()
|
||||
field = fields["key"]
|
||||
field.required = False
|
||||
return fields
|
||||
|
||||
|
||||
class NodeAssetsSerializer(serializers.ModelSerializer):
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
@ -97,3 +46,4 @@ class NodeAssetsSerializer(serializers.ModelSerializer):
|
|||
|
||||
class NodeAddChildrenSerializer(serializers.Serializer):
|
||||
nodes = serializers.ListField()
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from ..models import SystemUser
|
||||
from ..models import SystemUser, Asset
|
||||
from .base import AuthSerializer
|
||||
|
||||
|
||||
|
@ -21,17 +21,17 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
|||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.extend([
|
||||
'get_login_mode_display',
|
||||
'login_mode_display',
|
||||
])
|
||||
return fields
|
||||
|
||||
@staticmethod
|
||||
def get_unreachable_assets(obj):
|
||||
return obj.unreachable_assets
|
||||
return obj.assets_unreachable
|
||||
|
||||
@staticmethod
|
||||
def get_reachable_assets(obj):
|
||||
return obj.reachable_assets
|
||||
return obj.assets_reachable
|
||||
|
||||
def get_unreachable_amount(self, obj):
|
||||
return len(self.get_unreachable_assets(obj))
|
||||
|
@ -41,7 +41,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
|||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return len(obj.get_assets())
|
||||
return len(obj.get_related_assets())
|
||||
|
||||
|
||||
class SystemUserAuthSerializer(AuthSerializer):
|
||||
|
@ -75,4 +75,7 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer):
|
|||
"""
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = ('id', 'name', 'username')
|
||||
fields = ('id', 'name', 'username')
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from collections import defaultdict
|
||||
from django.db.models.signals import post_save, m2m_changed
|
||||
from django.db.models.signals import post_save, m2m_changed, post_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
from common.utils import get_logger
|
||||
from .models import Asset, SystemUser, Node
|
||||
from .tasks import update_assets_hardware_info_util, \
|
||||
test_asset_connectability_util, push_system_user_to_assets
|
||||
test_asset_connectivity_util, push_system_user_to_assets
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
@ -19,8 +19,8 @@ def update_asset_hardware_info_on_created(asset):
|
|||
|
||||
|
||||
def test_asset_conn_on_created(asset):
|
||||
logger.debug("Test asset `{}` connectability".format(asset))
|
||||
test_asset_connectability_util.delay([asset])
|
||||
logger.debug("Test asset `{}` connectivity".format(asset))
|
||||
test_asset_connectivity_util.delay([asset])
|
||||
|
||||
|
||||
def set_asset_root_node(asset):
|
||||
|
@ -35,6 +35,17 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
|||
update_asset_hardware_info_on_created(instance)
|
||||
test_asset_conn_on_created(instance)
|
||||
|
||||
# 过期节点资产数量
|
||||
nodes = instance.nodes.all()
|
||||
Node.expire_nodes_assets_amount(nodes)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Asset, dispatch_uid="my_unique_identifier")
|
||||
def on_asset_delete(sender, instance=None, **kwargs):
|
||||
# 过期节点资产数量
|
||||
nodes = instance.nodes.all()
|
||||
Node.expire_nodes_assets_amount(nodes)
|
||||
|
||||
|
||||
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
|
||||
def on_system_user_update(sender, instance=None, created=True, **kwargs):
|
||||
|
@ -63,10 +74,11 @@ def on_system_user_assets_change(sender, instance=None, **kwargs):
|
|||
|
||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||
def on_asset_node_changed(sender, instance=None, **kwargs):
|
||||
logger.debug("Asset node change signal received")
|
||||
if isinstance(instance, Asset):
|
||||
if kwargs['action'] == 'post_add':
|
||||
logger.debug("Asset node change signal received")
|
||||
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
Node.expire_nodes_assets_amount(nodes)
|
||||
system_users_assets = defaultdict(set)
|
||||
system_users = SystemUser.objects.filter(nodes__in=nodes)
|
||||
# 清理节点缓存
|
||||
|
@ -79,9 +91,11 @@ def on_asset_node_changed(sender, instance=None, **kwargs):
|
|||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||
def on_node_assets_changed(sender, instance=None, **kwargs):
|
||||
if isinstance(instance, Node):
|
||||
logger.debug("Node assets change signal received")
|
||||
# 当节点和资产关系发生改变时,过期资产数量缓存
|
||||
instance.expire_assets_amount()
|
||||
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
if kwargs['action'] == 'post_add':
|
||||
logger.debug("Node assets change signal received")
|
||||
# 重新关联系统用户和资产的关系
|
||||
system_users = SystemUser.objects.filter(nodes=instance)
|
||||
for system_user in system_users:
|
||||
|
|
|
@ -26,6 +26,23 @@ disk_pattern = re.compile(r'^hd|sd|xvd|vd')
|
|||
PERIOD_TASK = os.environ.get("PERIOD_TASK", "off")
|
||||
|
||||
|
||||
def clean_hosts(assets):
|
||||
clean_assets = []
|
||||
for asset in assets:
|
||||
if not asset.is_active:
|
||||
msg = _("Asset has been disabled, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
if not asset.support_ansible():
|
||||
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
clean_assets.append(asset)
|
||||
if not clean_assets:
|
||||
logger.info(_("No assets matched, stop task"))
|
||||
return clean_assets
|
||||
|
||||
|
||||
@shared_task
|
||||
def set_assets_hardware_info(assets, result, **kwargs):
|
||||
"""
|
||||
|
@ -60,9 +77,12 @@ def set_assets_hardware_info(assets, result, **kwargs):
|
|||
___cpu_model = 'Unknown'
|
||||
___cpu_model = ___cpu_model[:64]
|
||||
___cpu_count = info.get('ansible_processor_count', 0)
|
||||
___cpu_cores = info.get('ansible_processor_cores', None) or len(info.get('ansible_processor', []))
|
||||
___cpu_cores = info.get('ansible_processor_cores', None) or \
|
||||
len(info.get('ansible_processor', []))
|
||||
___cpu_vcpus = info.get('ansible_processor_vcpus', 0)
|
||||
___memory = '%s %s' % capacity_convert('{} MB'.format(info.get('ansible_memtotal_mb')))
|
||||
___memory = '%s %s' % capacity_convert(
|
||||
'{} MB'.format(info.get('ansible_memtotal_mb'))
|
||||
)
|
||||
disk_info = {}
|
||||
for dev, dev_info in info.get('ansible_devices', {}).items():
|
||||
if disk_pattern.match(dev) and dev_info['removable'] == '0':
|
||||
|
@ -96,19 +116,8 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
|||
if task_name is None:
|
||||
task_name = _("Update some assets hardware info")
|
||||
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||
hosts = []
|
||||
for asset in assets:
|
||||
if not asset.is_active:
|
||||
msg = _("Asset has been disabled, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
if not asset.support_ansible():
|
||||
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
hosts.append(asset)
|
||||
hosts = clean_hosts(assets)
|
||||
if not hosts:
|
||||
logger.info(_("No assets matched, stop task"))
|
||||
return {}
|
||||
created_by = str(assets[0].org_id)
|
||||
task, created = update_or_create_ansible_task(
|
||||
|
@ -125,7 +134,6 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
|||
@shared_task
|
||||
def update_asset_hardware_info_manual(asset):
|
||||
task_name = _("Update asset hardware info: {}").format(asset.hostname)
|
||||
# task_name = _("更新资产硬件信息")
|
||||
return update_assets_hardware_info_util(
|
||||
[asset], task_name=task_name
|
||||
)
|
||||
|
@ -141,123 +149,18 @@ def update_assets_hardware_info_period():
|
|||
logger.debug("Period task disabled, update assets hardware info pass")
|
||||
return
|
||||
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
from orgs.models import Organization
|
||||
orgs = Organization.objects.all().values_list('id', flat=True)
|
||||
orgs.append('')
|
||||
task_name = _("Update assets hardware info period")
|
||||
# for org_id in orgs:
|
||||
# org_id = str(org_id)
|
||||
# hostname_list = [
|
||||
# asset for asset in Asset.objects.all()
|
||||
# if asset.is_active and asset.is_unixlike()
|
||||
# ]
|
||||
# tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||
#
|
||||
# # Only create, schedule by celery beat
|
||||
# update_or_create_ansible_task(
|
||||
# task_name, hosts=hostname_list, tasks=tasks, pattern='all',
|
||||
# options=const.TASK_OPTIONS, run_as_admin=True, created_by='System',
|
||||
# interval=60*60*24, is_periodic=True, callback=set_assets_hardware_info.name,
|
||||
# )
|
||||
|
||||
|
||||
## ADMIN USER CONNECTIVE ##
|
||||
|
||||
def set_admin_user_connectability_info(result, **kwargs):
|
||||
admin_user = kwargs.get("admin_user")
|
||||
task_name = kwargs.get("task_name")
|
||||
if admin_user is None and task_name is not None:
|
||||
admin_user = task_name.split(":")[-1]
|
||||
|
||||
raw, summary = result
|
||||
cache_key = const.ADMIN_USER_CONN_CACHE_KEY.format(admin_user)
|
||||
cache.set(cache_key, summary, CACHE_MAX_TIME)
|
||||
|
||||
for i in summary.get('contacted', []):
|
||||
asset_conn_cache_key = const.ASSET_ADMIN_CONN_CACHE_KEY.format(i)
|
||||
cache.set(asset_conn_cache_key, 1, CACHE_MAX_TIME)
|
||||
|
||||
for i, msg in summary.get('dark', {}).items():
|
||||
asset_conn_cache_key = const.ASSET_ADMIN_CONN_CACHE_KEY.format(i)
|
||||
cache.set(asset_conn_cache_key, 0, CACHE_MAX_TIME)
|
||||
logger.error(msg)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_admin_user_connectability_util(admin_user, task_name):
|
||||
"""
|
||||
Test asset admin user can connect or not. Using ansible api do that
|
||||
:param admin_user:
|
||||
:param task_name:
|
||||
:return:
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
|
||||
assets = admin_user.get_related_assets()
|
||||
hosts = []
|
||||
for asset in assets:
|
||||
if not asset.is_active:
|
||||
msg = _("Asset has been disabled, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
if not asset.support_ansible():
|
||||
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
hosts.append(asset)
|
||||
if not hosts:
|
||||
logger.info(_("No assets matched, stop task"))
|
||||
return {}
|
||||
tasks = const.TEST_ADMIN_USER_CONN_TASKS
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by=admin_user.org_id,
|
||||
)
|
||||
result = task.run()
|
||||
set_admin_user_connectability_info(result, admin_user=admin_user.name)
|
||||
return result
|
||||
|
||||
|
||||
@shared_task
|
||||
@register_as_period_task(interval=3600)
|
||||
def test_admin_user_connectability_period():
|
||||
"""
|
||||
A period task that update the ansible task period
|
||||
"""
|
||||
admin_users = AdminUser.objects.all()
|
||||
for admin_user in admin_users:
|
||||
task_name = _("Test admin user connectability period: {}").format(admin_user.name)
|
||||
test_admin_user_connectability_util(admin_user, task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_admin_user_connectability_manual(admin_user):
|
||||
task_name = _("Test admin user connectability: {}").format(admin_user.name)
|
||||
# task_name = _("测试管理行号可连接性: {}").format(admin_user.name)
|
||||
return test_admin_user_connectability_util(admin_user, task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_asset_connectability_util(assets, task_name=None):
|
||||
def test_asset_connectivity_util(assets, task_name=None):
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
|
||||
if task_name is None:
|
||||
task_name = _("Test assets connectability")
|
||||
# task_name = _("测试资产可连接性")
|
||||
hosts = []
|
||||
for asset in assets:
|
||||
if not asset.is_active:
|
||||
msg = _("Asset has been disabled, skip: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
if not asset.support_ansible():
|
||||
msg = _("Asset may not be support ansible, skip: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
hosts.append(asset)
|
||||
task_name = _("Test assets connectivity")
|
||||
hosts = clean_hosts(assets)
|
||||
if not hosts:
|
||||
logger.info(_("No assets, task stop"))
|
||||
return {}
|
||||
tasks = const.TEST_ADMIN_USER_CONN_TASKS
|
||||
created_by = assets[0].org_id
|
||||
|
@ -267,18 +170,20 @@ def test_asset_connectability_util(assets, task_name=None):
|
|||
)
|
||||
result = task.run()
|
||||
summary = result[1]
|
||||
for k in summary.get('dark'):
|
||||
cache.set(const.ASSET_ADMIN_CONN_CACHE_KEY.format(k), 0, CACHE_MAX_TIME)
|
||||
|
||||
for k in summary.get('contacted'):
|
||||
cache.set(const.ASSET_ADMIN_CONN_CACHE_KEY.format(k), 1, CACHE_MAX_TIME)
|
||||
for asset in assets:
|
||||
if asset.hostname in summary.get('dark', {}):
|
||||
asset.connectivity = asset.UNREACHABLE
|
||||
elif asset.hostname in summary.get('contacted', []):
|
||||
asset.connectivity = asset.REACHABLE
|
||||
else:
|
||||
asset.connectivity = asset.UNKNOWN
|
||||
return summary
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_asset_connectability_manual(asset):
|
||||
task_name = _("Test assets connectability: {}").format(asset)
|
||||
summary = test_asset_connectability_util([asset], task_name=task_name)
|
||||
def test_asset_connectivity_manual(asset):
|
||||
task_name = _("Test assets connectivity: {}").format(asset)
|
||||
summary = test_asset_connectivity_util([asset], task_name=task_name)
|
||||
|
||||
if summary.get('dark'):
|
||||
return False, summary['dark']
|
||||
|
@ -286,21 +191,50 @@ def test_asset_connectability_manual(asset):
|
|||
return True, ""
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_admin_user_connectivity_util(admin_user, task_name):
|
||||
"""
|
||||
Test asset admin user can connect or not. Using ansible api do that
|
||||
:param admin_user:
|
||||
:param task_name:
|
||||
:return:
|
||||
"""
|
||||
assets = admin_user.get_related_assets()
|
||||
hosts = clean_hosts(assets)
|
||||
if not hosts:
|
||||
return {}
|
||||
summary = test_asset_connectivity_util(hosts, task_name)
|
||||
return summary
|
||||
|
||||
|
||||
@shared_task
|
||||
@register_as_period_task(interval=3600)
|
||||
def test_admin_user_connectivity_period():
|
||||
"""
|
||||
A period task that update the ansible task period
|
||||
"""
|
||||
admin_users = AdminUser.objects.all()
|
||||
for admin_user in admin_users:
|
||||
task_name = _("Test admin user connectivity period: {}").format(admin_user.name)
|
||||
test_admin_user_connectivity_util(admin_user, task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_admin_user_connectivity_manual(admin_user):
|
||||
task_name = _("Test admin user connectivity: {}").format(admin_user.name)
|
||||
return test_admin_user_connectivity_util(admin_user, task_name)
|
||||
|
||||
|
||||
## System user connective ##
|
||||
|
||||
@shared_task
|
||||
def set_system_user_connectablity_info(result, **kwargs):
|
||||
def set_system_user_connectivity_info(system_user, result):
|
||||
summary = result[1]
|
||||
task_name = kwargs.get("task_name")
|
||||
system_user = kwargs.get("system_user")
|
||||
if system_user is None:
|
||||
system_user = task_name.split(":")[-1]
|
||||
cache_key = const.SYSTEM_USER_CONN_CACHE_KEY.format(str(system_user.id))
|
||||
cache.set(cache_key, summary, CACHE_MAX_TIME)
|
||||
system_user.connectivity = summary
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_system_user_connectability_util(system_user, assets, task_name):
|
||||
def test_system_user_connectivity_util(system_user, assets, task_name):
|
||||
"""
|
||||
Test system cant connect his assets or not.
|
||||
:param system_user:
|
||||
|
@ -309,20 +243,9 @@ def test_system_user_connectability_util(system_user, assets, task_name):
|
|||
:return:
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
hosts = []
|
||||
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
|
||||
for asset in assets:
|
||||
if not asset.is_active:
|
||||
msg = _("Asset has been disabled, skip: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
if not asset.support_ansible():
|
||||
msg = _("Asset may not be support ansible, skip: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
hosts.append(asset)
|
||||
hosts = clean_hosts(assets)
|
||||
if not hosts:
|
||||
logger.info(_("No assets matched, stop task"))
|
||||
return {}
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
|
@ -330,35 +253,35 @@ def test_system_user_connectability_util(system_user, assets, task_name):
|
|||
run_as=system_user, created_by=system_user.org_id,
|
||||
)
|
||||
result = task.run()
|
||||
set_system_user_connectablity_info(result, system_user=system_user)
|
||||
set_system_user_connectivity_info(system_user, result)
|
||||
return result
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_system_user_connectability_manual(system_user):
|
||||
task_name = _("Test system user connectability: {}").format(system_user)
|
||||
assets = system_user.get_assets()
|
||||
return test_system_user_connectability_util(system_user, assets, task_name)
|
||||
def test_system_user_connectivity_manual(system_user):
|
||||
task_name = _("Test system user connectivity: {}").format(system_user)
|
||||
assets = system_user.get_related_assets()
|
||||
return test_system_user_connectivity_util(system_user, assets, task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_system_user_connectability_a_asset(system_user, asset):
|
||||
task_name = _("Test system user connectability: {} => {}").format(
|
||||
def test_system_user_connectivity_a_asset(system_user, asset):
|
||||
task_name = _("Test system user connectivity: {} => {}").format(
|
||||
system_user, asset
|
||||
)
|
||||
return test_system_user_connectability_util(system_user, [asset], task_name)
|
||||
return test_system_user_connectivity_util(system_user, [asset], task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_system_user_connectability_period():
|
||||
def test_system_user_connectivity_period():
|
||||
if PERIOD_TASK != "on":
|
||||
logger.debug("Period task disabled, test system user connectability pass")
|
||||
logger.debug("Period task disabled, test system user connectivity pass")
|
||||
return
|
||||
system_users = SystemUser.objects.all()
|
||||
for system_user in system_users:
|
||||
task_name = _("Test system user connectability period: {}").format(system_user)
|
||||
# task_name = _("定期测试系统用户可连接性: {}".format(system_user))
|
||||
test_system_user_connectability_util(system_user, task_name)
|
||||
task_name = _("Test system user connectivity period: {}").format(system_user)
|
||||
assets = system_user.get_related_assets()
|
||||
test_system_user_connectivity_util(system_user, assets, task_name)
|
||||
|
||||
|
||||
#### Push system user tasks ####
|
||||
|
@ -380,6 +303,24 @@ def get_push_system_user_tasks(system_user):
|
|||
),
|
||||
}
|
||||
})
|
||||
tasks.extend([
|
||||
{
|
||||
'name': 'Check home dir exists',
|
||||
'action': {
|
||||
'module': 'stat',
|
||||
'args': 'path=/home/{}'.format(system_user.username)
|
||||
},
|
||||
'register': 'home_existed'
|
||||
},
|
||||
{
|
||||
'name': "Set home dir permission",
|
||||
'action': {
|
||||
'module': 'file',
|
||||
'args': "path=/home/{0} owner={0} group={0} mode=700".format(system_user.username)
|
||||
},
|
||||
'when': 'home_existed.stat.exists == true'
|
||||
}
|
||||
])
|
||||
if system_user.public_key:
|
||||
tasks.append({
|
||||
'name': 'Set {} authorized key'.format(system_user.username),
|
||||
|
@ -416,19 +357,8 @@ def push_system_user_util(system_user, assets, task_name):
|
|||
return
|
||||
|
||||
tasks = get_push_system_user_tasks(system_user)
|
||||
hosts = []
|
||||
for asset in assets:
|
||||
if not asset.is_active:
|
||||
msg = _("Asset has been disabled, skip: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
if not asset.support_ansible():
|
||||
msg = _("Asset may not be support ansible, skip: {}").format(asset)
|
||||
logger.info(msg)
|
||||
continue
|
||||
hosts.append(asset)
|
||||
hosts = clean_hosts(assets)
|
||||
if not hosts:
|
||||
logger.info(_("No assets matched, stop task"))
|
||||
return {}
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
|
@ -440,7 +370,7 @@ def push_system_user_util(system_user, assets, task_name):
|
|||
|
||||
@shared_task
|
||||
def push_system_user_to_assets_manual(system_user):
|
||||
assets = system_user.get_assets()
|
||||
assets = system_user.get_related_assets()
|
||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||
return push_system_user_util(system_user, assets, task_name=task_name)
|
||||
|
||||
|
@ -459,6 +389,18 @@ def push_system_user_to_assets(system_user, assets):
|
|||
return push_system_user_util(system_user, assets, task_name)
|
||||
|
||||
|
||||
@shared_task
|
||||
@after_app_shutdown_clean
|
||||
def test_system_user_connectability_period():
|
||||
pass
|
||||
|
||||
|
||||
@shared_task
|
||||
@after_app_shutdown_clean
|
||||
def test_admin_user_connectability_period():
|
||||
pass
|
||||
|
||||
|
||||
# @shared_task
|
||||
# @register_as_period_task(interval=3600)
|
||||
# @after_app_ready_start
|
||||
|
|
|
@ -57,6 +57,10 @@
|
|||
<script>
|
||||
var zTree2, asset_table2 = 0;
|
||||
function initTable2() {
|
||||
if(asset_table2){
|
||||
return
|
||||
}
|
||||
|
||||
var options = {
|
||||
ele: $('#asset_list_modal_table'),
|
||||
ajax_url: '{% url "api-assets:asset-list" %}?show_current_asset=1',
|
||||
|
@ -71,14 +75,14 @@ function initTable2() {
|
|||
|
||||
function onSelected2(event, treeNode) {
|
||||
var url = asset_table2.ajax.url();
|
||||
url = setUrlParam(url, "node_id", treeNode.node_id);
|
||||
setCookie('node_selected', treeNode.id);
|
||||
url = setUrlParam(url, "node_id", treeNode.meta.node.id);
|
||||
asset_table2.ajax.url(url);
|
||||
asset_table2.ajax.reload();
|
||||
}
|
||||
|
||||
|
||||
function initTree2() {
|
||||
var url = '{% url 'api-assets:node-children-tree' %}?assets=0';
|
||||
var setting = {
|
||||
view: {
|
||||
dblClickExpand: false,
|
||||
|
@ -89,33 +93,22 @@ function initTree2() {
|
|||
enable: true
|
||||
}
|
||||
},
|
||||
async: {
|
||||
enable: true,
|
||||
url: url,
|
||||
autoParam: ["id=key", "name=n", "level=lv"],
|
||||
type: 'get'
|
||||
},
|
||||
callback: {
|
||||
onSelected: onSelected2
|
||||
}
|
||||
};
|
||||
|
||||
var zNodes = [];
|
||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
value["node_id"] = value["id"];
|
||||
value["id"] = value["tree_id"];
|
||||
value["pId"] = value["tree_parent"];
|
||||
{#value["open"] = true;#}
|
||||
if (value["key"] === "0") {
|
||||
value["open"] = true;
|
||||
}
|
||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||
});
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree2"), setting, zNodes);
|
||||
zTree2 = $.fn.zTree.getZTreeObj("assetTree2");
|
||||
var root = zTree2.getNodes()[0];
|
||||
zTree2.expandNode(root);
|
||||
});
|
||||
zTree2 = $.fn.zTree.init($("#assetTree2"), setting);
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function(){
|
||||
}).on('show.bs.modal', function () {
|
||||
initTable2();
|
||||
initTree2();
|
||||
})
|
||||
|
|
|
@ -45,13 +45,11 @@
|
|||
<table class="table table-striped table-bordered table-hover" id="asset_list_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Reachable' %}</th>
|
||||
<th>{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -91,26 +89,36 @@
|
|||
<script>
|
||||
|
||||
function initTable() {
|
||||
var reachable = {{ admin_user.REACHABLE }};
|
||||
var unreachable = {{ admin_user.UNREACHABLE }};
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
{targets: 0, createdCell: function (td, cellData, rowData) {
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
if (cellData === unreachable) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
} else if (cellData === reachable) {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}}],
|
||||
ajax_url: '{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}',
|
||||
} else {
|
||||
$(td).html('')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
$(td).html(test_btn);
|
||||
}}
|
||||
],
|
||||
|
||||
ajax_url: '{% url "api-assets:admin-user-assets" pk=admin_user.id %}',
|
||||
columns: [
|
||||
{data: function(){return ""}}, {data: "hostname" }, {data: "ip" },
|
||||
{data: "port" }, {data: "is_connective" }],
|
||||
{data: "hostname" }, {data: "ip" },
|
||||
{data: "port" }, {data: "connectivity" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
|
@ -119,6 +127,21 @@ function initTable() {
|
|||
$(document).ready(function () {
|
||||
initTable();
|
||||
})
|
||||
.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) {
|
||||
|
|
|
@ -136,6 +136,7 @@
|
|||
<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>
|
||||
|
||||
|
@ -147,6 +148,8 @@
|
|||
<script>
|
||||
var zTree, rMenu, asset_table, show = 0;
|
||||
var update_node_action = "";
|
||||
var current_node_id = null;
|
||||
var current_node = null;
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
|
@ -191,18 +194,20 @@ function addTreeNode() {
|
|||
if (!parentNode){
|
||||
return
|
||||
}
|
||||
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.node_id );
|
||||
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"],
|
||||
node_id: data["id"],
|
||||
pId: parentNode.id
|
||||
pId: parentNode.id,
|
||||
meta: {
|
||||
"node": data
|
||||
}
|
||||
};
|
||||
newNode.checked = zTree.getSelectedNodes()[0].checked;
|
||||
zTree.addNodes(parentNode, 0, newNode);
|
||||
var node = zTree.getNodeByParam('id', newNode.node_id, parentNode);
|
||||
var node = zTree.getNodeByParam('id', newNode.id, parentNode);
|
||||
zTree.editName(node);
|
||||
} else {
|
||||
alert("{% trans 'Create node failed' %}")
|
||||
|
@ -218,10 +223,10 @@ function removeTreeNode() {
|
|||
}
|
||||
if (current_node.children && current_node.children.length > 0) {
|
||||
toastr.error("{% trans 'Have child node, cancel' %}");
|
||||
} else if (current_node.assets_amount !== 0) {
|
||||
} 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.node_id );
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: "DELETE",
|
||||
|
@ -238,8 +243,8 @@ function editTreeNode() {
|
|||
if (!current_node){
|
||||
return
|
||||
}
|
||||
if (current_node.value) {
|
||||
current_node.name = current_node.value;
|
||||
if (current_node) {
|
||||
current_node.name = current_node.meta.node.value;
|
||||
}
|
||||
zTree.editName(current_node);
|
||||
}
|
||||
|
@ -281,7 +286,7 @@ function onBodyMouseDown(event){
|
|||
|
||||
|
||||
function onRename(event, treeId, treeNode, isCancel){
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", treeNode.node_id);
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
var data = {"value": treeNode.name};
|
||||
if (isCancel){
|
||||
return
|
||||
|
@ -291,13 +296,20 @@ function onRename(event, treeId, treeNode, isCancel){
|
|||
body: JSON.stringify(data),
|
||||
method: "PATCH",
|
||||
success_message: "{% trans 'Rename success' %}",
|
||||
fail_message: "{% trans 'Rename failed, do not change the root node name' %}"
|
||||
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;
|
||||
var url = asset_table.ajax.url();
|
||||
url = setUrlParam(url, "node_id", treeNode.node_id);
|
||||
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);
|
||||
|
@ -305,6 +317,9 @@ function onSelected(event, treeNode) {
|
|||
}
|
||||
|
||||
function selectQueryNode() {
|
||||
// TODO: 是否应该添加
|
||||
// 暂时忽略之前选中的内容
|
||||
return
|
||||
var query_node_id = $.getUrlParam("node");
|
||||
var cookie_node_id = getCookie('node_selected');
|
||||
var node;
|
||||
|
@ -355,6 +370,14 @@ function onDrop(event, treeId, treeNodes, targetNode, moveType) {
|
|||
}
|
||||
|
||||
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,
|
||||
|
@ -365,6 +388,12 @@ function initTree() {
|
|||
enable: true
|
||||
}
|
||||
},
|
||||
async: {
|
||||
enable: true,
|
||||
url: url,
|
||||
autoParam: ["id=key", "name=n", "level=lv"],
|
||||
type: 'get'
|
||||
},
|
||||
edit: {
|
||||
enable: true,
|
||||
showRemoveBtn: false,
|
||||
|
@ -387,26 +416,8 @@ function initTree() {
|
|||
};
|
||||
|
||||
var zNodes = [];
|
||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
value["node_id"] = value["id"];
|
||||
value["id"] = value["tree_id"];
|
||||
if (value["tree_id"] !== value["tree_parent"]){
|
||||
value["pId"] = value["tree_parent"];
|
||||
} else {
|
||||
value["isParent"] = true;
|
||||
}
|
||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||
value['value'] = value['value'];
|
||||
});
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||
var root = zTree.getNodes()[0];
|
||||
zTree.expandNode(root);
|
||||
rMenu = $("#rMenu");
|
||||
selectQueryNode();
|
||||
});
|
||||
zTree = $.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
rMenu = $("#rMenu");
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
|
@ -443,20 +454,15 @@ $(document).ready(function(){
|
|||
.on('click', '.btn_export', function () {
|
||||
var $data_table = $('#asset_list_table').DataTable();
|
||||
var rows = $data_table.rows('.selected').data();
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
var current_node;
|
||||
if (nodes && nodes.length === 1) {
|
||||
current_node = nodes[0];
|
||||
}
|
||||
|
||||
var assets = [];
|
||||
$.each(rows, function (index, obj) {
|
||||
assets.push(obj.id)
|
||||
});
|
||||
var _node_id = current_node ? current_node.node_id : null;
|
||||
$.ajax({
|
||||
url: "{% url "assets:asset-export" %}",
|
||||
method: 'POST',
|
||||
data: JSON.stringify({assets_id: assets, node_id: _node_id}),
|
||||
data: JSON.stringify({assets_id: assets, node_id: current_node_id}),
|
||||
dataType: "json",
|
||||
success: function (data, textStatus) {
|
||||
window.open(data.redirect)
|
||||
|
@ -469,12 +475,8 @@ $(document).ready(function(){
|
|||
.on('click', '#btn_asset_import', function () {
|
||||
var $form = $('#fm_asset_import');
|
||||
var action = $form.attr("action");
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
var current_node;
|
||||
if (nodes && nodes.length ===1 ){
|
||||
current_node = nodes[0];
|
||||
action = setUrlParam(action, 'node_id', current_node.node_id);
|
||||
{#action += "?node_id=" + current_node.node_id;#}
|
||||
if (current_node_id){
|
||||
action = setUrlParam(action, 'node_id', current_node_id);
|
||||
$form.attr("action", action)
|
||||
}
|
||||
$form.find('.help-block').remove();
|
||||
|
@ -496,25 +498,14 @@ $(document).ready(function(){
|
|||
})
|
||||
.on('click', '.btn-create-asset', function () {
|
||||
var url = "{% url 'assets:asset-create' %}";
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
var current_node;
|
||||
if (nodes && nodes.length ===1 ){
|
||||
current_node = nodes[0];
|
||||
url += "?node_id=" + current_node.node_id;
|
||||
if (current_node_id) {
|
||||
url += "?node_id=" + current_node_id;
|
||||
}
|
||||
window.open(url, '_self');
|
||||
})
|
||||
.on('click', '.btn-refresh-hardware', function () {
|
||||
var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}";
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
var current_node;
|
||||
if (nodes && nodes.length ===1 ){
|
||||
current_node = nodes[0];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
function success(data) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
var task_id = data.task;
|
||||
|
@ -531,15 +522,10 @@ $(document).ready(function(){
|
|||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}";
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
var current_node;
|
||||
if (nodes && nodes.length ===1 ){
|
||||
current_node = nodes[0];
|
||||
} else {
|
||||
if (!current_node_id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
function success(data) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
var task_id = data.task;
|
||||
|
@ -567,6 +553,10 @@ $(document).ready(function(){
|
|||
setCookie('show_current_asset', '');
|
||||
location.reload();
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
hideRMenu();
|
||||
|
||||
})
|
||||
.on('click', '.btn_asset_delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $("#asset_list_table").DataTable();
|
||||
|
@ -660,11 +650,8 @@ $(document).ready(function(){
|
|||
}
|
||||
|
||||
function doRemove() {
|
||||
var current_node;
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
if (nodes && nodes.length === 1) {
|
||||
current_node = nodes[0]
|
||||
} else {
|
||||
if (!current_node_id) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -677,7 +664,7 @@ $(document).ready(function(){
|
|||
};
|
||||
|
||||
APIUpdateAttr({
|
||||
'url': '/api/assets/v1/nodes/' + current_node.node_id + '/assets/remove/',
|
||||
'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/',
|
||||
'method': 'PUT',
|
||||
'body': JSON.stringify(data),
|
||||
'success': success
|
||||
|
@ -706,11 +693,7 @@ $(document).ready(function(){
|
|||
})
|
||||
.on('click', '#btn_asset_modal_confirm', function () {
|
||||
var assets_selected = asset_table2.selected;
|
||||
var current_node;
|
||||
var nodes = zTree.getSelectedNodes();
|
||||
if (nodes && nodes.length === 1) {
|
||||
current_node = nodes[0]
|
||||
} else {
|
||||
if (!current_node_id) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -722,9 +705,9 @@ $(document).ready(function(){
|
|||
|
||||
var url = '';
|
||||
if (update_node_action === "move") {
|
||||
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
url = "{% url 'api-assets:node-replace-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
} else {
|
||||
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node.node_id);
|
||||
url = "{% url 'api-assets:node-add-assets' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
}
|
||||
|
||||
APIUpdateAttr({
|
||||
|
|
|
@ -32,6 +32,7 @@ $(document).ready(function () {
|
|||
}).on('click', '.select2-selection__rendered', function (e) {
|
||||
e.preventDefault();
|
||||
$("#asset_list_modal").modal();
|
||||
initSelectedAssets2Table();
|
||||
})
|
||||
.on('click', '#btn_asset_modal_confirm', function () {
|
||||
var assets = asset_table2.selected;
|
||||
|
|
|
@ -136,7 +136,7 @@
|
|||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function initAssetsTable() {
|
||||
var unreachable = {{ system_user.unreachable_assets|safe }};
|
||||
var connectivity = {{ system_user.connectivity | safe }};
|
||||
var options = {
|
||||
ele: $('#system_user_list'),
|
||||
buttons: [],
|
||||
|
@ -147,11 +147,13 @@ function initAssetsTable() {
|
|||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
if (unreachable.indexOf(cellData) >= 0) {
|
||||
if (connectivity.unreachable.indexOf(cellData) >= 0) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
} else if (connectivity.reachable.indexOf(cellData) >= 0 ) {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
} else {
|
||||
$(td).html('')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
var push_btn = '';
|
||||
|
|
|
@ -95,7 +95,7 @@ function initTable() {
|
|||
}}],
|
||||
ajax_url: '{% url "api-assets:system-user-list" %}',
|
||||
columns: [
|
||||
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "get_login_mode_display"}, {data: "assets_amount" },
|
||||
{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" }
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
|
|
|
@ -24,35 +24,39 @@ cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-r
|
|||
|
||||
urlpatterns = [
|
||||
path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
|
||||
path('system-user/<uuid:pk>/auth-info/',
|
||||
api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
|
||||
path('system-user/<uuid:pk>/assets/',
|
||||
api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
|
||||
path('assets/<uuid:pk>/refresh/',
|
||||
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
|
||||
path('assets/<uuid:pk>/alive/',
|
||||
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
|
||||
path('assets/<uuid:pk>/gateway/',
|
||||
api.AssetGatewayApi.as_view(), name='asset-gateway'),
|
||||
|
||||
path('admin-user/<uuid:pk>/nodes/',
|
||||
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
||||
path('admin-user/<uuid:pk>/auth/',
|
||||
api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
|
||||
path('admin-user/<uuid:pk>/connective/',
|
||||
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
||||
path('admin-user/<uuid:pk>/assets/',
|
||||
api.AdminUserAssetsListView.as_view(), name='admin-user-assets'),
|
||||
|
||||
path('system-user/<uuid:pk>/auth-info/',
|
||||
api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
|
||||
path('system-user/<uuid:pk>/assets/',
|
||||
api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
|
||||
path('system-user/<uuid:pk>/push/',
|
||||
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
||||
path('system-user/<uuid:pk>/asset/<uuid:aid>/push/',
|
||||
api.SystemUserPushToAssetApi.as_view(), name='system-user-push-to-asset'),
|
||||
path('system-user/<uuid:pk>/asset/<uuid:aid>/test/',
|
||||
api.SystemUserTestAssetConnectabilityApi.as_view(), name='system-user-test-to-asset'),
|
||||
api.SystemUserTestAssetConnectivityApi.as_view(), name='system-user-test-to-asset'),
|
||||
path('system-user/<uuid:pk>/connective/',
|
||||
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
||||
|
||||
path('system-user/<uuid:pk>/cmd-filter-rules/',
|
||||
api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),
|
||||
|
||||
path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
|
||||
path('nodes/children/tree/', api.NodeChildrenAsTreeApi.as_view(), name='node-children-tree'),
|
||||
path('nodes/<uuid:pk>/children/',
|
||||
api.NodeChildrenApi.as_view(), name='node-children'),
|
||||
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),
|
||||
|
|
|
@ -102,7 +102,7 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
|||
'app': _('Assets'),
|
||||
'action': _('Admin user detail'),
|
||||
"total_amount": len(self.queryset),
|
||||
'unreachable_amount': len([asset for asset in self.queryset if asset.is_connective is False])
|
||||
'unreachable_amount': len([asset for asset in self.queryset if asset.connectivity is False])
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
|
|
@ -216,7 +216,6 @@ class AssetExportView(LoginRequiredMixin, View):
|
|||
return HttpResponse('Json object not valid', status=400)
|
||||
|
||||
if not assets_id:
|
||||
print(node_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:
|
||||
|
|
|
@ -13,6 +13,6 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='ftplog',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
field=models.CharField(blank=True, db_index=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -13,6 +13,6 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='ftplog',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -15,7 +15,7 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='OperateLog',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('user', models.CharField(max_length=128, verbose_name='User')),
|
||||
('action', models.CharField(choices=[('create', 'Create'), ('update', 'Update'), ('delete', 'Delete')], max_length=16, verbose_name='Action')),
|
||||
|
|
|
@ -15,8 +15,6 @@ class BaseForm(forms.Form):
|
|||
super().__init__(*args, **kwargs)
|
||||
for name, field in self.fields.items():
|
||||
value = getattr(settings, name, None)
|
||||
# django_value = getattr(settings, name) if hasattr(settings, name) else None
|
||||
|
||||
if value is None: # and django_value is None:
|
||||
continue
|
||||
|
||||
|
@ -24,8 +22,6 @@ class BaseForm(forms.Form):
|
|||
if isinstance(value, dict):
|
||||
value = json.dumps(value)
|
||||
initial_value = value
|
||||
# elif django_value is False or django_value:
|
||||
# initial_value = django_value
|
||||
else:
|
||||
initial_value = ''
|
||||
field.initial = initial_value
|
||||
|
@ -134,6 +130,14 @@ class TerminalSettingForm(BaseForm):
|
|||
('hostname', _('Hostname')),
|
||||
('ip', _('IP')),
|
||||
)
|
||||
PAGE_SIZE_CHOICES = (
|
||||
('all', _('All')),
|
||||
('auto', _('Auto')),
|
||||
(10, 10),
|
||||
(15, 15),
|
||||
(25, 25),
|
||||
(50, 50),
|
||||
)
|
||||
TERMINAL_PASSWORD_AUTH = forms.BooleanField(
|
||||
initial=True, required=False, label=_("Password auth")
|
||||
)
|
||||
|
@ -146,6 +150,14 @@ class TerminalSettingForm(BaseForm):
|
|||
TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
|
||||
choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by")
|
||||
)
|
||||
TERMINAL_ASSET_LIST_PAGE_SIZE = forms.ChoiceField(
|
||||
choices=PAGE_SIZE_CHOICES, initial='auto', label=_("List page size"),
|
||||
)
|
||||
TERMINAL_SESSION_KEEP_DURATION = forms.IntegerField(
|
||||
label=_("Session keep duration"),
|
||||
help_text=_("Units: days, Session, record, command will be delete "
|
||||
"if more than duration, only in database")
|
||||
)
|
||||
|
||||
|
||||
class TerminalCommandStorage(BaseForm):
|
||||
|
|
|
@ -45,6 +45,8 @@ class Setting(models.Model):
|
|||
def cleaned_value(self):
|
||||
try:
|
||||
value = self.value
|
||||
if not isinstance(value, (str, bytes)):
|
||||
return value
|
||||
if self.encrypted:
|
||||
value = signer.unsign(value)
|
||||
value = json.loads(value)
|
||||
|
|
|
@ -26,21 +26,20 @@ def refresh_settings_on_changed(sender, instance=None, **kwargs):
|
|||
def refresh_all_settings_on_django_ready(sender, **kwargs):
|
||||
logger.debug("Receive django ready signal")
|
||||
logger.debug(" - fresh all settings")
|
||||
CACHE_KEY_PREFIX = '_SETTING_'
|
||||
cache_key_prefix = '_SETTING_'
|
||||
|
||||
def monkey_patch_getattr(self, name):
|
||||
key = CACHE_KEY_PREFIX + name
|
||||
key = cache_key_prefix + name
|
||||
cached = cache.get(key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
if self._wrapped is empty:
|
||||
self._setup(name)
|
||||
val = getattr(self._wrapped, name)
|
||||
# self.__dict__[name] = val # Never set it
|
||||
return val
|
||||
|
||||
def monkey_patch_setattr(self, name, value):
|
||||
key = CACHE_KEY_PREFIX + name
|
||||
key = cache_key_prefix + name
|
||||
cache.set(key, value, None)
|
||||
if name == '_wrapped':
|
||||
self.__dict__.clear()
|
||||
|
@ -51,7 +50,7 @@ def refresh_all_settings_on_django_ready(sender, **kwargs):
|
|||
def monkey_patch_delattr(self, name):
|
||||
super(LazySettings, self).__delattr__(name)
|
||||
self.__dict__.pop(name, None)
|
||||
key = CACHE_KEY_PREFIX + name
|
||||
key = cache_key_prefix + name
|
||||
cache.delete(key)
|
||||
|
||||
try:
|
||||
|
|
|
@ -47,16 +47,13 @@ class TreeNode:
|
|||
def __gt__(self, other):
|
||||
if self.isParent and not other.isParent:
|
||||
return False
|
||||
elif not self.isParent and other.isParent:
|
||||
return True
|
||||
return self.id > other.id
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.id == other.id
|
||||
|
||||
def __lt__(self, other):
|
||||
if self.isParent and not other.isParent:
|
||||
return True
|
||||
return self.id < other.id
|
||||
|
||||
|
||||
class Tree:
|
||||
def __init__(self):
|
||||
|
|
|
@ -304,7 +304,7 @@ defaults = {
|
|||
'REDIS_DB_CELERY': 3,
|
||||
'REDIS_DB_CACHE': 4,
|
||||
'CAPTCHA_TEST_MODE': None,
|
||||
'TOKEN_EXPIRATION': 3600,
|
||||
'TOKEN_EXPIRATION': 3600 * 24,
|
||||
'DISPLAY_PER_PAGE': 25,
|
||||
'DEFAULT_EXPIRED_YEARS': 70,
|
||||
'SESSION_COOKIE_DOMAIN': None,
|
||||
|
@ -312,7 +312,14 @@ defaults = {
|
|||
'SESSION_COOKIE_AGE': 3600 * 24,
|
||||
'SESSION_EXPIRE_AT_BROWSER_CLOSE': False,
|
||||
'AUTH_OPENID': False,
|
||||
'EMAIL_SUFFIX': 'jumpserver.org'
|
||||
'OTP_ISSUER_NAME': 'Jumpserver',
|
||||
'EMAIL_SUFFIX': 'jumpserver.org',
|
||||
'TERMINAL_PASSWORD_AUTH': True,
|
||||
'TERMINAL_PUBLIC_KEY_AUTH': True,
|
||||
'TERMINAL_HEARTBEAT_INTERVAL': 5,
|
||||
'TERMINAL_ASSET_LIST_SORT_BY': 'hostname',
|
||||
'TERMINAL_ASSET_LIST_PAGE_SIZE': 'auto',
|
||||
'TERMINAL_SESSION_KEEP_DURATION': 9999,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -356,6 +356,7 @@ FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755
|
|||
|
||||
# OTP settings
|
||||
OTP_ISSUER_NAME = CONFIG.OTP_ISSUER_NAME
|
||||
OTP_VALID_WINDOW = CONFIG.OTP_VALID_WINDOW
|
||||
|
||||
# Auth LDAP settings
|
||||
AUTH_LDAP = False
|
||||
|
@ -466,6 +467,7 @@ DEFAULT_TERMINAL_REPLAY_STORAGE = {
|
|||
TERMINAL_REPLAY_STORAGE = {
|
||||
}
|
||||
|
||||
|
||||
SECURITY_MFA_AUTH = False
|
||||
SECURITY_LOGIN_LIMIT_COUNT = 7
|
||||
SECURITY_LOGIN_LIMIT_TIME = 30 # Unit: minute
|
||||
|
@ -484,6 +486,13 @@ SECURITY_PASSWORD_RULES = [
|
|||
'SECURITY_PASSWORD_SPECIAL_CHAR'
|
||||
]
|
||||
|
||||
TERMINAL_PASSWORD_AUTH = CONFIG.TERMINAL_PASSWORD_AUTH
|
||||
TERMINAL_PUBLIC_KEY_AUTH = CONFIG.TERMINAL_PUBLIC_KEY_AUTH
|
||||
TERMINAL_HEARTBEAT_INTERVAL = CONFIG.TERMINAL_HEARTBEAT_INTERVAL
|
||||
TERMINAL_ASSET_LIST_SORT_BY = CONFIG.TERMINAL_ASSET_LIST_SORT_BY
|
||||
TERMINAL_ASSET_LIST_PAGE_SIZE = CONFIG.TERMINAL_ASSET_LIST_PAGE_SIZE
|
||||
TERMINAL_SESSION_KEEP_DURATION = CONFIG.TERMINAL_SESSION_KEEP_DURATION
|
||||
|
||||
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
|
||||
BOOTSTRAP3 = {
|
||||
'horizontal_label_class': 'col-md-2',
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -24,8 +24,10 @@ class TaskViewSet(viewsets.ModelViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
if current_org:
|
||||
if current_org.is_real():
|
||||
queryset = queryset.filter(created_by=current_org.id)
|
||||
else:
|
||||
queryset = queryset.filter(created_by='')
|
||||
return queryset
|
||||
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ class AdHocRunHistorySerializer(serializers.ModelSerializer):
|
|||
@staticmethod
|
||||
def get_stat(obj):
|
||||
return {
|
||||
"total": len(obj.adhoc.hosts),
|
||||
"total": obj.adhoc.hosts.count(),
|
||||
"success": len(obj.summary.get("contacted", [])),
|
||||
"failed": len(obj.summary.get("dark", [])),
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ app_name = "ops"
|
|||
router = DefaultRouter()
|
||||
router.register(r'tasks', api.TaskViewSet, 'task')
|
||||
router.register(r'adhoc', api.AdHocViewSet, 'adhoc')
|
||||
router.register(r'history', api.AdHocRunHistoryViewSet, 'history')
|
||||
router.register(r'command-executions', api.CommandExecutionViewSet, 'command-execution')
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
@ -62,8 +62,11 @@ class TaskDetailView(AdminUserRequiredMixin, DetailView):
|
|||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
if current_org:
|
||||
# Todo: 需要整理默认组织等东西
|
||||
if current_org.is_real():
|
||||
queryset = queryset.filter(created_by=current_org.id)
|
||||
else:
|
||||
queryset = queryset.filter(created_by='')
|
||||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
|
|
@ -74,7 +74,7 @@ class OrgManager(models.Manager):
|
|||
|
||||
|
||||
class OrgModelMixin(models.Model):
|
||||
org_id = models.CharField(max_length=36, blank=True, default='', verbose_name=_("Organization"))
|
||||
org_id = models.CharField(max_length=36, blank=True, default='', verbose_name=_("Organization"), db_index=True)
|
||||
objects = OrgManager()
|
||||
|
||||
sep = '@'
|
||||
|
|
|
@ -16,7 +16,7 @@ from orgs.utils import set_to_root_org
|
|||
from .utils import AssetPermissionUtil
|
||||
from .models import AssetPermission
|
||||
from .hands import AssetGrantedSerializer, User, UserGroup, Asset, Node, \
|
||||
NodeGrantedSerializer, SystemUser, NodeSerializer
|
||||
SystemUser, NodeSerializer
|
||||
from . import serializers
|
||||
from .mixins import AssetsFilterMixin
|
||||
|
||||
|
@ -150,7 +150,7 @@ class UserGrantedNodesWithAssetsApi(AssetsFilterMixin, ListAPIView):
|
|||
用户授权的节点并带着节点下资产的api
|
||||
"""
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = NodeGrantedSerializer
|
||||
serializer_class = serializers.NodeGrantedSerializer
|
||||
|
||||
def change_org_if_need(self):
|
||||
if self.request.user.is_superuser or \
|
||||
|
@ -228,15 +228,22 @@ class UserGrantedNodesWithAssetsAsTreeApi(ListAPIView):
|
|||
@staticmethod
|
||||
def parse_asset_to_tree_node(node, asset, system_users):
|
||||
system_users_protocol_matched = [s for s in system_users if s.protocol == asset.protocol]
|
||||
system_user_serializer = serializers.GrantedSystemUserSerializer(
|
||||
system_users_protocol_matched, many=True
|
||||
)
|
||||
asset_serializer = serializers.GrantedAssetSerializer(asset)
|
||||
icon_skin = 'file'
|
||||
if asset.platform.lower() == 'windows':
|
||||
icon_skin = 'windows'
|
||||
elif asset.platform.lower() == 'linux':
|
||||
icon_skin = 'linux'
|
||||
system_users = []
|
||||
for system_user in system_users_protocol_matched:
|
||||
system_users.append({
|
||||
'id': system_user.id,
|
||||
'name': system_user.name,
|
||||
'username': system_user.username,
|
||||
'protocol': system_user.protocol,
|
||||
'priority': system_user.priority,
|
||||
'login_mode': system_user.login_mode,
|
||||
'comment': system_user.comment,
|
||||
})
|
||||
data = {
|
||||
'id': str(asset.id),
|
||||
'name': asset.hostname,
|
||||
|
@ -246,9 +253,19 @@ class UserGrantedNodesWithAssetsAsTreeApi(ListAPIView):
|
|||
'open': False,
|
||||
'iconSkin': icon_skin,
|
||||
'meta': {
|
||||
'system_users': system_user_serializer.data,
|
||||
'system_users': system_users,
|
||||
'type': 'asset',
|
||||
'asset': asset_serializer.data
|
||||
'asset': {
|
||||
'id': asset.id,
|
||||
'hostname': asset.hostname,
|
||||
'ip': asset.ip,
|
||||
'port': asset.port,
|
||||
'protocol': asset.protocol,
|
||||
'platform': asset.platform,
|
||||
'domain': None if not asset.domain else asset.domain.id,
|
||||
'is_active': asset.is_active,
|
||||
'comment': asset.comment
|
||||
},
|
||||
}
|
||||
}
|
||||
tree_node = TreeNode(**data)
|
||||
|
@ -360,7 +377,7 @@ class UserGroupGrantedNodesApi(ListAPIView):
|
|||
|
||||
class UserGroupGrantedNodesWithAssetsApi(ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = NodeGrantedSerializer
|
||||
serializer_class = serializers.NodeGrantedSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
user_group_id = self.kwargs.get('pk', '')
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
from common.permissions import AdminUserRequiredMixin
|
||||
from users.models import User, UserGroup
|
||||
from assets.models import Asset, SystemUser, Node
|
||||
from assets.serializers import AssetGrantedSerializer, NodeGrantedSerializer, NodeSerializer
|
||||
from assets.serializers import AssetGrantedSerializer, NodeSerializer
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -13,11 +13,11 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='assetpermission',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='nodepermission',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -83,6 +83,35 @@ class AssetPermissionNodeSerializer(serializers.ModelSerializer):
|
|||
return obj.parent_key
|
||||
|
||||
|
||||
class NodeGrantedSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
授权资产组
|
||||
"""
|
||||
assets_granted = AssetGrantedSerializer(many=True, read_only=True)
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
parent = serializers.SerializerMethodField()
|
||||
name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = [
|
||||
'id', 'key', 'name', 'value', 'parent',
|
||||
'assets_granted', 'assets_amount', 'org_id',
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return len(obj.assets_granted)
|
||||
|
||||
@staticmethod
|
||||
def get_name(obj):
|
||||
return obj.name
|
||||
|
||||
@staticmethod
|
||||
def get_parent(obj):
|
||||
return obj.parent.id
|
||||
|
||||
|
||||
class GrantedNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Node
|
||||
|
|
|
@ -113,6 +113,7 @@ $(document).ready(function () {
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$("#asset_list_modal").modal();
|
||||
initSelectedAssets2Table();
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -78,58 +78,24 @@ var zTree, table, show = 0;
|
|||
function onSelected(event, treeNode) {
|
||||
setCookie('node_selected', treeNode.id);
|
||||
var url = table.ajax.url();
|
||||
if (treeNode.is_node) {
|
||||
if (treeNode.meta.type === 'node') {
|
||||
url = setUrlParam(url, 'asset', "");
|
||||
url = setUrlParam(url, 'node', treeNode.node_id)
|
||||
url = setUrlParam(url, 'node', treeNode.meta.node.id)
|
||||
} else {
|
||||
url = setUrlParam(url, 'node', "");
|
||||
url = setUrlParam(url, 'asset', treeNode.node_id)
|
||||
url = setUrlParam(url, 'asset', treeNode.meta.asset.id)
|
||||
}
|
||||
setCookie('node_selected', treeNode.node_id);
|
||||
table.ajax.url(url);
|
||||
table.ajax.reload();
|
||||
}
|
||||
|
||||
function selectQueryNode() {
|
||||
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("id", node_id, null);
|
||||
if (node){
|
||||
zTree.selectNode(node[0]);
|
||||
node.open = true;
|
||||
}
|
||||
}
|
||||
|
||||
function filter(treeId, parentNode, childNodes) {
|
||||
$.each(childNodes, function (index, value) {
|
||||
value["node_id"] = value["id"];
|
||||
value["id"] = value["tree_id"];
|
||||
if (value["tree_id"] !== value["tree_parent"]) {
|
||||
value["pId"] = value["tree_parent"];
|
||||
} else {
|
||||
value["isParent"] = true;
|
||||
}
|
||||
value['name'] = value['value'];
|
||||
value["iconSkin"] = value["is_node"] ? null : 'file';
|
||||
|
||||
{#value["pId"] = value["parent"];#}
|
||||
{#value["name"] = value["value"];#}
|
||||
value["isParent"] = value["is_node"];
|
||||
});
|
||||
return childNodes;
|
||||
}
|
||||
|
||||
function beforeAsync(treeId, treeNode) {
|
||||
return treeNode.is_node
|
||||
if (treeNode) {
|
||||
return treeNode.meta.type === 'node'
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function makeLabel(data) {
|
||||
|
@ -235,9 +201,8 @@ function initTree() {
|
|||
},
|
||||
async: {
|
||||
enable: true,
|
||||
url: "{% url 'api-assets:node-children-2' %}?assets=1&all=",
|
||||
autoParam:["node_id=id", "name=n", "level=lv"],
|
||||
dataFilter: filter,
|
||||
url: "{% url 'api-assets:node-children-tree' %}?assets=1",
|
||||
autoParam:["id=key", "name=n", "level=lv"],
|
||||
type: 'get'
|
||||
},
|
||||
callback: {
|
||||
|
@ -245,25 +210,7 @@ function initTree() {
|
|||
beforeAsync: beforeAsync
|
||||
}
|
||||
};
|
||||
|
||||
var zNodes = [];
|
||||
$.get("{% url 'api-assets:node-children-2' %}?assets=1", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
value["node_id"] = value["id"];
|
||||
value["id"] = value["tree_id"];
|
||||
if (value["tree_id"] !== value["tree_parent"]) {
|
||||
value["pId"] = value["tree_parent"];
|
||||
}
|
||||
value["isParent"] = value["is_node"];
|
||||
value['name'] = value['value'];
|
||||
value["iconSkin"] = value["is_node"] ? null : 'file';
|
||||
});
|
||||
zNodes = data;
|
||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||
var root = zTree.getNodes()[0];
|
||||
zTree.expandNode(root);
|
||||
});
|
||||
zTree = $.fn.zTree.init($("#assetTree"), setting);
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
|
@ -299,10 +246,10 @@ $(document).ready(function(){
|
|||
var _nodes = [];
|
||||
var _assets = [];
|
||||
$.each(nodes, function (id, node) {
|
||||
if (node.is_node) {
|
||||
_nodes.push(node.node_id)
|
||||
if (node.meta.type === 'node') {
|
||||
_nodes.push(node.meta.node.id)
|
||||
} else {
|
||||
_assets.push(node.node_id)
|
||||
_assets.push(node.meta.asset.id)
|
||||
}
|
||||
});
|
||||
url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(",");
|
||||
|
|
|
@ -812,3 +812,32 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul
|
|||
$idPassword.pwstrength(options);
|
||||
popoverPasswordRules(password_check_rules, $el);
|
||||
}
|
||||
|
||||
// 解决input框中的资产和弹出表格中资产的显示不一致
|
||||
function initSelectedAssets2Table(){
|
||||
var inputAssets = $('#id_assets').val();
|
||||
var selectedAssets = asset_table2.selected.concat();
|
||||
|
||||
// input assets无,table assets选中,则取消勾选(再次click)
|
||||
if (selectedAssets.length !== 0){
|
||||
$.each(selectedAssets, function (index, assetId){
|
||||
if ($.inArray(assetId, inputAssets) === -1){
|
||||
$('#'+assetId).trigger('click'); // 取消勾选
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// input assets有,table assets没选,则选中(click)
|
||||
if (inputAssets !== null){
|
||||
asset_table2.selected = inputAssets;
|
||||
$.each(inputAssets, function(index, assetId){
|
||||
var dom = document.getElementById(assetId);
|
||||
if (dom !== null){
|
||||
var selected = dom.parentElement.parentElement.className.indexOf('selected')
|
||||
}
|
||||
if (selected === -1){
|
||||
$('#'+assetId).trigger('click');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,44 +94,15 @@ class SessionReplayViewSet(viewsets.ViewSet):
|
|||
serializer_class = serializers.ReplaySerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
session = None
|
||||
upload_to = 'replay' # 仅添加到本地存储中
|
||||
|
||||
def get_session_path(self, version=2):
|
||||
"""
|
||||
获取session日志的文件路径
|
||||
:param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz
|
||||
:return:
|
||||
"""
|
||||
suffix = '.replay.gz'
|
||||
if version == 1:
|
||||
suffix = '.gz'
|
||||
date = self.session.date_start.strftime('%Y-%m-%d')
|
||||
return os.path.join(date, str(self.session.id) + suffix)
|
||||
|
||||
def get_local_path(self, version=2):
|
||||
session_path = self.get_session_path(version=version)
|
||||
if version == 2:
|
||||
local_path = os.path.join(self.upload_to, session_path)
|
||||
else:
|
||||
local_path = session_path
|
||||
return local_path
|
||||
|
||||
def save_to_storage(self, f):
|
||||
local_path = self.get_local_path()
|
||||
try:
|
||||
name = default_storage.save(local_path, f)
|
||||
return name, None
|
||||
except OSError as e:
|
||||
return None, e
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
session_id = kwargs.get('pk')
|
||||
self.session = get_object_or_404(Session, id=session_id)
|
||||
session = get_object_or_404(Session, id=session_id)
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
|
||||
if serializer.is_valid():
|
||||
file = serializer.validated_data['file']
|
||||
name, err = self.save_to_storage(file)
|
||||
name, err = session.save_to_storage(file)
|
||||
if not name:
|
||||
msg = "Failed save replay `{}`: {}".format(session_id, err)
|
||||
logger.error(msg)
|
||||
|
@ -145,7 +116,7 @@ class SessionReplayViewSet(viewsets.ViewSet):
|
|||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
session_id = kwargs.get('pk')
|
||||
self.session = get_object_or_404(Session, id=session_id)
|
||||
session = get_object_or_404(Session, id=session_id)
|
||||
|
||||
data = {
|
||||
'type': 'guacamole' if self.session.protocol == 'rdp' else 'json',
|
||||
|
@ -153,9 +124,9 @@ class SessionReplayViewSet(viewsets.ViewSet):
|
|||
}
|
||||
|
||||
# 新版本和老版本的文件后缀不同
|
||||
session_path = self.get_session_path() # 存在外部存储上的路径
|
||||
local_path = self.get_local_path()
|
||||
local_path_v1 = self.get_local_path(version=1)
|
||||
session_path = session.get_rel_replay_path() # 存在外部存储上的路径
|
||||
local_path = session.get_local_path()
|
||||
local_path_v1 = session.get_local_path(version=1)
|
||||
|
||||
# 去default storage中查找
|
||||
for _local_path in (local_path, local_path_v1, session_path):
|
||||
|
|
|
@ -13,11 +13,11 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='command',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='session',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -10,14 +10,4 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='command',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='session',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage
|
||||
|
||||
from users.models import User
|
||||
from orgs.mixins import OrgModelMixin
|
||||
|
@ -148,6 +150,36 @@ class Session(OrgModelMixin):
|
|||
date_start = models.DateTimeField(verbose_name=_("Date start"), db_index=True, default=timezone.now)
|
||||
date_end = models.DateTimeField(verbose_name=_("Date end"), null=True)
|
||||
|
||||
upload_to = 'replay'
|
||||
|
||||
def get_rel_replay_path(self, version=2):
|
||||
"""
|
||||
获取session日志的文件路径
|
||||
:param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz
|
||||
:return:
|
||||
"""
|
||||
suffix = '.replay.gz'
|
||||
if version == 1:
|
||||
suffix = '.gz'
|
||||
date = self.date_start.strftime('%Y-%m-%d')
|
||||
return os.path.join(date, str(self.id) + suffix)
|
||||
|
||||
def get_local_path(self, version=2):
|
||||
rel_path = self.get_rel_replay_path(version=version)
|
||||
if version == 2:
|
||||
local_path = os.path.join(self.upload_to, rel_path)
|
||||
else:
|
||||
local_path = rel_path
|
||||
return local_path
|
||||
|
||||
def save_to_storage(self, f):
|
||||
local_path = self.get_local_path()
|
||||
try:
|
||||
name = default_storage.save(local_path, f)
|
||||
return name, None
|
||||
except OSError as e:
|
||||
return None, e
|
||||
|
||||
class Meta:
|
||||
db_table = "terminal_session"
|
||||
ordering = ["-date_start"]
|
||||
|
|
|
@ -4,15 +4,20 @@
|
|||
import datetime
|
||||
|
||||
from celery import shared_task
|
||||
from celery.utils.log import get_task_logger
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage
|
||||
|
||||
|
||||
from ops.celery.utils import register_as_period_task, after_app_ready_start, \
|
||||
after_app_shutdown_clean
|
||||
from .models import Status, Session
|
||||
from .models import Status, Session, Command
|
||||
|
||||
|
||||
CACHE_REFRESH_INTERVAL = 10
|
||||
RUNNING = False
|
||||
logger = get_task_logger(__name__)
|
||||
|
||||
|
||||
@shared_task
|
||||
|
@ -34,3 +39,28 @@ def clean_orphan_session():
|
|||
if not session.terminal or not session.terminal.is_active:
|
||||
session.is_finished = True
|
||||
session.save()
|
||||
|
||||
|
||||
@shared_task
|
||||
@register_as_period_task(interval=3600*24)
|
||||
@after_app_ready_start
|
||||
@after_app_shutdown_clean
|
||||
def clean_expired_session_period():
|
||||
logger.info("Start clean expired session record, commands and replay")
|
||||
days = settings.TERMINAL_SESSION_KEEP_DURATION
|
||||
dt = timezone.now() - timezone.timedelta(days=days)
|
||||
expired_sessions = Session.objects.filter(date_start__lt=dt)
|
||||
for session in expired_sessions:
|
||||
logger.info("Clean session: {}".format(session.id))
|
||||
Command.objects.filter(session=str(session.id)).delete()
|
||||
# 删除录像文件
|
||||
session_path = session.get_rel_replay_path()
|
||||
local_path = session.get_local_path()
|
||||
local_path_v1 = session.get_local_path(version=1)
|
||||
|
||||
# 去default storage中查找
|
||||
for _local_path in (local_path, local_path_v1, session_path):
|
||||
if default_storage.exists(_local_path):
|
||||
default_storage.delete(_local_path)
|
||||
# 删除session记录
|
||||
session.delete()
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.core.cache import cache
|
||||
from .models import Session
|
||||
|
||||
from assets.models import Asset, SystemUser
|
||||
from users.models import User
|
||||
|
||||
from .const import USERS_CACHE_KEY, ASSETS_CACHE_KEY, SYSTEM_USER_CACHE_KEY
|
||||
|
||||
|
||||
def get_session_asset_list():
|
||||
return set(list(Session.objects.values_list('asset', flat=True)))
|
||||
return Asset.objects.values_list('hostname', flat=True)
|
||||
|
||||
|
||||
def get_session_user_list():
|
||||
return set(list(Session.objects.values_list('user', flat=True)))
|
||||
return User.objects.values_list('username', flat=True)
|
||||
|
||||
|
||||
def get_session_system_user_list():
|
||||
return set(list(Session.objects.values_list('system_user', flat=True)))
|
||||
return SystemUser.objects.values_list('username', flat=True)
|
||||
|
||||
|
||||
def get_user_list_from_cache():
|
||||
|
|
|
@ -6,7 +6,7 @@ from rest_framework_bulk import BulkModelViewSet
|
|||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from ..serializers import UserGroupSerializer, \
|
||||
UserGroupUpdateMemeberSerializer
|
||||
UserGroupUpdateMemberSerializer
|
||||
from ..models import UserGroup
|
||||
from common.permissions import IsOrgAdmin
|
||||
from common.mixins import IDInFilterMixin
|
||||
|
@ -26,5 +26,5 @@ class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
|
|||
|
||||
class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
|
||||
queryset = UserGroup.objects.all()
|
||||
serializer_class = UserGroupUpdateMemeberSerializer
|
||||
serializer_class = UserGroupUpdateMemberSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
|
|
@ -46,6 +46,9 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet):
|
|||
self.permission_classes = (IsOrgAdminOrAppUser,)
|
||||
return super().get_permissions()
|
||||
|
||||
def allow_bulk_destroy(self, qs, filtered):
|
||||
return qs.count() == filtered.count()
|
||||
|
||||
|
||||
class UserChangePasswordApi(generics.RetrieveUpdateAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
|
|
@ -13,6 +13,6 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='usergroup',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default='', max_length=36, verbose_name='Organization'),
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -142,6 +142,18 @@ class User(AbstractUser):
|
|||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def groups_display(self):
|
||||
return ' '.join(self.groups.all().values_list('name', flat=True))
|
||||
|
||||
@property
|
||||
def role_display(self):
|
||||
return self.get_role_display()
|
||||
|
||||
@property
|
||||
def source_display(self):
|
||||
return self.get_source_display()
|
||||
|
||||
@property
|
||||
def is_expired(self):
|
||||
if self.date_expired and self.date_expired < timezone.now():
|
||||
|
|
|
@ -13,31 +13,18 @@ signer = get_signer()
|
|||
|
||||
|
||||
class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
groups_display = serializers.SerializerMethodField()
|
||||
groups = serializers.PrimaryKeyRelatedField(
|
||||
many=True, queryset=UserGroup.objects.all(), required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
list_serializer_class = BulkListSerializer
|
||||
exclude = [
|
||||
'first_name', 'last_name', 'password', '_private_key',
|
||||
'_public_key', '_otp_secret_key', 'user_permissions'
|
||||
fields = [
|
||||
'id', 'name', 'username', 'email', 'groups', 'groups_display',
|
||||
'role', 'role_display', 'avatar_url', 'wechat', 'phone',
|
||||
'otp_level', 'comment', 'source', 'source_display',
|
||||
'is_valid', 'is_expired', 'is_active',
|
||||
'created_by', 'is_first_login',
|
||||
'date_password_last_updated', 'date_expired',
|
||||
]
|
||||
# validators = []
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(UserSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.extend([
|
||||
'groups_display', 'get_role_display',
|
||||
'get_source_display', 'is_valid'
|
||||
])
|
||||
return fields
|
||||
|
||||
@staticmethod
|
||||
def get_groups_display(obj):
|
||||
return " ".join([group.name for group in obj.groups.all()])
|
||||
|
||||
|
||||
class UserPKUpdateSerializer(serializers.ModelSerializer):
|
||||
|
@ -74,7 +61,7 @@ class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
|||
return [user.name for user in obj.users.all()]
|
||||
|
||||
|
||||
class UserGroupUpdateMemeberSerializer(serializers.ModelSerializer):
|
||||
class UserGroupUpdateMemberSerializer(serializers.ModelSerializer):
|
||||
users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all())
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<th class="text-center">{% trans 'Role' %}</th>
|
||||
<th class="text-center">{% trans 'User group' %}</th>
|
||||
<th class="text-center">{% trans 'Source' %}</th>
|
||||
<th class="text-center">{% trans 'Active' %}</th>
|
||||
<th class="text-center">{% trans 'Validity' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -66,11 +66,14 @@ function initTable() {
|
|||
var innerHtml = cellData.length > 20 ? cellData.substring(0, 20) + '...': cellData;
|
||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData) {
|
||||
if (!cellData) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else {
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
if (cellData) {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
} else if (!rowData.is_active) {
|
||||
|
||||
$(td).html('<i class="fa fa-times text-danger inactive"></i>')
|
||||
} else if (rowData.is_expired) {
|
||||
$(td).html('<i class="fa fa-times text-danger expired"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 7, createdCell: function (td, cellData, rowData) {
|
||||
|
@ -91,9 +94,9 @@ function initTable() {
|
|||
ajax_url: '{% url "api-users:user-list" %}',
|
||||
columns: [
|
||||
{data: "id"}, {data: "name" }, {data: "username" },
|
||||
{data: "get_role_display", orderable: false},
|
||||
{data: "role_display", orderable: false},
|
||||
{data: "groups_display", orderable: false},
|
||||
{data: "get_source_display", orderable: false},
|
||||
{data: "source_display", orderable: false},
|
||||
{data: "is_valid", orderable: false},
|
||||
{data: "id", orderable: false}
|
||||
],
|
||||
|
@ -246,6 +249,13 @@ $(document).ready(function(){
|
|||
var uid = $this.data('uid');
|
||||
var the_url = '{% url "api-users:user-detail" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", uid);
|
||||
objectDelete($this, name, the_url);
|
||||
}).on('click', '.expired', function () {
|
||||
var msg = '{% trans "User is expired" %}';
|
||||
toastr.error(msg)
|
||||
}).on('click', '.inactive', function () {
|
||||
var msg = '{% trans 'User is inactive' %}';
|
||||
toastr.error(msg)
|
||||
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -292,7 +292,8 @@ def check_otp_code(otp_secret_key, otp_code):
|
|||
if not otp_secret_key or not otp_code:
|
||||
return False
|
||||
totp = pyotp.TOTP(otp_secret_key)
|
||||
return totp.verify(otp_code)
|
||||
otp_valid_window = settings.OTP_VALID_WINDOW or 0
|
||||
return totp.verify(otp=otp_code, valid_window=otp_valid_window)
|
||||
|
||||
|
||||
def get_password_check_rules():
|
||||
|
|
|
@ -100,6 +100,9 @@ class Config:
|
|||
}
|
||||
AUTH_LDAP_START_TLS = False
|
||||
|
||||
#
|
||||
# OTP_VALID_WINDOW = 0
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
@ -200,6 +203,10 @@ class DockerConfig(Config):
|
|||
AUTH_LDAP_START_TLS = False
|
||||
|
||||
|
||||
#
|
||||
OTP_VALID_WINDOW = int(os.environ.get("OTP_VALID_WINDOW")) if os.environ.get("OTP_VALID_WINDOW") else 0
|
||||
|
||||
|
||||
# Default using Config settings, you can write if/else for different env
|
||||
config = DockerConfig()
|
||||
|
||||
|
|
|
@ -90,6 +90,9 @@ class Config:
|
|||
# AUTH_OPENID_CLIENT_ID = 'client-id'
|
||||
# AUTH_OPENID_CLIENT_SECRET = 'client-secret'
|
||||
|
||||
#
|
||||
# OTP_VALID_WINDOW = 0
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
|
4
jms
4
jms
|
@ -26,8 +26,8 @@ LOG_DIR = os.path.join(BASE_DIR, 'logs')
|
|||
TMP_DIR = os.path.join(BASE_DIR, 'tmp')
|
||||
HTTP_HOST = CONFIG.HTTP_BIND_HOST or '127.0.0.1'
|
||||
HTTP_PORT = CONFIG.HTTP_LISTEN_PORT or 8080
|
||||
DEBUG = CONFIG.DEBUG
|
||||
LOG_LEVEL = CONFIG.LOG_LEVEL
|
||||
DEBUG = CONFIG.DEBUG or False
|
||||
LOG_LEVEL = CONFIG.LOG_LEVEL or 'INFO'
|
||||
|
||||
START_TIMEOUT = 40
|
||||
WORKERS = 4
|
||||
|
|
Loading…
Reference in New Issue