diff --git a/README.md b/README.md
index e44a23f9c..f0bceec9b 100644
--- a/README.md
+++ b/README.md
@@ -19,31 +19,25 @@ Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点
----
### 功能
- - 统一认证
- - 资产管理
- - 统一授权
- - 审计
- - 支持LDAP认证
- - Web terminal
- - SSH Server
- - 支持Windows RDP
+
+ 
### 开始使用
-快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/latest/quickstart.html)
+快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
-一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/latest/step_by_step.html)
+一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/docs/step_by_step.html)
也可以查看我们完整文档包括了使用和开发 [文档](http://docs.jumpserver.org)
-### Demo 和 截图
+### Demo 和 截图
我们提供了DEMO和截图可以让你快速了解Jumpserver
[DEMO](http://demo.jumpserver.org)
[截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
-### SDK
+### SDK
我们还编写了一些SDK,供你其它系统快速和Jumpserver APi交互,
diff --git a/apps/__init__.py b/apps/__init__.py
index def994bcd..7e96164aa 100644
--- a/apps/__init__.py
+++ b/apps/__init__.py
@@ -2,4 +2,4 @@
# -*- coding: utf-8 -*-
#
-__version__ = "1.2.1"
+__version__ = "1.3.2"
diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py
index 50c037df9..8c1f3d726 100644
--- a/apps/assets/api/asset.py
+++ b/apps/assets/api/asset.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
#
+import random
+
from rest_framework import generics
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
@@ -11,9 +13,8 @@ from django.db.models import Q
from common.mixins import IDInFilterMixin
from common.utils import get_logger
-from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \
- NodePermissionUtil
-from ..models import Asset, SystemUser, AdminUser, Node
+from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser
+from ..models import Asset, SystemUser, AdminUser, Node
from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \
test_asset_connectability_manual
@@ -22,8 +23,9 @@ from ..utils import LabelFilter
logger = get_logger(__file__)
__all__ = [
- 'AssetViewSet', 'UserAssetListView', 'AssetListUpdateApi',
- 'AssetRefreshHardwareApi', 'AssetAdminUserTestApi'
+ 'AssetViewSet', 'AssetListUpdateApi',
+ 'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
+ 'AssetGatewayApi'
]
@@ -40,32 +42,34 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
permission_classes = (IsSuperUserOrAppUser,)
def get_queryset(self):
- queryset = super().get_queryset()
+ queryset = super().get_queryset()\
+ .prefetch_related('labels', 'nodes')\
+ .select_related('admin_user')
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)
queryset = queryset.filter(admin_user=admin_user)
- if node_id:
+
+ if node_id and show_current_asset:
node = get_object_or_404(Node, id=node_id)
- if not node.is_root():
+ if node.is_root():
queryset = queryset.filter(
- nodes__key__regex='{}(:[0-9]+)*$'.format(node.key),
+ Q(nodes=node_id) | Q(nodes__isnull=True)
).distinct()
- return queryset
+ else:
+ queryset = queryset.filter(nodes=node).distinct()
-
-class UserAssetListView(generics.ListAPIView):
- queryset = Asset.objects.all()
- serializer_class = serializers.AssetSerializer
- permission_classes = (IsValidUser,)
-
- def get_queryset(self):
- assets_granted = NodePermissionUtil.get_user_assets(self.request.user).keys()
- queryset = self.queryset.filter(
- id__in=[asset.id for asset in assets_granted]
- )
+ if node_id and not show_current_asset:
+ node = get_object_or_404(Node, id=node_id)
+ if node.is_root():
+ queryset = Asset.objects.all()
+ else:
+ queryset = queryset.filter(
+ nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
+ ).distinct()
return queryset
@@ -105,3 +109,20 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
asset = get_object_or_404(Asset, pk=asset_id)
task = test_asset_connectability_manual.delay(asset)
return Response({"task": task.id})
+
+
+class AssetGatewayApi(generics.RetrieveAPIView):
+ queryset = Asset.objects.all()
+ permission_classes = (IsSuperUserOrAppUser,)
+
+ def retrieve(self, request, *args, **kwargs):
+ asset_id = kwargs.get('pk')
+ asset = get_object_or_404(Asset, pk=asset_id)
+
+ if asset.domain and \
+ asset.domain.gateways.filter(protocol=asset.protocol).exists():
+ gateway = random.choice(asset.domain.gateways.filter(protocol=asset.protocol))
+ serializer = serializers.GatewayWithAuthSerializer(instance=gateway)
+ return Response(serializer.data)
+ else:
+ return Response({"msg": "Not have gateway"}, status=404)
\ No newline at end of file
diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py
index f123e4649..e5ace021e 100644
--- a/apps/assets/api/node.py
+++ b/apps/assets/api/node.py
@@ -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
@@ -30,7 +31,7 @@ from .. import serializers
logger = get_logger(__file__)
__all__ = [
'NodeViewSet', 'NodeChildrenApi',
- 'NodeAssetsApi', 'NodeWithAssetsApi',
+ 'NodeAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
'NodeReplaceAssetsApi',
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
@@ -49,32 +50,32 @@ class NodeViewSet(BulkModelViewSet):
serializer.save()
-class NodeWithAssetsApi(generics.ListAPIView):
- permission_classes = (IsSuperUser,)
- serializers = serializers.NodeSerializer
-
- def get_node(self):
- pk = self.kwargs.get('pk') or self.request.query_params.get('node')
- if not pk:
- node = Node.root()
- else:
- node = get_object_or_404(Node, pk)
- return node
-
- def get_queryset(self):
- queryset = []
- node = self.get_node()
- children = node.get_children()
- assets = node.get_assets()
- queryset.extend(list(children))
-
- for asset in assets:
- node = Node()
- node.id = asset.id
- node.parent = node.id
- node.value = asset.hostname
- queryset.append(node)
- return queryset
+# class NodeWithAssetsApi(generics.ListAPIView):
+# permission_classes = (IsSuperUser,)
+# serializers = serializers.NodeSerializer
+#
+# def get_node(self):
+# pk = self.kwargs.get('pk') or self.request.query_params.get('node')
+# if not pk:
+# node = Node.root()
+# else:
+# node = get_object_or_404(Node, pk)
+# return node
+#
+# def get_queryset(self):
+# queryset = []
+# node = self.get_node()
+# children = node.get_children()
+# assets = node.get_assets()
+# queryset.extend(list(children))
+#
+# for asset in assets:
+# node = Node()
+# node.id = asset.id
+# node.parent = node.id
+# node.value = asset.hostname
+# queryset.append(node)
+# return queryset
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
@@ -83,16 +84,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},
@@ -102,7 +116,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
if not pk:
- node = Node.root()
+ node = None
else:
node = get_object_or_404(Node, pk=pk)
return node
@@ -112,7 +126,8 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
query_all = self.request.query_params.get("all")
query_assets = self.request.query_params.get('assets')
node = self.get_object()
- if node == Node.root():
+ if node is None:
+ node = Node.root()
queryset.append(node)
if query_all:
children = node.get_all_children()
@@ -125,10 +140,11 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
for asset in assets:
node_fake = Node()
node_fake.id = asset.id
- node_fake.parent = node
+ node_fake.is_node = False
+ node_fake.parent_id = node.id
node_fake.value = asset.hostname
- node_fake.is_asset = True
queryset.append(node_fake)
+ queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True)
return queryset
def get(self, request, *args, **kwargs):
@@ -163,7 +179,6 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
if not node:
continue
node.parent = instance
- node.save()
return Response("OK")
@@ -190,6 +205,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):
diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py
index b4e46cc78..66d62232d 100644
--- a/apps/assets/api/system_user.py
+++ b/apps/assets/api/system_user.py
@@ -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})
diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py
index f8f187b4d..5000c087d 100644
--- a/apps/assets/forms/asset.py
+++ b/apps/assets/forms/asset.py
@@ -16,7 +16,7 @@ class AssetCreateForm(forms.ModelForm):
fields = [
'hostname', 'ip', 'public_ip', 'port', 'comment',
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
- 'domain',
+ 'domain', 'protocol',
]
widgets = {
@@ -56,7 +56,7 @@ class AssetUpdateForm(forms.ModelForm):
fields = [
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
'public_ip', 'number', 'comment', 'admin_user', 'labels',
- 'domain',
+ 'domain', 'protocol',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
diff --git a/apps/assets/forms/user.py b/apps/assets/forms/user.py
index 2295dc005..b25e19d87 100644
--- a/apps/assets/forms/user.py
+++ b/apps/assets/forms/user.py
@@ -93,14 +93,21 @@ class SystemUserForm(PasswordAndKeyAuthForm):
# Because we define custom field, so we need rewrite :method: `save`
system_user = super().save()
password = self.cleaned_data.get('password', '') or None
+ login_mode = self.cleaned_data.get('login_mode', '') or None
+ protocol = self.cleaned_data.get('protocol') or None
auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
private_key, public_key = super().gen_keys()
+ if login_mode == SystemUser.MANUAL_LOGIN or protocol == SystemUser.TELNET_PROTOCOL:
+ system_user.auto_push = 0
+ system_user.save()
+
if auto_generate_key:
logger.info('Auto generate key and set system user auth')
system_user.auto_gen_auth()
else:
system_user.set_auth(password=password, private_key=private_key, public_key=public_key)
+
return system_user
def clean(self):
@@ -109,12 +116,24 @@ class SystemUserForm(PasswordAndKeyAuthForm):
if not self.instance and not auto_generate:
super().validate_password_key()
+ def is_valid(self):
+ validated = super().is_valid()
+ username = self.cleaned_data.get('username')
+ login_mode = self.cleaned_data.get('login_mode')
+ if login_mode == SystemUser.AUTO_LOGIN and not username:
+ self.add_error(
+ "username", _('* Automatic login mode,'
+ ' must fill in the username.')
+ )
+ return False
+ return validated
+
class Meta:
model = SystemUser
fields = [
'name', 'username', 'protocol', 'auto_generate_key',
'password', 'private_key_file', 'auto_push', 'sudo',
- 'comment', 'shell', 'priority',
+ 'comment', 'shell', 'priority', 'login_mode',
]
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
@@ -124,5 +143,8 @@ class SystemUserForm(PasswordAndKeyAuthForm):
'name': '* required',
'username': '* required',
'auto_push': _('Auto push system user to asset'),
- 'priority': _('High level will be using login asset as default, if user was granted more than 2 system user'),
- }
\ No newline at end of file
+ 'priority': _('High level will be using login asset as default, '
+ 'if user was granted more than 2 system user'),
+ 'login_mode': _('If you choose manual login mode, you do not '
+ 'need to fill in the username and password.')
+ }
diff --git a/apps/assets/hands.py b/apps/assets/hands.py
index ad44052d3..a1a376135 100644
--- a/apps/assets/hands.py
+++ b/apps/assets/hands.py
@@ -14,4 +14,3 @@
from common.mixins import AdminUserRequiredMixin
from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
from users.models import User, UserGroup
-from perms.utils import NodePermissionUtil
diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py
index 5b3009305..7a2b3fe57 100644
--- a/apps/assets/models/asset.py
+++ b/apps/assets/models/asset.py
@@ -5,6 +5,7 @@
import uuid
import logging
import random
+from functools import reduce
from django.db import models
from django.utils.translation import ugettext_lazy as _
@@ -35,6 +36,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 = (
@@ -43,45 +57,89 @@ class Asset(models.Model):
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
+ ('Windows2016', 'Windows(2016)'),
('Other', 'Other'),
)
+
+ SSH_PROTOCOL = 'ssh'
+ RDP_PROTOCOL = 'rdp'
+ TELNET_PROTOCOL = 'telnet'
+ PROTOCOL_CHOICES = (
+ (SSH_PROTOCOL, 'ssh'),
+ (RDP_PROTOCOL, 'rdp'),
+ (TELNET_PROTOCOL, 'telnet (beta)'),
+ )
+
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
- ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
- hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
+ ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'),
+ db_index=True)
+ hostname = models.CharField(max_length=128, unique=True,
+ verbose_name=_('Hostname'))
+ protocol = models.CharField(max_length=128, default=SSH_PROTOCOL,
+ choices=PROTOCOL_CHOICES,
+ verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
- platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
- domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
- nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
+ platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES,
+ default='Linux', verbose_name=_('Platform'))
+ domain = models.ForeignKey("assets.Domain", null=True, blank=True,
+ related_name='assets', verbose_name=_("Domain"),
+ on_delete=models.SET_NULL)
+ nodes = models.ManyToManyField('assets.Node', default=default_node,
+ related_name='assets',
+ verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
# Auth
- admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
+ admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT,
+ null=True, verbose_name=_("Admin user"))
# Some information
- public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
- number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
+ public_ip = models.GenericIPAddressField(max_length=32, blank=True,
+ null=True,
+ verbose_name=_('Public IP'))
+ number = models.CharField(max_length=32, null=True, blank=True,
+ verbose_name=_('Asset number'))
# Collect
- vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
- model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
- sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
+ vendor = models.CharField(max_length=64, null=True, blank=True,
+ verbose_name=_('Vendor'))
+ model = models.CharField(max_length=54, null=True, blank=True,
+ verbose_name=_('Model'))
+ sn = models.CharField(max_length=128, null=True, blank=True,
+ verbose_name=_('Serial number'))
- cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
+ cpu_model = models.CharField(max_length=64, null=True, blank=True,
+ verbose_name=_('CPU model'))
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
- memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
- disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
- disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
+ memory = models.CharField(max_length=64, null=True, blank=True,
+ verbose_name=_('Memory'))
+ disk_total = models.CharField(max_length=1024, null=True, blank=True,
+ verbose_name=_('Disk total'))
+ disk_info = models.CharField(max_length=1024, null=True, blank=True,
+ verbose_name=_('Disk info'))
- os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
- os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
- os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
- hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
+ os = models.CharField(max_length=128, null=True, blank=True,
+ verbose_name=_('OS'))
+ os_version = models.CharField(max_length=16, null=True, blank=True,
+ verbose_name=_('OS version'))
+ os_arch = models.CharField(max_length=16, blank=True, null=True,
+ verbose_name=_('OS arch'))
+ hostname_raw = models.CharField(max_length=128, blank=True, null=True,
+ verbose_name=_('Hostname raw'))
- labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
- created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
- date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
- comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
+ labels = models.ManyToManyField('assets.Label', blank=True,
+ related_name='assets',
+ verbose_name=_("Labels"))
+ created_by = models.CharField(max_length=32, null=True, blank=True,
+ verbose_name=_('Created by'))
+ date_created = models.DateTimeField(auto_now_add=True, null=True,
+ blank=True,
+ verbose_name=_('Date created'))
+ comment = models.TextField(max_length=128, default='', blank=True,
+ verbose_name=_('Comment'))
+
+ objects = AssetManager()
def __str__(self):
return '{0.hostname}({0.ip})'.format(self)
@@ -103,7 +161,17 @@ 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
+
+ def get_all_nodes(self, flat=False):
+ nodes = []
+ for node in self.get_nodes():
+ _nodes = node.get_ancestor(with_self=True)
+ _nodes.append(_nodes)
+ if flat:
+ nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
+ return nodes
@property
def hardware_info(self):
@@ -176,7 +244,8 @@ class Asset(models.Model):
seed()
for i in range(count):
- asset = cls(ip='%s.%s.%s.%s' % (i, i, i, i),
+ ip = [str(i) for i in random.sample(range(255), 4)]
+ asset = cls(ip='.'.join(ip),
hostname=forgery_py.internet.user_name(True),
admin_user=choice(AdminUser.objects.all()),
port=22,
diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py
index 2997b1b61..908e6b647 100644
--- a/apps/assets/models/base.py
+++ b/apps/assets/models/base.py
@@ -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, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
@@ -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,
diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py
index ad806342b..4f4f9ad8b 100644
--- a/apps/assets/models/node.py
+++ b/apps/assets/models/node.py
@@ -2,9 +2,10 @@
#
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 common.utils import with_cache
__all__ = ['Node']
@@ -12,25 +13,40 @@ __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, 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
+ def __eq__(self, other):
+ return self.key == other.key
+
+ def __gt__(self, other):
+ if self.is_root():
+ return True
+ self_key = [int(k) for k in self.key.split(':')]
+ other_key = [int(k) for k in other.key.split(':')]
+ if len(self_key) < len(other_key):
+ return True
+ elif len(self_key) > len(other_key):
+ return False
+ else:
+ return self_key[-1] < other_key[-1]
+
@property
def name(self):
return self.value
@property
def full_value(self):
- if self == self.__class__.root():
+ ancestor = [a.value for a in self.get_ancestor(with_self=True)]
+ if self.is_root():
return self.value
- else:
- return '{} / {}'.format(self.parent.full_value, self.value)
+ return ' / '.join(ancestor)
@property
def level(self):
@@ -43,77 +59,106 @@ class Node(models.Model):
return "{}:{}".format(self.key, mark)
def create_child(self, value):
- child_key = self.get_next_child_key()
- child = self.__class__.objects.create(key=child_key, value=value)
- return child
+ with transaction.atomic():
+ child_key = self.get_next_child_key()
+ child = self.__class__.objects.create(key=child_key, value=value)
+ return child
- def get_children(self):
- return self.__class__.objects.filter(key__regex=r'{}:[0-9]+$'.format(self.key))
+ def get_children(self, with_self=False):
+ pattern = r'^{0}$|^{}:[0-9]+$' if with_self else r'^{}:[0-9]+$'
+ return self.__class__.objects.filter(
+ key__regex=pattern.format(self.key)
+ )
- def get_all_children(self):
- return self.__class__.objects.filter(key__startswith='{}:'.format(self.key))
+ def get_all_children(self, with_self=False):
+ pattern = r'^{0}$|^{0}:' if with_self else r'^{0}'
+ return self.__class__.objects.filter(
+ key__regex=pattern.format(self.key)
+ )
+
+ def get_sibling(self, with_self=False):
+ key = ':'.join(self.key.split(':')[:-1])
+ pattern = r'^{}:[0-9]+$'.format(key)
+ sibling = self.__class__.objects.filter(
+ key__regex=pattern.format(self.key)
+ )
+ if not with_self:
+ sibling = sibling.exclude(key=self.key)
+ return sibling
def get_family(self):
- children = list(self.get_all_children())
- children.append(self)
- return children
+ ancestor = self.get_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 = self.assets.all()
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()
- assets = Asset.objects.filter(nodes__in=nodes).distinct()
+ pattern = r'^{0}$|^{0}:'.format(self.key)
+ assets = Asset.objects.filter(nodes__key__regex=pattern)
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":
+ if self.key == "0" or not self.key.startswith("0"):
return self.__class__.root()
- elif 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):
- self.key = parent.get_next_child_key()
-
- @property
- def ancestor(self):
- if self.parent == self.__class__.root():
- return [self.__class__.root()]
+ if self.is_node:
+ children = self.get_all_children()
+ old_key = self.key
+ with transaction.atomic():
+ self.key = parent.get_next_child_key()
+ for child in children:
+ child.key = child.key.replace(old_key, self.key, 1)
+ child.save()
+ self.save()
else:
- return [self.parent, *tuple(self.parent.ancestor)]
+ self.key = parent.key+':fake'
- @property
- def ancestor_with_node(self):
- ancestor = self.ancestor
- ancestor.insert(0, self)
+ def get_ancestor(self, with_self=False):
+ if self.is_root():
+ ancestor = self.__class__.objects.filter(key='0')
+ return ancestor
+
+ _key = self.key.split(':')
+ if not with_self:
+ _key.pop()
+ ancestor_keys = []
+ for i in range(len(_key)):
+ ancestor_keys.append(':'.join(_key))
+ _key.pop()
+ ancestor = self.__class__.objects.filter(
+ key__in=ancestor_keys
+ ).order_by('key')
return ancestor
@classmethod
@@ -121,4 +166,6 @@ class Node(models.Model):
obj, created = cls.objects.get_or_create(
key='0', defaults={"key": '0', 'value': "ROOT"}
)
+ print(obj)
return obj
+
diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py
index bf31b8491..5faca5da8 100644
--- a/apps/assets/models/user.py
+++ b/apps/assets/models/user.py
@@ -95,9 +95,18 @@ class AdminUser(AssetUser):
class SystemUser(AssetUser):
SSH_PROTOCOL = 'ssh'
RDP_PROTOCOL = 'rdp'
+ TELNET_PROTOCOL = 'telnet'
PROTOCOL_CHOICES = (
(SSH_PROTOCOL, 'ssh'),
(RDP_PROTOCOL, 'rdp'),
+ (TELNET_PROTOCOL, 'telnet (beta)'),
+ )
+
+ AUTO_LOGIN = 'auto'
+ MANUAL_LOGIN = 'manual'
+ LOGIN_MODE_CHOICES = (
+ (AUTO_LOGIN, _('Automatic login')),
+ (MANUAL_LOGIN, _('Manually login'))
)
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
@@ -107,6 +116,7 @@ class SystemUser(AssetUser):
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
+ login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=AUTO_LOGIN, max_length=10, verbose_name=_('Login mode'))
def __str__(self):
return '{0.name}({0.username})'.format(self)
diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py
index 8556be3bf..e63735794 100644
--- a/apps/assets/serializers/asset.py
+++ b/apps/assets/serializers/asset.py
@@ -12,36 +12,10 @@ __all__ = [
]
-class NodeTMPSerializer(serializers.ModelSerializer):
- parent = serializers.SerializerMethodField()
- assets_amount = serializers.SerializerMethodField()
-
- class Meta:
- model = Node
- fields = ['id', 'key', 'value', 'parent', 'assets_amount',
- 'is_asset']
- list_serializer_class = BulkListSerializer
-
- @staticmethod
- def get_parent(obj):
- return obj.parent.id
-
- @staticmethod
- def get_assets_amount(obj):
- return obj.get_all_assets().count()
-
- def get_fields(self):
- fields = super().get_fields()
- field = fields["key"]
- field.required = False
- return fields
-
-
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
"""
资产的数据结构
"""
-
class Meta:
model = Asset
list_serializer_class = BulkListSerializer
@@ -62,14 +36,14 @@ 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",
- "platform", "comment"
+ "is_active", "system_users_join", "os", 'domain',
+ "platform", "comment", "protocol",
)
@staticmethod
diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py
index 1c9c385e9..56e01f742 100644
--- a/apps/assets/serializers/node.py
+++ b/apps/assets/serializers/node.py
@@ -48,16 +48,27 @@ 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
+ return obj.parent.id if obj.is_node else obj.parent_id
@staticmethod
def get_assets_amount(obj):
- return obj.get_all_assets().count()
+ return obj.get_all_assets().count() if obj.is_node else 0
def get_fields(self):
fields = super().get_fields()
diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py
index 7abd09d29..7a4e3aadc 100644
--- a/apps/assets/serializers/system_user.py
+++ b/apps/assets/serializers/system_user.py
@@ -18,6 +18,13 @@ class SystemUserSerializer(serializers.ModelSerializer):
model = SystemUser
exclude = ('_password', '_private_key', '_public_key')
+ def get_field_names(self, declared_fields, info):
+ fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
+ fields.extend([
+ 'get_login_mode_display',
+ ])
+ return fields
+
@staticmethod
def get_unreachable_assets(obj):
return obj.unreachable_assets
@@ -56,7 +63,10 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
"""
class Meta:
model = SystemUser
- fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',)
+ fields = (
+ 'id', 'name', 'username', 'priority',
+ 'protocol', 'comment', 'login_mode'
+ )
class SystemUserSimpleSerializer(serializers.ModelSerializer):
diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py
index 06cd9f63e..157c88012 100644
--- a/apps/assets/signals_handler.py
+++ b/apps/assets/signals_handler.py
@@ -63,22 +63,26 @@ def on_system_user_assets_change(sender, instance=None, **kwargs):
@receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_node_changed(sender, instance=None, **kwargs):
- if isinstance(instance, Asset) and kwargs['action'] == 'post_add':
- logger.debug("Asset node change signal received")
- nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
- system_users_assets = defaultdict(set)
- system_users = SystemUser.objects.filter(nodes__in=nodes)
- for system_user in system_users:
- system_users_assets[system_user].update({instance})
- for system_user, assets in system_users_assets.items():
- system_user.assets.add(*tuple(assets))
+ if isinstance(instance, Asset):
+ if kwargs['action'] == 'post_add':
+ logger.debug("Asset node change signal received")
+ nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
+ system_users_assets = defaultdict(set)
+ system_users = SystemUser.objects.filter(nodes__in=nodes)
+ # 清理节点缓存
+ for system_user in system_users:
+ system_users_assets[system_user].update({instance})
+ for system_user, assets in system_users_assets.items():
+ system_user.assets.add(*tuple(assets))
@receiver(m2m_changed, sender=Asset.nodes.through)
def on_node_assets_changed(sender, instance=None, **kwargs):
- if isinstance(instance, Node) and kwargs['action'] == 'post_add':
- logger.debug("Node assets change signal received")
+ if isinstance(instance, Node):
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
- system_users = SystemUser.objects.filter(nodes=instance)
- for system_user in system_users:
- system_user.assets.add(*tuple(assets))
+ if kwargs['action'] == 'post_add':
+ logger.debug("Node assets change signal received")
+ # 重新关联系统用户和资产的关系
+ system_users = SystemUser.objects.filter(nodes=instance)
+ for system_user in system_users:
+ system_user.assets.add(*tuple(assets))
diff --git a/apps/assets/tasks.py b/apps/assets/tasks.py
index dd660bc42..f1773df11 100644
--- a/apps/assets/tasks.py
+++ b/apps/assets/tasks.py
@@ -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
diff --git a/apps/assets/templates/assets/_asset_list_modal.html b/apps/assets/templates/assets/_asset_list_modal.html
index c5cd48857..faf569137 100644
--- a/apps/assets/templates/assets/_asset_list_modal.html
+++ b/apps/assets/templates/assets/_asset_list_modal.html
@@ -59,7 +59,7 @@ var zTree2, asset_table2 = 0;
function initTable2() {
var options = {
ele: $('#asset_list_modal_table'),
- ajax_url: '{% url "api-assets:asset-list" %}',
+ ajax_url: '{% url "api-assets:asset-list" %}?show_current_asset=1',
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }
],
@@ -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'];
});
diff --git a/apps/assets/templates/assets/_system_user.html b/apps/assets/templates/assets/_system_user.html
index 528e271e6..4e1bc51a8 100644
--- a/apps/assets/templates/assets/_system_user.html
+++ b/apps/assets/templates/assets/_system_user.html
@@ -36,12 +36,13 @@
{% endif %}
{% trans 'Basic' %}
{% bootstrap_field form.name layout="horizontal" %}
+ {% bootstrap_field form.login_mode layout="horizontal" %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.priority layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
+ {% trans 'Auth' %}
{% block auth %}
- {% trans 'Auth' %}
@@ -55,7 +56,7 @@
{% bootstrap_field form.private_key_file layout="horizontal" %}
{% endblock %}
{% block custom_foot_js %}
-
+ }
+ else if($(login_mode_id).val() === 'auto'){
+ $('#auth_title_id').removeClass('hidden');
+ $(password_id).closest('.form-group').removeClass('hidden')
+ protocolChange();
+ }
+}
+
+$(document).ready(function () {
+ $('.select2').select2();
+ authFieldsDisplay();
+ protocolChange();
+ loginModeChange();
+})
+.on('change', protocol_id, function(){
+ protocolChange();
+})
+.on('change', auto_generate_key, function(){
+ authFieldsDisplay();
+})
+.on('change', login_mode_id, function(){
+ loginModeChange();
+})
+
+
{% endblock %}
\ No newline at end of file
diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html
index 80ff0cd5f..31314392f 100644
--- a/apps/assets/templates/assets/admin_user_assets.html
+++ b/apps/assets/templates/assets/admin_user_assets.html
@@ -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,
diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html
index bcb40fb29..15a870800 100644
--- a/apps/assets/templates/assets/admin_user_list.html
+++ b/apps/assets/templates/assets/admin_user_list.html
@@ -5,7 +5,7 @@
{% block help_message %}
- 管理用户是服务器的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
+ 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
Windows或其它硬件可以随意设置一个
{% endblock %}
@@ -107,6 +107,3 @@ $(document).ready(function(){
});
{% endblock %}
-
-
-
diff --git a/apps/assets/templates/assets/asset_create.html b/apps/assets/templates/assets/asset_create.html
index 99703d2e3..55e233d0d 100644
--- a/apps/assets/templates/assets/asset_create.html
+++ b/apps/assets/templates/assets/asset_create.html
@@ -17,6 +17,7 @@
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
+ {% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.domain layout="horizontal" %}
@@ -85,14 +86,14 @@ $(document).ready(function () {
allowClear: true,
templateSelection: format
});
- $("#id_platform").change(function (){
- var platform = $("#id_platform option:selected").text();
+ $("#id_protocol").change(function (){
+ var protocol = $("#id_protocol option:selected").text();
var port = 22;
- if(platform === 'Windows'){
+ if(protocol === 'rdp'){
port = 3389;
}
- if(platform === 'Other'){
- port = null;
+ if(protocol === 'telnet (beta)'){
+ port = 23;
}
$("#id_port").val(port);
});
diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html
index 0b1b2865a..b07a7c348 100644
--- a/apps/assets/templates/assets/asset_detail.html
+++ b/apps/assets/templates/assets/asset_detail.html
@@ -190,7 +190,7 @@
|
@@ -204,7 +204,7 @@
{% for node in asset.nodes.all %}
- {{ node.name }} |
+ {{ node }} |
|
diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html
index 04ba11e57..b5e53aaba 100644
--- a/apps/assets/templates/assets/asset_list.html
+++ b/apps/assets/templates/assets/asset_list.html
@@ -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 @@
@@ -87,7 +87,7 @@
{% trans 'IP' %} |
{% trans 'Hardware' %} |
{% trans 'Active' %} |
- {% trans 'Reachable' %} |
+{# {% trans 'Reachable' %} | #}
{% trans 'Action' %} |
@@ -127,6 +127,9 @@
+
+
{% trans 'Display only current node assets' %}
+
{% trans 'Displays all child node assets' %}
@@ -157,26 +160,35 @@ function initTable() {
$(td).html('')
}
}},
- {targets: 5, createdCell: function (td, cellData) {
- if (cellData === 'Unknown'){
- $(td).html('')
- } else if (!cellData) {
- $(td).html('')
- } else {
- $(td).html('')
- }
- }},
- {targets: 6, createdCell: function (td, cellData, rowData) {
+
+ {#{targets: 5, createdCell: function (td, cellData) {#}
+ {# if (cellData === 'Unknown'){#}
+ {# $(td).html('')#}
+ {# } else if (!cellData) {#}
+ {# $(td).html('')#}
+ {# } else {#}
+ {# $(td).html('')#}
+ {# }#}
+ {# }},#}
+
+ {targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '{% trans "Update" %}'.replace("{{ DEFAULT_PK }}", cellData);
var del_btn = '{% trans "Delete" %}'.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();
@@ -385,9 +402,9 @@ function initTree() {
$.get("{% url 'api-assets:node-list' %}", 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 +434,13 @@ function toggle() {
$(document).ready(function(){
initTable();
initTree();
+
+ if(getCookie('show_current_asset') === '1'){
+ $('#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 +559,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', '1');
+ location.reload();
+})
+.on('click', '.btn-show-all-asset', function(){
+ hideRMenu();
+ $(this).css('display', 'none');
+ $('#show_current_asset').css('display', 'inline-block');
+ setCookie('show_current_asset', '');
+ location.reload();
+})
.on('click', '.btn_asset_delete', function () {
var $this = $(this);
var $data_table = $("#asset_list_table").DataTable();
diff --git a/apps/assets/templates/assets/asset_update.html b/apps/assets/templates/assets/asset_update.html
index 3d42ca2b5..7ed1da05a 100644
--- a/apps/assets/templates/assets/asset_update.html
+++ b/apps/assets/templates/assets/asset_update.html
@@ -21,6 +21,7 @@
{% trans 'Basic' %}
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
+ {% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
diff --git a/apps/assets/templates/assets/domain_gateway_list.html b/apps/assets/templates/assets/domain_gateway_list.html
index 581f6c08a..c2d5528ee 100644
--- a/apps/assets/templates/assets/domain_gateway_list.html
+++ b/apps/assets/templates/assets/domain_gateway_list.html
@@ -85,6 +85,9 @@ function initTable() {
var update_btn = '{% trans "Update" %}'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '{% trans "Delete" %}'.replace('{{ DEFAULT_PK }}', cellData);
var test_btn = '{% trans "Test connection" %}'.replace('{{ DEFAULT_PK }}', cellData);
+ if(rowData.protocol === 'rdp'){
+ test_btn = '{% trans "Test connection" %}'.replace('{{ DEFAULT_PK }}', cellData);
+ }
$(td).html(update_btn + test_btn + del_btn)
}}
],
@@ -120,7 +123,6 @@ $(document).ready(function(){
success_message: "可连接",
fail_message: "连接失败"
})
-
-})
+});
{% endblock %}
diff --git a/apps/assets/templates/assets/domain_list.html b/apps/assets/templates/assets/domain_list.html
index 926c4bbc3..a4042d57e 100644
--- a/apps/assets/templates/assets/domain_list.html
+++ b/apps/assets/templates/assets/domain_list.html
@@ -1,6 +1,14 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}{% endblock %}
+
+{% block help_message %}
+
+ 网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登
+录。
+
+{% endblock %}
+
{% block table_container %}
{% trans "Create domain" %}
@@ -69,6 +77,3 @@ $(document).ready(function(){
});
{% endblock %}
-
-
-
diff --git a/apps/assets/templates/assets/gateway_create_update.html b/apps/assets/templates/assets/gateway_create_update.html
index 7d6800c41..7c1bf14b2 100644
--- a/apps/assets/templates/assets/gateway_create_update.html
+++ b/apps/assets/templates/assets/gateway_create_update.html
@@ -66,3 +66,28 @@
{% endblock %}
+
+{% block custom_foot_js %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/apps/assets/templates/assets/system_user_detail.html b/apps/assets/templates/assets/system_user_detail.html
index a02bf1e44..f0ed6030c 100644
--- a/apps/assets/templates/assets/system_user_detail.html
+++ b/apps/assets/templates/assets/system_user_detail.html
@@ -63,15 +63,19 @@
{{ system_user.username }} |
- {% trans 'Protocol' %}: |
- {{ system_user.protocol }} |
+ {% trans 'Login mode' %}: |
+ {{ system_user.get_login_mode_display }} |
+ {% trans 'Protocol' %}: |
+ {{ system_user.protocol }} |
+
+
{% trans 'Sudo' %}: |
{{ system_user.sudo }} |
{% if system_user.shell %}
-
+
{% trans 'Shell' %}: |
{{ system_user.shell }} |
@@ -107,14 +111,14 @@
-
+
{% trans 'Quick update' %}
-
+
{% trans 'Auto push' %}: |
@@ -130,8 +134,8 @@
|
-
{% if system_user.auto_push %}
+
{% trans 'Push system user now' %}: |
@@ -139,8 +143,8 @@
|
-
{% endif %}
+
{% trans 'Test assets connective' %}: |
@@ -149,6 +153,15 @@
|
+
+ {% trans 'Clear auth' %}: |
+
+
+
+
+ |
+
+
{# #}
{# {% trans 'Change auth period' %}: | #}
{# #}
@@ -236,6 +249,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 +313,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 +335,25 @@ $(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 %}';
+ var name = '{{ system_user.name }}';
+ swal({
+ title: '你确定清除该系统用户的认证信息吗 ?',
+ text: " [" + name + "] ",
+ type: "warning",
+ showCancelButton: true,
+ cancelButtonText: '取消',
+ confirmButtonColor: "#ed5565",
+ confirmButtonText: '确认',
+ closeOnConfirm: true
+ }, function () {
+ APIUpdateAttr({
+ url: the_url,
+ method: 'DELETE',
+ success_message: "{% trans 'Clear auth' %}" + " {% trans 'success' %}"
+ });
+ });
})
{% endblock %}
diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html
index 3daa47a81..3fa887df6 100644
--- a/apps/assets/templates/assets/system_user_list.html
+++ b/apps/assets/templates/assets/system_user_list.html
@@ -26,6 +26,7 @@
| {% trans 'Name' %} |
{% trans 'Username' %} |
{% trans 'Protocol' %} |
+ {% trans 'Login mode' %} |
{% trans 'Asset' %} |
{% trans 'Reachable' %} |
{% trans 'Unreachable' %} |
@@ -48,7 +49,7 @@ function initTable() {
var detail_btn = '' + cellData + '';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
- {targets: 5, createdCell: function (td, cellData) {
+ {targets: 6, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData !== 0) {
innerHtml = "" + cellData + "";
@@ -57,7 +58,7 @@ function initTable() {
}
$(td).html('' + innerHtml + '');
}},
- {targets: 6, createdCell: function (td, cellData) {
+ {targets: 7, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData !== 0) {
innerHtml = "" + cellData + "";
@@ -66,7 +67,7 @@ function initTable() {
}
$(td).html('' + innerHtml + '');
}},
- {targets: 7, createdCell: function (td, cellData, rowData) {
+ {targets: 8, createdCell: function (td, cellData, rowData) {
var val = 0;
var innerHtml = "";
var total = rowData.assets_amount;
@@ -84,14 +85,14 @@ function initTable() {
$(td).html('' + innerHtml + '');
}},
- {targets: 9, createdCell: function (td, cellData, rowData) {
+ {targets: 10, createdCell: function (td, cellData, rowData) {
var update_btn = '{% trans "Update" %}'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '{% trans "Delete" %}'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}],
ajax_url: '{% url "api-assets:system-user-list" %}',
columns: [
- {data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "assets_amount" },
+ {data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "get_login_mode_display"}, {data: "assets_amount" },
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }
],
op_html: $('#actions').html()
diff --git a/apps/assets/templates/assets/system_user_update.html b/apps/assets/templates/assets/system_user_update.html
index 46ef8d6a3..977c3ac31 100644
--- a/apps/assets/templates/assets/system_user_update.html
+++ b/apps/assets/templates/assets/system_user_update.html
@@ -4,7 +4,6 @@
{% load bootstrap3 %}
{% block auth %}
- {% trans 'Auth' %}
{% bootstrap_field form.password layout="horizontal" %}
{% bootstrap_field form.private_key_file layout="horizontal" %}
@@ -15,10 +14,3 @@
{% endblock %}
-{% block custom_foot_js %}
-
-{% endblock %}
\ No newline at end of file
diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py
index 4429d0f24..cf55d08ba 100644
--- a/apps/assets/urls/api_urls.py
+++ b/apps/assets/urls/api_urls.py
@@ -23,8 +23,8 @@ urlpatterns = [
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
url(r'^v1/assets/(?P[0-9a-zA-Z\-]{36})/alive/$',
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
- url(r'^v1/assets/user-assets/$',
- api.UserAssetListView.as_view(), name='user-asset-list'),
+ url(r'^v1/assets/(?P[0-9a-zA-Z\-]{36})/gateway/$',
+ api.AssetGatewayApi.as_view(), name='asset-gateway'),
url(r'^v1/admin-user/(?P[0-9a-zA-Z\-]{36})/nodes/$',
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
url(r'^v1/admin-user/(?P[0-9a-zA-Z\-]{36})/auth/$',
@@ -35,17 +35,26 @@ urlpatterns = [
api.SystemUserPushApi.as_view(), name='system-user-push'),
url(r'^v1/system-user/(?P[0-9a-zA-Z\-]{36})/connective/$',
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
- url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/children/$', api.NodeChildrenApi.as_view(), name='node-children'),
+ url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/children/$',
+ api.NodeChildrenApi.as_view(), name='node-children'),
url(r'^v1/nodes/children/$', api.NodeChildrenApi.as_view(), name='node-children-2'),
- url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/children/add/$', api.NodeAddChildrenApi.as_view(), name='node-add-children'),
- url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/assets/$', api.NodeAssetsApi.as_view(), name='node-assets'),
- url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/assets/add/$', api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
- url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/assets/replace/$', api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'),
- url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/assets/remove/$', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
- url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/refresh-hardware-info/$', api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
- url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/test-connective/$', api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
+ url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/children/add/$',
+ api.NodeAddChildrenApi.as_view(), name='node-add-children'),
+ url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/assets/$',
+ api.NodeAssetsApi.as_view(), name='node-assets'),
+ url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/assets/add/$',
+ api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
+ url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/assets/replace/$',
+ api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'),
+ url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/assets/remove/$',
+ api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
+ url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/refresh-hardware-info/$',
+ api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
+ url(r'^v1/nodes/(?P[0-9a-zA-Z\-]{36})/test-connective/$',
+ api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
- url(r'^v1/gateway/(?P[0-9a-zA-Z\-]{36})/test-connective/$', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
+ url(r'^v1/gateway/(?P[0-9a-zA-Z\-]{36})/test-connective/$',
+ api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
]
urlpatterns += router.urls
diff --git a/apps/assets/utils.py b/apps/assets/utils.py
index 5fb5eae84..f841e4a79 100644
--- a/apps/assets/utils.py
+++ b/apps/assets/utils.py
@@ -1,7 +1,8 @@
# ~*~ coding: utf-8 ~*~
#
-
+import os
import paramiko
+from paramiko.ssh_exception import SSHException
from common.utils import get_object_or_none
from .models import Asset, SystemUser, Label
@@ -49,22 +50,23 @@ def test_gateway_connectability(gateway):
"""
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-
- proxy_command = [
- "ssh", "{}@{}".format(gateway.username, gateway.ip),
- "-p", str(gateway.port), "-W", "127.0.0.1:{}".format(gateway.port),
- ]
-
- if gateway.password:
- proxy_command.insert(0, "sshpass -p '{}'".format(gateway.password))
- if gateway.private_key:
- proxy_command.append("-i {}".format(gateway.private_key_file))
+ proxy = paramiko.SSHClient()
+ proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
- sock = paramiko.ProxyCommand(" ".join(proxy_command))
- except paramiko.ProxyCommandFailure as e:
+ proxy.connect(gateway.ip, gateway.port,
+ username=gateway.username,
+ password=gateway.password,
+ pkey=gateway.private_key_obj)
+ except(paramiko.AuthenticationException,
+ paramiko.BadAuthenticationType,
+ SSHException) as e:
return False, str(e)
+ sock = proxy.get_transport().open_channel(
+ 'direct-tcpip', ('127.0.0.1', gateway.port), ('127.0.0.1', 0)
+ )
+
try:
client.connect("127.0.0.1", port=gateway.port,
username=gateway.username,
diff --git a/apps/assets/views/domain.py b/apps/assets/views/domain.py
index e6a353373..be6528219 100644
--- a/apps/assets/views/domain.py
+++ b/apps/assets/views/domain.py
@@ -140,11 +140,6 @@ class DomainGatewayUpdateView(AdminUserRequiredMixin, UpdateView):
domain = self.object.domain
return reverse('assets:domain-gateway-list', kwargs={"pk": domain.id})
- def form_valid(self, form):
- response = super().form_valid(form)
- print(form.cleaned_data)
- return response
-
def get_context_data(self, **kwargs):
context = {
'app': _('Assets'),
diff --git a/apps/common/api.py b/apps/common/api.py
index 4b74b6b0d..63ed9723d 100644
--- a/apps/common/api.py
+++ b/apps/common/api.py
@@ -21,23 +21,13 @@ class MailTestingAPI(APIView):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
email_host_user = serializer.validated_data["EMAIL_HOST_USER"]
- kwargs = {
- "host": serializer.validated_data["EMAIL_HOST"],
- "port": serializer.validated_data["EMAIL_PORT"],
- "username": serializer.validated_data["EMAIL_HOST_USER"],
- "password": serializer.validated_data["EMAIL_HOST_PASSWORD"],
- "use_ssl": serializer.validated_data["EMAIL_USE_SSL"],
- "use_tls": serializer.validated_data["EMAIL_USE_TLS"]
- }
- connection = get_connection(timeout=5, **kwargs)
+ for k, v in serializer.validated_data.items():
+ if k.startswith('EMAIL'):
+ setattr(settings, k, v)
try:
- connection.open()
- except Exception as e:
- return Response({"error": str(e)}, status=401)
-
- try:
- send_mail("Test", "Test smtp setting", email_host_user,
- [email_host_user], connection=connection)
+ subject = "Test"
+ message = "Test smtp setting"
+ send_mail(subject, message, email_host_user, [email_host_user])
except Exception as e:
return Response({"error": str(e)}, status=401)
@@ -96,14 +86,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')
diff --git a/apps/common/forms.py b/apps/common/forms.py
index 02af6e47a..a11420498 100644
--- a/apps/common/forms.py
+++ b/apps/common/forms.py
@@ -168,3 +168,63 @@ class TerminalSettingForm(BaseForm):
)
)
+
+class SecuritySettingForm(BaseForm):
+ # MFA global setting
+ SECURITY_MFA_AUTH = forms.BooleanField(
+ initial=False, required=False,
+ label=_("MFA Secondary certification"),
+ help_text=_(
+ 'After opening, the user login must use MFA secondary '
+ 'authentication (valid for all users, including administrators)'
+ )
+ )
+ # limit login count
+ SECURITY_LOGIN_LIMIT_COUNT = forms.IntegerField(
+ initial=3, min_value=3,
+ label=_("Limit the number of login failures")
+ )
+ # limit login time
+ SECURITY_LOGIN_LIMIT_TIME = forms.IntegerField(
+ initial=30, min_value=5,
+ label=_("No logon interval"),
+ help_text=_(
+ "Tip :(unit/minute) if the user has failed to log in for a limited "
+ "number of times, no login is allowed during this time interval."
+ )
+ )
+ # min length
+ SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField(
+ initial=6, label=_("Password minimum length"),
+ min_value=6
+ )
+ # upper case
+ SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField(
+
+ initial=False, required=False,
+ label=_("Must contain capital letters"),
+ help_text=_(
+ 'After opening, the user password changes '
+ 'and resets must contain uppercase letters')
+ )
+ # lower case
+ SECURITY_PASSWORD_LOWER_CASE = forms.BooleanField(
+ initial=False, required=False,
+ label=_("Must contain lowercase letters"),
+ help_text=_('After opening, the user password changes '
+ 'and resets must contain lowercase letters')
+ )
+ # number
+ SECURITY_PASSWORD_NUMBER = forms.BooleanField(
+ initial=False, required=False,
+ label=_("Must contain numeric characters"),
+ help_text=_('After opening, the user password changes '
+ 'and resets must contain numeric characters')
+ )
+ # special char
+ SECURITY_PASSWORD_SPECIAL_CHAR= forms.BooleanField(
+ initial=False, required=False,
+ label=_("Must contain special characters"),
+ help_text=_('After opening, the user password changes '
+ 'and resets must contain special characters')
+ )
diff --git a/apps/common/signals_handler.py b/apps/common/signals_handler.py
index df2ea5a5e..bff3a2193 100644
--- a/apps/common/signals_handler.py
+++ b/apps/common/signals_handler.py
@@ -34,7 +34,7 @@ def refresh_all_settings_on_django_ready(sender, **kwargs):
def ldap_auth_on_changed(sender, enabled=True, **kwargs):
if enabled:
logger.debug("Enable LDAP auth")
- if settings.AUTH_LDAP_BACKEND not in settings.AUTH_LDAP_BACKEND:
+ if settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND)
else:
diff --git a/apps/common/tasks.py b/apps/common/tasks.py
index dec738921..bfb005511 100644
--- a/apps/common/tasks.py
+++ b/apps/common/tasks.py
@@ -2,6 +2,7 @@ from django.core.mail import send_mail
from django.conf import settings
from celery import shared_task
from .utils import get_logger
+from .models import Setting
logger = get_logger(__file__)
@@ -21,6 +22,10 @@ def send_mail_async(*args, **kwargs):
Example:
send_mail_sync.delay(subject, message, recipient_list, fail_silently=False, html_message=None)
"""
+ configs = Setting.objects.filter(name__startswith='EMAIL')
+ for config in configs:
+ setattr(settings, config.name, config.cleaned_value)
+
if len(args) == 3:
args = list(args)
args[0] = settings.EMAIL_SUBJECT_PREFIX + args[0]
diff --git a/apps/common/templates/common/basic_setting.html b/apps/common/templates/common/basic_setting.html
index 496eca977..9c9258e33 100644
--- a/apps/common/templates/common/basic_setting.html
+++ b/apps/common/templates/common/basic_setting.html
@@ -23,6 +23,9 @@
{% trans 'Terminal setting' %}
+
+ {% trans 'Security setting' %}
+
diff --git a/apps/common/templates/common/email_setting.html b/apps/common/templates/common/email_setting.html
index 1fd772db1..2f0951e00 100644
--- a/apps/common/templates/common/email_setting.html
+++ b/apps/common/templates/common/email_setting.html
@@ -23,6 +23,9 @@
{% trans 'Terminal setting' %}
+
+ {% trans 'Security setting' %}
+
diff --git a/apps/common/templates/common/ldap_setting.html b/apps/common/templates/common/ldap_setting.html
index f0569f873..e55da5a8f 100644
--- a/apps/common/templates/common/ldap_setting.html
+++ b/apps/common/templates/common/ldap_setting.html
@@ -23,6 +23,9 @@
{% trans 'Terminal setting' %}
+
+ {% trans 'Security setting' %}
+
diff --git a/apps/common/templates/common/security_setting.html b/apps/common/templates/common/security_setting.html
new file mode 100644
index 000000000..08d978d23
--- /dev/null
+++ b/apps/common/templates/common/security_setting.html
@@ -0,0 +1,87 @@
+{% extends 'base.html' %}
+{% load static %}
+{% load bootstrap3 %}
+{% load i18n %}
+{% load common_tags %}
+
+{% block content %}
+
+
+{% endblock %}
+{% block custom_foot_js %}
+
+{% endblock %}
diff --git a/apps/common/templates/common/terminal_setting.html b/apps/common/templates/common/terminal_setting.html
index 16927c05a..320f628b0 100644
--- a/apps/common/templates/common/terminal_setting.html
+++ b/apps/common/templates/common/terminal_setting.html
@@ -27,6 +27,9 @@
{% trans 'Terminal setting' %}
+
+ {% trans 'Security setting' %}
+
@@ -39,6 +42,7 @@
{% endif %}
{% csrf_token %}
+
{% trans "Basic setting" %}
{% for field in form %}
{% if not field.field|is_bool_field %}
@@ -60,6 +64,7 @@
{% endfor %}
+
{% trans "Command storage" %}
diff --git a/apps/common/urls/view_urls.py b/apps/common/urls/view_urls.py
index 466f7c49c..e7ccddd06 100644
--- a/apps/common/urls/view_urls.py
+++ b/apps/common/urls/view_urls.py
@@ -11,4 +11,5 @@ urlpatterns = [
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'),
url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'),
+ url(r'^security/$', views.SecuritySettingView.as_view(), name='security-setting'),
]
diff --git a/apps/common/utils.py b/apps/common/utils.py
index d73c094ec..deaeb5280 100644
--- a/apps/common/utils.py
+++ b/apps/common/utils.py
@@ -16,6 +16,7 @@ import calendar
import threading
from io import StringIO
import uuid
+from functools import wraps
import paramiko
import sshpubkeys
@@ -395,3 +396,17 @@ class TeeObj:
def close(self):
self.file_obj.close()
+
+def with_cache(func):
+ cache = {}
+ key = "_{}.{}".format(func.__module__, func.__name__)
+
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ cached = cache.get(key)
+ if cached:
+ return cached
+ res = func(*args, **kwargs)
+ cache[key] = res
+ return res
+ return wrapper
diff --git a/apps/common/validators.py b/apps/common/validators.py
new file mode 100644
index 000000000..b273bd1de
--- /dev/null
+++ b/apps/common/validators.py
@@ -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'))
\ No newline at end of file
diff --git a/apps/common/views.py b/apps/common/views.py
index ee7a2225f..6a7d37f49 100644
--- a/apps/common/views.py
+++ b/apps/common/views.py
@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _
from django.conf import settings
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
- TerminalSettingForm
+ TerminalSettingForm, SecuritySettingForm
from .mixins import AdminUserRequiredMixin
from .signals import ldap_auth_enable
@@ -82,7 +82,7 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
if form.is_valid():
form.save()
if "AUTH_LDAP" in form.cleaned_data:
- ldap_auth_enable.send(form.cleaned_data["AUTH_LDAP"])
+ ldap_auth_enable.send(sender=self.__class__, enabled=form.cleaned_data["AUTH_LDAP"])
msg = _("Update setting successfully, please restart program")
messages.success(request, msg)
return redirect('settings:ldap-setting')
@@ -122,3 +122,27 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView):
return render(request, self.template_name, context)
+class SecuritySettingView(AdminUserRequiredMixin, TemplateView):
+ form_class = SecuritySettingForm
+ template_name = "common/security_setting.html"
+
+ def get_context_data(self, **kwargs):
+ context = {
+ 'app': _('Settings'),
+ 'action': _('Security setting'),
+ 'form': self.form_class(),
+ }
+ kwargs.update(context)
+ return super().get_context_data(**kwargs)
+
+ def post(self, request):
+ form = self.form_class(request.POST)
+ if form.is_valid():
+ form.save()
+ msg = _("Update setting successfully, please restart program")
+ messages.success(request, msg)
+ return redirect('settings:security-setting')
+ else:
+ context = self.get_context_data()
+ context.update({"form": form})
+ return render(request, self.template_name, context)
diff --git a/apps/i18n/zh/LC_MESSAGES/django.mo b/apps/i18n/zh/LC_MESSAGES/django.mo
index ebfffbfe7..78d56177e 100644
Binary files a/apps/i18n/zh/LC_MESSAGES/django.mo and b/apps/i18n/zh/LC_MESSAGES/django.mo differ
diff --git a/apps/i18n/zh/LC_MESSAGES/django.po b/apps/i18n/zh/LC_MESSAGES/django.po
index 627e44ed6..0cf576ebe 100644
--- a/apps/i18n/zh/LC_MESSAGES/django.po
+++ b/apps/i18n/zh/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-04-23 19:51+0800\n"
+"POT-Creation-Date: 2018-07-06 13:11+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler \n"
"Language-Team: Jumpserver team\n"
@@ -17,51 +17,51 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: assets/api/node.py:88
+#: assets/api/node.py:99
msgid "New node {}"
msgstr "新节点 {}"
-#: assets/api/node.py:216
+#: assets/api/node.py:234
msgid "更新节点资产硬件信息: {}"
msgstr ""
-#: assets/api/node.py:229
+#: assets/api/node.py:247
msgid "测试节点下资产是否可连接: {}"
msgstr ""
-#: assets/forms/asset.py:24 assets/models/asset.py:54 assets/models/user.py:103
+#: assets/forms/asset.py:24 assets/models/asset.py:89 assets/models/user.py:112
#: assets/templates/assets/asset_detail.html:183
#: assets/templates/assets/asset_detail.html:191
-#: assets/templates/assets/system_user_detail.html:166 perms/models.py:23
+#: assets/templates/assets/system_user_detail.html:179 perms/models.py:33
msgid "Nodes"
msgstr "节点管理"
#: assets/forms/asset.py:27 assets/forms/asset.py:66 assets/forms/asset.py:109
-#: assets/forms/asset.py:113 assets/models/asset.py:58
+#: assets/forms/asset.py:113 assets/models/asset.py:94
#: assets/models/cluster.py:19 assets/models/user.py:72
#: assets/templates/assets/asset_detail.html:73 templates/_nav.html:25
msgid "Admin user"
msgstr "管理用户"
#: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:125
-#: assets/templates/assets/asset_create.html:35
-#: assets/templates/assets/asset_create.html:37
+#: assets/templates/assets/asset_create.html:36
+#: assets/templates/assets/asset_create.html:38
#: assets/templates/assets/asset_list.html:75
-#: assets/templates/assets/asset_update.html:40
-#: assets/templates/assets/asset_update.html:42
+#: assets/templates/assets/asset_update.html:41
+#: assets/templates/assets/asset_update.html:43
#: assets/templates/assets/user_asset_list.html:34
msgid "Label"
msgstr "标签"
-#: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:53
+#: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:85
#: assets/models/domain.py:46
msgid "Domain"
msgstr "网域"
#: assets/forms/asset.py:38 assets/forms/asset.py:63 assets/forms/asset.py:77
-#: assets/forms/asset.py:128 assets/templates/assets/asset_create.html:29
-#: assets/templates/assets/asset_update.html:34 perms/forms.py:40
-#: perms/forms.py:47 perms/models.py:67
+#: assets/forms/asset.py:128 assets/templates/assets/asset_create.html:30
+#: assets/templates/assets/asset_update.html:35 perms/forms.py:40
+#: perms/forms.py:47 perms/models.py:76
#: perms/templates/perms/asset_permission_list.html:57
#: perms/templates/perms/asset_permission_list.html:142
msgid "Node"
@@ -90,7 +90,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,
msgid "Select assets"
msgstr "选择资产"
-#: assets/forms/asset.py:105 assets/models/asset.py:51
+#: assets/forms/asset.py:105 assets/models/asset.py:81
#: assets/models/domain.py:44 assets/templates/assets/admin_user_assets.html:53
#: assets/templates/assets/asset_detail.html:69
#: assets/templates/assets/domain_gateway_list.html:58
@@ -99,18 +99,18 @@ msgid "Port"
msgstr "端口"
#: assets/forms/domain.py:14 assets/forms/label.py:13
-#: assets/models/asset.py:169 assets/templates/assets/admin_user_list.html:25
+#: assets/models/asset.py:237 assets/templates/assets/admin_user_list.html:25
#: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:15
#: assets/templates/assets/label_list.html:16
-#: assets/templates/assets/system_user_list.html:29 audits/models.py:11
+#: assets/templates/assets/system_user_list.html:30 audits/models.py:11
#: audits/templates/audits/ftp_log_list.html:41
#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:37
-#: perms/models.py:22
+#: perms/models.py:32
#: perms/templates/perms/asset_permission_create_update.html:40
#: perms/templates/perms/asset_permission_list.html:56
#: perms/templates/perms/asset_permission_list.html:139
-#: terminal/backends/command/models.py:11 terminal/models.py:123
+#: terminal/backends/command/models.py:11 terminal/models.py:127
#: terminal/templates/terminal/command_list.html:40
#: terminal/templates/terminal/command_list.html:73
#: terminal/templates/terminal/session_list.html:41
@@ -118,8 +118,8 @@ msgstr "端口"
msgid "Asset"
msgstr "资产"
-#: assets/forms/domain.py:54 assets/forms/user.py:79 assets/forms/user.py:120
-#: assets/models/base.py:20 assets/models/cluster.py:18
+#: assets/forms/domain.py:54 assets/forms/user.py:79 assets/forms/user.py:139
+#: assets/models/base.py:21 assets/models/cluster.py:18
#: assets/models/domain.py:17 assets/models/group.py:20
#: assets/models/label.py:17 assets/templates/assets/admin_user_detail.html:56
#: assets/templates/assets/admin_user_list.html:23
@@ -129,15 +129,15 @@ msgstr "资产"
#: assets/templates/assets/label_list.html:14
#: assets/templates/assets/system_user_detail.html:58
#: assets/templates/assets/system_user_list.html:26 common/models.py:26
-#: common/templates/common/terminal_setting.html:67
-#: common/templates/common/terminal_setting.html:85 ops/models/adhoc.py:36
+#: common/templates/common/terminal_setting.html:72
+#: common/templates/common/terminal_setting.html:90 ops/models/adhoc.py:36
#: ops/templates/ops/task_detail.html:59 ops/templates/ops/task_list.html:35
-#: perms/models.py:19 perms/templates/perms/asset_permission_detail.html:62
+#: perms/models.py:29 perms/templates/perms/asset_permission_detail.html:62
#: perms/templates/perms/asset_permission_list.html:53
#: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:16
-#: terminal/models.py:149 terminal/templates/terminal/terminal_detail.html:43
-#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
-#: users/models/user.py:42 users/templates/users/_select_user_modal.html:13
+#: terminal/models.py:154 terminal/templates/terminal/terminal_detail.html:43
+#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:12
+#: users/models/user.py:49 users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_detail.html:63
#: users/templates/users/user_group_detail.html:55
#: users/templates/users/user_group_list.html:12
@@ -147,16 +147,16 @@ msgstr "资产"
msgid "Name"
msgstr "名称"
-#: assets/forms/domain.py:55 assets/forms/user.py:80 assets/forms/user.py:121
-#: assets/models/base.py:21 assets/templates/assets/admin_user_detail.html:60
+#: assets/forms/domain.py:55 assets/forms/user.py:80 assets/forms/user.py:140
+#: assets/models/base.py:22 assets/templates/assets/admin_user_detail.html:60
#: assets/templates/assets/admin_user_list.html:24
#: assets/templates/assets/domain_gateway_list.html:60
#: assets/templates/assets/system_user_detail.html:62
#: assets/templates/assets/system_user_list.html:27
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13
-#: users/forms.py:22 users/models/authentication.py:45 users/models/user.py:40
+#: users/forms.py:31 users/models/authentication.py:70 users/models/user.py:47
#: users/templates/users/_select_user_modal.html:14
-#: users/templates/users/login.html:56
+#: users/templates/users/login.html:60
#: users/templates/users/login_log_list.html:49
#: users/templates/users/user_detail.html:67
#: users/templates/users/user_list.html:24
@@ -168,19 +168,19 @@ msgstr "用户名"
msgid "Password or private key passphrase"
msgstr "密码或密钥密码"
-#: assets/forms/user.py:25 assets/models/base.py:22 common/forms.py:113
-#: users/forms.py:15 users/forms.py:24 users/forms.py:36
-#: users/templates/users/login.html:59
-#: users/templates/users/reset_password.html:52
+#: assets/forms/user.py:25 assets/models/base.py:23 common/forms.py:113
+#: users/forms.py:15 users/forms.py:33 users/forms.py:45
+#: users/templates/users/login.html:63
+#: users/templates/users/reset_password.html:53
#: users/templates/users/user_create.html:10
#: users/templates/users/user_password_authentication.html:14
-#: users/templates/users/user_password_update.html:40
+#: users/templates/users/user_password_update.html:42
#: users/templates/users/user_profile_update.html:40
#: users/templates/users/user_pubkey_update.html:40
msgid "Password"
msgstr "密码"
-#: assets/forms/user.py:28 users/models/user.py:69
+#: assets/forms/user.py:28 users/models/user.py:76
msgid "Private key"
msgstr "ssh私钥"
@@ -192,17 +192,27 @@ msgstr "ssh密钥不合法"
msgid "Password and private key file must be input one"
msgstr "密码和私钥, 必须输入一个"
-#: assets/forms/user.py:126
+#: assets/forms/user.py:125
+msgid "* Automatic login mode, must fill in the username."
+msgstr "自动登录模式,必须填写用户名"
+
+#: assets/forms/user.py:145
msgid "Auto push system user to asset"
msgstr "自动推送系统用户到资产"
-#: assets/forms/user.py:127
+#: assets/forms/user.py:146
msgid ""
"High level will be using login asset as default, if user was granted more "
"than 2 system user"
msgstr "高优先级的系统用户将会作为默认登录用户"
-#: assets/models/asset.py:49 assets/models/domain.py:43
+#: assets/forms/user.py:148
+msgid ""
+"If you choose manual login mode, you do not need to fill in the username and "
+"password."
+msgstr "如果选择手动登录模式,用户名和密码则不需要填写"
+
+#: assets/models/asset.py:74 assets/models/domain.py:43
#: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:52
#: assets/templates/assets/asset_detail.html:61
@@ -217,7 +227,7 @@ msgstr "高优先级的系统用户将会作为默认登录用户"
msgid "IP"
msgstr "IP"
-#: assets/models/asset.py:50 assets/templates/assets/_asset_list_modal.html:45
+#: assets/models/asset.py:77 assets/templates/assets/_asset_list_modal.html:45
#: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/asset_detail.html:57
#: assets/templates/assets/asset_list.html:86
@@ -229,107 +239,116 @@ msgstr "IP"
msgid "Hostname"
msgstr "主机名"
-#: assets/models/asset.py:52 assets/templates/assets/asset_detail.html:97
+#: assets/models/asset.py:80 assets/models/domain.py:45
+#: assets/models/user.py:115
+#: assets/templates/assets/domain_gateway_list.html:59
+#: assets/templates/assets/system_user_detail.html:70
+#: assets/templates/assets/system_user_list.html:28
+#: terminal/templates/terminal/session_list.html:75
+msgid "Protocol"
+msgstr "协议"
+
+#: assets/models/asset.py:83 assets/templates/assets/asset_detail.html:97
msgid "Platform"
msgstr "系统平台"
-#: assets/models/asset.py:55 assets/models/domain.py:48
+#: assets/models/asset.py:90 assets/models/domain.py:48
#: assets/models/label.py:20 assets/templates/assets/asset_detail.html:105
msgid "Is active"
msgstr "激活"
-#: assets/models/asset.py:61 assets/templates/assets/asset_detail.html:65
+#: assets/models/asset.py:99 assets/templates/assets/asset_detail.html:65
msgid "Public IP"
msgstr "公网IP"
-#: assets/models/asset.py:62 assets/templates/assets/asset_detail.html:113
+#: assets/models/asset.py:101 assets/templates/assets/asset_detail.html:113
msgid "Asset number"
msgstr "资产编号"
-#: assets/models/asset.py:65 assets/templates/assets/asset_detail.html:77
+#: assets/models/asset.py:105 assets/templates/assets/asset_detail.html:77
msgid "Vendor"
msgstr "制造商"
-#: assets/models/asset.py:66 assets/templates/assets/asset_detail.html:81
+#: assets/models/asset.py:107 assets/templates/assets/asset_detail.html:81
msgid "Model"
msgstr "型号"
-#: assets/models/asset.py:67 assets/templates/assets/asset_detail.html:109
+#: assets/models/asset.py:109 assets/templates/assets/asset_detail.html:109
msgid "Serial number"
msgstr "序列号"
-#: assets/models/asset.py:69
+#: assets/models/asset.py:112
msgid "CPU model"
msgstr "CPU型号"
-#: assets/models/asset.py:70
+#: assets/models/asset.py:113
msgid "CPU count"
msgstr "CPU数量"
-#: assets/models/asset.py:71
+#: assets/models/asset.py:114
msgid "CPU cores"
msgstr "CPU核数"
-#: assets/models/asset.py:72 assets/templates/assets/asset_detail.html:89
+#: assets/models/asset.py:116 assets/templates/assets/asset_detail.html:89
msgid "Memory"
msgstr "内存"
-#: assets/models/asset.py:73
+#: assets/models/asset.py:118
msgid "Disk total"
msgstr "硬盘大小"
-#: assets/models/asset.py:74
+#: assets/models/asset.py:120
msgid "Disk info"
msgstr "硬盘信息"
-#: assets/models/asset.py:76 assets/templates/assets/asset_detail.html:101
+#: assets/models/asset.py:123 assets/templates/assets/asset_detail.html:101
msgid "OS"
msgstr "操作系统"
-#: assets/models/asset.py:77
+#: assets/models/asset.py:125
msgid "OS version"
msgstr "系统版本"
-#: assets/models/asset.py:78
+#: assets/models/asset.py:127
msgid "OS arch"
msgstr "系统架构"
-#: assets/models/asset.py:79
+#: assets/models/asset.py:129
msgid "Hostname raw"
msgstr "主机名原始"
-#: assets/models/asset.py:81 assets/templates/assets/asset_create.html:33
+#: assets/models/asset.py:133 assets/templates/assets/asset_create.html:34
#: assets/templates/assets/asset_detail.html:220
-#: assets/templates/assets/asset_update.html:38 templates/_nav.html:27
+#: assets/templates/assets/asset_update.html:39 templates/_nav.html:27
msgid "Labels"
msgstr "标签管理"
-#: assets/models/asset.py:82 assets/models/base.py:28
+#: assets/models/asset.py:135 assets/models/base.py:29
#: assets/models/cluster.py:28 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68
#: assets/templates/assets/asset_detail.html:117
#: assets/templates/assets/domain_detail.html:72
-#: assets/templates/assets/system_user_detail.html:96
-#: ops/templates/ops/adhoc_detail.html:86 perms/models.py:28 perms/models.py:72
+#: assets/templates/assets/system_user_detail.html:100
+#: ops/templates/ops/adhoc_detail.html:86 perms/models.py:38 perms/models.py:81
#: perms/templates/perms/asset_permission_detail.html:98
-#: users/models/user.py:83 users/templates/users/user_detail.html:107
+#: users/models/user.py:90 users/templates/users/user_detail.html:111
msgid "Created by"
msgstr "创建者"
-#: assets/models/asset.py:83 assets/models/cluster.py:26
+#: assets/models/asset.py:138 assets/models/cluster.py:26
#: assets/models/domain.py:20 assets/models/group.py:22
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:64
#: assets/templates/assets/domain_detail.html:68
-#: assets/templates/assets/system_user_detail.html:92
+#: assets/templates/assets/system_user_detail.html:96
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63
-#: perms/models.py:29 perms/models.py:73
+#: perms/models.py:39 perms/models.py:82
#: perms/templates/perms/asset_permission_detail.html:94
-#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17
+#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:15
#: users/templates/users/user_group_detail.html:63
msgid "Date created"
msgstr "创建日期"
-#: assets/models/asset.py:84 assets/models/base.py:25
+#: assets/models/asset.py:140 assets/models/base.py:26
#: assets/models/cluster.py:29 assets/models/domain.py:18
#: assets/models/domain.py:47 assets/models/group.py:23
#: assets/models/label.py:21 assets/templates/assets/admin_user_detail.html:72
@@ -338,23 +357,23 @@ msgstr "创建日期"
#: assets/templates/assets/domain_detail.html:76
#: assets/templates/assets/domain_gateway_list.html:61
#: assets/templates/assets/domain_list.html:17
-#: assets/templates/assets/system_user_detail.html:100
-#: assets/templates/assets/system_user_list.html:33 common/models.py:30
-#: ops/models/adhoc.py:42 perms/models.py:30 perms/models.py:74
+#: assets/templates/assets/system_user_detail.html:104
+#: assets/templates/assets/system_user_list.html:34 common/models.py:30
+#: ops/models/adhoc.py:42 perms/models.py:40 perms/models.py:83
#: perms/templates/perms/asset_permission_detail.html:102 terminal/models.py:26
-#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15
-#: users/models/user.py:75 users/templates/users/user_detail.html:119
+#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:13
+#: users/models/user.py:82 users/templates/users/user_detail.html:123
#: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:14
-#: users/templates/users/user_profile.html:123
+#: users/templates/users/user_profile.html:130
msgid "Comment"
msgstr "备注"
-#: assets/models/base.py:23
+#: assets/models/base.py:24
msgid "SSH private key"
msgstr "ssh密钥"
-#: assets/models/base.py:24
+#: assets/models/base.py:25
msgid "SSH public key"
msgstr "ssh公钥"
@@ -366,7 +385,7 @@ msgstr "带宽"
msgid "Contact"
msgstr "联系人"
-#: assets/models/cluster.py:22 users/models/user.py:61
+#: assets/models/cluster.py:22 users/models/user.py:68
#: users/templates/users/user_detail.html:76
msgid "Phone"
msgstr "手机"
@@ -392,7 +411,7 @@ msgid "Default"
msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:13
-#: users/models/user.py:330
+#: users/models/user.py:345
msgid "System"
msgstr "系统"
@@ -404,13 +423,6 @@ msgstr "默认Cluster"
msgid "Cluster"
msgstr "集群"
-#: assets/models/domain.py:45 assets/models/user.py:106
-#: assets/templates/assets/domain_gateway_list.html:59
-#: assets/templates/assets/system_user_detail.html:66
-#: assets/templates/assets/system_user_list.html:28
-msgid "Protocol"
-msgstr "协议"
-
#: assets/models/group.py:30
msgid "Asset group"
msgstr "资产组"
@@ -422,22 +434,22 @@ msgstr "默认资产组"
#: assets/models/label.py:14 audits/models.py:9
#: audits/templates/audits/ftp_log_list.html:33
#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:14
-#: perms/forms.py:31 perms/models.py:20
+#: perms/forms.py:31 perms/models.py:30
#: perms/templates/perms/asset_permission_create_update.html:36
#: perms/templates/perms/asset_permission_list.html:54
#: perms/templates/perms/asset_permission_list.html:133
-#: terminal/backends/command/models.py:10 terminal/models.py:122
+#: terminal/backends/command/models.py:10 terminal/models.py:126
#: terminal/templates/terminal/command_list.html:32
#: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33
-#: terminal/templates/terminal/session_list.html:71 users/forms.py:273
-#: users/models/user.py:30 users/models/user.py:318
+#: terminal/templates/terminal/session_list.html:71 users/forms.py:282
+#: users/models/user.py:31 users/models/user.py:333
#: users/templates/users/user_group_detail.html:78
-#: users/templates/users/user_group_list.html:13 users/views/user.py:339
+#: users/templates/users/user_group_list.html:13 users/views/user.py:361
msgid "User"
msgstr "用户"
-#: assets/models/label.py:18 assets/models/node.py:15
+#: assets/models/label.py:18 assets/models/node.py:16
#: assets/templates/assets/label_list.html:15 common/models.py:27
msgid "Value"
msgstr "值"
@@ -446,11 +458,19 @@ msgstr "值"
msgid "Category"
msgstr "分类"
-#: assets/models/node.py:14
+#: assets/models/node.py:15
msgid "Key"
msgstr ""
-#: assets/models/user.py:104
+#: assets/models/user.py:108
+msgid "Automatic login"
+msgstr "自动登录"
+
+#: assets/models/user.py:109
+msgid "Manually login"
+msgstr "手动登录"
+
+#: assets/models/user.py:113
#: assets/templates/assets/_asset_group_bulk_update_modal.html:11
#: assets/templates/assets/system_user_asset.html:21
#: assets/views/admin_user.py:29 assets/views/admin_user.py:47
@@ -460,7 +480,7 @@ msgstr ""
#: assets/views/asset.py:197 assets/views/domain.py:29
#: assets/views/domain.py:45 assets/views/domain.py:61
#: assets/views/domain.py:74 assets/views/domain.py:98
-#: assets/views/domain.py:126 assets/views/domain.py:150
+#: assets/views/domain.py:126 assets/views/domain.py:145
#: assets/views/label.py:26 assets/views/label.py:42 assets/views/label.py:58
#: assets/views/system_user.py:28 assets/views/system_user.py:44
#: assets/views/system_user.py:60 assets/views/system_user.py:74
@@ -468,32 +488,37 @@ msgstr ""
msgid "Assets"
msgstr "资产管理"
-#: assets/models/user.py:105
+#: assets/models/user.py:114
msgid "Priority"
msgstr "优先级"
-#: assets/models/user.py:107 assets/templates/assets/_system_user.html:58
-#: assets/templates/assets/system_user_detail.html:118
-#: assets/templates/assets/system_user_update.html:11
+#: assets/models/user.py:116 assets/templates/assets/_system_user.html:59
+#: assets/templates/assets/system_user_detail.html:122
+#: assets/templates/assets/system_user_update.html:10
msgid "Auto push"
msgstr "自动推送"
-#: assets/models/user.py:108 assets/templates/assets/system_user_detail.html:70
+#: assets/models/user.py:117 assets/templates/assets/system_user_detail.html:74
msgid "Sudo"
msgstr "Sudo"
-#: assets/models/user.py:109 assets/templates/assets/system_user_detail.html:75
+#: assets/models/user.py:118 assets/templates/assets/system_user_detail.html:79
msgid "Shell"
msgstr "Shell"
-#: assets/models/user.py:149 audits/models.py:12
+#: assets/models/user.py:119 assets/templates/assets/system_user_detail.html:66
+#: assets/templates/assets/system_user_list.html:29
+msgid "Login mode"
+msgstr "登录模式"
+
+#: assets/models/user.py:159 audits/models.py:12
#: audits/templates/audits/ftp_log_list.html:49
#: audits/templates/audits/ftp_log_list.html:73 perms/forms.py:43
-#: perms/models.py:24 perms/models.py:69
+#: perms/models.py:34 perms/models.py:78
#: perms/templates/perms/asset_permission_detail.html:140
#: perms/templates/perms/asset_permission_list.html:58
#: perms/templates/perms/asset_permission_list.html:145 templates/_nav.html:26
-#: terminal/backends/command/models.py:12 terminal/models.py:124
+#: terminal/backends/command/models.py:12 terminal/models.py:128
#: terminal/templates/terminal/command_list.html:48
#: terminal/templates/terminal/command_list.html:74
#: terminal/templates/terminal/session_list.html:49
@@ -561,7 +586,6 @@ msgid "Select System Users"
msgstr "选择系统用户"
#: assets/templates/assets/_asset_group_bulk_update_modal.html:34
-#, fuzzy
msgid "Enable-MFA"
msgstr "启用MFA"
@@ -601,72 +625,73 @@ msgid "Basic"
msgstr "基本"
#: assets/templates/assets/_system_user.html:44
-#: assets/templates/assets/asset_create.html:25
-#: assets/templates/assets/asset_update.html:30
+#: assets/templates/assets/asset_create.html:26
+#: assets/templates/assets/asset_update.html:31
#: assets/templates/assets/gateway_create_update.html:45
-#: assets/templates/assets/system_user_update.html:7
#: users/templates/users/_user.html:21
msgid "Auth"
msgstr "认证"
-#: assets/templates/assets/_system_user.html:47
+#: assets/templates/assets/_system_user.html:48
msgid "Auto generate key"
msgstr "自动生成密钥"
-#: assets/templates/assets/_system_user.html:64
-#: assets/templates/assets/asset_create.html:59
-#: assets/templates/assets/asset_update.html:63
+#: assets/templates/assets/_system_user.html:65
+#: assets/templates/assets/asset_create.html:60
+#: assets/templates/assets/asset_update.html:64
#: assets/templates/assets/gateway_create_update.html:53
#: perms/templates/perms/asset_permission_create_update.html:45
#: terminal/templates/terminal/terminal_update.html:42
msgid "Other"
msgstr "其它"
-#: assets/templates/assets/_system_user.html:70
+#: assets/templates/assets/_system_user.html:71
#: assets/templates/assets/admin_user_create_update.html:45
#: assets/templates/assets/asset_bulk_update.html:23
-#: assets/templates/assets/asset_create.html:66
-#: assets/templates/assets/asset_update.html:70
+#: assets/templates/assets/asset_create.html:67
+#: assets/templates/assets/asset_update.html:71
#: assets/templates/assets/domain_create_update.html:16
#: assets/templates/assets/gateway_create_update.html:58
#: assets/templates/assets/label_create_update.html:18
-#: common/templates/common/basic_setting.html:58
-#: common/templates/common/email_setting.html:59
-#: common/templates/common/ldap_setting.html:59
-#: common/templates/common/terminal_setting.html:101
+#: common/templates/common/basic_setting.html:61
+#: common/templates/common/email_setting.html:62
+#: common/templates/common/ldap_setting.html:62
+#: common/templates/common/security_setting.html:70
+#: common/templates/common/terminal_setting.html:106
#: perms/templates/perms/asset_permission_create_update.html:69
#: terminal/templates/terminal/terminal_update.html:47
#: users/templates/users/_user.html:46
#: users/templates/users/user_bulk_update.html:23
-#: users/templates/users/user_password_update.html:58
-#: users/templates/users/user_profile.html:181
+#: users/templates/users/user_password_update.html:70
+#: users/templates/users/user_profile.html:188
#: users/templates/users/user_profile_update.html:63
#: users/templates/users/user_pubkey_update.html:70
#: users/templates/users/user_pubkey_update.html:76
msgid "Reset"
msgstr "重置"
-#: assets/templates/assets/_system_user.html:71
+#: assets/templates/assets/_system_user.html:72
#: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24
-#: assets/templates/assets/asset_create.html:67
+#: assets/templates/assets/asset_create.html:68
#: assets/templates/assets/asset_list.html:108
-#: assets/templates/assets/asset_update.html:71
+#: assets/templates/assets/asset_update.html:72
#: assets/templates/assets/domain_create_update.html:17
#: assets/templates/assets/gateway_create_update.html:59
#: assets/templates/assets/label_create_update.html:19
-#: common/templates/common/basic_setting.html:59
-#: common/templates/common/email_setting.html:60
-#: common/templates/common/ldap_setting.html:60
-#: common/templates/common/terminal_setting.html:103
+#: common/templates/common/basic_setting.html:62
+#: common/templates/common/email_setting.html:63
+#: common/templates/common/ldap_setting.html:63
+#: common/templates/common/security_setting.html:71
+#: common/templates/common/terminal_setting.html:108
#: perms/templates/perms/asset_permission_create_update.html:70
-#: terminal/templates/terminal/session_list.html:120
+#: terminal/templates/terminal/session_list.html:126
#: terminal/templates/terminal/terminal_update.html:48
#: users/templates/users/_user.html:47
#: users/templates/users/forgot_password.html:44
#: users/templates/users/user_bulk_update.html:24
-#: users/templates/users/user_list.html:44
-#: users/templates/users/user_password_update.html:59
+#: users/templates/users/user_list.html:45
+#: users/templates/users/user_password_update.html:71
#: users/templates/users/user_profile_update.html:64
#: users/templates/users/user_pubkey_update.html:77
msgid "Submit"
@@ -699,16 +724,15 @@ msgstr "资产列表"
#: assets/templates/assets/admin_user_assets.html:54
#: assets/templates/assets/admin_user_list.html:26
-#: assets/templates/assets/asset_list.html:90
#: assets/templates/assets/system_user_asset.html:52
-#: assets/templates/assets/system_user_list.html:30
+#: assets/templates/assets/system_user_list.html:31
#: users/templates/users/user_group_granted_asset.html:47
msgid "Reachable"
msgstr "可连接"
#: assets/templates/assets/admin_user_assets.html:66
#: assets/templates/assets/system_user_asset.html:64
-#: assets/templates/assets/system_user_detail.html:112
+#: assets/templates/assets/system_user_detail.html:116
#: perms/templates/perms/asset_permission_detail.html:114
msgid "Quick update"
msgstr "快速更新"
@@ -721,21 +745,21 @@ msgstr "测试可连接性"
#: assets/templates/assets/admin_user_assets.html:75
#: assets/templates/assets/asset_detail.html:171
#: assets/templates/assets/system_user_asset.html:81
-#: assets/templates/assets/system_user_detail.html:147
+#: assets/templates/assets/system_user_detail.html:151
msgid "Test"
msgstr "测试"
#: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:85
#: assets/templates/assets/asset_detail.html:24
-#: assets/templates/assets/asset_list.html:170
+#: assets/templates/assets/asset_list.html:175
#: assets/templates/assets/domain_detail.html:24
#: assets/templates/assets/domain_detail.html:103
#: assets/templates/assets/domain_gateway_list.html:85
#: assets/templates/assets/domain_list.html:42
#: assets/templates/assets/label_list.html:38
#: assets/templates/assets/system_user_detail.html:26
-#: assets/templates/assets/system_user_list.html:88
+#: assets/templates/assets/system_user_list.html:89
#: perms/templates/perms/asset_permission_detail.html:30
#: perms/templates/perms/asset_permission_list.html:191
#: terminal/templates/terminal/terminal_detail.html:16
@@ -743,23 +767,23 @@ msgstr "测试"
#: users/templates/users/user_detail.html:25
#: users/templates/users/user_group_detail.html:28
#: users/templates/users/user_group_list.html:43
-#: users/templates/users/user_list.html:76
-#: users/templates/users/user_profile.html:144
-#: users/templates/users/user_profile.html:173
+#: users/templates/users/user_list.html:77
+#: users/templates/users/user_profile.html:151
+#: users/templates/users/user_profile.html:180
msgid "Update"
msgstr "更新"
#: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:86
#: assets/templates/assets/asset_detail.html:28
-#: assets/templates/assets/asset_list.html:171
+#: assets/templates/assets/asset_list.html:176
#: assets/templates/assets/domain_detail.html:28
#: assets/templates/assets/domain_detail.html:104
#: assets/templates/assets/domain_gateway_list.html:86
#: assets/templates/assets/domain_list.html:43
#: assets/templates/assets/label_list.html:39
#: assets/templates/assets/system_user_detail.html:30
-#: assets/templates/assets/system_user_list.html:89
+#: assets/templates/assets/system_user_list.html:90
#: ops/templates/ops/task_list.html:72
#: perms/templates/perms/asset_permission_detail.html:34
#: perms/templates/perms/asset_permission_list.html:192
@@ -767,8 +791,8 @@ msgstr "更新"
#: users/templates/users/user_detail.html:30
#: users/templates/users/user_group_detail.html:32
#: users/templates/users/user_group_list.html:45
-#: users/templates/users/user_list.html:80
-#: users/templates/users/user_list.html:84
+#: users/templates/users/user_list.html:81
+#: users/templates/users/user_list.html:85
msgid "Delete"
msgstr "删除"
@@ -783,17 +807,17 @@ msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:200
-#: assets/templates/assets/asset_list.html:600
-#: assets/templates/assets/system_user_detail.html:183
-#: assets/templates/assets/system_user_list.html:138 templates/_modal.html:22
+#: assets/templates/assets/asset_list.html:638
+#: assets/templates/assets/system_user_detail.html:196
+#: assets/templates/assets/system_user_list.html:139 templates/_modal.html:22
#: terminal/templates/terminal/session_detail.html:108
-#: users/templates/users/user_detail.html:362
-#: users/templates/users/user_detail.html:387
-#: users/templates/users/user_detail.html:410
+#: users/templates/users/user_detail.html:366
+#: users/templates/users/user_detail.html:391
+#: users/templates/users/user_detail.html:414
#: users/templates/users/user_group_create_update.html:32
#: users/templates/users/user_group_list.html:86
-#: users/templates/users/user_list.html:196
-#: users/templates/users/user_profile.html:215
+#: users/templates/users/user_list.html:200
+#: users/templates/users/user_profile.html:222
msgid "Confirm"
msgstr "确认"
@@ -803,12 +827,12 @@ msgid "Create admin user"
msgstr "创建管理用户"
#: assets/templates/assets/admin_user_list.html:27
-#: assets/templates/assets/system_user_list.html:31
+#: assets/templates/assets/system_user_list.html:32
msgid "Unreachable"
msgstr "不可达"
#: assets/templates/assets/admin_user_list.html:28
-#: assets/templates/assets/system_user_list.html:32
+#: assets/templates/assets/system_user_list.html:33
#: ops/templates/ops/adhoc_history.html:54
#: ops/templates/ops/task_history.html:60
msgid "Ratio"
@@ -819,14 +843,14 @@ msgstr "比例"
#: assets/templates/assets/domain_gateway_list.html:62
#: assets/templates/assets/domain_list.html:18
#: assets/templates/assets/label_list.html:17
-#: assets/templates/assets/system_user_list.html:34
+#: assets/templates/assets/system_user_list.html:35
#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64
#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:42
#: perms/templates/perms/asset_permission_list.html:60
-#: terminal/templates/terminal/session_list.html:80
+#: terminal/templates/terminal/session_list.html:81
#: terminal/templates/terminal/terminal_list.html:36
#: users/templates/users/user_group_list.html:15
-#: users/templates/users/user_list.html:28
+#: users/templates/users/user_list.html:29
msgid "Action"
msgstr "动作"
@@ -843,31 +867,31 @@ msgid "Disk"
msgstr "硬盘"
#: assets/templates/assets/asset_detail.html:121
-#: users/templates/users/user_detail.html:111
-#: users/templates/users/user_profile.html:97
+#: users/templates/users/user_detail.html:115
+#: users/templates/users/user_profile.html:104
msgid "Date joined"
msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:137
#: terminal/templates/terminal/session_detail.html:81
-#: users/templates/users/user_detail.html:130
-#: users/templates/users/user_profile.html:135
+#: users/templates/users/user_detail.html:134
+#: users/templates/users/user_profile.html:142
msgid "Quick modify"
msgstr "快速修改"
#: assets/templates/assets/asset_detail.html:143
#: assets/templates/assets/asset_list.html:89
-#: assets/templates/assets/user_asset_list.html:47 perms/models.py:25
-#: perms/models.py:70
+#: assets/templates/assets/user_asset_list.html:47 perms/models.py:35
+#: perms/models.py:79
#: perms/templates/perms/asset_permission_create_update.html:47
#: perms/templates/perms/asset_permission_detail.html:120
#: perms/templates/perms/asset_permission_list.html:59
#: terminal/templates/terminal/terminal_list.html:34
#: users/templates/users/_select_user_modal.html:18
-#: users/templates/users/user_detail.html:136
+#: users/templates/users/user_detail.html:140
#: users/templates/users/user_granted_asset.html:46
#: users/templates/users/user_group_granted_asset.html:46
-#: users/templates/users/user_list.html:27
+#: users/templates/users/user_list.html:28
#: users/templates/users/user_profile.html:63
msgid "Active"
msgstr "激活中"
@@ -881,8 +905,8 @@ msgid "Refresh"
msgstr "刷新"
#: assets/templates/assets/asset_detail.html:300
-#: users/templates/users/user_detail.html:282
-#: users/templates/users/user_detail.html:309
+#: users/templates/users/user_detail.html:286
+#: users/templates/users/user_detail.html:313
msgid "Update successfully!"
msgstr "更新成功"
@@ -905,12 +929,12 @@ msgid "Hardware"
msgstr "硬件"
#: assets/templates/assets/asset_list.html:100
-#: users/templates/users/user_list.html:37
+#: users/templates/users/user_list.html:38
msgid "Delete selected"
msgstr "批量删除"
#: assets/templates/assets/asset_list.html:101
-#: users/templates/users/user_list.html:38
+#: users/templates/users/user_list.html:39
msgid "Update selected"
msgstr "批量更新"
@@ -919,12 +943,12 @@ msgid "Remove from this node"
msgstr "从节点移除"
#: assets/templates/assets/asset_list.html:103
-#: users/templates/users/user_list.html:39
+#: users/templates/users/user_list.html:40
msgid "Deactive selected"
msgstr "禁用所选"
#: assets/templates/assets/asset_list.html:104
-#: users/templates/users/user_list.html:40
+#: users/templates/users/user_list.html:41
msgid "Active selected"
msgstr "激活所选"
@@ -956,45 +980,53 @@ msgstr "更新节点资产硬件信息"
msgid "Test node connective"
msgstr "测试节点资产可连接性"
-#: assets/templates/assets/asset_list.html:204
+#: assets/templates/assets/asset_list.html:131
+msgid "Display only current node assets"
+msgstr "仅显示当前节点资产"
+
+#: assets/templates/assets/asset_list.html:132
+msgid "Displays all child node assets"
+msgstr "显示所有子节点资产"
+
+#: assets/templates/assets/asset_list.html:218
msgid "Create node failed"
msgstr "创建节点失败"
-#: assets/templates/assets/asset_list.html:216
+#: assets/templates/assets/asset_list.html:230
msgid "Have child node, cancel"
msgstr "存在子节点,不能删除"
-#: assets/templates/assets/asset_list.html:218
+#: assets/templates/assets/asset_list.html:232
msgid "Have assets, cancel"
msgstr "存在资产,不能删除"
-#: assets/templates/assets/asset_list.html:595
-#: assets/templates/assets/system_user_list.html:133
-#: users/templates/users/user_detail.html:357
-#: users/templates/users/user_detail.html:382
+#: assets/templates/assets/asset_list.html:633
+#: assets/templates/assets/system_user_list.html:134
+#: users/templates/users/user_detail.html:361
+#: users/templates/users/user_detail.html:386
#: users/templates/users/user_group_list.html:81
-#: users/templates/users/user_list.html:191
+#: users/templates/users/user_list.html:195
msgid "Are you sure?"
msgstr "你确认吗?"
-#: assets/templates/assets/asset_list.html:596
+#: assets/templates/assets/asset_list.html:634
msgid "This will delete the selected assets !!!"
msgstr "删除选择资产"
-#: assets/templates/assets/asset_list.html:604
+#: assets/templates/assets/asset_list.html:642
msgid "Asset Deleted."
msgstr "已被删除"
-#: assets/templates/assets/asset_list.html:605
-#: assets/templates/assets/asset_list.html:610
+#: assets/templates/assets/asset_list.html:643
+#: assets/templates/assets/asset_list.html:648
msgid "Asset Delete"
msgstr "删除"
-#: assets/templates/assets/asset_list.html:609
+#: assets/templates/assets/asset_list.html:647
msgid "Asset Deleting failed."
msgstr "删除失败"
-#: assets/templates/assets/asset_update.html:59
+#: assets/templates/assets/asset_update.html:60
msgid "Configuration"
msgstr "配置"
@@ -1025,8 +1057,9 @@ msgid "Create gateway"
msgstr "创建网关"
#: assets/templates/assets/domain_gateway_list.html:87
-#: common/templates/common/email_setting.html:58
-#: common/templates/common/ldap_setting.html:58
+#: assets/templates/assets/domain_gateway_list.html:89
+#: common/templates/common/email_setting.html:61
+#: common/templates/common/ldap_setting.html:61
msgid "Test connection"
msgstr "测试连接"
@@ -1043,17 +1076,17 @@ msgid "Assets of "
msgstr "资产"
#: assets/templates/assets/system_user_asset.html:70
-#: assets/templates/assets/system_user_detail.html:135
+#: assets/templates/assets/system_user_detail.html:139
msgid "Push system user now"
msgstr "立刻推送系统"
#: assets/templates/assets/system_user_asset.html:73
-#: assets/templates/assets/system_user_detail.html:138
+#: assets/templates/assets/system_user_detail.html:142
msgid "Push"
msgstr "推送"
#: assets/templates/assets/system_user_asset.html:78
-#: assets/templates/assets/system_user_detail.html:144
+#: assets/templates/assets/system_user_detail.html:148
msgid "Test assets connective"
msgstr "测试资产可连接性"
@@ -1065,37 +1098,50 @@ msgstr "任务已下发,查看ops任务列表"
msgid "Task has been send, seen left assets status"
msgstr "任务已下发,查看左侧资产状态"
-#: assets/templates/assets/system_user_detail.html:81
+#: assets/templates/assets/system_user_detail.html:85
msgid "Home"
msgstr "家目录"
-#: assets/templates/assets/system_user_detail.html:87
+#: assets/templates/assets/system_user_detail.html:91
msgid "Uid"
msgstr "Uid"
-#: assets/templates/assets/system_user_detail.html:174
+#: assets/templates/assets/system_user_detail.html:157
+#: assets/templates/assets/system_user_detail.html:343
+msgid "Clear auth"
+msgstr "清除认证信息"
+
+#: assets/templates/assets/system_user_detail.html:160
+msgid "Clear"
+msgstr "清除"
+
+#: assets/templates/assets/system_user_detail.html:187
msgid "Add to node"
msgstr "添加到节点"
+#: assets/templates/assets/system_user_detail.html:343
+msgid "success"
+msgstr "成功"
+
#: assets/templates/assets/system_user_list.html:18
#: assets/views/system_user.py:45
msgid "Create system user"
msgstr "创建系统用户"
-#: assets/templates/assets/system_user_list.html:134
+#: assets/templates/assets/system_user_list.html:135
msgid "This will delete the selected System Users !!!"
msgstr "删除选择系统用户"
-#: assets/templates/assets/system_user_list.html:142
+#: assets/templates/assets/system_user_list.html:143
msgid "System Users Deleted."
msgstr "已被删除"
-#: assets/templates/assets/system_user_list.html:143
-#: assets/templates/assets/system_user_list.html:148
+#: assets/templates/assets/system_user_list.html:144
+#: assets/templates/assets/system_user_list.html:149
msgid "System Users Delete"
msgstr "删除系统用户"
-#: assets/templates/assets/system_user_list.html:147
+#: assets/templates/assets/system_user_list.html:148
msgid "System Users Deleting failed."
msgstr "系统用户删除失败"
@@ -1123,7 +1169,7 @@ msgstr "批量更新资产"
msgid "Update asset"
msgstr "更新资产"
-#: assets/views/asset.py:311
+#: assets/views/asset.py:314
msgid "already exists"
msgstr "已经存在"
@@ -1143,7 +1189,7 @@ msgstr "网域详情"
msgid "Domain gateway list"
msgstr "域网关列表"
-#: assets/views/domain.py:151
+#: assets/views/domain.py:146
msgid "Update gateway"
msgstr "创建网关"
@@ -1176,7 +1222,7 @@ msgid "System user asset"
msgstr "系统用户集群资产"
#: audits/models.py:10 audits/templates/audits/ftp_log_list.html:74
-#: terminal/models.py:126 terminal/templates/terminal/session_list.html:74
+#: terminal/models.py:130 terminal/templates/terminal/session_list.html:74
#: terminal/templates/terminal/terminal_detail.html:47
msgid "Remote addr"
msgstr "远端地址"
@@ -1191,16 +1237,16 @@ msgid "Filename"
msgstr "文件名"
#: audits/models.py:15 audits/templates/audits/ftp_log_list.html:77
-#: ops/templates/ops/task_list.html:39
+#: ops/templates/ops/task_list.html:39 users/models/authentication.py:66
msgid "Success"
msgstr "成功"
#: audits/templates/audits/ftp_log_list.html:78
#: ops/templates/ops/adhoc_history.html:52
#: ops/templates/ops/adhoc_history_detail.html:61
-#: ops/templates/ops/task_history.html:58 perms/models.py:26
-#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:132
-#: terminal/templates/terminal/session_list.html:77
+#: ops/templates/ops/task_history.html:58 perms/models.py:36
+#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:137
+#: terminal/templates/terminal/session_list.html:78
msgid "Date start"
msgstr "开始日期"
@@ -1216,11 +1262,11 @@ msgstr "FTP日志"
msgid "Test mail sent to {}, please check"
msgstr "邮件已经发送{}, 请检查"
-#: common/api.py:52
+#: common/api.py:42
msgid "Test ldap success"
msgstr "连接LDAP成功"
-#: common/api.py:90
+#: common/api.py:80
msgid "Match {} s users"
msgstr "匹配 {} 个用户"
@@ -1355,7 +1401,7 @@ msgstr "密码认证"
msgid "Public key auth"
msgstr "密钥认证"
-#: common/forms.py:159 common/templates/common/terminal_setting.html:63
+#: common/forms.py:159 common/templates/common/terminal_setting.html:68
#: terminal/forms.py:30 terminal/models.py:20
msgid "Command storage"
msgstr "命令存储"
@@ -1366,7 +1412,7 @@ msgid ""
"other storage and some terminal using"
msgstr "设置终端命令存储,default是默认用的存储方式"
-#: common/forms.py:165 common/templates/common/terminal_setting.html:81
+#: common/forms.py:165 common/templates/common/terminal_setting.html:86
#: terminal/forms.py:35 terminal/models.py:21
msgid "Replay storage"
msgstr "录像存储"
@@ -1377,6 +1423,75 @@ msgid ""
"other storage and some terminal using"
msgstr "设置终端录像存储,default是默认用的存储方式"
+#: common/forms.py:176
+msgid "MFA Secondary certification"
+msgstr "MFA 二次认证"
+
+#: common/forms.py:178
+msgid ""
+"After opening, the user login must use MFA secondary authentication (valid "
+"for all users, including administrators)"
+msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)"
+
+#: common/forms.py:185
+msgid "Limit the number of login failures"
+msgstr "限制登录失败次数"
+
+#: common/forms.py:190
+msgid "No logon interval"
+msgstr "禁止登录时间间隔"
+
+#: common/forms.py:192
+msgid ""
+"Tip :(unit/minute) if the user has failed to log in for a limited number of "
+"times, no login is allowed during this time interval."
+msgstr ""
+"提示:(单位 / 分钟)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录."
+
+#: common/forms.py:198
+msgid "Password minimum length"
+msgstr "密码最小长度 "
+
+#: common/forms.py:205
+msgid "Must contain capital letters"
+msgstr "必须包含大写字母"
+
+#: common/forms.py:207
+msgid ""
+"After opening, the user password changes and resets must contain uppercase "
+"letters"
+msgstr "开启后,用户密码修改、重置必须包含大写字母"
+
+#: common/forms.py:213
+msgid "Must contain lowercase letters"
+msgstr "必须包含小写字母"
+
+#: common/forms.py:214
+msgid ""
+"After opening, the user password changes and resets must contain lowercase "
+"letters"
+msgstr "开启后,用户密码修改、重置必须包含小写字母"
+
+#: common/forms.py:220
+msgid "Must contain numeric characters"
+msgstr "必须包含数字字符"
+
+#: common/forms.py:221
+msgid ""
+"After opening, the user password changes and resets must contain numeric "
+"characters"
+msgstr "开启后,用户密码修改、重置必须包含数字字符"
+
+#: common/forms.py:227
+msgid "Must contain special characters"
+msgstr "必须包含特殊字符"
+
+#: common/forms.py:228
+msgid ""
+"After opening, the user password changes and resets must contain special "
+"characters"
+msgstr "开启后,用户密码修改、重置必须包含特殊字符"
+
#: common/mixins.py:29
msgid "is discard"
msgstr ""
@@ -1385,21 +1500,24 @@ msgstr ""
msgid "discard time"
msgstr ""
-#: common/models.py:29 users/templates/users/user_detail.html:96
+#: common/models.py:29 users/models/authentication.py:51
+#: users/templates/users/user_detail.html:96
msgid "Enabled"
msgstr "启用"
#: common/templates/common/basic_setting.html:15
#: common/templates/common/email_setting.html:15
#: common/templates/common/ldap_setting.html:15
+#: common/templates/common/security_setting.html:15
#: common/templates/common/terminal_setting.html:16
-#: common/templates/common/terminal_setting.html:42 common/views.py:22
+#: common/templates/common/terminal_setting.html:46 common/views.py:22
msgid "Basic setting"
msgstr "基本设置"
#: common/templates/common/basic_setting.html:18
#: common/templates/common/email_setting.html:18
#: common/templates/common/ldap_setting.html:18
+#: common/templates/common/security_setting.html:18
#: common/templates/common/terminal_setting.html:20 common/views.py:48
msgid "Email setting"
msgstr "邮件设置"
@@ -1407,6 +1525,7 @@ msgstr "邮件设置"
#: common/templates/common/basic_setting.html:21
#: common/templates/common/email_setting.html:21
#: common/templates/common/ldap_setting.html:21
+#: common/templates/common/security_setting.html:21
#: common/templates/common/terminal_setting.html:24 common/views.py:74
msgid "LDAP setting"
msgstr "LDAP设置"
@@ -1414,22 +1533,44 @@ msgstr "LDAP设置"
#: common/templates/common/basic_setting.html:24
#: common/templates/common/email_setting.html:24
#: common/templates/common/ldap_setting.html:24
+#: common/templates/common/security_setting.html:24
#: common/templates/common/terminal_setting.html:28 common/views.py:104
msgid "Terminal setting"
msgstr "终端设置"
-#: common/templates/common/terminal_setting.html:68
-#: common/templates/common/terminal_setting.html:86
+#: common/templates/common/basic_setting.html:27
+#: common/templates/common/email_setting.html:27
+#: common/templates/common/ldap_setting.html:27
+#: common/templates/common/security_setting.html:27
+#: common/templates/common/terminal_setting.html:31 common/views.py:132
+msgid "Security setting"
+msgstr "安全设置"
+
+#: common/templates/common/security_setting.html:42
+msgid "User login settings"
+msgstr "用户登录设置"
+
+#: common/templates/common/security_setting.html:46
+msgid "Password check rule"
+msgstr "密码校验规则"
+
+#: common/templates/common/terminal_setting.html:73
+#: common/templates/common/terminal_setting.html:91
#: users/templates/users/login_log_list.html:50
msgid "Type"
msgstr "类型"
+#: common/validators.py:7
+msgid "Special char not allowed"
+msgstr "不能包含特殊字符"
+
#: common/views.py:21 common/views.py:47 common/views.py:73 common/views.py:103
-#: templates/_nav.html:81
+#: common/views.py:131 templates/_nav.html:81
msgid "Settings"
msgstr "系统设置"
#: common/views.py:32 common/views.py:58 common/views.py:86 common/views.py:116
+#: common/views.py:142
msgid "Update setting successfully, please restart program"
msgstr "更新设置成功, 请手动重启程序"
@@ -1678,7 +1819,7 @@ msgid "Versions"
msgstr "版本"
#: ops/templates/ops/task_list.html:40
-#: users/templates/users/login_log_list.html:54
+#: users/templates/users/login_log_list.html:57
msgid "Date"
msgstr "日期"
@@ -1703,17 +1844,17 @@ msgstr "任务列表"
msgid "Task run history"
msgstr "执行历史"
-#: perms/forms.py:18 users/forms.py:230 users/forms.py:235 users/forms.py:247
-#: users/forms.py:277
+#: perms/forms.py:18 users/forms.py:239 users/forms.py:244 users/forms.py:256
+#: users/forms.py:286
msgid "Select users"
msgstr "选择用户"
-#: perms/forms.py:34 perms/models.py:21 perms/models.py:68
+#: perms/forms.py:34 perms/models.py:31 perms/models.py:77
#: perms/templates/perms/asset_permission_list.html:55
#: perms/templates/perms/asset_permission_list.html:136 templates/_nav.html:14
-#: users/models/group.py:25 users/models/user.py:48
+#: users/models/group.py:23 users/models/user.py:55
#: users/templates/users/_select_user_modal.html:16
-#: users/templates/users/user_detail.html:188
+#: users/templates/users/user_detail.html:192
#: users/templates/users/user_list.html:26
msgid "User group"
msgstr "用户组"
@@ -1726,14 +1867,14 @@ msgstr ""
msgid "Asset or group at least one required"
msgstr ""
-#: perms/models.py:27 perms/models.py:71
+#: perms/models.py:37 perms/models.py:80
#: perms/templates/perms/asset_permission_detail.html:90
-#: users/models/user.py:80 users/templates/users/user_detail.html:103
-#: users/templates/users/user_profile.html:105
+#: users/models/user.py:87 users/templates/users/user_detail.html:107
+#: users/templates/users/user_profile.html:112
msgid "Date expired"
msgstr "失效日期"
-#: perms/models.py:81 templates/_nav.html:34
+#: perms/models.py:90 templates/_nav.html:34
msgid "Asset permission"
msgstr "资产授权"
@@ -1766,10 +1907,14 @@ msgid "Add node to this permission"
msgstr "添加节点"
#: perms/templates/perms/asset_permission_asset.html:125
-#: users/templates/users/user_detail.html:205
+#: users/templates/users/user_detail.html:209
msgid "Join"
msgstr "加入"
+#: perms/templates/perms/asset_permission_create_update.html:53
+msgid "Validity period"
+msgstr "有效期"
+
#: perms/templates/perms/asset_permission_detail.html:66
msgid "User count"
msgstr "用户数量"
@@ -1852,14 +1997,14 @@ msgstr "商业支持"
msgid "Docs"
msgstr "文档"
-#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:113
+#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:122
#: users/templates/users/_user.html:39
#: users/templates/users/first_login.html:39
-#: users/templates/users/user_password_update.html:37
+#: users/templates/users/user_password_update.html:39
#: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:57
-#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:322
+#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:343
msgid "Profile"
msgstr "个人信息"
@@ -1876,7 +2021,7 @@ msgid "Logout"
msgstr "注销登录"
#: templates/_header_bar.html:49 users/templates/users/login.html:44
-#: users/templates/users/login.html:64
+#: users/templates/users/login.html:68
msgid "Login"
msgstr "登录"
@@ -1916,13 +2061,13 @@ msgstr "关闭"
#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44
#: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95
-#: users/views/login.py:241 users/views/login.py:289 users/views/user.py:64
-#: users/views/user.py:79 users/views/user.py:99 users/views/user.py:155
-#: users/views/user.py:310 users/views/user.py:357 users/views/user.py:379
+#: users/views/login.py:330 users/views/login.py:388 users/views/user.py:65
+#: users/views/user.py:80 users/views/user.py:102 users/views/user.py:175
+#: users/views/user.py:330 users/views/user.py:380 users/views/user.py:415
msgid "Users"
msgstr "用户管理"
-#: templates/_nav.html:13 users/views/user.py:65
+#: templates/_nav.html:13 users/views/user.py:66
msgid "User list"
msgstr "用户列表"
@@ -1950,11 +2095,10 @@ msgstr "命令记录"
msgid "Web terminal"
msgstr "Web终端"
-#: templates/_nav.html:51 terminal/templates/terminal/session_list.html:75
-#: terminal/views/command.py:47 terminal/views/session.py:75
-#: terminal/views/session.py:93 terminal/views/session.py:115
-#: terminal/views/terminal.py:31 terminal/views/terminal.py:46
-#: terminal/views/terminal.py:58
+#: templates/_nav.html:51 terminal/views/command.py:47
+#: terminal/views/session.py:75 terminal/views/session.py:93
+#: terminal/views/session.py:115 terminal/views/terminal.py:31
+#: terminal/views/terminal.py:46 terminal/views/terminal.py:58
msgid "Terminal"
msgstr "终端管理"
@@ -2033,26 +2177,26 @@ msgstr "线程数"
msgid "Boot Time"
msgstr "运行时间"
-#: terminal/models.py:128 terminal/templates/terminal/session_list.html:102
+#: terminal/models.py:132 terminal/templates/terminal/session_list.html:104
msgid "Replay"
msgstr "回放"
-#: terminal/models.py:129 terminal/templates/terminal/command_list.html:55
+#: terminal/models.py:133 terminal/templates/terminal/command_list.html:55
#: terminal/templates/terminal/command_list.html:71
#: terminal/templates/terminal/session_detail.html:48
-#: terminal/templates/terminal/session_list.html:76
+#: terminal/templates/terminal/session_list.html:77
msgid "Command"
msgstr "命令"
-#: terminal/models.py:131
+#: terminal/models.py:136
msgid "Date last active"
msgstr "最后活跃日期"
-#: terminal/models.py:133
+#: terminal/models.py:138
msgid "Date end"
msgstr "结束日期"
-#: terminal/models.py:150
+#: terminal/models.py:155
msgid "Args"
msgstr "参数"
@@ -2091,23 +2235,28 @@ msgstr "监控"
msgid "Terminate session"
msgstr "终止会话"
-#: terminal/templates/terminal/session_list.html:79
+#: terminal/templates/terminal/session_list.html:76
+msgid "Login from"
+msgstr "登录来源"
+
+#: terminal/templates/terminal/session_list.html:80
msgid "Duration"
msgstr "时长"
-#: terminal/templates/terminal/session_list.html:104
+#: terminal/templates/terminal/session_list.html:106
msgid "Monitor"
msgstr "监控"
-#: terminal/templates/terminal/session_list.html:105
+#: terminal/templates/terminal/session_list.html:108
+#: terminal/templates/terminal/session_list.html:110
msgid "Terminate"
msgstr "终断"
-#: terminal/templates/terminal/session_list.html:116
+#: terminal/templates/terminal/session_list.html:122
msgid "Terminate selected"
msgstr "终断所选"
-#: terminal/templates/terminal/session_list.html:136
+#: terminal/templates/terminal/session_list.html:142
msgid "Terminate task send, waiting ..."
msgstr "终断任务已发送,请等待"
@@ -2177,6 +2326,10 @@ msgid ""
"You should use your ssh client tools connect terminal: {}
{}"
msgstr "你可以使用ssh客户端工具连接终端"
+#: users/api.py:208 users/templates/users/login.html:50
+msgid "Log in frequently and try again later"
+msgstr "登录频繁, 稍后重试"
+
#: users/authentication.py:56
msgid "Invalid signature header. No credentials provided."
msgstr ""
@@ -2228,11 +2381,11 @@ msgstr ""
msgid "Invalid token or cache refreshed."
msgstr ""
-#: users/forms.py:30
+#: users/forms.py:39
msgid "MFA code"
msgstr "MFA 验证码"
-#: users/forms.py:41 users/models/user.py:52
+#: users/forms.py:50 users/models/user.py:59
#: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:25
@@ -2240,31 +2393,31 @@ msgstr "MFA 验证码"
msgid "Role"
msgstr "角色"
-#: users/forms.py:44 users/forms.py:193
+#: users/forms.py:53 users/forms.py:202
msgid "ssh public key"
msgstr "ssh公钥"
-#: users/forms.py:45 users/forms.py:194
+#: users/forms.py:54 users/forms.py:203
msgid "ssh-rsa AAAA..."
msgstr ""
-#: users/forms.py:46
+#: users/forms.py:55
msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里"
-#: users/forms.py:64 users/templates/users/user_detail.html:196
+#: users/forms.py:73 users/templates/users/user_detail.html:200
msgid "Join user groups"
msgstr "添加到用户组"
-#: users/forms.py:75 users/forms.py:208
+#: users/forms.py:84 users/forms.py:217
msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同"
-#: users/forms.py:79 users/forms.py:212 users/serializers.py:45
+#: users/forms.py:88 users/forms.py:221 users/serializers.py:48
msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法"
-#: users/forms.py:119
+#: users/forms.py:128
msgid ""
"Tip: when enabled, you will enter the MFA binding process the next time you "
"log in. you can also directly bind in \"personal information -> quick "
@@ -2273,16 +2426,17 @@ msgstr ""
"提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修"
"改->更改MFA设置)中直接绑定!"
-#: users/forms.py:129
+#: users/forms.py:138
msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全."
-#: users/forms.py:134 users/models/user.py:64
+#: users/forms.py:143 users/models/authentication.py:75 users/models/user.py:71
#: users/templates/users/first_login.html:45
+#: users/templates/users/login_log_list.html:54
msgid "MFA"
msgstr "MFA"
-#: users/forms.py:139
+#: users/forms.py:148
msgid ""
"In order to protect you and your company, please keep your account, password "
"and key sensitive information properly. (for example: setting complex "
@@ -2291,42 +2445,43 @@ msgstr ""
"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
"设置复杂密码,启用MFA认证)"
-#: users/forms.py:146 users/templates/users/first_login.html:48
-#: users/templates/users/first_login.html:110
+#: users/forms.py:155 users/templates/users/first_login.html:48
+#: users/templates/users/first_login.html:107
+#: users/templates/users/first_login.html:130
msgid "Finish"
msgstr "完成"
-#: users/forms.py:152
+#: users/forms.py:161
msgid "Old password"
msgstr "原来密码"
-#: users/forms.py:157
+#: users/forms.py:166
msgid "New password"
msgstr "新密码"
-#: users/forms.py:162
+#: users/forms.py:171
msgid "Confirm password"
msgstr "确认密码"
-#: users/forms.py:172
+#: users/forms.py:181
msgid "Old password error"
msgstr "原来密码错误"
-#: users/forms.py:180
+#: users/forms.py:189
msgid "Password does not match"
msgstr "密码不一致"
-#: users/forms.py:191
+#: users/forms.py:200
msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥"
-#: users/forms.py:195
+#: users/forms.py:204
msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里"
-#: users/forms.py:223 users/models/user.py:72
+#: users/forms.py:232 users/models/user.py:79
#: users/templates/users/first_login.html:42
-#: users/templates/users/user_password_update.html:43
+#: users/templates/users/user_password_update.html:45
#: users/templates/users/user_profile.html:68
#: users/templates/users/user_profile_update.html:43
#: users/templates/users/user_pubkey_update.html:43
@@ -2337,63 +2492,99 @@ msgstr "ssh公钥"
msgid "Private Token"
msgstr "ssh密钥"
-#: users/models/authentication.py:46
+#: users/models/authentication.py:50 users/templates/users/user_detail.html:98
+msgid "Disabled"
+msgstr "禁用"
+
+#: users/models/authentication.py:52 users/models/authentication.py:60
+msgid "-"
+msgstr ""
+
+#: users/models/authentication.py:61
+msgid "Username/password check failed"
+msgstr "用户名/密码 校验失败"
+
+#: users/models/authentication.py:62
+msgid "MFA authentication failed"
+msgstr "MFA 认证失败"
+
+#: users/models/authentication.py:67
+msgid "Failed"
+msgstr "失败"
+
+#: users/models/authentication.py:71
msgid "Login type"
msgstr "登录方式"
-#: users/models/authentication.py:47
+#: users/models/authentication.py:72
msgid "Login ip"
msgstr "登录IP"
-#: users/models/authentication.py:48
+#: users/models/authentication.py:73
msgid "Login city"
msgstr "登录城市"
-#: users/models/authentication.py:49
+#: users/models/authentication.py:74
msgid "User agent"
msgstr "Agent"
-#: users/models/authentication.py:50
+#: users/models/authentication.py:76
+#: users/templates/users/login_log_list.html:55
+msgid "Reason"
+msgstr "原因"
+
+#: users/models/authentication.py:77
+#: users/templates/users/login_log_list.html:56
+msgid "Status"
+msgstr "状态"
+
+#: users/models/authentication.py:78
msgid "Date login"
msgstr "登录日期"
-#: users/models/user.py:29 users/models/user.py:326
+#: users/models/user.py:30 users/models/user.py:341
msgid "Administrator"
msgstr "管理员"
-#: users/models/user.py:31
+#: users/models/user.py:32
msgid "Application"
msgstr "应用程序"
-#: users/models/user.py:34 users/templates/users/user_profile.html:92
-#: users/templates/users/user_profile.html:156
-#: users/templates/users/user_profile.html:159
+#: users/models/user.py:35 users/templates/users/user_profile.html:92
+#: users/templates/users/user_profile.html:163
+#: users/templates/users/user_profile.html:166
msgid "Disable"
msgstr "禁用"
-#: users/models/user.py:35 users/templates/users/user_profile.html:90
-#: users/templates/users/user_profile.html:163
+#: users/models/user.py:36 users/templates/users/user_profile.html:90
+#: users/templates/users/user_profile.html:170
msgid "Enable"
msgstr "启用"
-#: users/models/user.py:36 users/templates/users/user_profile.html:88
+#: users/models/user.py:37 users/templates/users/user_profile.html:88
msgid "Force enable"
msgstr "强制启用"
-#: users/models/user.py:44 users/templates/users/user_detail.html:71
+#: users/models/user.py:51 users/templates/users/user_detail.html:71
#: users/templates/users/user_profile.html:59
msgid "Email"
msgstr "邮件"
-#: users/models/user.py:55
+#: users/models/user.py:62
msgid "Avatar"
msgstr "头像"
-#: users/models/user.py:58 users/templates/users/user_detail.html:82
+#: users/models/user.py:65 users/templates/users/user_detail.html:82
msgid "Wechat"
msgstr "微信"
-#: users/models/user.py:329
+#: users/models/user.py:94 users/templates/users/user_detail.html:103
+#: users/templates/users/user_list.html:27
+#: users/templates/users/user_profile.html:100
+msgid "Source"
+msgstr "用户来源"
+
+#: users/models/user.py:344
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
@@ -2443,11 +2634,15 @@ msgstr "首次登陆"
msgid "I agree with the terms and conditions."
msgstr "我同意条款和条件"
-#: users/templates/users/first_login.html:100
+#: users/templates/users/first_login.html:73
+msgid "Please choose the terms and conditions."
+msgstr "请选择同意条款和条件"
+
+#: users/templates/users/first_login.html:101
msgid "Previous"
msgstr "上一步"
-#: users/templates/users/first_login.html:108
+#: users/templates/users/first_login.html:105
#: users/templates/users/login_otp.html:66
#: users/templates/users/user_otp_authentication.html:22
#: users/templates/users/user_otp_enable_bind.html:19
@@ -2469,7 +2664,7 @@ msgid " for more information"
msgstr "获取更多信息"
#: users/templates/users/forgot_password.html:26
-#: users/templates/users/login.html:73
+#: users/templates/users/login.html:77
msgid "Forgot password"
msgstr "忘记密码"
@@ -2477,7 +2672,7 @@ msgstr "忘记密码"
msgid "Input your email, that will send a mail to your"
msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中"
-#: users/templates/users/login.html:50
+#: users/templates/users/login.html:53
msgid "Captcha invalid"
msgstr "验证码错误"
@@ -2505,22 +2700,34 @@ msgstr "6位数字"
msgid "Can't provide security? Please contact the administrator!"
msgstr "如果不能提供MFA验证码,请联系管理员!"
-#: users/templates/users/reset_password.html:45
-#: users/templates/users/user_detail.html:348 users/utils.py:73
+#: users/templates/users/reset_password.html:46
+#: users/templates/users/user_detail.html:352 users/utils.py:80
msgid "Reset password"
msgstr "重置密码"
-#: users/templates/users/reset_password.html:55
+#: users/templates/users/reset_password.html:59
+#: users/templates/users/user_password_update.html:60
+#: users/templates/users/user_update.html:12
+msgid "Your password must satisfy"
+msgstr "您的密码必须满足:"
+
+#: users/templates/users/reset_password.html:60
+#: users/templates/users/user_password_update.html:61
+#: users/templates/users/user_update.html:13
+msgid "Password strength"
+msgstr "密码强度:"
+
+#: users/templates/users/reset_password.html:66
msgid "Password again"
msgstr "再次输入密码"
-#: users/templates/users/reset_password.html:57
+#: users/templates/users/reset_password.html:68
#: users/templates/users/user_profile.html:20
msgid "Setting"
msgstr "设置"
#: users/templates/users/user_create.html:4
-#: users/templates/users/user_list.html:16 users/views/user.py:79
+#: users/templates/users/user_list.html:16 users/views/user.py:80
msgid "Create user"
msgstr "创建用户"
@@ -2529,7 +2736,7 @@ msgid "Reset link will be generated and sent to the user. "
msgstr "生成重置密码连接,通过邮件发送给用户"
#: users/templates/users/user_detail.html:19
-#: users/templates/users/user_granted_asset.html:18 users/views/user.py:156
+#: users/templates/users/user_granted_asset.html:18 users/views/user.py:176
msgid "User detail"
msgstr "用户详情"
@@ -2544,67 +2751,63 @@ msgstr "授权的资产"
msgid "Force enabled"
msgstr "强制启用"
-#: users/templates/users/user_detail.html:98
-msgid "Disabled"
-msgstr "禁用"
-
-#: users/templates/users/user_detail.html:115
-#: users/templates/users/user_profile.html:101
+#: users/templates/users/user_detail.html:119
+#: users/templates/users/user_profile.html:108
msgid "Last login"
msgstr "最后登录"
-#: users/templates/users/user_detail.html:151
+#: users/templates/users/user_detail.html:155
msgid "Force enabled MFA"
msgstr "强制启用MFA"
-#: users/templates/users/user_detail.html:166
+#: users/templates/users/user_detail.html:170
msgid "Send reset password mail"
msgstr "发送重置密码邮件"
-#: users/templates/users/user_detail.html:169
-#: users/templates/users/user_detail.html:177
+#: users/templates/users/user_detail.html:173
+#: users/templates/users/user_detail.html:181
msgid "Send"
msgstr "发送"
-#: users/templates/users/user_detail.html:174
+#: users/templates/users/user_detail.html:178
msgid "Send reset ssh key mail"
msgstr "发送重置密钥邮件"
-#: users/templates/users/user_detail.html:291
+#: users/templates/users/user_detail.html:295
msgid "Goto profile page enable MFA"
msgstr "请去个人信息页面启用自己的MFA"
-#: users/templates/users/user_detail.html:347
+#: users/templates/users/user_detail.html:351
msgid "An e-mail has been sent to the user`s mailbox."
msgstr "已发送邮件到用户邮箱"
-#: users/templates/users/user_detail.html:358
+#: users/templates/users/user_detail.html:362
msgid "This will reset the user password and send a reset mail"
msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱"
-#: users/templates/users/user_detail.html:372
+#: users/templates/users/user_detail.html:376
msgid ""
"The reset-ssh-public-key E-mail has been sent successfully. Please inform "
"the user to update his new ssh public key."
msgstr "重设密钥邮件将会发送到用户邮箱"
-#: users/templates/users/user_detail.html:373
+#: users/templates/users/user_detail.html:377
msgid "Reset SSH public key"
msgstr "重置SSH密钥"
-#: users/templates/users/user_detail.html:383
+#: users/templates/users/user_detail.html:387
msgid "This will reset the user public key and send a reset mail"
msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱"
-#: users/templates/users/user_detail.html:400
-#: users/templates/users/user_profile.html:204
+#: users/templates/users/user_detail.html:404
+#: users/templates/users/user_profile.html:211
msgid "Successfully updated the SSH public key."
msgstr "更新ssh密钥成功"
-#: users/templates/users/user_detail.html:401
#: users/templates/users/user_detail.html:405
-#: users/templates/users/user_profile.html:205
-#: users/templates/users/user_profile.html:210
+#: users/templates/users/user_detail.html:409
+#: users/templates/users/user_profile.html:212
+#: users/templates/users/user_profile.html:217
msgid "User SSH public key update"
msgstr "ssh密钥"
@@ -2643,45 +2846,49 @@ msgstr "用户组删除"
msgid "UserGroup Deleting failed."
msgstr "用户组删除失败"
-#: users/templates/users/user_list.html:192
+#: users/templates/users/user_list.html:196
msgid "This will delete the selected users !!!"
msgstr "删除选中用户 !!!"
-#: users/templates/users/user_list.html:200
+#: users/templates/users/user_list.html:204
msgid "User Deleted."
msgstr "已被删除"
-#: users/templates/users/user_list.html:201
-#: users/templates/users/user_list.html:206
+#: users/templates/users/user_list.html:205
+#: users/templates/users/user_list.html:210
msgid "User Delete"
msgstr "删除"
-#: users/templates/users/user_list.html:205
+#: users/templates/users/user_list.html:209
msgid "User Deleting failed."
msgstr "用户删除失败"
-#: users/templates/users/user_profile.html:109 users/views/user.py:185
-#: users/views/user.py:239
+#: users/templates/users/user_profile.html:95
+msgid "Administrator Settings force MFA login"
+msgstr "管理员设置强制使用MFA登录"
+
+#: users/templates/users/user_profile.html:116 users/views/user.py:205
+#: users/views/user.py:259
msgid "User groups"
msgstr "用户组"
-#: users/templates/users/user_profile.html:141
+#: users/templates/users/user_profile.html:148
msgid "Update password"
msgstr "更改密码"
-#: users/templates/users/user_profile.html:149
+#: users/templates/users/user_profile.html:156
msgid "Update MFA settings"
msgstr "更改MFA设置"
-#: users/templates/users/user_profile.html:170
+#: users/templates/users/user_profile.html:177
msgid "Update SSH public key"
msgstr "更改SSH密钥"
-#: users/templates/users/user_profile.html:178
+#: users/templates/users/user_profile.html:185
msgid "Reset public key and download"
msgstr "重置并下载SSH密钥"
-#: users/templates/users/user_profile.html:208
+#: users/templates/users/user_profile.html:215
msgid "Failed to update SSH public key."
msgstr "更新密钥失败"
@@ -2701,15 +2908,21 @@ msgstr "更新密钥"
msgid "Or reset by server"
msgstr "或者重置并下载密钥"
-#: users/templates/users/user_update.html:4 users/views/user.py:99
+#: users/templates/users/user_pubkey_update.html:94
+msgid ""
+"The new public key has been set successfully, Please download the "
+"corresponding private key."
+msgstr "新的公钥已设置成功,请下载对应的私钥"
+
+#: users/templates/users/user_update.html:4 users/views/user.py:103
msgid "Update user"
msgstr "更新用户"
-#: users/utils.py:37
+#: users/utils.py:41
msgid "Create account successfully"
msgstr "创建账户成功"
-#: users/utils.py:39
+#: users/utils.py:43
#, python-format
msgid ""
"\n"
@@ -2717,6 +2930,8 @@ msgid ""
" \n"
" Your account has been created successfully\n"
" \n"
+" Username: %(username)s\n"
+" \n"
" click "
"here to set your password\n"
" \n"
@@ -2736,6 +2951,8 @@ msgstr ""
" 你好 %(name)s:\n"
" \n"
" 恭喜您,您的账号已经创建成功 \n"
+" 用户名: %(username)s\n"
+" \n"
" 请点击这"
"里设置密码 \n"
" 这个链接有效期1小时, 超过时间您可以 \n"
" "
-#: users/utils.py:75
+#: users/utils.py:82
#, python-format
msgid ""
"\n"
@@ -2794,11 +3011,11 @@ msgstr ""
" \n"
" "
-#: users/utils.py:106
+#: users/utils.py:113
msgid "SSH Key Reset"
msgstr "重置ssh密钥"
-#: users/utils.py:108
+#: users/utils.py:115
#, python-format
msgid ""
"\n"
@@ -2823,18 +3040,22 @@ msgstr ""
" \n"
" "
-#: users/utils.py:141
+#: users/utils.py:148
msgid "User not exist"
msgstr "用户不存在"
-#: users/utils.py:143
+#: users/utils.py:150
msgid "Disabled or expired"
msgstr "禁用或失效"
-#: users/utils.py:156
+#: users/utils.py:163
msgid "Password or SSH public key invalid"
msgstr "密码或密钥不合法"
+#: users/utils.py:289 users/utils.py:299
+msgid "Bit"
+msgstr " 位"
+
#: users/views/group.py:29
msgid "User group list"
msgstr "用户组列表"
@@ -2847,105 +3068,106 @@ msgstr "更新用户组"
msgid "User group granted asset"
msgstr "用户组授权资产"
-#: users/views/login.py:56
+#: users/views/login.py:75
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
-#: users/views/login.py:107 users/views/user.py:464 users/views/user.py:489
+#: users/views/login.py:178 users/views/user.py:500 users/views/user.py:525
msgid "MFA code invalid"
msgstr "MFA码认证失败"
-#: users/views/login.py:133
+#: users/views/login.py:207
msgid "Logout success"
msgstr "退出登录成功"
-#: users/views/login.py:134
+#: users/views/login.py:208
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
-#: users/views/login.py:150
+#: users/views/login.py:224
msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入"
-#: users/views/login.py:163
+#: users/views/login.py:237
msgid "Send reset password message"
msgstr "发送重置密码邮件"
-#: users/views/login.py:164
+#: users/views/login.py:238
msgid "Send reset password mail success, login your mail box and follow it "
msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
-#: users/views/login.py:177
+#: users/views/login.py:251
msgid "Reset password success"
msgstr "重置密码成功"
-#: users/views/login.py:178
+#: users/views/login.py:252
msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面"
-#: users/views/login.py:195 users/views/login.py:208
+#: users/views/login.py:273 users/views/login.py:286
msgid "Token invalid or expired"
msgstr "Token错误或失效"
-#: users/views/login.py:204
+#: users/views/login.py:282
msgid "Password not same"
msgstr "密码不一致"
-#: users/views/login.py:241
+#: users/views/login.py:292 users/views/user.py:118 users/views/user.py:398
+msgid "* Your password does not meet the requirements"
+msgstr "* 您的密码不符合要求"
+
+#: users/views/login.py:330
msgid "First login"
msgstr "首次登陆"
-#: users/views/login.py:290
+#: users/views/login.py:389
msgid "Login log list"
msgstr "登录日志"
-#: users/views/user.py:109
+#: users/views/user.py:129
msgid "Bulk update user success"
msgstr "批量更新用户成功"
-#: users/views/user.py:214
+#: users/views/user.py:234
msgid "Invalid file."
msgstr "文件不合法"
-#: users/views/user.py:311
+#: users/views/user.py:331
msgid "User granted assets"
msgstr "用户授权资产"
-#: users/views/user.py:340
+#: users/views/user.py:362
msgid "Profile setting"
msgstr "个人信息设置"
-#: users/views/user.py:358
+#: users/views/user.py:381
msgid "Password update"
msgstr "密码更新"
-#: users/views/user.py:380
+#: users/views/user.py:416
msgid "Public key update"
msgstr "密钥更新"
-#: users/views/user.py:421
+#: users/views/user.py:457
msgid "Password invalid"
msgstr "用户名或密码无效"
-#: users/views/user.py:515
+#: users/views/user.py:551
msgid "MFA enable success"
msgstr "MFA 绑定成功"
-#: users/views/user.py:516
+#: users/views/user.py:552
msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面"
-#: users/views/user.py:518
+#: users/views/user.py:554
msgid "MFA disable success"
msgstr "MFA 解绑成功"
-#: users/views/user.py:519
+#: users/views/user.py:555
msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面"
-#~ msgid "Step"
-#~ msgstr "Step"
-
-#~ msgid "Add asset"
-#~ msgstr "添加资产到节点"
+#~ msgid "MFA setting"
+#~ msgstr "MFA 设置"
diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py
index 0afcd4e72..c0a536276 100644
--- a/apps/jumpserver/settings.py
+++ b/apps/jumpserver/settings.py
@@ -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'
@@ -336,10 +343,11 @@ if AUTH_LDAP:
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
# Celery using redis as broker
-CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/3' % {
+CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % {
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
'host': CONFIG.REDIS_HOST or '127.0.0.1',
'port': CONFIG.REDIS_PORT or 6379,
+ 'db':CONFIG.REDIS_DB_CELERY_BROKER or 3,
}
CELERY_TASK_SERIALIZER = 'pickle'
CELERY_RESULT_SERIALIZER = 'pickle'
@@ -360,10 +368,11 @@ CELERY_WORKER_HIJACK_ROOT_LOGGER = False
CACHES = {
'default': {
'BACKEND': 'redis_cache.RedisCache',
- 'LOCATION': 'redis://:%(password)s@%(host)s:%(port)s/4' % {
+ 'LOCATION': 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % {
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
'host': CONFIG.REDIS_HOST or '127.0.0.1',
'port': CONFIG.REDIS_PORT or 6379,
+ 'db':CONFIG.REDIS_DB_CACHE or 4,
}
}
}
@@ -394,6 +403,11 @@ TERMINAL_REPLAY_STORAGE = {
},
}
+
+DEFAULT_PASSWORD_MIN_LENGTH = 6
+DEFAULT_LOGIN_LIMIT_COUNT = 3
+DEFAULT_LOGIN_LIMIT_TIME = 30
+
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3 = {
'horizontal_label_class': 'col-md-2',
diff --git a/apps/ops/api.py b/apps/ops/api.py
index 0134fbd4a..35c9b8dc5 100644
--- a/apps/ops/api.py
+++ b/apps/ops/api.py
@@ -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):
diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py
index 6230e8167..7ed89e75c 100644
--- a/apps/ops/inventory.py
+++ b/apps/ops/inventory.py
@@ -86,13 +86,14 @@ class JMSInventory(BaseInventory):
gateway = asset.domain.random_gateway()
proxy_command_list = [
"ssh", "-p", str(gateway.port),
+ "-o", "StrictHostKeyChecking=no",
"{}@{}".format(gateway.username, gateway.ip),
"-W", "%h:%p", "-q",
]
if gateway.password:
proxy_command_list.insert(
- 0, "sshpass -p {}".format(gateway.password)
+ 0, "sshpass -p '{}'".format(gateway.password)
)
if gateway.private_key:
proxy_command_list.append("-i {}".format(gateway.private_key_file))
diff --git a/apps/ops/templates/ops/adhoc_history_detail.html b/apps/ops/templates/ops/adhoc_history_detail.html
index 16adbc4e3..b8a48d4e5 100644
--- a/apps/ops/templates/ops/adhoc_history_detail.html
+++ b/apps/ops/templates/ops/adhoc_history_detail.html
@@ -19,7 +19,7 @@
{% trans 'Run history detail' %}
- {% trans 'Output' %}
+ {% trans 'Output' %}
diff --git a/apps/ops/templates/ops/celery_task_log.html b/apps/ops/templates/ops/celery_task_log.html
index 9b0826949..13885c2cc 100644
--- a/apps/ops/templates/ops/celery_task_log.html
+++ b/apps/ops/templates/ops/celery_task_log.html
@@ -2,38 +2,25 @@
term.js
+
+
-
-
-
-
+
{% block custom_head_css_js_create %} {% endblock %}
{% endblock %}
diff --git a/apps/templates/_footer.html b/apps/templates/_footer.html
index f23c89399..c78f0da33 100644
--- a/apps/templates/_footer.html
+++ b/apps/templates/_footer.html
@@ -1,7 +1,7 @@
+
{% endblock %}
{% block custom_foot_js %}
diff --git a/apps/users/templates/users/first_login.html b/apps/users/templates/users/first_login.html
index 7138199a3..9687949d1 100644
--- a/apps/users/templates/users/first_login.html
+++ b/apps/users/templates/users/first_login.html
@@ -70,6 +70,7 @@
+
* {% trans 'Please choose the terms and conditions.' %}
{% endif %}
{% bootstrap_form wizard.form %}
@@ -99,11 +100,7 @@
{% if wizard.steps.prev %}
{% trans "Previous" %}
{% endif %}
- {#{% if wizard.steps.next %}#}
- {#
{% trans "Next" %}#}
- {#{% endif %}#}
- {#
{% trans "Submit" %}#}
- {#将原来的下一页-替换为提交;修复 每页都提交,最后才能成功问题#}
+
{% if wizard.steps.next %}
{% trans "Next" %}
{% else %}
@@ -124,20 +121,26 @@
{% block custom_foot_js %}
{% endblock %}
diff --git a/apps/users/templates/users/login.html b/apps/users/templates/users/login.html
index 7dd3f5d0a..6b55a58bf 100644
--- a/apps/users/templates/users/login.html
+++ b/apps/users/templates/users/login.html
@@ -45,13 +45,17 @@