mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-07-07 11:59:18 +00:00
Merge branch 'master' of https://github.com/jumpserver/jumpserver
Conflicts: apps/terminal/api.py
This commit is contained in:
commit
a1905ecfdb
10
README.md
10
README.md
@ -19,14 +19,8 @@ Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点
|
|||||||
----
|
----
|
||||||
|
|
||||||
### 功能
|
### 功能
|
||||||
- 统一认证
|
|
||||||
- 资产管理
|

|
||||||
- 统一授权
|
|
||||||
- 审计
|
|
||||||
- 支持LDAP认证
|
|
||||||
- Web terminal
|
|
||||||
- SSH Server
|
|
||||||
- 支持Windows RDP
|
|
||||||
|
|
||||||
### 开始使用
|
### 开始使用
|
||||||
|
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "1.2.1"
|
__version__ = "1.3.1"
|
||||||
|
@ -43,6 +43,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
|||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||||
node_id = self.request.query_params.get("node_id")
|
node_id = self.request.query_params.get("node_id")
|
||||||
|
show_current_asset = self.request.query_params.get("show_current_asset")
|
||||||
|
|
||||||
if admin_user_id:
|
if admin_user_id:
|
||||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||||
@ -51,8 +52,11 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
|||||||
node = get_object_or_404(Node, id=node_id)
|
node = get_object_or_404(Node, id=node_id)
|
||||||
if not node.is_root():
|
if not node.is_root():
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
nodes__key__regex='{}(:[0-9]+)*$'.format(node.key),
|
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
||||||
).distinct()
|
).distinct()
|
||||||
|
if show_current_asset and node_id:
|
||||||
|
queryset = queryset.filter(nodes=node_id).distinct()
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from rest_framework import generics, mixins
|
from rest_framework import generics, mixins
|
||||||
|
from rest_framework.serializers import ValidationError
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
@ -41,7 +42,14 @@ __all__ = [
|
|||||||
class NodeViewSet(BulkModelViewSet):
|
class NodeViewSet(BulkModelViewSet):
|
||||||
queryset = Node.objects.all()
|
queryset = Node.objects.all()
|
||||||
permission_classes = (IsSuperUser,)
|
permission_classes = (IsSuperUser,)
|
||||||
serializer_class = serializers.NodeSerializer
|
# serializer_class = serializers.NodeSerializer
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
show_current_asset = self.request.query_params.get('show_current_asset')
|
||||||
|
if show_current_asset:
|
||||||
|
return serializers.NodeCurrentSerializer
|
||||||
|
else:
|
||||||
|
return serializers.NodeSerializer
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
child_key = Node.root().get_next_child_key()
|
child_key = Node.root().get_next_child_key()
|
||||||
@ -83,16 +91,29 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
|||||||
serializer_class = serializers.NodeSerializer
|
serializer_class = serializers.NodeSerializer
|
||||||
instance = None
|
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):
|
def post(self, request, *args, **kwargs):
|
||||||
if not request.data.get("value"):
|
if not request.data.get("value"):
|
||||||
request.data["value"] = _("New node {}").format(
|
request.data["value"] = _("New node {}").format(self.counter())
|
||||||
Node.root().get_next_child_key().split(":")[-1]
|
|
||||||
)
|
|
||||||
return super().post(request, *args, **kwargs)
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
value = request.data.get("value")
|
value = request.data.get("value")
|
||||||
|
values = [child.value for child in instance.get_children()]
|
||||||
|
if value in values:
|
||||||
|
raise ValidationError(
|
||||||
|
'The same level node name cannot be the same'
|
||||||
|
)
|
||||||
node = instance.create_child(value=value)
|
node = instance.create_child(value=value)
|
||||||
return Response(
|
return Response(
|
||||||
{"id": node.id, "key": node.key, "value": node.value},
|
{"id": node.id, "key": node.key, "value": node.value},
|
||||||
@ -127,8 +148,9 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
|||||||
node_fake.id = asset.id
|
node_fake.id = asset.id
|
||||||
node_fake.parent = node
|
node_fake.parent = node
|
||||||
node_fake.value = asset.hostname
|
node_fake.value = asset.hostname
|
||||||
node_fake.is_asset = True
|
node_fake.is_node = False
|
||||||
queryset.append(node_fake)
|
queryset.append(node_fake)
|
||||||
|
queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
@ -162,8 +184,9 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
|
|||||||
for node in children:
|
for node in children:
|
||||||
if not node:
|
if not node:
|
||||||
continue
|
continue
|
||||||
node.parent = instance
|
# node.parent = instance
|
||||||
node.save()
|
# node.save()
|
||||||
|
node.set_parent(instance)
|
||||||
return Response("OK")
|
return Response("OK")
|
||||||
|
|
||||||
|
|
||||||
@ -190,6 +213,9 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
|
|||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
if instance != Node.root():
|
if instance != Node.root():
|
||||||
instance.assets.remove(*tuple(assets))
|
instance.assets.remove(*tuple(assets))
|
||||||
|
else:
|
||||||
|
assets = [asset for asset in assets if asset.nodes.count() > 1]
|
||||||
|
instance.assets.remove(*tuple(assets))
|
||||||
|
|
||||||
|
|
||||||
class NodeReplaceAssetsApi(generics.UpdateAPIView):
|
class NodeReplaceAssetsApi(generics.UpdateAPIView):
|
||||||
|
@ -40,7 +40,7 @@ class SystemUserViewSet(BulkModelViewSet):
|
|||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
|
|
||||||
|
|
||||||
class SystemUserAuthInfoApi(generics.RetrieveUpdateAPIView):
|
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""
|
||||||
Get system user auth info
|
Get system user auth info
|
||||||
"""
|
"""
|
||||||
@ -48,6 +48,11 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateAPIView):
|
|||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
serializer_class = serializers.SystemUserAuthSerializer
|
serializer_class = serializers.SystemUserAuthSerializer
|
||||||
|
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
instance.clear_auth()
|
||||||
|
return Response(status=204)
|
||||||
|
|
||||||
|
|
||||||
class SystemUserPushApi(generics.RetrieveAPIView):
|
class SystemUserPushApi(generics.RetrieveAPIView):
|
||||||
"""
|
"""
|
||||||
@ -58,6 +63,9 @@ class SystemUserPushApi(generics.RetrieveAPIView):
|
|||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
system_user = self.get_object()
|
system_user = self.get_object()
|
||||||
|
nodes = system_user.nodes.all()
|
||||||
|
for node in nodes:
|
||||||
|
system_user.assets.add(*tuple(node.get_all_assets()))
|
||||||
task = push_system_user_to_assets_manual.delay(system_user)
|
task = push_system_user_to_assets_manual.delay(system_user)
|
||||||
return Response({"task": task.id})
|
return Response({"task": task.id})
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
import logging
|
import logging
|
||||||
import random
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
@ -35,6 +34,19 @@ def default_node():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class AssetQuerySet(models.QuerySet):
|
||||||
|
def active(self):
|
||||||
|
return self.filter(is_active=True)
|
||||||
|
|
||||||
|
def valid(self):
|
||||||
|
return self.active()
|
||||||
|
|
||||||
|
|
||||||
|
class AssetManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return AssetQuerySet(self.model, using=self._db)
|
||||||
|
|
||||||
|
|
||||||
class Asset(models.Model):
|
class Asset(models.Model):
|
||||||
# Important
|
# Important
|
||||||
PLATFORM_CHOICES = (
|
PLATFORM_CHOICES = (
|
||||||
@ -83,6 +95,8 @@ class Asset(models.Model):
|
|||||||
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
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'))
|
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||||
|
|
||||||
|
objects = AssetManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{0.hostname}({0.ip})'.format(self)
|
return '{0.hostname}({0.ip})'.format(self)
|
||||||
|
|
||||||
@ -103,7 +117,8 @@ class Asset(models.Model):
|
|||||||
|
|
||||||
def get_nodes(self):
|
def get_nodes(self):
|
||||||
from .node import Node
|
from .node import Node
|
||||||
return self.nodes.all() or [Node.root()]
|
nodes = self.nodes.all() or [Node.root()]
|
||||||
|
return nodes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hardware_info(self):
|
def hardware_info(self):
|
||||||
|
@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
|
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
|
||||||
|
from common.validators import alphanumeric
|
||||||
from .utils import private_key_validator
|
from .utils import private_key_validator
|
||||||
|
|
||||||
signer = get_signer()
|
signer = get_signer()
|
||||||
@ -18,7 +19,7 @@ signer = get_signer()
|
|||||||
class AssetUser(models.Model):
|
class AssetUser(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||||
username = models.CharField(max_length=128, verbose_name=_('Username'))
|
username = models.CharField(max_length=32, verbose_name=_('Username'), validators=[alphanumeric])
|
||||||
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||||
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
||||||
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||||
@ -103,10 +104,16 @@ class AssetUser(models.Model):
|
|||||||
if update_fields:
|
if update_fields:
|
||||||
self.save(update_fields=update_fields)
|
self.save(update_fields=update_fields)
|
||||||
|
|
||||||
|
def clear_auth(self):
|
||||||
|
self._password = ''
|
||||||
|
self._private_key = ''
|
||||||
|
self._public_key = ''
|
||||||
|
self.save()
|
||||||
|
|
||||||
def auto_gen_auth(self):
|
def auto_gen_auth(self):
|
||||||
password = str(uuid.uuid4())
|
password = str(uuid.uuid4())
|
||||||
private_key, public_key = ssh_key_gen(
|
private_key, public_key = ssh_key_gen(
|
||||||
username=self.username, password=password
|
username=self.username
|
||||||
)
|
)
|
||||||
self.set_auth(password=password,
|
self.set_auth(password=password,
|
||||||
private_key=private_key,
|
private_key=private_key,
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
#
|
#
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.db import models
|
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_lazy as _
|
||||||
|
|
||||||
|
|
||||||
@ -12,11 +13,14 @@ __all__ = ['Node']
|
|||||||
class Node(models.Model):
|
class Node(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||||
value = models.CharField(max_length=128, unique=True, verbose_name=_("Value"))
|
# value = models.CharField(
|
||||||
|
# max_length=128, unique=True, verbose_name=_("Value")
|
||||||
|
# )
|
||||||
|
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||||
child_mark = models.IntegerField(default=0)
|
child_mark = models.IntegerField(default=0)
|
||||||
date_create = models.DateTimeField(auto_now_add=True)
|
date_create = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
is_asset = False
|
is_node = True
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.full_value
|
return self.full_value
|
||||||
@ -36,6 +40,16 @@ class Node(models.Model):
|
|||||||
def level(self):
|
def level(self):
|
||||||
return len(self.key.split(':'))
|
return len(self.key.split(':'))
|
||||||
|
|
||||||
|
def set_parent(self, instance):
|
||||||
|
children = self.get_all_children()
|
||||||
|
old_key = self.key
|
||||||
|
with transaction.atomic():
|
||||||
|
self.parent = instance
|
||||||
|
for child in children:
|
||||||
|
child.key = child.key.replace(old_key, self.key, 1)
|
||||||
|
child.save()
|
||||||
|
self.save()
|
||||||
|
|
||||||
def get_next_child_key(self):
|
def get_next_child_key(self):
|
||||||
mark = self.child_mark
|
mark = self.child_mark
|
||||||
self.child_mark += 1
|
self.child_mark += 1
|
||||||
@ -48,56 +62,77 @@ class Node(models.Model):
|
|||||||
return child
|
return child
|
||||||
|
|
||||||
def get_children(self):
|
def get_children(self):
|
||||||
return self.__class__.objects.filter(key__regex=r'{}:[0-9]+$'.format(self.key))
|
return self.__class__.objects.filter(
|
||||||
|
key__regex=r'^{}:[0-9]+$'.format(self.key)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_children_with_self(self):
|
||||||
|
return self.__class__.objects.filter(
|
||||||
|
key__regex=r'^{0}$|^{0}:[0-9]+$'.format(self.key)
|
||||||
|
)
|
||||||
|
|
||||||
def get_all_children(self):
|
def get_all_children(self):
|
||||||
return self.__class__.objects.filter(key__startswith='{}:'.format(self.key))
|
return self.__class__.objects.filter(
|
||||||
|
key__startswith='{}:'.format(self.key)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_all_children_with_self(self):
|
||||||
|
return self.__class__.objects.filter(
|
||||||
|
key__regex=r'^{0}$|^{0}:'.format(self.key)
|
||||||
|
)
|
||||||
|
|
||||||
def get_family(self):
|
def get_family(self):
|
||||||
children = list(self.get_all_children())
|
ancestor = self.ancestor
|
||||||
children.append(self)
|
children = self.get_all_children()
|
||||||
return children
|
return [*tuple(ancestor), self, *tuple(children)]
|
||||||
|
|
||||||
def get_assets(self):
|
def get_assets(self):
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
assets = Asset.objects.filter(nodes__id=self.id)
|
if self.is_root():
|
||||||
|
assets = Asset.objects.filter(
|
||||||
|
Q(nodes__id=self.id) | Q(nodes__isnull=True)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
assets = Asset.objects.filter(nodes__id=self.id)
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
def get_active_assets(self):
|
def get_valid_assets(self):
|
||||||
return self.get_assets().filter(is_active=True)
|
return self.get_assets().valid()
|
||||||
|
|
||||||
def get_all_assets(self):
|
def get_all_assets(self):
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
if self.is_root():
|
if self.is_root():
|
||||||
assets = Asset.objects.all()
|
assets = Asset.objects.all()
|
||||||
else:
|
else:
|
||||||
nodes = self.get_family()
|
nodes = self.get_all_children_with_self()
|
||||||
assets = Asset.objects.filter(nodes__in=nodes).distinct()
|
assets = Asset.objects.filter(nodes__in=nodes).distinct()
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
|
def get_current_assets(self):
|
||||||
|
from .asset import Asset
|
||||||
|
assets = Asset.objects.filter(nodes=self).distinct()
|
||||||
|
return assets
|
||||||
|
|
||||||
def has_assets(self):
|
def has_assets(self):
|
||||||
return self.get_all_assets()
|
return self.get_all_assets()
|
||||||
|
|
||||||
def get_all_active_assets(self):
|
def get_all_valid_assets(self):
|
||||||
return self.get_all_assets().filter(is_active=True)
|
return self.get_all_assets().valid()
|
||||||
|
|
||||||
def is_root(self):
|
def is_root(self):
|
||||||
return self.key == '0'
|
return self.key == '0'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent(self):
|
def parent(self):
|
||||||
if self.key == "0":
|
if self.key == "0" or not self.key.startswith("0"):
|
||||||
return self.__class__.root()
|
|
||||||
elif not self.key.startswith("0"):
|
|
||||||
return self.__class__.root()
|
return self.__class__.root()
|
||||||
|
|
||||||
parent_key = ":".join(self.key.split(":")[:-1])
|
parent_key = ":".join(self.key.split(":")[:-1])
|
||||||
try:
|
try:
|
||||||
parent = self.__class__.objects.get(key=parent_key)
|
parent = self.__class__.objects.get(key=parent_key)
|
||||||
|
return parent
|
||||||
except Node.DoesNotExist:
|
except Node.DoesNotExist:
|
||||||
return self.__class__.root()
|
return self.__class__.root()
|
||||||
else:
|
|
||||||
return parent
|
|
||||||
|
|
||||||
@parent.setter
|
@parent.setter
|
||||||
def parent(self, parent):
|
def parent(self, parent):
|
||||||
@ -105,14 +140,20 @@ class Node(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def ancestor(self):
|
def ancestor(self):
|
||||||
if self.parent == self.__class__.root():
|
_key = self.key.split(':')
|
||||||
|
ancestor_keys = []
|
||||||
|
|
||||||
|
if self.is_root():
|
||||||
return [self.__class__.root()]
|
return [self.__class__.root()]
|
||||||
else:
|
|
||||||
return [self.parent, *tuple(self.parent.ancestor)]
|
for i in range(len(_key)-1):
|
||||||
|
_key.pop()
|
||||||
|
ancestor_keys.append(':'.join(_key))
|
||||||
|
return self.__class__.objects.filter(key__in=ancestor_keys)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ancestor_with_node(self):
|
def ancestor_with_self(self):
|
||||||
ancestor = self.ancestor
|
ancestor = list(self.ancestor)
|
||||||
ancestor.insert(0, self)
|
ancestor.insert(0, self)
|
||||||
return ancestor
|
return ancestor
|
||||||
|
|
||||||
|
@ -18,8 +18,7 @@ class NodeTMPSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Node
|
model = Node
|
||||||
fields = ['id', 'key', 'value', 'parent', 'assets_amount',
|
fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_node']
|
||||||
'is_asset']
|
|
||||||
list_serializer_class = BulkListSerializer
|
list_serializer_class = BulkListSerializer
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -62,13 +61,13 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
|
|||||||
"""
|
"""
|
||||||
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
|
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
|
||||||
system_users_join = serializers.SerializerMethodField()
|
system_users_join = serializers.SerializerMethodField()
|
||||||
nodes = NodeTMPSerializer(many=True, read_only=True)
|
# nodes = NodeTMPSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Asset
|
model = Asset
|
||||||
fields = (
|
fields = (
|
||||||
"id", "hostname", "ip", "port", "system_users_granted",
|
"id", "hostname", "ip", "port", "system_users_granted",
|
||||||
"is_active", "system_users_join", "os", 'domain', "nodes",
|
"is_active", "system_users_join", "os", 'domain',
|
||||||
"platform", "comment"
|
"platform", "comment"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ from .asset import AssetGrantedSerializer
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'NodeSerializer', "NodeGrantedSerializer", "NodeAddChildrenSerializer",
|
'NodeSerializer', "NodeGrantedSerializer", "NodeAddChildrenSerializer",
|
||||||
"NodeAssetsSerializer",
|
"NodeAssetsSerializer", "NodeCurrentSerializer",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -48,9 +48,20 @@ class NodeSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Node
|
model = Node
|
||||||
fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_asset']
|
fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_node']
|
||||||
list_serializer_class = BulkListSerializer
|
list_serializer_class = BulkListSerializer
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
value = data.get('value')
|
||||||
|
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:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
'The same level node name cannot be the same'
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_parent(obj):
|
def get_parent(obj):
|
||||||
return obj.parent.id
|
return obj.parent.id
|
||||||
@ -66,6 +77,12 @@ class NodeSerializer(serializers.ModelSerializer):
|
|||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
class NodeCurrentSerializer(NodeSerializer):
|
||||||
|
@staticmethod
|
||||||
|
def get_assets_amount(obj):
|
||||||
|
return obj.get_current_assets().count()
|
||||||
|
|
||||||
|
|
||||||
class NodeAssetsSerializer(serializers.ModelSerializer):
|
class NodeAssetsSerializer(serializers.ModelSerializer):
|
||||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ TIMEOUT = 60
|
|||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
CACHE_MAX_TIME = 60*60*60
|
CACHE_MAX_TIME = 60*60*60
|
||||||
disk_pattern = re.compile(r'^hd|sd|xvd|vd')
|
disk_pattern = re.compile(r'^hd|sd|xvd|vd')
|
||||||
PERIOD_TASK = os.environ.get("PERIOD_TASK", "on")
|
PERIOD_TASK = os.environ.get("PERIOD_TASK", "off")
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
|
@ -98,7 +98,10 @@ function initTree2() {
|
|||||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||||
$.each(data, function (index, value) {
|
$.each(data, function (index, value) {
|
||||||
value["pId"] = value["parent"];
|
value["pId"] = value["parent"];
|
||||||
value["open"] = true;
|
{#value["open"] = true;#}
|
||||||
|
if (value["key"] === "0") {
|
||||||
|
value["open"] = true;
|
||||||
|
}
|
||||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||||
value['value'] = value['value'];
|
value['value'] = value['value'];
|
||||||
});
|
});
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
|
<label for="{{ form.auto_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
{{ form.auto_push}}
|
{{ form.auto_push}}
|
||||||
</div>
|
</div>
|
||||||
@ -79,43 +79,50 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
||||||
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
|
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
|
||||||
var password_id = '#' + '{{ form.password.id_for_label }}';
|
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
|
||||||
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
|
var auto_push_id = '#' + '{{ form.auto_push.id_for_label }}';
|
||||||
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
|
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
|
||||||
var shell_id = '#' + '{{ form.shell.id_for_label }}';
|
var shell_id = '#' + '{{ form.shell.id_for_label }}';
|
||||||
|
var need_change_field = [
|
||||||
|
auto_generate_key, private_key_id, auto_push_id, sudo_id, shell_id
|
||||||
|
];
|
||||||
|
|
||||||
var need_change_field = [auto_generate_key, private_key_id, sudo_id, shell_id] ;
|
function protocolChange() {
|
||||||
|
if ($(protocol_id + " option:selected").text() === 'rdp') {
|
||||||
|
$('.auth-fields').removeClass('hidden');
|
||||||
|
$.each(need_change_field, function (index, value) {
|
||||||
|
$(value).closest('.form-group').addClass('hidden')
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
authFieldsDisplay();
|
||||||
|
$.each(need_change_field, function (index, value) {
|
||||||
|
$(value).closest('.form-group').removeClass('hidden')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function authFieldsDisplay() {
|
function authFieldsDisplay() {
|
||||||
if ($(auto_generate_key).prop('checked')) {
|
if ($(auto_generate_key).prop('checked')) {
|
||||||
$('.auth-fields').addClass('hidden');
|
$('.auth-fields').addClass('hidden');
|
||||||
} else {
|
} else {
|
||||||
$('.auth-fields').removeClass('hidden');
|
$('.auth-fields').removeClass('hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function protocolChange() {
|
$(document).ready(function () {
|
||||||
if ($(protocol_id).attr('value') === 'rdp') {
|
$('.select2').select2();
|
||||||
$.each(need_change_field, function (index, value) {
|
authFieldsDisplay();
|
||||||
$(value).addClass('hidden')
|
protocolChange();
|
||||||
});
|
})
|
||||||
$(password_id).removeClass('hidden')
|
.on('change', protocol_id, function(){
|
||||||
} else {
|
protocolChange();
|
||||||
$.each(need_change_field, function (index, value) {
|
})
|
||||||
$(value).removeClass('hidden')
|
.on('change', auto_generate_key, function(){
|
||||||
});
|
authFieldsDisplay();
|
||||||
}
|
});
|
||||||
}
|
|
||||||
$(document).ready(function () {
|
</script>
|
||||||
$('.select2').select2();
|
|
||||||
authFieldsDisplay();
|
|
||||||
protocolChange();
|
|
||||||
$(auto_generate_key).change(function () {
|
|
||||||
authFieldsDisplay();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -124,7 +124,7 @@ $(document).ready(function () {
|
|||||||
var success = function (data) {
|
var success = function (data) {
|
||||||
var task_id = data.task;
|
var task_id = data.task;
|
||||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||||
window.open(url, '', 'width=800,height=600')
|
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||||
};
|
};
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
|
@ -190,7 +190,7 @@
|
|||||||
<td colspan="2" class="no-borders">
|
<td colspan="2" class="no-borders">
|
||||||
<select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
|
<select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
|
||||||
{% for node in nodes_remain %}
|
{% for node in nodes_remain %}
|
||||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.name }}</option>
|
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
@ -204,7 +204,7 @@
|
|||||||
|
|
||||||
{% for node in asset.nodes.all %}
|
{% for node in asset.nodes.all %}
|
||||||
<tr>
|
<tr>
|
||||||
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.name }}</b></td>
|
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-danger pull-right btn-xs btn-leave-node" type="button"><i class="fa fa-minus"></i></button>
|
<button class="btn btn-danger pull-right btn-xs btn-leave-node" type="button"><i class="fa fa-minus"></i></button>
|
||||||
</td>
|
</td>
|
||||||
|
@ -17,20 +17,21 @@
|
|||||||
position:absolute;
|
position:absolute;
|
||||||
visibility:hidden;
|
visibility:hidden;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
top: 100%;
|
{#top: 100%;#}
|
||||||
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
float: left;
|
{#float: left;#}
|
||||||
padding: 5px 0;
|
padding: 0 0;
|
||||||
margin: 2px 0 0;
|
margin: 2px 0 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
}
|
}
|
||||||
div#rMenu li{
|
div#rMenu li{
|
||||||
margin: 1px 0;
|
margin: 1px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
{#list-style: none outside none;#}
|
list-style: none outside none;
|
||||||
}
|
}
|
||||||
.dropdown a:hover {
|
.dropdown a:hover {
|
||||||
background-color: #f1f1f1
|
background-color: #f1f1f1
|
||||||
}
|
}
|
||||||
@ -47,7 +48,6 @@
|
|||||||
<div class="file-manager ">
|
<div class="file-manager ">
|
||||||
<div id="assetTree" class="ztree">
|
<div id="assetTree" class="ztree">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -87,7 +87,7 @@
|
|||||||
<th class="text-center">{% trans 'IP' %}</th>
|
<th class="text-center">{% trans 'IP' %}</th>
|
||||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||||
<th class="text-center">{% trans 'Active' %}</th>
|
<th class="text-center">{% trans 'Active' %}</th>
|
||||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
{# <th class="text-center">{% trans 'Reachable' %}</th>#}
|
||||||
<th class="text-center">{% trans 'Action' %}</th>
|
<th class="text-center">{% trans 'Action' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -127,6 +127,9 @@
|
|||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li id="menu_refresh_hardware_info" class="btn-refresh-hardware" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh node hardware info' %}</a></li>
|
<li id="menu_refresh_hardware_info" class="btn-refresh-hardware" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh node hardware info' %}</a></li>
|
||||||
<li id="menu_test_connective" class="btn-test-connective" tabindex="-1"><a><i class="fa fa-chain"></i> {% trans 'Test node connective' %}</a></li>
|
<li id="menu_test_connective" class="btn-test-connective" tabindex="-1"><a><i class="fa fa-chain"></i> {% trans 'Test node connective' %}</a></li>
|
||||||
|
<li class="divider"></li>
|
||||||
|
<li id="show_current_asset" class="btn-show-current-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-hand-o-up"></i> {% trans 'Display only current node assets' %}</a></li>
|
||||||
|
<li id="show_all_asset" class="btn-show-all-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-th"></i> {% trans 'Displays all child node assets' %}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -157,26 +160,35 @@ function initTable() {
|
|||||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
{targets: 5, createdCell: function (td, cellData) {
|
|
||||||
if (cellData === 'Unknown'){
|
{#{targets: 5, createdCell: function (td, cellData) {#}
|
||||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
{# if (cellData === 'Unknown'){#}
|
||||||
} else if (!cellData) {
|
{# $(td).html('<i class="fa fa-circle text-warning"></i>')#}
|
||||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
{# } else if (!cellData) {#}
|
||||||
} else {
|
{# $(td).html('<i class="fa fa-circle text-danger"></i>')#}
|
||||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
{# } else {#}
|
||||||
}
|
{# $(td).html('<i class="fa fa-circle text-navy"></i>')#}
|
||||||
}},
|
{# }#}
|
||||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
{# }},#}
|
||||||
|
|
||||||
|
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||||
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
$(td).html(update_btn + del_btn)
|
$(td).html(update_btn + del_btn)
|
||||||
}}
|
}}
|
||||||
],
|
],
|
||||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
ajax_url: '{% url "api-assets:asset-list" %}',
|
||||||
|
|
||||||
|
{#columns: [#}
|
||||||
|
{# {data: "id"}, {data: "hostname" }, {data: "ip" },#}
|
||||||
|
{# {data: "cpu_cores"}, {data: "is_active", orderable: false },#}
|
||||||
|
{# {data: "is_connective", orderable: false}, {data: "id", orderable: false }#}
|
||||||
|
{#],#}
|
||||||
|
|
||||||
columns: [
|
columns: [
|
||||||
{data: "id"}, {data: "hostname" }, {data: "ip" },
|
{data: "id"}, {data: "hostname" }, {data: "ip" },
|
||||||
{data: "cpu_cores"}, {data: "is_active", orderable: false },
|
{data: "cpu_cores"}, {data: "is_active", orderable: false },
|
||||||
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
|
{data: "id", orderable: false }
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
@ -200,6 +212,8 @@ function addTreeNode() {
|
|||||||
};
|
};
|
||||||
newNode.checked = zTree.getSelectedNodes()[0].checked;
|
newNode.checked = zTree.getSelectedNodes()[0].checked;
|
||||||
zTree.addNodes(parentNode, 0, newNode);
|
zTree.addNodes(parentNode, 0, newNode);
|
||||||
|
var node = zTree.getNodeByParam('id', newNode.id, parentNode)
|
||||||
|
zTree.editName(node);
|
||||||
} else {
|
} else {
|
||||||
alert("{% trans 'Create node failed' %}")
|
alert("{% trans 'Create node failed' %}")
|
||||||
}
|
}
|
||||||
@ -230,9 +244,9 @@ function removeTreeNode() {
|
|||||||
|
|
||||||
function editTreeNode() {
|
function editTreeNode() {
|
||||||
hideRMenu();
|
hideRMenu();
|
||||||
var current_node = zTree.getSelectedNodes()[0];
|
var current_node = zTree.getSelectedNodes()[0];
|
||||||
if (!current_node){
|
if (!current_node){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (current_node.value) {
|
if (current_node.value) {
|
||||||
current_node.name = current_node.value;
|
current_node.name = current_node.value;
|
||||||
@ -253,6 +267,8 @@ function OnRightClick(event, treeId, treeNode) {
|
|||||||
function showRMenu(type, x, y) {
|
function showRMenu(type, x, y) {
|
||||||
$("#rMenu ul").show();
|
$("#rMenu ul").show();
|
||||||
x -= 220;
|
x -= 220;
|
||||||
|
x += document.body.scrollLeft;
|
||||||
|
y += document.body.scrollTop+document.documentElement.scrollTop;
|
||||||
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
|
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
|
||||||
|
|
||||||
$("body").bind("mousedown", onBodyMouseDown);
|
$("body").bind("mousedown", onBodyMouseDown);
|
||||||
@ -290,6 +306,7 @@ function onRename(event, treeId, treeNode, isCancel){
|
|||||||
function onSelected(event, treeNode) {
|
function onSelected(event, treeNode) {
|
||||||
var url = asset_table.ajax.url();
|
var url = asset_table.ajax.url();
|
||||||
url = setUrlParam(url, "node_id", treeNode.id);
|
url = setUrlParam(url, "node_id", treeNode.id);
|
||||||
|
url = setUrlParam(url, "show_current_asset", getCookie('show_current_asset'));
|
||||||
setCookie('node_selected', treeNode.id);
|
setCookie('node_selected', treeNode.id);
|
||||||
asset_table.ajax.url(url);
|
asset_table.ajax.url(url);
|
||||||
asset_table.ajax.reload();
|
asset_table.ajax.reload();
|
||||||
@ -382,12 +399,13 @@ function initTree() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var zNodes = [];
|
var zNodes = [];
|
||||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
var query_params = {'show_current_asset': getCookie('show_current_asset')};
|
||||||
|
$.get("{% url 'api-assets:node-list' %}", query_params, function(data, status){
|
||||||
$.each(data, function (index, value) {
|
$.each(data, function (index, value) {
|
||||||
value["pId"] = value["parent"];
|
value["pId"] = value["parent"];
|
||||||
{#if (value["key"] === "0") {#}
|
if (value["key"] === "0") {
|
||||||
value["open"] = true;
|
value["open"] = true;
|
||||||
{# }#}
|
}
|
||||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||||
value['value'] = value['value'];
|
value['value'] = value['value'];
|
||||||
});
|
});
|
||||||
@ -417,6 +435,13 @@ function toggle() {
|
|||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
initTable();
|
initTable();
|
||||||
initTree();
|
initTree();
|
||||||
|
|
||||||
|
if(getCookie('show_current_asset') === 'yes'){
|
||||||
|
$('#show_all_asset').css('display', 'inline-block');
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$('#show_current_asset').css('display', 'inline-block');
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.on('click', '.labels li', function () {
|
.on('click', '.labels li', function () {
|
||||||
var val = $(this).text();
|
var val = $(this).text();
|
||||||
@ -535,6 +560,20 @@ $(document).ready(function(){
|
|||||||
flash_message: false
|
flash_message: false
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
.on('click', '.btn-show-current-asset', function(){
|
||||||
|
hideRMenu();
|
||||||
|
$(this).css('display', 'none');
|
||||||
|
$('#show_all_asset').css('display', 'inline-block');
|
||||||
|
setCookie('show_current_asset', 'yes');
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
.on('click', '.btn-show-all-asset', function(){
|
||||||
|
hideRMenu();
|
||||||
|
$(this).css('display', 'none');
|
||||||
|
$('#show_current_asset').css('display', 'inline-block');
|
||||||
|
setCookie('show_current_asset', '');
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
.on('click', '.btn_asset_delete', function () {
|
.on('click', '.btn_asset_delete', function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var $data_table = $("#asset_list_table").DataTable();
|
var $data_table = $("#asset_list_table").DataTable();
|
||||||
|
@ -85,6 +85,9 @@ function initTable() {
|
|||||||
var update_btn = '<a href="{% url "assets:domain-gateway-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
var update_btn = '<a href="{% url "assets:domain-gateway-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
var test_btn = '<a class="btn btn-xs btn-warning m-l-xs btn-test" data-uid="{{ DEFAULT_PK }}">{% trans "Test connection" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
var test_btn = '<a class="btn btn-xs btn-warning m-l-xs btn-test" data-uid="{{ DEFAULT_PK }}">{% trans "Test connection" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
|
if(rowData.protocol === 'rdp'){
|
||||||
|
test_btn = '<a class="btn btn-xs btn-warning m-l-xs btn-test" disabled data-uid="{{ DEFAULT_PK }}">{% trans "Test connection" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
|
}
|
||||||
$(td).html(update_btn + test_btn + del_btn)
|
$(td).html(update_btn + test_btn + del_btn)
|
||||||
}}
|
}}
|
||||||
],
|
],
|
||||||
@ -120,7 +123,6 @@ $(document).ready(function(){
|
|||||||
success_message: "可连接",
|
success_message: "可连接",
|
||||||
fail_message: "连接失败"
|
fail_message: "连接失败"
|
||||||
})
|
})
|
||||||
|
});
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -66,3 +66,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script>
|
||||||
|
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
|
||||||
|
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
|
||||||
|
var port = '#' + '{{ form.port.id_for_label }}';
|
||||||
|
|
||||||
|
function protocolChange() {
|
||||||
|
if ($(protocol_id + " option:selected").text() === 'rdp') {
|
||||||
|
$(port).val(3389);
|
||||||
|
$(private_key_id).closest('.form-group').addClass('hidden')
|
||||||
|
} else {
|
||||||
|
$(port).val(22);
|
||||||
|
$(private_key_id).closest('.form-group').removeClass('hidden')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
protocolChange();
|
||||||
|
})
|
||||||
|
.on('change', protocol_id, function(){
|
||||||
|
protocolChange();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@ -64,14 +64,14 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Protocol' %}:</td>
|
<td>{% trans 'Protocol' %}:</td>
|
||||||
<td><b>{{ system_user.protocol }}</b></td>
|
<td><b id="id_protocol_type">{{ system_user.protocol }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr class="only-ssh">
|
||||||
<td>{% trans 'Sudo' %}:</td>
|
<td>{% trans 'Sudo' %}:</td>
|
||||||
<td><b>{{ system_user.sudo }}</b></td>
|
<td><b>{{ system_user.sudo }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if system_user.shell %}
|
{% if system_user.shell %}
|
||||||
<tr>
|
<tr class="only-ssh">
|
||||||
<td>{% trans 'Shell' %}:</td>
|
<td>{% trans 'Shell' %}:</td>
|
||||||
<td><b>{{ system_user.shell }}</b></td>
|
<td><b>{{ system_user.shell }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -107,14 +107,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
||||||
<div class="panel panel-primary">
|
<div class="panel panel-primary ">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
|
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="no-borders-tr">
|
<tr class="only-ssh">
|
||||||
<td width="50%">{% trans 'Auto push' %}:</td>
|
<td width="50%">{% trans 'Auto push' %}:</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="pull-right">
|
<span class="pull-right">
|
||||||
@ -130,8 +130,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="no-borders-tr">
|
|
||||||
{% if system_user.auto_push %}
|
{% if system_user.auto_push %}
|
||||||
|
<tr class="only-ssh">
|
||||||
<td width="50%">{% trans 'Push system user now' %}:</td>
|
<td width="50%">{% trans 'Push system user now' %}:</td>
|
||||||
<td>
|
<td>
|
||||||
<span style="float: right">
|
<span style="float: right">
|
||||||
@ -139,8 +139,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<tr class="only-ssh">
|
||||||
<td width="50%">{% trans 'Test assets connective' %}:</td>
|
<td width="50%">{% trans 'Test assets connective' %}:</td>
|
||||||
<td>
|
<td>
|
||||||
<span style="float: right">
|
<span style="float: right">
|
||||||
@ -149,6 +149,15 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td width="50%">{% trans 'Clear auth' %}:</td>
|
||||||
|
<td>
|
||||||
|
<span style="float: right">
|
||||||
|
<button type="button" class="btn btn-primary btn-xs btn-clear-auth" style="width: 54px">{% trans 'Clear' %}</button>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
{# <tr>#}
|
{# <tr>#}
|
||||||
{# <td width="50%">{% trans 'Change auth period' %}:</td>#}
|
{# <td width="50%">{% trans 'Change auth period' %}:</td>#}
|
||||||
{# <td>#}
|
{# <td>#}
|
||||||
@ -236,6 +245,10 @@ function updateSystemUserNode(nodes) {
|
|||||||
}
|
}
|
||||||
jumpserver.nodes_selected = {};
|
jumpserver.nodes_selected = {};
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
if($('#id_protocol_type').text() === 'rdp'){
|
||||||
|
$('.only-ssh').addClass('hidden')
|
||||||
|
}
|
||||||
|
$(".panel-body .table tr:visible:first").addClass('no-borders-tr');
|
||||||
$('.select2').select2()
|
$('.select2').select2()
|
||||||
.on('select2:select', function(evt) {
|
.on('select2:select', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
@ -296,7 +309,7 @@ $(document).ready(function () {
|
|||||||
var success = function (data) {
|
var success = function (data) {
|
||||||
var task_id = data.task;
|
var task_id = data.task;
|
||||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||||
window.open(url, '', 'width=800,height=600')
|
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||||
};
|
};
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
@ -318,6 +331,13 @@ $(document).ready(function () {
|
|||||||
success: success,
|
success: success,
|
||||||
flash_message: false
|
flash_message: false
|
||||||
});
|
});
|
||||||
|
}).on('click', '.btn-clear-auth', function () {
|
||||||
|
var the_url = '{% url "api-assets:system-user-auth-info" pk=system_user.id %}';
|
||||||
|
APIUpdateAttr({
|
||||||
|
url: the_url,
|
||||||
|
method: 'DELETE',
|
||||||
|
success_message: "{% trans 'Clear auth' %}" + " {% trans 'success' %}"
|
||||||
|
});
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -15,10 +15,3 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block custom_foot_js %}
|
|
||||||
<script>
|
|
||||||
$(document).ready(function () {
|
|
||||||
$('.select2').select2();
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
@ -96,14 +96,7 @@ class LDAPTestingAPI(APIView):
|
|||||||
|
|
||||||
class DjangoSettingsAPI(APIView):
|
class DjangoSettingsAPI(APIView):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
if not settings.DEBUG:
|
return Response('Danger, Close now')
|
||||||
return Response('Only debug mode support')
|
|
||||||
|
|
||||||
configs = {}
|
|
||||||
for i in dir(settings):
|
|
||||||
if i.isupper():
|
|
||||||
configs[i] = str(getattr(settings, i))
|
|
||||||
return Response(configs)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
7
apps/common/validators.py
Normal file
7
apps/common/validators.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from django.core.validators import RegexValidator
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
alphanumeric = RegexValidator(r'^[0-9a-zA-Z_@\-\.]*$', _('Special char not allowed'))
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -229,7 +229,11 @@ LOGGING = {
|
|||||||
'django_auth_ldap': {
|
'django_auth_ldap': {
|
||||||
'handlers': ['console', 'ansible_logs'],
|
'handlers': ['console', 'ansible_logs'],
|
||||||
'level': "INFO",
|
'level': "INFO",
|
||||||
}
|
},
|
||||||
|
# 'django.db': {
|
||||||
|
# 'handlers': ['console', 'file'],
|
||||||
|
# 'level': 'DEBUG'
|
||||||
|
# }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,6 +333,9 @@ AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
|||||||
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
|
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
|
||||||
AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER
|
AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||||
)
|
)
|
||||||
|
AUTH_LDAP_CONNECTION_OPTIONS = {
|
||||||
|
ldap.OPT_TIMEOUT: 5
|
||||||
|
}
|
||||||
AUTH_LDAP_ALWAYS_UPDATE_USER = True
|
AUTH_LDAP_ALWAYS_UPDATE_USER = True
|
||||||
AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend'
|
AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend'
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ class CeleryTaskLogApi(generics.RetrieveAPIView):
|
|||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
mark = request.query_params.get("mark") or str(uuid.uuid4())
|
mark = request.query_params.get("mark") or str(uuid.uuid4())
|
||||||
task = super().get_object()
|
task = self.get_object()
|
||||||
log_path = task.full_log_path
|
log_path = task.full_log_path
|
||||||
|
|
||||||
if not log_path or not os.path.isfile(log_path):
|
if not log_path or not os.path.isfile(log_path):
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<a href="{% url 'ops:adhoc-history-detail' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history detail' %} </a>
|
<a href="{% url 'ops:adhoc-history-detail' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history detail' %} </a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.pk %}','', 'width=800,height=600')"><i class="fa fa-laptop"></i> {% trans 'Output' %} </a>
|
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.pk %}','', 'width=800,height=600,left=400,top=400')"><i class="fa fa-laptop"></i> {% trans 'Output' %} </a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,38 +2,25 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>term.js</title>
|
<title>term.js</title>
|
||||||
<script src="{% static 'js/jquery-2.1.1.js' %}"></script>
|
<script src="{% static 'js/jquery-2.1.1.js' %}"></script>
|
||||||
|
<script src="{% static 'js/plugins/xterm/xterm.js' %}"></script>
|
||||||
|
<link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}" />
|
||||||
<style>
|
<style>
|
||||||
html {
|
body {
|
||||||
background: #000;
|
background-color: black;
|
||||||
}
|
}
|
||||||
h1 {
|
.xterm-rows {
|
||||||
margin-bottom: 20px;
|
{#padding: 15px;#}
|
||||||
font: 20px/1.5 sans-serif;
|
font-family: "Bitstream Vera Sans Mono", Monaco, "Consolas", Courier, monospace;
|
||||||
}
|
font-size: 13px;
|
||||||
.terminal {
|
}
|
||||||
float: left;
|
|
||||||
font-family: 'Monaco', 'Consolas', "DejaVu Sans Mono", "Liberation Mono", monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #f0f0f0;
|
|
||||||
background-color: #555;
|
|
||||||
padding: 20px 20px 20px;
|
|
||||||
}
|
|
||||||
.terminal-cursor {
|
|
||||||
color: #000;
|
|
||||||
background: #f0f0f0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<div class="container">
|
<div id="term" style="height: 100%;width: 100%">
|
||||||
<div id="term">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<script src="{% static 'js/term.js' %}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
var rowHeight = 1;
|
var rowHeight = 18;
|
||||||
var colWidth = 1;
|
var colWidth = 10;
|
||||||
var mark = '';
|
var mark = '';
|
||||||
var url = "{% url 'api-ops:celery-task-log' pk=object.id %}";
|
var url = "{% url 'api-ops:celery-task-log' pk=object.id %}";
|
||||||
var term;
|
var term;
|
||||||
@ -42,13 +29,16 @@
|
|||||||
var interval = 200;
|
var interval = 200;
|
||||||
|
|
||||||
function calWinSize() {
|
function calWinSize() {
|
||||||
var t = $('.terminal');
|
var t = $('#marker');
|
||||||
rowHeight = 1.00 * t.height() / 24;
|
{#rowHeight = 1.00 * t.height();#}
|
||||||
colWidth = 1.00 * t.width() / 80;
|
{#colWidth = 1.00 * t.width() / 6;#}
|
||||||
}
|
}
|
||||||
function resize() {
|
function resize() {
|
||||||
var rows = Math.floor(window.innerHeight / rowHeight) - 2;
|
{#console.log(rowHeight, window.innerHeight);#}
|
||||||
var cols = Math.floor(window.innerWidth / colWidth) - 10;
|
{#console.log(colWidth, window.innerWidth);#}
|
||||||
|
var rows = Math.floor(window.innerHeight / rowHeight) - 1;
|
||||||
|
var cols = Math.floor(window.innerWidth / colWidth) - 2;
|
||||||
|
console.log(rows, cols);
|
||||||
term.resize(cols, rows);
|
term.resize(cols, rows);
|
||||||
}
|
}
|
||||||
function requestAndWrite() {
|
function requestAndWrite() {
|
||||||
@ -74,21 +64,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
term = new Terminal({
|
term = new Terminal();
|
||||||
cols: 80,
|
term.open(document.getElementById('term'));
|
||||||
rows: 24,
|
term.resize(80, 24);
|
||||||
useStyle: true,
|
|
||||||
screenKeys: false,
|
|
||||||
convertEol: false,
|
|
||||||
cursorBlink: false
|
|
||||||
});
|
|
||||||
term.open();
|
|
||||||
term.on('data', function (data) {
|
|
||||||
term.write(data.replace('\r', '\r\n'))
|
|
||||||
});
|
|
||||||
calWinSize();
|
|
||||||
resize();
|
resize();
|
||||||
$('.terminal').detach().appendTo('#term');
|
term.on('data', function (data) {
|
||||||
|
{#term.write(data.replace('\r', '\r\n'))#}
|
||||||
|
term.write(data);
|
||||||
|
});
|
||||||
|
window.onresize = function () {
|
||||||
|
resize()
|
||||||
|
};
|
||||||
|
{#$('.terminal').detach().appendTo('#term');#}
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
requestAndWrite()
|
requestAndWrite()
|
||||||
}, interval)
|
}, interval)
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
|
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.latest_history.pk %}','', 'width=800,height=600')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
|
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.latest_history.pk %}','', 'width=800,height=600,left=400,top=400')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
|
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.latest_history.pk %}','', 'width=800,height=600')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
|
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.latest_history.pk %}','', 'width=800,height=600,left=400,top=400')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
|
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.latest_history.pk %}','', 'width=800,height=600')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
|
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.latest_history.pk %}','', 'width=800,height=600,left=400,top=400')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -115,7 +115,7 @@ $(document).ready(function() {
|
|||||||
var success = function(data) {
|
var success = function(data) {
|
||||||
var task_id = data.task;
|
var task_id = data.task;
|
||||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||||
window.open(url, '', 'width=800,height=600')
|
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||||
};
|
};
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
|
@ -6,7 +6,7 @@ from rest_framework.views import APIView, Response
|
|||||||
from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView
|
from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
|
||||||
from common.utils import set_or_append_attr_bulk
|
from common.utils import set_or_append_attr_bulk, get_object_or_none
|
||||||
from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser
|
from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser
|
||||||
from .utils import AssetPermissionUtil
|
from .utils import AssetPermissionUtil
|
||||||
from .models import AssetPermission
|
from .models import AssetPermission
|
||||||
@ -41,7 +41,7 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
|
|||||||
asset = get_object_or_404(Asset, pk=asset_id)
|
asset = get_object_or_404(Asset, pk=asset_id)
|
||||||
permissions = set(queryset.filter(assets=asset))
|
permissions = set(queryset.filter(assets=asset))
|
||||||
for node in asset.nodes.all():
|
for node in asset.nodes.all():
|
||||||
inherit_nodes.update(set(node.ancestor_with_node))
|
inherit_nodes.update(set(node.ancestor_with_self))
|
||||||
elif node_id:
|
elif node_id:
|
||||||
node = get_object_or_404(Node, pk=node_id)
|
node = get_object_or_404(Node, pk=node_id)
|
||||||
permissions = set(queryset.filter(nodes=node))
|
permissions = set(queryset.filter(nodes=node))
|
||||||
@ -147,8 +147,13 @@ class UserGrantedNodeAssetsApi(ListAPIView):
|
|||||||
user = get_object_or_404(User, id=user_id)
|
user = get_object_or_404(User, id=user_id)
|
||||||
else:
|
else:
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
node = get_object_or_404(Node, id=node_id)
|
|
||||||
nodes = AssetPermissionUtil.get_user_nodes_with_assets(user)
|
nodes = AssetPermissionUtil.get_user_nodes_with_assets(user)
|
||||||
|
node = get_object_or_none(Node, id=node_id)
|
||||||
|
|
||||||
|
if not node:
|
||||||
|
unnode = [node for node in nodes if node.name == 'Unnode']
|
||||||
|
node = unnode[0] if unnode else None
|
||||||
|
|
||||||
assets = nodes.get(node, [])
|
assets = nodes.get(node, [])
|
||||||
for asset, system_users in assets.items():
|
for asset, system_users in assets.items():
|
||||||
asset.system_users_granted = system_users
|
asset.system_users_granted = system_users
|
||||||
|
@ -7,13 +7,23 @@ from django.utils import timezone
|
|||||||
from common.utils import date_expired_default, set_or_append_attr_bulk
|
from common.utils import date_expired_default, set_or_append_attr_bulk
|
||||||
|
|
||||||
|
|
||||||
class ValidManager(models.Manager):
|
class AssetPermissionQuerySet(models.QuerySet):
|
||||||
def get_queryset(self):
|
def active(self):
|
||||||
return super().get_queryset().filter(is_active=True) \
|
return self.filter(is_active=True)
|
||||||
.filter(date_start__lt=timezone.now())\
|
|
||||||
|
def valid(self):
|
||||||
|
return self.active().filter(date_start__lt=timezone.now())\
|
||||||
.filter(date_expired__gt=timezone.now())
|
.filter(date_expired__gt=timezone.now())
|
||||||
|
|
||||||
|
|
||||||
|
class AssetPermissionManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return AssetPermissionQuerySet(self.model, using=self._db)
|
||||||
|
|
||||||
|
def valid(self):
|
||||||
|
return self.get_queryset().valid()
|
||||||
|
|
||||||
|
|
||||||
class AssetPermission(models.Model):
|
class AssetPermission(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||||
@ -23,14 +33,13 @@ class AssetPermission(models.Model):
|
|||||||
nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes"))
|
nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes"))
|
||||||
system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user"))
|
system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user"))
|
||||||
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
|
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
|
||||||
date_start = models.DateTimeField(default=timezone.now, verbose_name=_("Date start"))
|
date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start"))
|
||||||
date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_('Date expired'))
|
date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired'))
|
||||||
created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))
|
created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))
|
||||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
|
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
|
||||||
comment = models.TextField(verbose_name=_('Comment'), blank=True)
|
comment = models.TextField(verbose_name=_('Comment'), blank=True)
|
||||||
|
|
||||||
objects = models.Manager()
|
objects = AssetPermissionManager()
|
||||||
valid = ValidManager()
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -9,7 +9,7 @@ from common.fields import StringManyToManyField
|
|||||||
class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AssetPermission
|
model = AssetPermission
|
||||||
exclude = ('id', 'created_by', 'date_created')
|
exclude = ('created_by', 'date_created')
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionListSerializer(serializers.ModelSerializer):
|
class AssetPermissionListSerializer(serializers.ModelSerializer):
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group {% if form.date_expired.errors or form.date_start.errors %} has-error {% endif %}" id="date_5">
|
<div class="form-group {% if form.date_expired.errors or form.date_start.errors %} has-error {% endif %}" id="date_5">
|
||||||
<label for="{{ form.date_expired.id_for_label }}" class="col-sm-2 control-label">{{ form.date_expired.label }}</label>
|
<label for="{{ form.date_expired.id_for_label }}" class="col-sm-2 control-label">{% trans 'Validity period' %}</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-daterange input-group" id="datepicker">
|
<div class="input-daterange input-group" id="datepicker">
|
||||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||||
|
@ -78,12 +78,12 @@ var zTree, table, show = 0;
|
|||||||
function onSelected(event, treeNode) {
|
function onSelected(event, treeNode) {
|
||||||
setCookie('node_selected', treeNode.id);
|
setCookie('node_selected', treeNode.id);
|
||||||
var url = table.ajax.url();
|
var url = table.ajax.url();
|
||||||
if (treeNode.is_asset) {
|
if (treeNode.is_node) {
|
||||||
url = setUrlParam(url, 'node', "");
|
|
||||||
url = setUrlParam(url, 'asset', treeNode.id)
|
|
||||||
} else {
|
|
||||||
url = setUrlParam(url, 'asset', "");
|
url = setUrlParam(url, 'asset', "");
|
||||||
url = setUrlParam(url, 'node', treeNode.id)
|
url = setUrlParam(url, 'node', treeNode.id)
|
||||||
|
} else {
|
||||||
|
url = setUrlParam(url, 'node', "");
|
||||||
|
url = setUrlParam(url, 'asset', treeNode.id)
|
||||||
}
|
}
|
||||||
setCookie('node_selected', treeNode.id);
|
setCookie('node_selected', treeNode.id);
|
||||||
table.ajax.url(url);
|
table.ajax.url(url);
|
||||||
@ -113,14 +113,14 @@ function filter(treeId, parentNode, childNodes) {
|
|||||||
$.each(childNodes, function (index, value) {
|
$.each(childNodes, function (index, value) {
|
||||||
value["pId"] = value["parent"];
|
value["pId"] = value["parent"];
|
||||||
value["name"] = value["value"];
|
value["name"] = value["value"];
|
||||||
value["isParent"] = value["assets_amount"] !== 0;
|
value["isParent"] = value["is_node"];
|
||||||
value["iconSkin"] = value["is_asset"] ? "file" : null;
|
value["iconSkin"] = value["is_node"] ? null : 'file';
|
||||||
});
|
});
|
||||||
return childNodes;
|
return childNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
function beforeAsync(treeId, treeNode) {
|
function beforeAsync(treeId, treeNode) {
|
||||||
return true;
|
return treeNode.is_node
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeLabel(data) {
|
function makeLabel(data) {
|
||||||
@ -226,7 +226,7 @@ function initTree() {
|
|||||||
},
|
},
|
||||||
async: {
|
async: {
|
||||||
enable: true,
|
enable: true,
|
||||||
url: "{% url 'api-assets:node-children-2' %}?assets=1",
|
url: "{% url 'api-assets:node-children-2' %}?assets=1&all=",
|
||||||
autoParam:["id", "name=n", "level=lv"],
|
autoParam:["id", "name=n", "level=lv"],
|
||||||
dataFilter: filter,
|
dataFilter: filter,
|
||||||
type: 'get'
|
type: 'get'
|
||||||
@ -238,18 +238,19 @@ function initTree() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var zNodes = [];
|
var zNodes = [];
|
||||||
$.get("{% url 'api-assets:node-children-2' %}", function(data, status){
|
$.get("{% url 'api-assets:node-children-2' %}?assets=1&all=", function(data, status){
|
||||||
$.each(data, function (index, value) {
|
$.each(data, function (index, value) {
|
||||||
value["pId"] = value["parent"];
|
value["pId"] = value["parent"];
|
||||||
value["isParent"] = value["assets_amount"] !== 0;
|
|
||||||
value["name"] = value["value"];
|
value["name"] = value["value"];
|
||||||
value["open"] = value["key"] === "0";
|
value["open"] = value["key"] === "0";
|
||||||
|
value["isParent"] = value["is_node"];
|
||||||
|
value["iconSkin"] = value["is_node"] ? null : 'file';
|
||||||
});
|
});
|
||||||
zNodes = data;
|
zNodes = data;
|
||||||
{#$.fn.zTree.init($("#assetTree"), setting);#}
|
{#$.fn.zTree.init($("#assetTree"), setting);#}
|
||||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||||
selectQueryNode();
|
{#selectQueryNode();#}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,10 +287,10 @@ $(document).ready(function(){
|
|||||||
var _nodes = [];
|
var _nodes = [];
|
||||||
var _assets = [];
|
var _assets = [];
|
||||||
$.each(nodes, function (id, node) {
|
$.each(nodes, function (id, node) {
|
||||||
if (node.is_asset) {
|
if (node.is_node) {
|
||||||
_assets.push(node.id)
|
|
||||||
} else {
|
|
||||||
_nodes.push(node.id)
|
_nodes.push(node.id)
|
||||||
|
} else {
|
||||||
|
_assets.push(node.id)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(",");
|
url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(",");
|
||||||
@ -303,6 +304,7 @@ $(document).ready(function(){
|
|||||||
|
|
||||||
if (row.child.isShown()) {
|
if (row.child.isShown()) {
|
||||||
tr.removeClass('details');
|
tr.removeClass('details');
|
||||||
|
$(this).children('i:first-child').removeClass('fa-angle-down').addClass('fa-angle-right');
|
||||||
row.child.hide();
|
row.child.hide();
|
||||||
|
|
||||||
// Remove from the 'open' array
|
// Remove from the 'open' array
|
||||||
@ -310,7 +312,7 @@ $(document).ready(function(){
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tr.addClass('details');
|
tr.addClass('details');
|
||||||
$('.toggle i').removeClass('fa-angle-right').addClass('fa-angle-down');
|
$(this).children('i:first-child').removeClass('fa-angle-right').addClass('fa-angle-down');
|
||||||
row.child(format(row.data())).show();
|
row.child(format(row.data())).show();
|
||||||
// Add to the 'open' array
|
// Add to the 'open' array
|
||||||
if ( idx === -1 ) {
|
if ( idx === -1 ) {
|
||||||
|
@ -8,31 +8,75 @@ import copy
|
|||||||
|
|
||||||
from common.utils import set_or_append_attr_bulk, get_logger
|
from common.utils import set_or_append_attr_bulk, get_logger
|
||||||
from .models import AssetPermission
|
from .models import AssetPermission
|
||||||
|
from .hands import Node
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionUtil:
|
class Tree:
|
||||||
|
def __init__(self):
|
||||||
|
self.__all_nodes = list(Node.objects.all())
|
||||||
|
self.__node_asset_map = defaultdict(set)
|
||||||
|
self.nodes = defaultdict(dict)
|
||||||
|
self.root = Node.root()
|
||||||
|
self.init_node_asset_map()
|
||||||
|
|
||||||
|
def init_node_asset_map(self):
|
||||||
|
for node in self.__all_nodes:
|
||||||
|
assets = node.get_assets().values_list('id', flat=True)
|
||||||
|
for asset in assets:
|
||||||
|
self.__node_asset_map[str(asset)].add(node)
|
||||||
|
|
||||||
|
def add_asset(self, asset, system_users):
|
||||||
|
nodes = self.__node_asset_map.get(str(asset.id), [])
|
||||||
|
self.add_nodes(nodes)
|
||||||
|
for node in nodes:
|
||||||
|
self.nodes[node][asset].update(system_users)
|
||||||
|
|
||||||
|
def add_node(self, node):
|
||||||
|
if node in self.nodes:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.nodes[node] = defaultdict(set)
|
||||||
|
if node.key == self.root.key:
|
||||||
|
return
|
||||||
|
parent_key = ':'.join(node.key.split(':')[:-1])
|
||||||
|
for n in self.__all_nodes:
|
||||||
|
if n.key == parent_key:
|
||||||
|
self.add_node(n)
|
||||||
|
break
|
||||||
|
|
||||||
|
def add_nodes(self, nodes):
|
||||||
|
for node in nodes:
|
||||||
|
self.add_node(node)
|
||||||
|
|
||||||
|
|
||||||
|
class AssetPermissionUtil:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_user_permissions(user):
|
def get_user_permissions(user):
|
||||||
return AssetPermission.valid.all().filter(users=user)
|
return AssetPermission.objects.all().valid().filter(users=user)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_user_group_permissions(user_group):
|
def get_user_group_permissions(user_group):
|
||||||
return AssetPermission.valid.all().filter(user_groups=user_group)
|
return AssetPermission.objects.all().valid().filter(
|
||||||
|
user_groups=user_group
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_asset_permissions(asset):
|
def get_asset_permissions(asset):
|
||||||
return AssetPermission.valid.all().filter(assets=asset)
|
return AssetPermission.objects.all().valid().filter(
|
||||||
|
assets=asset
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_node_permissions(node):
|
def get_node_permissions(node):
|
||||||
return AssetPermission.valid.all().filter(nodes=node)
|
return AssetPermission.objects.all().valid().filter(nodes=node)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_system_user_permissions(system_user):
|
def get_system_user_permissions(system_user):
|
||||||
return AssetPermission.objects.all().filter(system_users=system_user)
|
return AssetPermission.objects.valid().all().filter(
|
||||||
|
system_users=system_user
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_user_group_nodes(cls, group):
|
def get_user_group_nodes(cls, group):
|
||||||
@ -51,7 +95,7 @@ class AssetPermissionUtil:
|
|||||||
assets = defaultdict(set)
|
assets = defaultdict(set)
|
||||||
permissions = cls.get_user_group_permissions(group)
|
permissions = cls.get_user_group_permissions(group)
|
||||||
for perm in permissions:
|
for perm in permissions:
|
||||||
_assets = perm.assets.all()
|
_assets = perm.assets.all().valid()
|
||||||
_system_users = perm.system_users.all()
|
_system_users = perm.system_users.all()
|
||||||
set_or_append_attr_bulk(_assets, 'permission', perm.id)
|
set_or_append_attr_bulk(_assets, 'permission', perm.id)
|
||||||
for asset in _assets:
|
for asset in _assets:
|
||||||
@ -63,7 +107,7 @@ class AssetPermissionUtil:
|
|||||||
assets = defaultdict(set)
|
assets = defaultdict(set)
|
||||||
nodes = cls.get_user_group_nodes(group)
|
nodes = cls.get_user_group_nodes(group)
|
||||||
for node, _system_users in nodes.items():
|
for node, _system_users in nodes.items():
|
||||||
_assets = node.get_all_assets()
|
_assets = node.get_all_valid_assets()
|
||||||
set_or_append_attr_bulk(_assets, 'inherit_node', node.id)
|
set_or_append_attr_bulk(_assets, 'inherit_node', node.id)
|
||||||
set_or_append_attr_bulk(_assets, 'permission', getattr(node, 'permission', None))
|
set_or_append_attr_bulk(_assets, 'permission', getattr(node, 'permission', None))
|
||||||
for asset in _assets:
|
for asset in _assets:
|
||||||
@ -82,28 +126,26 @@ class AssetPermissionUtil:
|
|||||||
return assets
|
return assets
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_user_group_nodes_with_assets(cls, user):
|
def get_user_group_nodes_with_assets(cls, group):
|
||||||
"""
|
"""
|
||||||
:param user:
|
:param group:
|
||||||
:return: {node: {asset: set(su1, su2)}}
|
:return: {node: {asset: set(su1, su2)}}
|
||||||
"""
|
"""
|
||||||
nodes = defaultdict(dict)
|
_assets = cls.get_user_group_assets(group)
|
||||||
_assets = cls.get_user_group_assets(user)
|
tree = Tree()
|
||||||
for asset, _system_users in _assets.items():
|
for asset, _system_users in _assets.items():
|
||||||
_nodes = asset.get_nodes()
|
_nodes = asset.get_nodes()
|
||||||
|
tree.add_nodes(_nodes)
|
||||||
for node in _nodes:
|
for node in _nodes:
|
||||||
if asset in nodes[node]:
|
tree.nodes[node][asset].update(_system_users)
|
||||||
nodes[node][asset].update(_system_users)
|
return tree.nodes
|
||||||
else:
|
|
||||||
nodes[node][asset] = _system_users
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_user_assets_direct(cls, user):
|
def get_user_assets_direct(cls, user):
|
||||||
assets = defaultdict(set)
|
assets = defaultdict(set)
|
||||||
permissions = list(cls.get_user_permissions(user))
|
permissions = list(cls.get_user_permissions(user))
|
||||||
for perm in permissions:
|
for perm in permissions:
|
||||||
_assets = perm.assets.all()
|
_assets = perm.assets.all().valid()
|
||||||
_system_users = perm.system_users.all()
|
_system_users = perm.system_users.all()
|
||||||
set_or_append_attr_bulk(_assets, 'permission', perm.id)
|
set_or_append_attr_bulk(_assets, 'permission', perm.id)
|
||||||
for asset in _assets:
|
for asset in _assets:
|
||||||
@ -122,12 +164,30 @@ class AssetPermissionUtil:
|
|||||||
nodes[node].update(set(_system_users))
|
nodes[node].update(set(_system_users))
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_user_nodes_inherit_group(cls, user):
|
||||||
|
nodes = defaultdict(set)
|
||||||
|
groups = user.groups.all()
|
||||||
|
for group in groups:
|
||||||
|
_nodes = cls.get_user_group_nodes(group)
|
||||||
|
for node, system_users in _nodes.items():
|
||||||
|
nodes[node].update(set(system_users))
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_user_nodes(cls, user):
|
||||||
|
nodes = cls.get_user_nodes_direct(user)
|
||||||
|
nodes_inherit = cls.get_user_nodes_inherit_group(user)
|
||||||
|
for node, system_users in nodes_inherit.items():
|
||||||
|
nodes[node].update(set(system_users))
|
||||||
|
return nodes
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_user_nodes_assets_direct(cls, user):
|
def get_user_nodes_assets_direct(cls, user):
|
||||||
assets = defaultdict(set)
|
assets = defaultdict(set)
|
||||||
nodes = cls.get_user_nodes_direct(user)
|
nodes = cls.get_user_nodes_direct(user)
|
||||||
for node, _system_users in nodes.items():
|
for node, _system_users in nodes.items():
|
||||||
_assets = node.get_all_assets()
|
_assets = node.get_all_valid_assets()
|
||||||
set_or_append_attr_bulk(_assets, 'inherit_node', node.id)
|
set_or_append_attr_bulk(_assets, 'inherit_node', node.id)
|
||||||
set_or_append_attr_bulk(_assets, 'permission', getattr(node, 'permission', None))
|
set_or_append_attr_bulk(_assets, 'permission', getattr(node, 'permission', None))
|
||||||
for asset in _assets:
|
for asset in _assets:
|
||||||
@ -164,26 +224,25 @@ class AssetPermissionUtil:
|
|||||||
:param user:
|
:param user:
|
||||||
:return: {node: {asset: set(su1, su2)}}
|
:return: {node: {asset: set(su1, su2)}}
|
||||||
"""
|
"""
|
||||||
nodes = defaultdict(dict)
|
tree = Tree()
|
||||||
_assets = cls.get_user_assets(user)
|
_assets = cls.get_user_assets(user)
|
||||||
for asset, _system_users in _assets.items():
|
for asset, _system_users in _assets.items():
|
||||||
_nodes = asset.get_nodes()
|
tree.add_asset(asset, _system_users)
|
||||||
for node in _nodes:
|
# _nodes = asset.get_nodes()
|
||||||
if asset in nodes[node]:
|
# tree.add_nodes(_nodes)
|
||||||
nodes[node][asset].update(_system_users)
|
# for node in _nodes:
|
||||||
else:
|
# tree.nodes[node][asset].update(_system_users)
|
||||||
nodes[node][asset] = _system_users
|
return tree.nodes
|
||||||
return nodes
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_system_user_assets(cls, system_user):
|
def get_system_user_assets(cls, system_user):
|
||||||
assets = set()
|
assets = set()
|
||||||
permissions = cls.get_system_user_permissions(system_user)
|
permissions = cls.get_system_user_permissions(system_user)
|
||||||
for perm in permissions:
|
for perm in permissions:
|
||||||
assets.update(set(perm.assets.all()))
|
assets.update(set(perm.assets.all().valid()))
|
||||||
nodes = perm.nodes.all()
|
nodes = perm.nodes.all()
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
assets.update(set(node.get_all_assets()))
|
assets.update(set(node.get_all_valid_assets()))
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -228,7 +287,7 @@ class NodePermissionUtil:
|
|||||||
|
|
||||||
nodes = copy.deepcopy(nodes_directed)
|
nodes = copy.deepcopy(nodes_directed)
|
||||||
for node, system_users in nodes_directed.items():
|
for node, system_users in nodes_directed.items():
|
||||||
for child in node.get_family():
|
for child in node.get_all_children_with_self():
|
||||||
nodes[child].update(system_users)
|
nodes[child].update(system_users)
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
@ -243,7 +302,7 @@ class NodePermissionUtil:
|
|||||||
nodes_with_assets = dict()
|
nodes_with_assets = dict()
|
||||||
for node, system_users in nodes.items():
|
for node, system_users in nodes.items():
|
||||||
nodes_with_assets[node] = {
|
nodes_with_assets[node] = {
|
||||||
'assets': node.get_active_assets(),
|
'assets': node.get_valid_assets(),
|
||||||
'system_users': system_users
|
'system_users': system_users
|
||||||
}
|
}
|
||||||
return nodes_with_assets
|
return nodes_with_assets
|
||||||
@ -274,7 +333,7 @@ class NodePermissionUtil:
|
|||||||
nodes_with_assets = dict()
|
nodes_with_assets = dict()
|
||||||
for node, system_users in nodes.items():
|
for node, system_users in nodes.items():
|
||||||
nodes_with_assets[node] = {
|
nodes_with_assets[node] = {
|
||||||
'assets': node.get_active_assets(),
|
'assets': node.get_valid_assets(),
|
||||||
'system_users': system_users
|
'system_users': system_users
|
||||||
}
|
}
|
||||||
return nodes_with_assets
|
return nodes_with_assets
|
||||||
|
@ -42,7 +42,7 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
|
|||||||
|
|
||||||
if nodes_id:
|
if nodes_id:
|
||||||
nodes_id = nodes_id.split(",")
|
nodes_id = nodes_id.split(",")
|
||||||
nodes = Node.objects.filter(id__in=nodes_id)
|
nodes = Node.objects.filter(id__in=nodes_id).exclude(id=Node.root().id)
|
||||||
form['nodes'].initial = nodes
|
form['nodes'].initial = nodes
|
||||||
if assets_id:
|
if assets_id:
|
||||||
assets_id = assets_id.split(",")
|
assets_id = assets_id.split(",")
|
||||||
|
2261
apps/static/js/plugins/xterm/xterm.css
Normal file
2261
apps/static/js/plugins/xterm/xterm.css
Normal file
File diff suppressed because it is too large
Load Diff
5132
apps/static/js/plugins/xterm/xterm.js
Normal file
5132
apps/static/js/plugins/xterm/xterm.js
Normal file
File diff suppressed because it is too large
Load Diff
1
apps/static/js/plugins/xterm/xterm.js.map
Normal file
1
apps/static/js/plugins/xterm/xterm.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
<div class="footer fixed">
|
<div class="footer fixed">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
Version <strong>1.2.1-{% include '_build.html' %}</strong> GPLv2.
|
Version <strong>1.3.1-{% include '_build.html' %}</strong> GPLv2.
|
||||||
<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">
|
<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -9,6 +9,7 @@ from django.core.cache import cache
|
|||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
|
from django.http.response import HttpResponseRedirectBase
|
||||||
from django.http import HttpResponseNotFound
|
from django.http import HttpResponseNotFound
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ from .serializers import TerminalSerializer, StatusSerializer, \
|
|||||||
SessionSerializer, TaskSerializer, ReplaySerializer
|
SessionSerializer, TaskSerializer, ReplaySerializer
|
||||||
from .hands import IsSuperUserOrAppUser, IsAppUser, \
|
from .hands import IsSuperUserOrAppUser, IsAppUser, \
|
||||||
IsSuperUserOrAppUserOrUserReadonly
|
IsSuperUserOrAppUserOrUserReadonly
|
||||||
from .backends import get_command_store, get_multi_command_store, \
|
from .backends import get_command_storage, get_multi_command_storage, \
|
||||||
SessionCommandSerializer
|
SessionCommandSerializer
|
||||||
|
|
||||||
logger = logging.getLogger(__file__)
|
logger = logging.getLogger(__file__)
|
||||||
@ -108,7 +109,9 @@ class StatusViewSet(viewsets.ModelViewSet):
|
|||||||
task_serializer_class = TaskSerializer
|
task_serializer_class = TaskSerializer
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
self.handle_sessions()
|
from_gua = self.request.query_params.get("from_guacamole", None)
|
||||||
|
if not from_gua:
|
||||||
|
self.handle_sessions()
|
||||||
super().create(request, *args, **kwargs)
|
super().create(request, *args, **kwargs)
|
||||||
tasks = self.request.user.terminal.task_set.filter(is_finished=False)
|
tasks = self.request.user.terminal.task_set.filter(is_finished=False)
|
||||||
serializer = self.task_serializer_class(tasks, many=True)
|
serializer = self.task_serializer_class(tasks, many=True)
|
||||||
@ -224,8 +227,8 @@ class CommandViewSet(viewsets.ViewSet):
|
|||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
command_store = get_command_store()
|
command_store = get_command_storage()
|
||||||
multi_command_storage = get_multi_command_store()
|
multi_command_storage = get_multi_command_storage()
|
||||||
serializer_class = SessionCommandSerializer
|
serializer_class = SessionCommandSerializer
|
||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
|
|
||||||
@ -288,74 +291,37 @@ class SessionReplayViewSet(viewsets.ViewSet):
|
|||||||
url = default_storage.url(path)
|
url = default_storage.url(path)
|
||||||
return redirect(url)
|
return redirect(url)
|
||||||
else:
|
else:
|
||||||
configs = settings.TERMINAL_REPLAY_STORAGE.items()
|
configs = settings.TERMINAL_REPLAY_STORAGE
|
||||||
|
configs = [cfg for cfg in configs if cfg['TYPE'] != 'server']
|
||||||
if not configs:
|
if not configs:
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
for name, config in configs:
|
date = self.session.date_start.strftime('%Y-%m-%d')
|
||||||
client = jms_storage.init(config)
|
file_path = os.path.join(date, str(self.session.id) + '.replay.gz')
|
||||||
date = self.session.date_start.strftime('%Y-%m-%d')
|
target_path = default_storage.base_location + '/' + path
|
||||||
file_path = os.path.join(date, str(self.session.id) + '.replay.gz')
|
storage = jms_storage.get_multi_object_storage(configs)
|
||||||
target_path = default_storage.base_location + '/' + path
|
ok, err = storage.download(file_path, target_path)
|
||||||
folder_path = default_storage.base_location + date
|
if ok:
|
||||||
|
return redirect(default_storage.url(path))
|
||||||
if not default_storage.exists(folder_path):
|
else:
|
||||||
os.mkdir(folder_path)
|
logger.error("Failed download replay file: {}".format(err))
|
||||||
|
|
||||||
if client and client.has_file(file_path) and \
|
|
||||||
client.download_file(file_path, target_path):
|
|
||||||
return redirect(default_storage.url(path))
|
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
|
|
||||||
class SessionReplayV2ViewSet(viewsets.ViewSet):
|
class SessionReplayV2ViewSet(SessionReplayViewSet):
|
||||||
serializer_class = ReplaySerializer
|
serializer_class = ReplaySerializer
|
||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
session = None
|
session = None
|
||||||
|
|
||||||
def gen_session_path(self):
|
|
||||||
date = self.session.date_start.strftime('%Y-%m-%d')
|
|
||||||
replay = {
|
|
||||||
"id": self.session.id,
|
|
||||||
# "width": 100,
|
|
||||||
# "heith": 100
|
|
||||||
}
|
|
||||||
if self.session.protocol == "ssh":
|
|
||||||
replay['type'] = "json"
|
|
||||||
replay['path'] = os.path.join(date, str(self.session.id) + '.gz')
|
|
||||||
return replay
|
|
||||||
elif self.session.protocol == "rdp":
|
|
||||||
replay['type'] = "mp4"
|
|
||||||
replay['path'] = os.path.join(date, str(self.session.id) + '.mp4')
|
|
||||||
return replay
|
|
||||||
else:
|
|
||||||
return replay
|
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
session_id = kwargs.get('pk')
|
response = super().retrieve(request, *args, **kwargs)
|
||||||
self.session = get_object_or_404(Session, id=session_id)
|
data = {
|
||||||
replay = self.gen_session_path()
|
'type': 'guacamole' if self.session.protocol == 'rdp' else 'json',
|
||||||
|
'src': '',
|
||||||
if replay.get("path", "") == "":
|
}
|
||||||
return HttpResponseNotFound()
|
if isinstance(response, HttpResponseRedirectBase):
|
||||||
|
data['src'] = response.url
|
||||||
if default_storage.exists(replay["path"]):
|
return Response(data)
|
||||||
replay["src"] = default_storage.url(replay["path"])
|
|
||||||
return Response(replay)
|
|
||||||
else:
|
|
||||||
configs = settings.TERMINAL_REPLAY_STORAGE.items()
|
|
||||||
if not configs:
|
|
||||||
return HttpResponseNotFound()
|
|
||||||
|
|
||||||
for name, config in configs:
|
|
||||||
client = jms_storage.init(config)
|
|
||||||
|
|
||||||
target_path = default_storage.base_location + '/' + replay["path"]
|
|
||||||
|
|
||||||
if client and client.has_file(replay["path"]) and \
|
|
||||||
client.download_file(replay["path"], target_path):
|
|
||||||
replay["src"] = default_storage.url(replay["path"])
|
|
||||||
return Response(replay)
|
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,19 +7,19 @@ TYPE_ENGINE_MAPPING = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_command_store():
|
def get_command_storage():
|
||||||
params = settings.COMMAND_STORAGE
|
config = settings.COMMAND_STORAGE
|
||||||
engine_class = import_module(params['ENGINE'])
|
engine_class = import_module(config['ENGINE'])
|
||||||
storage = engine_class.CommandStore(params)
|
storage = engine_class.CommandStore(config)
|
||||||
return storage
|
return storage
|
||||||
|
|
||||||
|
|
||||||
def get_terminal_command_store():
|
def get_terminal_command_storages():
|
||||||
storage_list = {}
|
storage_list = {}
|
||||||
for name, params in settings.TERMINAL_COMMAND_STORAGE.items():
|
for name, params in settings.TERMINAL_COMMAND_STORAGE.items():
|
||||||
tp = params['TYPE']
|
tp = params['TYPE']
|
||||||
if tp == 'server':
|
if tp == 'server':
|
||||||
storage = get_command_store()
|
storage = get_command_storage()
|
||||||
else:
|
else:
|
||||||
if not TYPE_ENGINE_MAPPING.get(tp):
|
if not TYPE_ENGINE_MAPPING.get(tp):
|
||||||
continue
|
continue
|
||||||
@ -29,9 +29,9 @@ def get_terminal_command_store():
|
|||||||
return storage_list
|
return storage_list
|
||||||
|
|
||||||
|
|
||||||
def get_multi_command_store():
|
def get_multi_command_storage():
|
||||||
from .command.multi import CommandStore
|
from .command.multi import CommandStore
|
||||||
storage_list = get_terminal_command_store().values()
|
storage_list = get_terminal_command_storages().values()
|
||||||
storage = CommandStore(storage_list)
|
storage = CommandStore(storage_list)
|
||||||
return storage
|
return storage
|
||||||
|
|
||||||
|
@ -1,41 +1,22 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from jms_es_sdk import ESStore
|
from jms_storage.es import ESStorage
|
||||||
from .base import CommandBase
|
from .base import CommandBase
|
||||||
from .models import AbstractSessionCommand
|
from .models import AbstractSessionCommand
|
||||||
|
|
||||||
|
|
||||||
class CommandStore(CommandBase, ESStore):
|
class CommandStore(ESStorage, CommandBase):
|
||||||
def __init__(self, params):
|
def __init__(self, params):
|
||||||
hosts = params.get('HOSTS', ['http://localhost'])
|
super().__init__(params)
|
||||||
ESStore.__init__(self, hosts=hosts)
|
|
||||||
|
|
||||||
def save(self, command):
|
|
||||||
return ESStore.save(self, command)
|
|
||||||
|
|
||||||
def bulk_save(self, commands):
|
|
||||||
return ESStore.bulk_save(self, commands)
|
|
||||||
|
|
||||||
def filter(self, date_from=None, date_to=None,
|
def filter(self, date_from=None, date_to=None,
|
||||||
user=None, asset=None, system_user=None,
|
user=None, asset=None, system_user=None,
|
||||||
input=None, session=None):
|
input=None, session=None):
|
||||||
|
|
||||||
data = ESStore.filter(
|
data = super().filter(date_from=date_from, date_to=date_to,
|
||||||
self, date_from=date_from, date_to=date_to,
|
user=user, asset=asset, system_user=system_user,
|
||||||
user=user, asset=asset, system_user=system_user,
|
input=input, session=session)
|
||||||
input=input, session=session
|
|
||||||
)
|
|
||||||
return AbstractSessionCommand.from_multi_dict(
|
return AbstractSessionCommand.from_multi_dict(
|
||||||
[item["_source"] for item in data["hits"] if item]
|
[item["_source"] for item in data["hits"] if item]
|
||||||
)
|
)
|
||||||
|
|
||||||
def count(self, date_from=None, date_to=None,
|
|
||||||
user=None, asset=None, system_user=None,
|
|
||||||
input=None, session=None):
|
|
||||||
amount = ESStore.count(
|
|
||||||
self, date_from=date_from, date_to=date_to,
|
|
||||||
user=user, asset=asset, system_user=system_user,
|
|
||||||
input=input, session=session
|
|
||||||
)
|
|
||||||
return amount
|
|
||||||
|
@ -9,7 +9,7 @@ from rest_framework_bulk.serializers import BulkListSerializer
|
|||||||
from common.mixins import BulkSerializerMixin
|
from common.mixins import BulkSerializerMixin
|
||||||
from common.utils import get_object_or_none
|
from common.utils import get_object_or_none
|
||||||
from .models import Terminal, Status, Session, Task
|
from .models import Terminal, Status, Session, Task
|
||||||
from .backends import get_multi_command_store
|
from .backends import get_multi_command_storage
|
||||||
|
|
||||||
|
|
||||||
class TerminalSerializer(serializers.ModelSerializer):
|
class TerminalSerializer(serializers.ModelSerializer):
|
||||||
@ -47,7 +47,7 @@ class TerminalSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class SessionSerializer(serializers.ModelSerializer):
|
class SessionSerializer(serializers.ModelSerializer):
|
||||||
command_amount = serializers.SerializerMethodField()
|
command_amount = serializers.SerializerMethodField()
|
||||||
command_store = get_multi_command_store()
|
command_store = get_multi_command_storage()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Session
|
model = Session
|
||||||
|
@ -88,7 +88,7 @@
|
|||||||
<td>{% trans 'Replay session' %}:</td>
|
<td>{% trans 'Replay session' %}:</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="pull-right">
|
<span class="pull-right">
|
||||||
<button type="button" onclick="window.open('/luna/replay/{{ object.id }}','luna', 'height=600, width=800, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-primary btn-xs" id="btn_reset_password" style="width: 54px">{% trans 'Go' %}</button>
|
<button type="button" onclick="window.open('/luna/replay/{{ object.id }}','luna', 'height=600, width=800, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-primary btn-xs" id="btn_reset_password" style="width: 54px">{% trans 'Go' %}</button>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -99,10 +99,14 @@
|
|||||||
<td class="text-center">{{ session.date_start|time_util_with_seconds:session.date_end }}</td>
|
<td class="text-center">{{ session.date_start|time_util_with_seconds:session.date_end }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if session.is_finished %}
|
{% if session.is_finished %}
|
||||||
<a onclick="window.open('/luna/replay/{{ session.id }}','luna', 'height=600, width=800, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-xs btn-warning btn-replay" >{% trans "Replay" %}</a>
|
<a onclick="window.open('/luna/replay/{{ session.id }}','luna', 'height=600, width=800, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-xs btn-warning btn-replay" >{% trans "Replay" %}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<!--<a onclick="window.open('/luna/monitor/{{ session.id }}','luna', 'height=600, width=800, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-xs btn-warning btn-monitor" >{% trans "Monitor" %}</a>-->
|
<!--<a onclick="window.open('/luna/monitor/{{ session.id }}','luna', 'height=600, width=800, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-xs btn-warning btn-monitor" >{% trans "Monitor" %}</a>-->
|
||||||
<a class="btn btn-xs btn-danger btn-term" value="{{ session.id }}" terminal="{{ session.terminal.id }}" >{% trans "Terminate" %}</a>
|
{% if session.protocol == 'rdp' %}
|
||||||
|
<a class="btn btn-xs btn-danger btn-term" disabled value="{{ session.id }}" terminal="{{ session.terminal.id }}" >{% trans "Terminate" %}</a>
|
||||||
|
{% else %}
|
||||||
|
<a class="btn btn-xs btn-danger btn-term" value="{{ session.id }}" terminal="{{ session.terminal.id }}" >{% trans "Terminate" %}</a>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from ..backends import get_multi_command_store
|
from ..backends import get_multi_command_storage
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
command_store = get_multi_command_store()
|
command_store = get_multi_command_storage()
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
|
@ -9,10 +9,10 @@ from django.utils.translation import ugettext as _
|
|||||||
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
|
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
|
||||||
from ..models import Command
|
from ..models import Command
|
||||||
from .. import utils
|
from .. import utils
|
||||||
from ..backends import get_multi_command_store
|
from ..backends import get_multi_command_storage
|
||||||
|
|
||||||
__all__ = ['CommandListView']
|
__all__ = ['CommandListView']
|
||||||
common_storage = get_multi_command_store()
|
common_storage = get_multi_command_storage()
|
||||||
|
|
||||||
|
|
||||||
class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
|
class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
|
||||||
|
@ -10,7 +10,7 @@ from django.conf import settings
|
|||||||
from users.utils import AdminUserRequiredMixin
|
from users.utils import AdminUserRequiredMixin
|
||||||
from common.mixins import DatetimeSearchMixin
|
from common.mixins import DatetimeSearchMixin
|
||||||
from ..models import Session, Command, Terminal
|
from ..models import Session, Command, Terminal
|
||||||
from ..backends import get_multi_command_store
|
from ..backends import get_multi_command_storage
|
||||||
from .. import utils
|
from .. import utils
|
||||||
|
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ __all__ = [
|
|||||||
'SessionDetailView',
|
'SessionDetailView',
|
||||||
]
|
]
|
||||||
|
|
||||||
command_store = get_multi_command_store()
|
command_store = get_multi_command_storage()
|
||||||
|
|
||||||
|
|
||||||
class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||||
|
@ -11,7 +11,7 @@ __all__ = ['UserGroup']
|
|||||||
|
|
||||||
class UserGroup(NoDeleteModelMixin):
|
class UserGroup(NoDeleteModelMixin):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||||
date_created = models.DateTimeField(auto_now_add=True, null=True,
|
date_created = models.DateTimeField(auto_now_add=True, null=True,
|
||||||
verbose_name=_('Date created'))
|
verbose_name=_('Date created'))
|
||||||
|
@ -77,7 +77,7 @@ class User(AbstractUser):
|
|||||||
is_first_login = models.BooleanField(default=True)
|
is_first_login = models.BooleanField(default=True)
|
||||||
date_expired = models.DateTimeField(
|
date_expired = models.DateTimeField(
|
||||||
default=date_expired_default, blank=True, null=True,
|
default=date_expired_default, blank=True, null=True,
|
||||||
verbose_name=_('Date expired')
|
db_index=True, verbose_name=_('Date expired')
|
||||||
)
|
)
|
||||||
created_by = models.CharField(
|
created_by = models.CharField(
|
||||||
max_length=30, default='', verbose_name=_('Created by')
|
max_length=30, default='', verbose_name=_('Created by')
|
||||||
|
@ -70,6 +70,7 @@
|
|||||||
<br>
|
<br>
|
||||||
<input type="checkbox" id="acceptTerms">
|
<input type="checkbox" id="acceptTerms">
|
||||||
<label for="acceptTerms" style="margin-top:20px">{% trans "I agree with the terms and conditions." %}</label>
|
<label for="acceptTerms" style="margin-top:20px">{% trans "I agree with the terms and conditions." %}</label>
|
||||||
|
<p id="noTerms" class="red-fonts" style="visibility: hidden; font-size: 10px; margin-top: 10px;">* {% trans 'Please choose the terms and conditions.' %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% bootstrap_form wizard.form %}
|
{% bootstrap_form wizard.form %}
|
||||||
@ -99,11 +100,7 @@
|
|||||||
{% if wizard.steps.prev %}
|
{% if wizard.steps.prev %}
|
||||||
<li><a class="fl_goto" name="wizard_goto_step" data-goto="{{ wizard.steps.prev }}">{% trans "Previous" %}</a></li>
|
<li><a class="fl_goto" name="wizard_goto_step" data-goto="{{ wizard.steps.prev }}">{% trans "Previous" %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{#{% if wizard.steps.next %}#}
|
|
||||||
{#<li><a class="fl_goto" name="wizard_goto_step" data-goto="{{ wizard.steps.next }}">{% trans "Next" %}</a></li>#}
|
|
||||||
{#{% endif %}#}
|
|
||||||
{#<li><a id="fl_submit">{% trans "Submit" %}</a></li>#}
|
|
||||||
{#将原来的下一页-替换为提交;修复 每页都提交,最后才能成功问题#}
|
|
||||||
{% if wizard.steps.next %}
|
{% if wizard.steps.next %}
|
||||||
<li><a id="fl_submit" >{% trans "Next" %}</a></li>
|
<li><a id="fl_submit" >{% trans "Next" %}</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -124,16 +121,21 @@
|
|||||||
|
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
$('#id_2-otp_level div').eq(2).css('display', 'none');
|
|
||||||
|
|
||||||
$(document).on('click', ".fl_goto", function(){
|
$(document).on('click', ".fl_goto", function(){
|
||||||
var $form = $('#fl_form');
|
var $form = $('#fl_form');
|
||||||
$('<input />', {'name': 'wizard_goto_step', 'value': $(this).data('goto'), 'type': 'hidden'}).appendTo($form);
|
$('<input />', {'name': 'wizard_goto_step', 'value': $(this).data('goto'), 'type': 'hidden'}).appendTo($form);
|
||||||
$form.submit();
|
$form.submit();
|
||||||
return false;
|
return false;
|
||||||
}).on('click', '#fl_submit', function(){
|
}).on('click', '#fl_submit', function(){
|
||||||
$('#fl_form').submit();
|
var isFinish = $('#fl_submit').html() === "{% trans 'Finish' %}";
|
||||||
return false;
|
var noChecked = !$('#acceptTerms').prop('checked');
|
||||||
|
if ( isFinish && noChecked){
|
||||||
|
$('#noTerms').css('visibility', 'visible');
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$('#fl_form').submit();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}).on('click', '#btn-reset-pubkey', function () {
|
}).on('click', '#btn-reset-pubkey', function () {
|
||||||
var the_url = '{% url "users:user-pubkey-generate" %}';
|
var the_url = '{% url "users:user-pubkey-generate" %}';
|
||||||
window.open(the_url, "_blank")
|
window.open(the_url, "_blank")
|
||||||
|
@ -68,7 +68,7 @@ var asset_table;
|
|||||||
|
|
||||||
function initTable() {
|
function initTable() {
|
||||||
if (inited){
|
if (inited){
|
||||||
return
|
return asset_table
|
||||||
} else {
|
} else {
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
|
@ -64,10 +64,11 @@
|
|||||||
var zTree;
|
var zTree;
|
||||||
var inited = false;
|
var inited = false;
|
||||||
var url;
|
var url;
|
||||||
|
var asset_table;
|
||||||
|
|
||||||
function initTable() {
|
function initTable() {
|
||||||
if (inited){
|
if (inited){
|
||||||
return
|
return asset_table
|
||||||
} else {
|
} else {
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,8 @@ def send_user_created_mail(user):
|
|||||||
</br>
|
</br>
|
||||||
Your account has been created successfully
|
Your account has been created successfully
|
||||||
</br>
|
</br>
|
||||||
|
Username: %(username)s
|
||||||
|
</br>
|
||||||
<a href="%(rest_password_url)s?token=%(rest_password_token)s">click here to set your password</a>
|
<a href="%(rest_password_url)s?token=%(rest_password_token)s">click here to set your password</a>
|
||||||
</br>
|
</br>
|
||||||
This link is valid for 1 hour. After it expires, <a href="%(forget_password_url)s?email=%(email)s">request new one</a>
|
This link is valid for 1 hour. After it expires, <a href="%(forget_password_url)s?email=%(email)s">request new one</a>
|
||||||
@ -54,6 +56,7 @@ def send_user_created_mail(user):
|
|||||||
</br>
|
</br>
|
||||||
""") % {
|
""") % {
|
||||||
'name': user.name,
|
'name': user.name,
|
||||||
|
'username': user.username,
|
||||||
'rest_password_url': reverse('users:reset-password', external=True),
|
'rest_password_url': reverse('users:reset-password', external=True),
|
||||||
'rest_password_token': user.generate_reset_token(),
|
'rest_password_token': user.generate_reset_token(),
|
||||||
'forget_password_url': reverse('users:forgot-password', external=True),
|
'forget_password_url': reverse('users:forgot-password', external=True),
|
||||||
|
@ -278,6 +278,16 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
|
|||||||
def get_form(self, step=None, data=None, files=None):
|
def get_form(self, step=None, data=None, files=None):
|
||||||
form = super().get_form(step, data, files)
|
form = super().get_form(step, data, files)
|
||||||
form.instance = self.request.user
|
form.instance = self.request.user
|
||||||
|
|
||||||
|
if isinstance(form, forms.UserMFAForm):
|
||||||
|
choices = form.fields["otp_level"].choices
|
||||||
|
if self.request.user.otp_force_enabled:
|
||||||
|
choices = [(k, v) for k, v in choices if k == 2]
|
||||||
|
else:
|
||||||
|
choices = [(k, v) for k, v in choices if k in [0, 1]]
|
||||||
|
form.fields["otp_level"].choices = choices
|
||||||
|
form.fields["otp_level"].initial = self.request.user.otp_level
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,10 +3,10 @@ ansible==2.4.2.0
|
|||||||
asn1crypto==0.24.0
|
asn1crypto==0.24.0
|
||||||
bcrypt==3.1.4
|
bcrypt==3.1.4
|
||||||
billiard==3.5.0.3
|
billiard==3.5.0.3
|
||||||
boto3==1.6.4
|
boto3==1.6.5
|
||||||
botocore==1.9.4
|
botocore==1.9.5
|
||||||
celery==4.1.0
|
celery==4.1.0
|
||||||
certifi==2017.11.5
|
certifi==2018.1.18
|
||||||
cffi==1.11.2
|
cffi==1.11.2
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
configparser==3.5.0
|
configparser==3.5.0
|
||||||
@ -31,7 +31,7 @@ ecdsa==0.13
|
|||||||
elasticsearch==6.1.1
|
elasticsearch==6.1.1
|
||||||
enum-compat==0.0.2
|
enum-compat==0.0.2
|
||||||
ephem==3.7.6.0
|
ephem==3.7.6.0
|
||||||
eventlet==0.21.0
|
eventlet==0.22.1
|
||||||
ForgeryPy==0.1
|
ForgeryPy==0.1
|
||||||
greenlet==0.4.12
|
greenlet==0.4.12
|
||||||
gunicorn==19.7.1
|
gunicorn==19.7.1
|
||||||
@ -40,7 +40,6 @@ itsdangerous==0.24
|
|||||||
itypes==1.1.0
|
itypes==1.1.0
|
||||||
Jinja2==2.10
|
Jinja2==2.10
|
||||||
jmespath==0.9.3
|
jmespath==0.9.3
|
||||||
jms-es-sdk
|
|
||||||
kombu==4.0.2
|
kombu==4.0.2
|
||||||
ldap3==2.4
|
ldap3==2.4
|
||||||
MarkupSafe==1.0
|
MarkupSafe==1.0
|
||||||
@ -58,11 +57,11 @@ pyotp==2.2.6
|
|||||||
PyNaCl==1.2.1
|
PyNaCl==1.2.1
|
||||||
python-dateutil==2.6.1
|
python-dateutil==2.6.1
|
||||||
python-gssapi==0.6.4
|
python-gssapi==0.6.4
|
||||||
pytz==2017.3
|
pytz==2018.3
|
||||||
PyYAML==3.12
|
PyYAML==3.12
|
||||||
redis==2.10.6
|
redis==2.10.6
|
||||||
requests==2.18.4
|
requests==2.18.4
|
||||||
jms-storage==0.0.13
|
jms-storage==0.0.17
|
||||||
s3transfer==0.1.13
|
s3transfer==0.1.13
|
||||||
simplejson==3.13.2
|
simplejson==3.13.2
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
|
70
utils/clean_duplicate_user_groups.py
Normal file
70
utils/clean_duplicate_user_groups.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from collections import Counter
|
||||||
|
import django
|
||||||
|
from django.db.models import Count
|
||||||
|
|
||||||
|
|
||||||
|
if os.path.exists('../apps'):
|
||||||
|
sys.path.insert(0, '../apps')
|
||||||
|
elif os.path.exists('./apps'):
|
||||||
|
sys.path.insert(0, './apps')
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings")
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
from users.models import UserGroup
|
||||||
|
|
||||||
|
|
||||||
|
def clean_group(interactive=True):
|
||||||
|
groups = UserGroup.objects.all()
|
||||||
|
groups_name_list = groups.values_list('name', flat=True)
|
||||||
|
groups_with_info = groups.annotate(Count('users'))\
|
||||||
|
.annotate(Count('asset_permissions'))
|
||||||
|
|
||||||
|
counter = Counter(groups_name_list)
|
||||||
|
for name, count in counter.items():
|
||||||
|
if count == 0:
|
||||||
|
continue
|
||||||
|
groups_duplicate = groups_with_info.filter(name=name)
|
||||||
|
need_clean_count = groups_duplicate.count()
|
||||||
|
|
||||||
|
for group in groups_duplicate:
|
||||||
|
need_clean = True
|
||||||
|
if group.users__count > 0:
|
||||||
|
need_clean = False
|
||||||
|
elif group.asset_permissions__count > 0:
|
||||||
|
need_clean = False
|
||||||
|
elif need_clean_count == 1:
|
||||||
|
need_clean = False
|
||||||
|
|
||||||
|
if need_clean:
|
||||||
|
confirm = True
|
||||||
|
if interactive:
|
||||||
|
confirm = False
|
||||||
|
while True:
|
||||||
|
confirm = input(
|
||||||
|
"Delete user group <{}>, create at {}? ([y]/n)".format(
|
||||||
|
name, group.date_created)
|
||||||
|
)
|
||||||
|
if confirm.lower() == "y":
|
||||||
|
confirm = True
|
||||||
|
break
|
||||||
|
elif confirm.lower() == "n":
|
||||||
|
confirm = False
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("No valid input")
|
||||||
|
continue
|
||||||
|
if confirm:
|
||||||
|
group.delete()
|
||||||
|
print("Delete success: {}".format(name))
|
||||||
|
need_clean_count -= 1
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
clean_group()
|
@ -1,24 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
if [ ! -d "/opt/py3" ]; then
|
if grep -q 'source ~/.autoenv/activate.sh' ~/.bashrc; then
|
||||||
echo -e "\033[31m python3虚拟环境不是默认路径 \033[0m"
|
echo -e "\033[31m 正在自动载入 python 环境 \033[0m"
|
||||||
ps -ef | grep jumpserver/tmp/beat.pid | grep -v grep
|
|
||||||
if [ $? -ne 0 ]
|
|
||||||
then
|
|
||||||
echo -e "\033[31m jumpserver未运行,请到jumpserver目录使用 ./jms start all -d 启动 \033[0m"
|
|
||||||
exit 0
|
|
||||||
else
|
else
|
||||||
echo -e "\033[31m 正在计算python3虚拟环境路径 \033[0m"
|
echo -e "\033[31m 不支持自动升级,请参考 http://docs.jumpserver.org/zh/docs/upgrade.html 手动升级 \033[0m"
|
||||||
fi
|
exit 0
|
||||||
py3pid=`ps -ef | grep jumpserver/tmp/beat.pid | grep -v grep | awk '{print $2}'`
|
|
||||||
py3file=`cat /proc/$py3pid/cmdline`
|
|
||||||
py3even=`echo ${py3file%/bin/python3*}`
|
|
||||||
echo -e "\033[31m python3虚拟环境路径为$py3even \033[0m"
|
|
||||||
source $py3even/bin/activate
|
|
||||||
else
|
|
||||||
source /opt/py3/bin/activate
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
source ~/.bashrc
|
||||||
|
|
||||||
cd `dirname $0`/ && cd .. && ./jms stop
|
cd `dirname $0`/ && cd .. && ./jms stop
|
||||||
|
|
||||||
jumpserver_backup=/tmp/jumpserver_backup$(date -d "today" +"%Y%m%d_%H%M%S")
|
jumpserver_backup=/tmp/jumpserver_backup$(date -d "today" +"%Y%m%d_%H%M%S")
|
||||||
@ -29,21 +19,20 @@ echo -e "\033[31m 是否需要备份Jumpserver数据库 \033[0m"
|
|||||||
stty erase ^H
|
stty erase ^H
|
||||||
read -p "确认备份请按Y,否则按其他键跳过备份 " a
|
read -p "确认备份请按Y,否则按其他键跳过备份 " a
|
||||||
if [ "$a" == y -o "$a" == Y ];then
|
if [ "$a" == y -o "$a" == Y ];then
|
||||||
echo -e "\033[31m 正在备份数据库 \033[0m"
|
echo -e "\033[31m 正在备份数据库 \033[0m"
|
||||||
echo -e "\033[31m 请手动输入数据库信息 \033[0m"
|
echo -e "\033[31m 请手动输入数据库信息 \033[0m"
|
||||||
read -p '请输入Jumpserver数据库ip:' DB_HOST
|
read -p '请输入Jumpserver数据库ip:' DB_HOST
|
||||||
read -p '请输入Jumpserver数据库端口:' DB_PORT
|
read -p '请输入Jumpserver数据库端口:' DB_PORT
|
||||||
read -p '请输入Jumpserver数据库名称:' DB_NAME
|
read -p '请输入Jumpserver数据库名称:' DB_NAME
|
||||||
read -p '请输入有权限导出数据库的用户:' DB_USER
|
read -p '请输入有权限导出数据库的用户:' DB_USER
|
||||||
read -p '请输入该用户的密码:' DB_PASSWORD
|
read -p '请输入该用户的密码:' DB_PASSWORD
|
||||||
mysqldump -h$DB_HOST -P$DB_PORT -u$DB_USER -p$DB_PASSWORD $DB_NAME > /$jumpserver_backup/$DB_NAME$(date -d "today" +"%Y%m%d_%H%M%S").sql || {
|
mysqldump -h$DB_HOST -P$DB_PORT -u$DB_USER -p$DB_PASSWORD $DB_NAME > /$jumpserver_backup/$DB_NAME$(date -d "today" +"%Y%m%d_%H%M%S").sql || {
|
||||||
echo -e "\033[31m 备份数据库失败,请检查输入是否有误 \033[0m"
|
echo -e "\033[31m 备份数据库失败,请检查输入是否有误 \033[0m"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
echo -e "\033[31m 备份数据库完成 \033[0m"
|
echo -e "\033[31m 备份数据库完成 \033[0m"
|
||||||
|
|
||||||
else
|
else
|
||||||
echo -e "\033[31m 已取消备份数据库操作 \033[0m"
|
echo -e "\033[31m 已取消备份数据库操作 \033[0m"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git pull && pip install -r requirements/requirements.txt && cd utils && sh make_migrations.sh
|
git pull && pip install -r requirements/requirements.txt && cd utils && sh make_migrations.sh
|
||||||
|
Loading…
Reference in New Issue
Block a user