mirror of https://github.com/jumpserver/jumpserver
Merge branch 'master' of https://github.com/jumpserver/jumpserver
Conflicts: apps/terminal/api.pypull/1369/head
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 -*-
|
||||
#
|
||||
|
||||
__version__ = "1.2.1"
|
||||
__version__ = "1.3.1"
|
||||
|
|
|
@ -43,6 +43,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
|||
queryset = super().get_queryset()
|
||||
admin_user_id = self.request.query_params.get('admin_user_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:
|
||||
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)
|
||||
if not node.is_root():
|
||||
queryset = queryset.filter(
|
||||
nodes__key__regex='{}(:[0-9]+)*$'.format(node.key),
|
||||
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
||||
).distinct()
|
||||
if show_current_asset and node_id:
|
||||
queryset = queryset.filter(nodes=node_id).distinct()
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
# limitations under the License.
|
||||
|
||||
from rest_framework import generics, mixins
|
||||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
@ -41,7 +42,14 @@ __all__ = [
|
|||
class NodeViewSet(BulkModelViewSet):
|
||||
queryset = Node.objects.all()
|
||||
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):
|
||||
child_key = Node.root().get_next_child_key()
|
||||
|
@ -83,16 +91,29 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
|||
serializer_class = serializers.NodeSerializer
|
||||
instance = None
|
||||
|
||||
def counter(self):
|
||||
values = [
|
||||
child.value[child.value.rfind(' '):]
|
||||
for child in self.get_object().get_children()
|
||||
if child.value.startswith("新节点 ")
|
||||
]
|
||||
values = [int(value) for value in values if value.strip().isdigit()]
|
||||
count = max(values)+1 if values else 1
|
||||
return count
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if not request.data.get("value"):
|
||||
request.data["value"] = _("New node {}").format(
|
||||
Node.root().get_next_child_key().split(":")[-1]
|
||||
)
|
||||
request.data["value"] = _("New node {}").format(self.counter())
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
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)
|
||||
return Response(
|
||||
{"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.parent = node
|
||||
node_fake.value = asset.hostname
|
||||
node_fake.is_asset = True
|
||||
node_fake.is_node = False
|
||||
queryset.append(node_fake)
|
||||
queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True)
|
||||
return queryset
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
@ -162,8 +184,9 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
|
|||
for node in children:
|
||||
if not node:
|
||||
continue
|
||||
node.parent = instance
|
||||
node.save()
|
||||
# node.parent = instance
|
||||
# node.save()
|
||||
node.set_parent(instance)
|
||||
return Response("OK")
|
||||
|
||||
|
||||
|
@ -190,6 +213,9 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
|
|||
instance = self.get_object()
|
||||
if instance != Node.root():
|
||||
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):
|
||||
|
|
|
@ -40,7 +40,7 @@ class SystemUserViewSet(BulkModelViewSet):
|
|||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
|
||||
class SystemUserAuthInfoApi(generics.RetrieveUpdateAPIView):
|
||||
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
Get system user auth info
|
||||
"""
|
||||
|
@ -48,6 +48,11 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateAPIView):
|
|||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
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):
|
||||
"""
|
||||
|
@ -58,6 +63,9 @@ class SystemUserPushApi(generics.RetrieveAPIView):
|
|||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
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)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
import uuid
|
||||
import logging
|
||||
import random
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
@ -35,6 +34,19 @@ def default_node():
|
|||
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):
|
||||
# Important
|
||||
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'))
|
||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||
|
||||
objects = AssetManager()
|
||||
|
||||
def __str__(self):
|
||||
return '{0.hostname}({0.ip})'.format(self)
|
||||
|
||||
|
@ -103,7 +117,8 @@ class Asset(models.Model):
|
|||
|
||||
def get_nodes(self):
|
||||
from .node import Node
|
||||
return self.nodes.all() or [Node.root()]
|
||||
nodes = self.nodes.all() or [Node.root()]
|
||||
return nodes
|
||||
|
||||
@property
|
||||
def hardware_info(self):
|
||||
|
|
|
@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.conf import settings
|
||||
|
||||
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
|
||||
|
||||
signer = get_signer()
|
||||
|
@ -18,7 +19,7 @@ signer = get_signer()
|
|||
class AssetUser(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
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'))
|
||||
_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'))
|
||||
|
@ -103,10 +104,16 @@ class AssetUser(models.Model):
|
|||
if 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):
|
||||
password = str(uuid.uuid4())
|
||||
private_key, public_key = ssh_key_gen(
|
||||
username=self.username, password=password
|
||||
username=self.username
|
||||
)
|
||||
self.set_auth(password=password,
|
||||
private_key=private_key,
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
#
|
||||
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 _
|
||||
|
||||
|
||||
|
@ -12,11 +13,14 @@ __all__ = ['Node']
|
|||
class Node(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||
value = models.CharField(max_length=128, 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)
|
||||
date_create = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
is_asset = False
|
||||
is_node = True
|
||||
|
||||
def __str__(self):
|
||||
return self.full_value
|
||||
|
@ -36,6 +40,16 @@ class Node(models.Model):
|
|||
def level(self):
|
||||
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):
|
||||
mark = self.child_mark
|
||||
self.child_mark += 1
|
||||
|
@ -48,56 +62,77 @@ class Node(models.Model):
|
|||
return child
|
||||
|
||||
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):
|
||||
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):
|
||||
children = list(self.get_all_children())
|
||||
children.append(self)
|
||||
return children
|
||||
ancestor = self.ancestor
|
||||
children = self.get_all_children()
|
||||
return [*tuple(ancestor), self, *tuple(children)]
|
||||
|
||||
def get_assets(self):
|
||||
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
|
||||
|
||||
def get_active_assets(self):
|
||||
return self.get_assets().filter(is_active=True)
|
||||
def get_valid_assets(self):
|
||||
return self.get_assets().valid()
|
||||
|
||||
def get_all_assets(self):
|
||||
from .asset import Asset
|
||||
if self.is_root():
|
||||
assets = Asset.objects.all()
|
||||
else:
|
||||
nodes = self.get_family()
|
||||
nodes = self.get_all_children_with_self()
|
||||
assets = Asset.objects.filter(nodes__in=nodes).distinct()
|
||||
return assets
|
||||
|
||||
def get_current_assets(self):
|
||||
from .asset import Asset
|
||||
assets = Asset.objects.filter(nodes=self).distinct()
|
||||
return assets
|
||||
|
||||
def has_assets(self):
|
||||
return self.get_all_assets()
|
||||
|
||||
def get_all_active_assets(self):
|
||||
return self.get_all_assets().filter(is_active=True)
|
||||
def get_all_valid_assets(self):
|
||||
return self.get_all_assets().valid()
|
||||
|
||||
def is_root(self):
|
||||
return self.key == '0'
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if self.key == "0":
|
||||
return self.__class__.root()
|
||||
elif not self.key.startswith("0"):
|
||||
if self.key == "0" or not self.key.startswith("0"):
|
||||
return self.__class__.root()
|
||||
|
||||
parent_key = ":".join(self.key.split(":")[:-1])
|
||||
try:
|
||||
parent = self.__class__.objects.get(key=parent_key)
|
||||
return parent
|
||||
except Node.DoesNotExist:
|
||||
return self.__class__.root()
|
||||
else:
|
||||
return parent
|
||||
|
||||
@parent.setter
|
||||
def parent(self, parent):
|
||||
|
@ -105,14 +140,20 @@ class Node(models.Model):
|
|||
|
||||
@property
|
||||
def ancestor(self):
|
||||
if self.parent == self.__class__.root():
|
||||
_key = self.key.split(':')
|
||||
ancestor_keys = []
|
||||
|
||||
if self.is_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
|
||||
def ancestor_with_node(self):
|
||||
ancestor = self.ancestor
|
||||
def ancestor_with_self(self):
|
||||
ancestor = list(self.ancestor)
|
||||
ancestor.insert(0, self)
|
||||
return ancestor
|
||||
|
||||
|
|
|
@ -18,8 +18,7 @@ class NodeTMPSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = ['id', 'key', 'value', 'parent', 'assets_amount',
|
||||
'is_asset']
|
||||
fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_node']
|
||||
list_serializer_class = BulkListSerializer
|
||||
|
||||
@staticmethod
|
||||
|
@ -62,13 +61,13 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
|
|||
"""
|
||||
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
|
||||
system_users_join = serializers.SerializerMethodField()
|
||||
nodes = NodeTMPSerializer(many=True, read_only=True)
|
||||
# nodes = NodeTMPSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "ip", "port", "system_users_granted",
|
||||
"is_active", "system_users_join", "os", 'domain', "nodes",
|
||||
"is_active", "system_users_join", "os", 'domain',
|
||||
"platform", "comment"
|
||||
)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from .asset import AssetGrantedSerializer
|
|||
|
||||
__all__ = [
|
||||
'NodeSerializer', "NodeGrantedSerializer", "NodeAddChildrenSerializer",
|
||||
"NodeAssetsSerializer",
|
||||
"NodeAssetsSerializer", "NodeCurrentSerializer",
|
||||
]
|
||||
|
||||
|
||||
|
@ -48,9 +48,20 @@ class NodeSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Node
|
||||
fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_asset']
|
||||
fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_node']
|
||||
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
|
||||
def get_parent(obj):
|
||||
return obj.parent.id
|
||||
|
@ -66,6 +77,12 @@ class NodeSerializer(serializers.ModelSerializer):
|
|||
return fields
|
||||
|
||||
|
||||
class NodeCurrentSerializer(NodeSerializer):
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.get_current_assets().count()
|
||||
|
||||
|
||||
class NodeAssetsSerializer(serializers.ModelSerializer):
|
||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ TIMEOUT = 60
|
|||
logger = get_logger(__file__)
|
||||
CACHE_MAX_TIME = 60*60*60
|
||||
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
|
||||
|
|
|
@ -98,7 +98,10 @@ function initTree2() {
|
|||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||
$.each(data, function (index, value) {
|
||||
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['value'] = value['value'];
|
||||
});
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||
</div>
|
||||
<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">
|
||||
{{ form.auto_push}}
|
||||
</div>
|
||||
|
@ -79,43 +79,50 @@
|
|||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.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 sudo_id = '#' + '{{ form.sudo.id_for_label }}';
|
||||
var shell_id = '#' + '{{ form.shell.id_for_label }}';
|
||||
<script>
|
||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
||||
var protocol_id = '#' + '{{ form.protocol.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 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() {
|
||||
if ($(auto_generate_key).prop('checked')) {
|
||||
$('.auth-fields').addClass('hidden');
|
||||
} else {
|
||||
$('.auth-fields').removeClass('hidden');
|
||||
}
|
||||
}
|
||||
function authFieldsDisplay() {
|
||||
if ($(auto_generate_key).prop('checked')) {
|
||||
$('.auth-fields').addClass('hidden');
|
||||
} else {
|
||||
$('.auth-fields').removeClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function protocolChange() {
|
||||
if ($(protocol_id).attr('value') === 'rdp') {
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).addClass('hidden')
|
||||
});
|
||||
$(password_id).removeClass('hidden')
|
||||
} else {
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).removeClass('hidden')
|
||||
});
|
||||
}
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
authFieldsDisplay();
|
||||
protocolChange();
|
||||
$(auto_generate_key).change(function () {
|
||||
authFieldsDisplay();
|
||||
});
|
||||
})
|
||||
</script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
authFieldsDisplay();
|
||||
protocolChange();
|
||||
})
|
||||
.on('change', protocol_id, function(){
|
||||
protocolChange();
|
||||
})
|
||||
.on('change', auto_generate_key, function(){
|
||||
authFieldsDisplay();
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -124,7 +124,7 @@ $(document).ready(function () {
|
|||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600')
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
|
|
|
@ -190,7 +190,7 @@
|
|||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
|
||||
{% 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 %}
|
||||
</select>
|
||||
</td>
|
||||
|
@ -204,7 +204,7 @@
|
|||
|
||||
{% for node in asset.nodes.all %}
|
||||
<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>
|
||||
<button class="btn btn-danger pull-right btn-xs btn-leave-node" type="button"><i class="fa fa-minus"></i></button>
|
||||
</td>
|
||||
|
|
|
@ -17,20 +17,21 @@
|
|||
position:absolute;
|
||||
visibility:hidden;
|
||||
text-align: left;
|
||||
top: 100%;
|
||||
{#top: 100%;#}
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
float: left;
|
||||
padding: 5px 0;
|
||||
{#float: left;#}
|
||||
padding: 0 0;
|
||||
margin: 2px 0 0;
|
||||
list-style: none;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
}
|
||||
div#rMenu li{
|
||||
margin: 1px 0;
|
||||
cursor: pointer;
|
||||
{#list-style: none outside none;#}
|
||||
}
|
||||
list-style: none outside none;
|
||||
}
|
||||
.dropdown a:hover {
|
||||
background-color: #f1f1f1
|
||||
}
|
||||
|
@ -47,7 +48,6 @@
|
|||
<div class="file-manager ">
|
||||
<div id="assetTree" class="ztree">
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -87,7 +87,7 @@
|
|||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Hardware' %}</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>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -127,6 +127,9 @@
|
|||
<li class="divider"></li>
|
||||
<li id="menu_refresh_hardware_info" class="btn-refresh-hardware" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh node hardware info' %}</a></li>
|
||||
<li id="menu_test_connective" class="btn-test-connective" tabindex="-1"><a><i class="fa fa-chain"></i> {% trans 'Test node connective' %}</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="show_current_asset" class="btn-show-current-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-hand-o-up"></i> {% trans 'Display only current node assets' %}</a></li>
|
||||
<li id="show_all_asset" class="btn-show-all-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-th"></i> {% trans 'Displays all child node assets' %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
@ -157,26 +160,35 @@ function initTable() {
|
|||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
}
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
if (cellData === 'Unknown'){
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
} else if (!cellData) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></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) {#}
|
||||
{# if (cellData === 'Unknown'){#}
|
||||
{# $(td).html('<i class="fa fa-circle text-warning"></i>')#}
|
||||
{# } else if (!cellData) {#}
|
||||
{# $(td).html('<i class="fa fa-circle text-danger"></i>')#}
|
||||
{# } else {#}
|
||||
{# $(td).html('<i class="fa fa-circle text-navy"></i>')#}
|
||||
{# }#}
|
||||
{# }},#}
|
||||
|
||||
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
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: [
|
||||
{data: "id"}, {data: "hostname" }, {data: "ip" },
|
||||
{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()
|
||||
};
|
||||
|
@ -200,6 +212,8 @@ function addTreeNode() {
|
|||
};
|
||||
newNode.checked = zTree.getSelectedNodes()[0].checked;
|
||||
zTree.addNodes(parentNode, 0, newNode);
|
||||
var node = zTree.getNodeByParam('id', newNode.id, parentNode)
|
||||
zTree.editName(node);
|
||||
} else {
|
||||
alert("{% trans 'Create node failed' %}")
|
||||
}
|
||||
|
@ -230,9 +244,9 @@ function removeTreeNode() {
|
|||
|
||||
function editTreeNode() {
|
||||
hideRMenu();
|
||||
var current_node = zTree.getSelectedNodes()[0];
|
||||
if (!current_node){
|
||||
return
|
||||
var current_node = zTree.getSelectedNodes()[0];
|
||||
if (!current_node){
|
||||
return
|
||||
}
|
||||
if (current_node.value) {
|
||||
current_node.name = current_node.value;
|
||||
|
@ -253,6 +267,8 @@ function OnRightClick(event, treeId, treeNode) {
|
|||
function showRMenu(type, x, y) {
|
||||
$("#rMenu ul").show();
|
||||
x -= 220;
|
||||
x += document.body.scrollLeft;
|
||||
y += document.body.scrollTop+document.documentElement.scrollTop;
|
||||
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
|
||||
|
||||
$("body").bind("mousedown", onBodyMouseDown);
|
||||
|
@ -290,6 +306,7 @@ function onRename(event, treeId, treeNode, isCancel){
|
|||
function onSelected(event, treeNode) {
|
||||
var url = asset_table.ajax.url();
|
||||
url = setUrlParam(url, "node_id", treeNode.id);
|
||||
url = setUrlParam(url, "show_current_asset", getCookie('show_current_asset'));
|
||||
setCookie('node_selected', treeNode.id);
|
||||
asset_table.ajax.url(url);
|
||||
asset_table.ajax.reload();
|
||||
|
@ -382,12 +399,13 @@ function initTree() {
|
|||
};
|
||||
|
||||
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) {
|
||||
value["pId"] = value["parent"];
|
||||
{#if (value["key"] === "0") {#}
|
||||
value["open"] = true;
|
||||
{# }#}
|
||||
if (value["key"] === "0") {
|
||||
value["open"] = true;
|
||||
}
|
||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||
value['value'] = value['value'];
|
||||
});
|
||||
|
@ -417,6 +435,13 @@ function toggle() {
|
|||
$(document).ready(function(){
|
||||
initTable();
|
||||
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 () {
|
||||
var val = $(this).text();
|
||||
|
@ -535,6 +560,20 @@ $(document).ready(function(){
|
|||
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 () {
|
||||
var $this = $(this);
|
||||
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 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);
|
||||
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)
|
||||
}}
|
||||
],
|
||||
|
@ -120,7 +123,6 @@ $(document).ready(function(){
|
|||
success_message: "可连接",
|
||||
fail_message: "连接失败"
|
||||
})
|
||||
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -66,3 +66,28 @@
|
|||
</div>
|
||||
</div>
|
||||
{% 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>
|
||||
<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 class="only-ssh">
|
||||
<td>{% trans 'Sudo' %}:</td>
|
||||
<td><b>{{ system_user.sudo }}</b></td>
|
||||
</tr>
|
||||
{% if system_user.shell %}
|
||||
<tr>
|
||||
<tr class="only-ssh">
|
||||
<td>{% trans 'Shell' %}:</td>
|
||||
<td><b>{{ system_user.shell }}</b></td>
|
||||
</tr>
|
||||
|
@ -107,14 +107,14 @@
|
|||
</div>
|
||||
|
||||
<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">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<tr class="only-ssh">
|
||||
<td width="50%">{% trans 'Auto push' %}:</td>
|
||||
<td>
|
||||
<span class="pull-right">
|
||||
|
@ -130,8 +130,8 @@
|
|||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="no-borders-tr">
|
||||
{% if system_user.auto_push %}
|
||||
<tr class="only-ssh">
|
||||
<td width="50%">{% trans 'Push system user now' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
|
@ -139,8 +139,8 @@
|
|||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
{% endif %}
|
||||
<tr class="only-ssh">
|
||||
<td width="50%">{% trans 'Test assets connective' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
|
@ -149,6 +149,15 @@
|
|||
</td>
|
||||
</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>#}
|
||||
{# <td width="50%">{% trans 'Change auth period' %}:</td>#}
|
||||
{# <td>#}
|
||||
|
@ -236,6 +245,10 @@ function updateSystemUserNode(nodes) {
|
|||
}
|
||||
jumpserver.nodes_selected = {};
|
||||
$(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()
|
||||
.on('select2:select', function(evt) {
|
||||
var data = evt.params.data;
|
||||
|
@ -296,7 +309,7 @@ $(document).ready(function () {
|
|||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600')
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
|
@ -318,6 +331,13 @@ $(document).ready(function () {
|
|||
success: success,
|
||||
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>
|
||||
{% endblock %}
|
||||
|
|
|
@ -15,10 +15,3 @@
|
|||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -96,14 +96,7 @@ class LDAPTestingAPI(APIView):
|
|||
|
||||
class DjangoSettingsAPI(APIView):
|
||||
def get(self, request):
|
||||
if not settings.DEBUG:
|
||||
return Response('Only debug mode support')
|
||||
|
||||
configs = {}
|
||||
for i in dir(settings):
|
||||
if i.isupper():
|
||||
configs[i] = str(getattr(settings, i))
|
||||
return Response(configs)
|
||||
return Response('Danger, Close now')
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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': {
|
||||
'handlers': ['console', 'ansible_logs'],
|
||||
'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_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_BACKEND = 'django_auth_ldap.backend.LDAPBackend'
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ class CeleryTaskLogApi(generics.RetrieveAPIView):
|
|||
|
||||
def get(self, request, *args, **kwargs):
|
||||
mark = request.query_params.get("mark") or str(uuid.uuid4())
|
||||
task = super().get_object()
|
||||
task = self.get_object()
|
||||
log_path = task.full_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>
|
||||
</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>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -2,38 +2,25 @@
|
|||
<head>
|
||||
<title>term.js</title>
|
||||
<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>
|
||||
html {
|
||||
background: #000;
|
||||
}
|
||||
h1 {
|
||||
margin-bottom: 20px;
|
||||
font: 20px/1.5 sans-serif;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
.xterm-rows {
|
||||
{#padding: 15px;#}
|
||||
font-family: "Bitstream Vera Sans Mono", Monaco, "Consolas", Courier, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<div class="container">
|
||||
<div id="term">
|
||||
<div id="term" style="height: 100%;width: 100%">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="{% static 'js/term.js' %}"></script>
|
||||
<script>
|
||||
var rowHeight = 1;
|
||||
var colWidth = 1;
|
||||
var rowHeight = 18;
|
||||
var colWidth = 10;
|
||||
var mark = '';
|
||||
var url = "{% url 'api-ops:celery-task-log' pk=object.id %}";
|
||||
var term;
|
||||
|
@ -42,13 +29,16 @@
|
|||
var interval = 200;
|
||||
|
||||
function calWinSize() {
|
||||
var t = $('.terminal');
|
||||
rowHeight = 1.00 * t.height() / 24;
|
||||
colWidth = 1.00 * t.width() / 80;
|
||||
var t = $('#marker');
|
||||
{#rowHeight = 1.00 * t.height();#}
|
||||
{#colWidth = 1.00 * t.width() / 6;#}
|
||||
}
|
||||
function resize() {
|
||||
var rows = Math.floor(window.innerHeight / rowHeight) - 2;
|
||||
var cols = Math.floor(window.innerWidth / colWidth) - 10;
|
||||
{#console.log(rowHeight, window.innerHeight);#}
|
||||
{#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);
|
||||
}
|
||||
function requestAndWrite() {
|
||||
|
@ -74,21 +64,18 @@
|
|||
}
|
||||
}
|
||||
$(document).ready(function () {
|
||||
term = new Terminal({
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
useStyle: true,
|
||||
screenKeys: false,
|
||||
convertEol: false,
|
||||
cursorBlink: false
|
||||
});
|
||||
term.open();
|
||||
term.on('data', function (data) {
|
||||
term.write(data.replace('\r', '\r\n'))
|
||||
});
|
||||
calWinSize();
|
||||
term = new Terminal();
|
||||
term.open(document.getElementById('term'));
|
||||
term.resize(80, 24);
|
||||
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 () {
|
||||
requestAndWrite()
|
||||
}, 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>
|
||||
</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>
|
||||
</ul>
|
||||
</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>
|
||||
</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>
|
||||
</ul>
|
||||
</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>
|
||||
</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>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -115,7 +115,7 @@ $(document).ready(function() {
|
|||
var success = function(data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600')
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
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 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 .utils import AssetPermissionUtil
|
||||
from .models import AssetPermission
|
||||
|
@ -41,7 +41,7 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
|
|||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
permissions = set(queryset.filter(assets=asset))
|
||||
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:
|
||||
node = get_object_or_404(Node, pk=node_id)
|
||||
permissions = set(queryset.filter(nodes=node))
|
||||
|
@ -147,8 +147,13 @@ class UserGrantedNodeAssetsApi(ListAPIView):
|
|||
user = get_object_or_404(User, id=user_id)
|
||||
else:
|
||||
user = self.request.user
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
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, [])
|
||||
for asset, system_users in assets.items():
|
||||
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
|
||||
|
||||
|
||||
class ValidManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(is_active=True) \
|
||||
.filter(date_start__lt=timezone.now())\
|
||||
class AssetPermissionQuerySet(models.QuerySet):
|
||||
def active(self):
|
||||
return self.filter(is_active=True)
|
||||
|
||||
def valid(self):
|
||||
return self.active().filter(date_start__lt=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):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
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"))
|
||||
system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
|
||||
date_start = models.DateTimeField(default=timezone.now, verbose_name=_("Date start"))
|
||||
date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_('Date expired'))
|
||||
date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start"))
|
||||
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'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(verbose_name=_('Comment'), blank=True)
|
||||
|
||||
objects = models.Manager()
|
||||
valid = ValidManager()
|
||||
objects = AssetPermissionManager()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
|
|
@ -9,7 +9,7 @@ from common.fields import StringManyToManyField
|
|||
class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
exclude = ('id', 'created_by', 'date_created')
|
||||
exclude = ('created_by', 'date_created')
|
||||
|
||||
|
||||
class AssetPermissionListSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<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="input-daterange input-group" id="datepicker">
|
||||
<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) {
|
||||
setCookie('node_selected', treeNode.id);
|
||||
var url = table.ajax.url();
|
||||
if (treeNode.is_asset) {
|
||||
url = setUrlParam(url, 'node', "");
|
||||
url = setUrlParam(url, 'asset', treeNode.id)
|
||||
} else {
|
||||
if (treeNode.is_node) {
|
||||
url = setUrlParam(url, 'asset', "");
|
||||
url = setUrlParam(url, 'node', treeNode.id)
|
||||
} else {
|
||||
url = setUrlParam(url, 'node', "");
|
||||
url = setUrlParam(url, 'asset', treeNode.id)
|
||||
}
|
||||
setCookie('node_selected', treeNode.id);
|
||||
table.ajax.url(url);
|
||||
|
@ -113,14 +113,14 @@ function filter(treeId, parentNode, childNodes) {
|
|||
$.each(childNodes, function (index, value) {
|
||||
value["pId"] = value["parent"];
|
||||
value["name"] = value["value"];
|
||||
value["isParent"] = value["assets_amount"] !== 0;
|
||||
value["iconSkin"] = value["is_asset"] ? "file" : null;
|
||||
value["isParent"] = value["is_node"];
|
||||
value["iconSkin"] = value["is_node"] ? null : 'file';
|
||||
});
|
||||
return childNodes;
|
||||
}
|
||||
|
||||
function beforeAsync(treeId, treeNode) {
|
||||
return true;
|
||||
return treeNode.is_node
|
||||
}
|
||||
|
||||
function makeLabel(data) {
|
||||
|
@ -226,7 +226,7 @@ function initTree() {
|
|||
},
|
||||
async: {
|
||||
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"],
|
||||
dataFilter: filter,
|
||||
type: 'get'
|
||||
|
@ -238,18 +238,19 @@ function initTree() {
|
|||
};
|
||||
|
||||
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) {
|
||||
value["pId"] = value["parent"];
|
||||
value["isParent"] = value["assets_amount"] !== 0;
|
||||
value["name"] = value["value"];
|
||||
value["open"] = value["key"] === "0";
|
||||
value["isParent"] = value["is_node"];
|
||||
value["iconSkin"] = value["is_node"] ? null : 'file';
|
||||
});
|
||||
zNodes = data;
|
||||
{#$.fn.zTree.init($("#assetTree"), setting);#}
|
||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||
selectQueryNode();
|
||||
{#selectQueryNode();#}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -286,10 +287,10 @@ $(document).ready(function(){
|
|||
var _nodes = [];
|
||||
var _assets = [];
|
||||
$.each(nodes, function (id, node) {
|
||||
if (node.is_asset) {
|
||||
_assets.push(node.id)
|
||||
} else {
|
||||
if (node.is_node) {
|
||||
_nodes.push(node.id)
|
||||
} else {
|
||||
_assets.push(node.id)
|
||||
}
|
||||
});
|
||||
url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(",");
|
||||
|
@ -303,6 +304,7 @@ $(document).ready(function(){
|
|||
|
||||
if (row.child.isShown()) {
|
||||
tr.removeClass('details');
|
||||
$(this).children('i:first-child').removeClass('fa-angle-down').addClass('fa-angle-right');
|
||||
row.child.hide();
|
||||
|
||||
// Remove from the 'open' array
|
||||
|
@ -310,7 +312,7 @@ $(document).ready(function(){
|
|||
}
|
||||
else {
|
||||
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();
|
||||
// Add to the 'open' array
|
||||
if ( idx === -1 ) {
|
||||
|
|
|
@ -8,31 +8,75 @@ import copy
|
|||
|
||||
from common.utils import set_or_append_attr_bulk, get_logger
|
||||
from .models import AssetPermission
|
||||
from .hands import Node
|
||||
|
||||
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
|
||||
def get_user_permissions(user):
|
||||
return AssetPermission.valid.all().filter(users=user)
|
||||
return AssetPermission.objects.all().valid().filter(users=user)
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
def get_asset_permissions(asset):
|
||||
return AssetPermission.valid.all().filter(assets=asset)
|
||||
return AssetPermission.objects.all().valid().filter(
|
||||
assets=asset
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_node_permissions(node):
|
||||
return AssetPermission.valid.all().filter(nodes=node)
|
||||
return AssetPermission.objects.all().valid().filter(nodes=node)
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
def get_user_group_nodes(cls, group):
|
||||
|
@ -51,7 +95,7 @@ class AssetPermissionUtil:
|
|||
assets = defaultdict(set)
|
||||
permissions = cls.get_user_group_permissions(group)
|
||||
for perm in permissions:
|
||||
_assets = perm.assets.all()
|
||||
_assets = perm.assets.all().valid()
|
||||
_system_users = perm.system_users.all()
|
||||
set_or_append_attr_bulk(_assets, 'permission', perm.id)
|
||||
for asset in _assets:
|
||||
|
@ -63,7 +107,7 @@ class AssetPermissionUtil:
|
|||
assets = defaultdict(set)
|
||||
nodes = cls.get_user_group_nodes(group)
|
||||
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, 'permission', getattr(node, 'permission', None))
|
||||
for asset in _assets:
|
||||
|
@ -82,28 +126,26 @@ class AssetPermissionUtil:
|
|||
return assets
|
||||
|
||||
@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)}}
|
||||
"""
|
||||
nodes = defaultdict(dict)
|
||||
_assets = cls.get_user_group_assets(user)
|
||||
_assets = cls.get_user_group_assets(group)
|
||||
tree = Tree()
|
||||
for asset, _system_users in _assets.items():
|
||||
_nodes = asset.get_nodes()
|
||||
tree.add_nodes(_nodes)
|
||||
for node in _nodes:
|
||||
if asset in nodes[node]:
|
||||
nodes[node][asset].update(_system_users)
|
||||
else:
|
||||
nodes[node][asset] = _system_users
|
||||
return nodes
|
||||
tree.nodes[node][asset].update(_system_users)
|
||||
return tree.nodes
|
||||
|
||||
@classmethod
|
||||
def get_user_assets_direct(cls, user):
|
||||
assets = defaultdict(set)
|
||||
permissions = list(cls.get_user_permissions(user))
|
||||
for perm in permissions:
|
||||
_assets = perm.assets.all()
|
||||
_assets = perm.assets.all().valid()
|
||||
_system_users = perm.system_users.all()
|
||||
set_or_append_attr_bulk(_assets, 'permission', perm.id)
|
||||
for asset in _assets:
|
||||
|
@ -122,12 +164,30 @@ class AssetPermissionUtil:
|
|||
nodes[node].update(set(_system_users))
|
||||
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
|
||||
def get_user_nodes_assets_direct(cls, user):
|
||||
assets = defaultdict(set)
|
||||
nodes = cls.get_user_nodes_direct(user)
|
||||
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, 'permission', getattr(node, 'permission', None))
|
||||
for asset in _assets:
|
||||
|
@ -164,26 +224,25 @@ class AssetPermissionUtil:
|
|||
:param user:
|
||||
:return: {node: {asset: set(su1, su2)}}
|
||||
"""
|
||||
nodes = defaultdict(dict)
|
||||
tree = Tree()
|
||||
_assets = cls.get_user_assets(user)
|
||||
for asset, _system_users in _assets.items():
|
||||
_nodes = asset.get_nodes()
|
||||
for node in _nodes:
|
||||
if asset in nodes[node]:
|
||||
nodes[node][asset].update(_system_users)
|
||||
else:
|
||||
nodes[node][asset] = _system_users
|
||||
return nodes
|
||||
tree.add_asset(asset, _system_users)
|
||||
# _nodes = asset.get_nodes()
|
||||
# tree.add_nodes(_nodes)
|
||||
# for node in _nodes:
|
||||
# tree.nodes[node][asset].update(_system_users)
|
||||
return tree.nodes
|
||||
|
||||
@classmethod
|
||||
def get_system_user_assets(cls, system_user):
|
||||
assets = set()
|
||||
permissions = cls.get_system_user_permissions(system_user)
|
||||
for perm in permissions:
|
||||
assets.update(set(perm.assets.all()))
|
||||
assets.update(set(perm.assets.all().valid()))
|
||||
nodes = perm.nodes.all()
|
||||
for node in nodes:
|
||||
assets.update(set(node.get_all_assets()))
|
||||
assets.update(set(node.get_all_valid_assets()))
|
||||
return assets
|
||||
|
||||
@classmethod
|
||||
|
@ -228,7 +287,7 @@ class NodePermissionUtil:
|
|||
|
||||
nodes = copy.deepcopy(nodes_directed)
|
||||
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)
|
||||
return nodes
|
||||
|
||||
|
@ -243,7 +302,7 @@ class NodePermissionUtil:
|
|||
nodes_with_assets = dict()
|
||||
for node, system_users in nodes.items():
|
||||
nodes_with_assets[node] = {
|
||||
'assets': node.get_active_assets(),
|
||||
'assets': node.get_valid_assets(),
|
||||
'system_users': system_users
|
||||
}
|
||||
return nodes_with_assets
|
||||
|
@ -274,7 +333,7 @@ class NodePermissionUtil:
|
|||
nodes_with_assets = dict()
|
||||
for node, system_users in nodes.items():
|
||||
nodes_with_assets[node] = {
|
||||
'assets': node.get_active_assets(),
|
||||
'assets': node.get_valid_assets(),
|
||||
'system_users': system_users
|
||||
}
|
||||
return nodes_with_assets
|
||||
|
|
|
@ -42,7 +42,7 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
|
|||
|
||||
if nodes_id:
|
||||
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
|
||||
if assets_id:
|
||||
assets_id = assets_id.split(",")
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
|||
<div class="footer fixed">
|
||||
<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">
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -9,6 +9,7 @@ from django.core.cache import cache
|
|||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils import timezone
|
||||
from django.core.files.storage import default_storage
|
||||
from django.http.response import HttpResponseRedirectBase
|
||||
from django.http import HttpResponseNotFound
|
||||
from django.conf import settings
|
||||
|
||||
|
@ -25,7 +26,7 @@ from .serializers import TerminalSerializer, StatusSerializer, \
|
|||
SessionSerializer, TaskSerializer, ReplaySerializer
|
||||
from .hands import IsSuperUserOrAppUser, IsAppUser, \
|
||||
IsSuperUserOrAppUserOrUserReadonly
|
||||
from .backends import get_command_store, get_multi_command_store, \
|
||||
from .backends import get_command_storage, get_multi_command_storage, \
|
||||
SessionCommandSerializer
|
||||
|
||||
logger = logging.getLogger(__file__)
|
||||
|
@ -108,7 +109,9 @@ class StatusViewSet(viewsets.ModelViewSet):
|
|||
task_serializer_class = TaskSerializer
|
||||
|
||||
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)
|
||||
tasks = self.request.user.terminal.task_set.filter(is_finished=False)
|
||||
serializer = self.task_serializer_class(tasks, many=True)
|
||||
|
@ -224,8 +227,8 @@ class CommandViewSet(viewsets.ViewSet):
|
|||
}
|
||||
|
||||
"""
|
||||
command_store = get_command_store()
|
||||
multi_command_storage = get_multi_command_store()
|
||||
command_store = get_command_storage()
|
||||
multi_command_storage = get_multi_command_storage()
|
||||
serializer_class = SessionCommandSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
||||
|
@ -288,74 +291,37 @@ class SessionReplayViewSet(viewsets.ViewSet):
|
|||
url = default_storage.url(path)
|
||||
return redirect(url)
|
||||
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:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
for name, config in configs:
|
||||
client = jms_storage.init(config)
|
||||
date = self.session.date_start.strftime('%Y-%m-%d')
|
||||
file_path = os.path.join(date, str(self.session.id) + '.replay.gz')
|
||||
target_path = default_storage.base_location + '/' + path
|
||||
folder_path = default_storage.base_location + date
|
||||
|
||||
if not default_storage.exists(folder_path):
|
||||
os.mkdir(folder_path)
|
||||
|
||||
if client and client.has_file(file_path) and \
|
||||
client.download_file(file_path, target_path):
|
||||
return redirect(default_storage.url(path))
|
||||
date = self.session.date_start.strftime('%Y-%m-%d')
|
||||
file_path = os.path.join(date, str(self.session.id) + '.replay.gz')
|
||||
target_path = default_storage.base_location + '/' + path
|
||||
storage = jms_storage.get_multi_object_storage(configs)
|
||||
ok, err = storage.download(file_path, target_path)
|
||||
if ok:
|
||||
return redirect(default_storage.url(path))
|
||||
else:
|
||||
logger.error("Failed download replay file: {}".format(err))
|
||||
return HttpResponseNotFound()
|
||||
|
||||
|
||||
class SessionReplayV2ViewSet(viewsets.ViewSet):
|
||||
class SessionReplayV2ViewSet(SessionReplayViewSet):
|
||||
serializer_class = ReplaySerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
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):
|
||||
session_id = kwargs.get('pk')
|
||||
self.session = get_object_or_404(Session, id=session_id)
|
||||
replay = self.gen_session_path()
|
||||
|
||||
if replay.get("path", "") == "":
|
||||
return HttpResponseNotFound()
|
||||
|
||||
if default_storage.exists(replay["path"]):
|
||||
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)
|
||||
response = super().retrieve(request, *args, **kwargs)
|
||||
data = {
|
||||
'type': 'guacamole' if self.session.protocol == 'rdp' else 'json',
|
||||
'src': '',
|
||||
}
|
||||
if isinstance(response, HttpResponseRedirectBase):
|
||||
data['src'] = response.url
|
||||
return Response(data)
|
||||
return HttpResponseNotFound()
|
||||
|
||||
|
||||
|
|
|
@ -7,19 +7,19 @@ TYPE_ENGINE_MAPPING = {
|
|||
}
|
||||
|
||||
|
||||
def get_command_store():
|
||||
params = settings.COMMAND_STORAGE
|
||||
engine_class = import_module(params['ENGINE'])
|
||||
storage = engine_class.CommandStore(params)
|
||||
def get_command_storage():
|
||||
config = settings.COMMAND_STORAGE
|
||||
engine_class = import_module(config['ENGINE'])
|
||||
storage = engine_class.CommandStore(config)
|
||||
return storage
|
||||
|
||||
|
||||
def get_terminal_command_store():
|
||||
def get_terminal_command_storages():
|
||||
storage_list = {}
|
||||
for name, params in settings.TERMINAL_COMMAND_STORAGE.items():
|
||||
tp = params['TYPE']
|
||||
if tp == 'server':
|
||||
storage = get_command_store()
|
||||
storage = get_command_storage()
|
||||
else:
|
||||
if not TYPE_ENGINE_MAPPING.get(tp):
|
||||
continue
|
||||
|
@ -29,9 +29,9 @@ def get_terminal_command_store():
|
|||
return storage_list
|
||||
|
||||
|
||||
def get_multi_command_store():
|
||||
def get_multi_command_storage():
|
||||
from .command.multi import CommandStore
|
||||
storage_list = get_terminal_command_store().values()
|
||||
storage_list = get_terminal_command_storages().values()
|
||||
storage = CommandStore(storage_list)
|
||||
return storage
|
||||
|
||||
|
|
|
@ -1,41 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from jms_es_sdk import ESStore
|
||||
from jms_storage.es import ESStorage
|
||||
from .base import CommandBase
|
||||
from .models import AbstractSessionCommand
|
||||
|
||||
|
||||
class CommandStore(CommandBase, ESStore):
|
||||
class CommandStore(ESStorage, CommandBase):
|
||||
def __init__(self, params):
|
||||
hosts = params.get('HOSTS', ['http://localhost'])
|
||||
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)
|
||||
super().__init__(params)
|
||||
|
||||
def filter(self, date_from=None, date_to=None,
|
||||
user=None, asset=None, system_user=None,
|
||||
input=None, session=None):
|
||||
|
||||
data = ESStore.filter(
|
||||
self, date_from=date_from, date_to=date_to,
|
||||
user=user, asset=asset, system_user=system_user,
|
||||
input=input, session=session
|
||||
)
|
||||
data = super().filter(date_from=date_from, date_to=date_to,
|
||||
user=user, asset=asset, system_user=system_user,
|
||||
input=input, session=session)
|
||||
return AbstractSessionCommand.from_multi_dict(
|
||||
[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.utils import get_object_or_none
|
||||
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):
|
||||
|
@ -47,7 +47,7 @@ class TerminalSerializer(serializers.ModelSerializer):
|
|||
|
||||
class SessionSerializer(serializers.ModelSerializer):
|
||||
command_amount = serializers.SerializerMethodField()
|
||||
command_store = get_multi_command_store()
|
||||
command_store = get_multi_command_storage()
|
||||
|
||||
class Meta:
|
||||
model = Session
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
<td>{% trans 'Replay session' %}:</td>
|
||||
<td>
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -99,10 +99,14 @@
|
|||
<td class="text-center">{{ session.date_start|time_util_with_seconds:session.date_end }}</td>
|
||||
<td>
|
||||
{% 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 %}
|
||||
<!--<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 %}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from django import template
|
||||
from ..backends import get_multi_command_store
|
||||
from ..backends import get_multi_command_storage
|
||||
|
||||
register = template.Library()
|
||||
command_store = get_multi_command_store()
|
||||
command_store = get_multi_command_storage()
|
||||
|
||||
|
||||
@register.filter
|
||||
|
|
|
@ -9,10 +9,10 @@ from django.utils.translation import ugettext as _
|
|||
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
|
||||
from ..models import Command
|
||||
from .. import utils
|
||||
from ..backends import get_multi_command_store
|
||||
from ..backends import get_multi_command_storage
|
||||
|
||||
__all__ = ['CommandListView']
|
||||
common_storage = get_multi_command_store()
|
||||
common_storage = get_multi_command_storage()
|
||||
|
||||
|
||||
class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
|
||||
|
|
|
@ -10,7 +10,7 @@ from django.conf import settings
|
|||
from users.utils import AdminUserRequiredMixin
|
||||
from common.mixins import DatetimeSearchMixin
|
||||
from ..models import Session, Command, Terminal
|
||||
from ..backends import get_multi_command_store
|
||||
from ..backends import get_multi_command_storage
|
||||
from .. import utils
|
||||
|
||||
|
||||
|
@ -19,7 +19,7 @@ __all__ = [
|
|||
'SessionDetailView',
|
||||
]
|
||||
|
||||
command_store = get_multi_command_store()
|
||||
command_store = get_multi_command_storage()
|
||||
|
||||
|
||||
class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
|
|
|
@ -11,7 +11,7 @@ __all__ = ['UserGroup']
|
|||
|
||||
class UserGroup(NoDeleteModelMixin):
|
||||
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'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True,
|
||||
verbose_name=_('Date created'))
|
||||
|
|
|
@ -77,7 +77,7 @@ class User(AbstractUser):
|
|||
is_first_login = models.BooleanField(default=True)
|
||||
date_expired = models.DateTimeField(
|
||||
default=date_expired_default, blank=True, null=True,
|
||||
verbose_name=_('Date expired')
|
||||
db_index=True, verbose_name=_('Date expired')
|
||||
)
|
||||
created_by = models.CharField(
|
||||
max_length=30, default='', verbose_name=_('Created by')
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
<br>
|
||||
<input type="checkbox" id="acceptTerms">
|
||||
<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 %}
|
||||
|
||||
{% bootstrap_form wizard.form %}
|
||||
|
@ -99,11 +100,7 @@
|
|||
{% if wizard.steps.prev %}
|
||||
<li><a class="fl_goto" name="wizard_goto_step" data-goto="{{ wizard.steps.prev }}">{% trans "Previous" %}</a></li>
|
||||
{% 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 %}
|
||||
<li><a id="fl_submit" >{% trans "Next" %}</a></li>
|
||||
{% else %}
|
||||
|
@ -124,16 +121,21 @@
|
|||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$('#id_2-otp_level div').eq(2).css('display', 'none');
|
||||
|
||||
$(document).on('click', ".fl_goto", function(){
|
||||
var $form = $('#fl_form');
|
||||
$('<input />', {'name': 'wizard_goto_step', 'value': $(this).data('goto'), 'type': 'hidden'}).appendTo($form);
|
||||
$form.submit();
|
||||
return false;
|
||||
}).on('click', '#fl_submit', function(){
|
||||
$('#fl_form').submit();
|
||||
return false;
|
||||
var isFinish = $('#fl_submit').html() === "{% trans 'Finish' %}";
|
||||
var noChecked = !$('#acceptTerms').prop('checked');
|
||||
if ( isFinish && noChecked){
|
||||
$('#noTerms').css('visibility', 'visible');
|
||||
}
|
||||
else{
|
||||
$('#fl_form').submit();
|
||||
return false;
|
||||
}
|
||||
}).on('click', '#btn-reset-pubkey', function () {
|
||||
var the_url = '{% url "users:user-pubkey-generate" %}';
|
||||
window.open(the_url, "_blank")
|
||||
|
|
|
@ -68,7 +68,7 @@ var asset_table;
|
|||
|
||||
function initTable() {
|
||||
if (inited){
|
||||
return
|
||||
return asset_table
|
||||
} else {
|
||||
inited = true;
|
||||
}
|
||||
|
|
|
@ -64,10 +64,11 @@
|
|||
var zTree;
|
||||
var inited = false;
|
||||
var url;
|
||||
var asset_table;
|
||||
|
||||
function initTable() {
|
||||
if (inited){
|
||||
return
|
||||
return asset_table
|
||||
} else {
|
||||
inited = true;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,8 @@ def send_user_created_mail(user):
|
|||
</br>
|
||||
Your account has been created successfully
|
||||
</br>
|
||||
Username: %(username)s
|
||||
</br>
|
||||
<a href="%(rest_password_url)s?token=%(rest_password_token)s">click here to set your password</a>
|
||||
</br>
|
||||
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>
|
||||
""") % {
|
||||
'name': user.name,
|
||||
'username': user.username,
|
||||
'rest_password_url': reverse('users:reset-password', external=True),
|
||||
'rest_password_token': user.generate_reset_token(),
|
||||
'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):
|
||||
form = super().get_form(step, data, files)
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@ ansible==2.4.2.0
|
|||
asn1crypto==0.24.0
|
||||
bcrypt==3.1.4
|
||||
billiard==3.5.0.3
|
||||
boto3==1.6.4
|
||||
botocore==1.9.4
|
||||
boto3==1.6.5
|
||||
botocore==1.9.5
|
||||
celery==4.1.0
|
||||
certifi==2017.11.5
|
||||
certifi==2018.1.18
|
||||
cffi==1.11.2
|
||||
chardet==3.0.4
|
||||
configparser==3.5.0
|
||||
|
@ -31,7 +31,7 @@ ecdsa==0.13
|
|||
elasticsearch==6.1.1
|
||||
enum-compat==0.0.2
|
||||
ephem==3.7.6.0
|
||||
eventlet==0.21.0
|
||||
eventlet==0.22.1
|
||||
ForgeryPy==0.1
|
||||
greenlet==0.4.12
|
||||
gunicorn==19.7.1
|
||||
|
@ -40,7 +40,6 @@ itsdangerous==0.24
|
|||
itypes==1.1.0
|
||||
Jinja2==2.10
|
||||
jmespath==0.9.3
|
||||
jms-es-sdk
|
||||
kombu==4.0.2
|
||||
ldap3==2.4
|
||||
MarkupSafe==1.0
|
||||
|
@ -58,11 +57,11 @@ pyotp==2.2.6
|
|||
PyNaCl==1.2.1
|
||||
python-dateutil==2.6.1
|
||||
python-gssapi==0.6.4
|
||||
pytz==2017.3
|
||||
pytz==2018.3
|
||||
PyYAML==3.12
|
||||
redis==2.10.6
|
||||
requests==2.18.4
|
||||
jms-storage==0.0.13
|
||||
jms-storage==0.0.17
|
||||
s3transfer==0.1.13
|
||||
simplejson==3.13.2
|
||||
six==1.11.0
|
||||
|
|
|
@ -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
|
||||
|
||||
if [ ! -d "/opt/py3" ]; then
|
||||
echo -e "\033[31m python3虚拟环境不是默认路径 \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
|
||||
echo -e "\033[31m 正在计算python3虚拟环境路径 \033[0m"
|
||||
fi
|
||||
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
|
||||
if grep -q 'source ~/.autoenv/activate.sh' ~/.bashrc; then
|
||||
echo -e "\033[31m 正在自动载入 python 环境 \033[0m"
|
||||
else
|
||||
source /opt/py3/bin/activate
|
||||
echo -e "\033[31m 不支持自动升级,请参考 http://docs.jumpserver.org/zh/docs/upgrade.html 手动升级 \033[0m"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
source ~/.bashrc
|
||||
|
||||
cd `dirname $0`/ && cd .. && ./jms stop
|
||||
|
||||
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
|
||||
read -p "确认备份请按Y,否则按其他键跳过备份 " a
|
||||
if [ "$a" == y -o "$a" == Y ];then
|
||||
echo -e "\033[31m 正在备份数据库 \033[0m"
|
||||
echo -e "\033[31m 请手动输入数据库信息 \033[0m"
|
||||
read -p '请输入Jumpserver数据库ip:' DB_HOST
|
||||
read -p '请输入Jumpserver数据库端口:' DB_PORT
|
||||
read -p '请输入Jumpserver数据库名称:' DB_NAME
|
||||
read -p '请输入有权限导出数据库的用户:' DB_USER
|
||||
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 || {
|
||||
echo -e "\033[31m 备份数据库失败,请检查输入是否有误 \033[0m"
|
||||
exit 1
|
||||
}
|
||||
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数据库端口:' DB_PORT
|
||||
read -p '请输入Jumpserver数据库名称:' DB_NAME
|
||||
read -p '请输入有权限导出数据库的用户:' DB_USER
|
||||
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 || {
|
||||
echo -e "\033[31m 备份数据库失败,请检查输入是否有误 \033[0m"
|
||||
exit 1
|
||||
}
|
||||
echo -e "\033[31m 备份数据库完成 \033[0m"
|
||||
else
|
||||
echo -e "\033[31m 已取消备份数据库操作 \033[0m"
|
||||
echo -e "\033[31m 已取消备份数据库操作 \033[0m"
|
||||
fi
|
||||
|
||||
git pull && pip install -r requirements/requirements.txt && cd utils && sh make_migrations.sh
|
||||
|
|
Loading…
Reference in New Issue