Unverified Commit 1194932b authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #1770 from jumpserver/dev

settings加密存储
单独推送系统用户到某个资产
支持了 改密日志 操作日志
翻译更加完善,支持切换语言
parents 9f96f1c5 bb13003a
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
__version__ = "1.4.0" __version__ = "1.4.1"
...@@ -2,9 +2,8 @@ ...@@ -2,9 +2,8 @@
# #
import random import random
import time
from rest_framework import generics, permissions from rest_framework import generics
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
...@@ -14,8 +13,8 @@ from django.db.models import Q ...@@ -14,8 +13,8 @@ from django.db.models import Q
from common.mixins import IDInFilterMixin from common.mixins import IDInFilterMixin
from common.utils import get_logger from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsAppUser, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from ..models import Asset, SystemUser, AdminUser, Node from ..models import Asset, AdminUser, Node
from .. import serializers from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \ from ..tasks import update_asset_hardware_info_manual, \
test_asset_connectability_manual test_asset_connectability_manual
...@@ -40,7 +39,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): ...@@ -40,7 +39,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
queryset = Asset.objects.all() queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination pagination_class = LimitOffsetPagination
permission_classes = (permissions.AllowAny,) permission_classes = (IsOrgAdminOrAppUser,)
def filter_node(self): def filter_node(self):
node_id = self.request.query_params.get("node_id") node_id = self.request.query_params.get("node_id")
......
...@@ -55,7 +55,7 @@ class NodeViewSet(viewsets.ModelViewSet): ...@@ -55,7 +55,7 @@ class NodeViewSet(viewsets.ModelViewSet):
post_value = request.data.get('value') post_value = request.data.get('value')
if node_value != post_value: if node_value != post_value:
return Response( return Response(
{"msg": _("You cant update the root node name")}, {"msg": _("You can't update the root node name")},
status=400 status=400
) )
return super().update(request, *args, **kwargs) return super().update(request, *args, **kwargs)
...@@ -218,7 +218,8 @@ class RefreshNodeHardwareInfoApi(APIView): ...@@ -218,7 +218,8 @@ class RefreshNodeHardwareInfoApi(APIView):
node_id = kwargs.get('pk') node_id = kwargs.get('pk')
node = get_object_or_404(self.model, id=node_id) node = get_object_or_404(self.model, id=node_id)
assets = node.assets.all() assets = node.assets.all()
task_name = _("更新节点资产硬件信息: {}".format(node.name)) # task_name = _("更新节点资产硬件信息: {}".format(node.name))
task_name = _("Update node asset hardware information: {}").format(node.name)
task = update_assets_hardware_info_util.delay(assets, task_name=task_name) task = update_assets_hardware_info_util.delay(assets, task_name=task_name)
return Response({"task": task.id}) return Response({"task": task.id})
...@@ -231,6 +232,7 @@ class TestNodeConnectiveApi(APIView): ...@@ -231,6 +232,7 @@ class TestNodeConnectiveApi(APIView):
node_id = kwargs.get('pk') node_id = kwargs.get('pk')
node = get_object_or_404(self.model, id=node_id) node = get_object_or_404(self.model, id=node_id)
assets = node.assets.all() assets = node.assets.all()
task_name = _("测试节点下资产是否可连接: {}".format(node.name)) # task_name = _("测试节点下资产是否可连接: {}".format(node.name))
task_name = _("Test if the assets under the node are connectable: {}".format(node.name))
task = test_asset_connectability_util.delay(assets, task_name=task_name) task = test_asset_connectability_util.delay(assets, task_name=task_name)
return Response({"task": task.id}) return Response({"task": task.id})
...@@ -13,22 +13,27 @@ ...@@ -13,22 +13,27 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from django.shortcuts import get_object_or_404
from rest_framework import generics from rest_framework import generics
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination
from common.utils import get_logger from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from ..models import SystemUser from ..models import SystemUser, Asset
from .. import serializers from .. import serializers
from ..tasks import push_system_user_to_assets_manual, \ from ..tasks import push_system_user_to_assets_manual, \
test_system_user_connectability_manual test_system_user_connectability_manual, push_system_user_a_asset_manual, \
test_system_user_connectability_a_asset
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = [ __all__ = [
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserViewSet', 'SystemUserAuthInfoApi',
'SystemUserPushApi', 'SystemUserTestConnectiveApi' 'SystemUserPushApi', 'SystemUserTestConnectiveApi',
'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
'SystemUserTestAssetConnectabilityApi',
] ]
...@@ -82,3 +87,43 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView): ...@@ -82,3 +87,43 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
system_user = self.get_object() system_user = self.get_object()
task = test_system_user_connectability_manual.delay(system_user) task = test_system_user_connectability_manual.delay(system_user)
return Response({"task": task.id}) return Response({"task": task.id})
class SystemUserAssetsListView(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination
filter_fields = ("hostname", "ip")
search_fields = filter_fields
def get_object(self):
pk = self.kwargs.get('pk')
return get_object_or_404(SystemUser, pk=pk)
def get_queryset(self):
system_user = self.get_object()
return system_user.assets.all()
class SystemUserPushToAssetApi(generics.RetrieveAPIView):
queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
asset_id = self.kwargs.get('aid')
asset = get_object_or_404(Asset, id=asset_id)
task = push_system_user_a_asset_manual.delay(system_user, asset)
return Response({"task": task.id})
class SystemUserTestAssetConnectabilityApi(generics.RetrieveAPIView):
queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
asset_id = self.kwargs.get('aid')
asset = get_object_or_404(Asset, id=asset_id)
task = test_system_user_connectability_a_asset.delay(system_user, asset)
return Response({"task": task.id})
\ No newline at end of file
...@@ -98,15 +98,18 @@ class SystemUserForm(PasswordAndKeyAuthForm): ...@@ -98,15 +98,18 @@ class SystemUserForm(PasswordAndKeyAuthForm):
auto_generate_key = self.cleaned_data.get('auto_generate_key', False) auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
private_key, public_key = super().gen_keys() private_key, public_key = super().gen_keys()
if login_mode == SystemUser.MANUAL_LOGIN or protocol == SystemUser.TELNET_PROTOCOL: if login_mode == SystemUser.MANUAL_LOGIN or \
protocol in [SystemUser.RDP_PROTOCOL, SystemUser.TELNET_PROTOCOL]:
system_user.auto_push = 0 system_user.auto_push = 0
auto_generate_key = False
system_user.save() system_user.save()
if auto_generate_key: if auto_generate_key:
logger.info('Auto generate key and set system user auth') logger.info('Auto generate key and set system user auth')
system_user.auto_gen_auth() system_user.auto_gen_auth()
else: else:
system_user.set_auth(password=password, private_key=private_key, public_key=public_key) system_user.set_auth(password=password, private_key=private_key,
public_key=public_key)
return system_user return system_user
......
...@@ -52,7 +52,8 @@ class Cluster(models.Model): ...@@ -52,7 +52,8 @@ class Cluster(models.Model):
contact=forgery_py.name.full_name(), contact=forgery_py.name.full_name(),
phone=forgery_py.address.phone(), phone=forgery_py.address.phone(),
address=forgery_py.address.city() + forgery_py.address.street_address(), address=forgery_py.address.city() + forgery_py.address.street_address(),
operator=choice(['北京联通', '北京电信', 'BGP全网通']), # operator=choice(['北京联通', '北京电信', 'BGP全网通']),
operator=choice([_('Beijing unicom'), _('Beijing telecom'), _('BGP full netcom')]),
comment=forgery_py.lorem_ipsum.sentence(), comment=forgery_py.lorem_ipsum.sentence(),
created_by='Fake') created_by='Fake')
try: try:
......
...@@ -20,6 +20,9 @@ class Domain(OrgModelMixin): ...@@ -20,6 +20,9 @@ class Domain(OrgModelMixin):
date_created = models.DateTimeField(auto_now_add=True, null=True, date_created = models.DateTimeField(auto_now_add=True, null=True,
verbose_name=_('Date created')) verbose_name=_('Date created'))
class Meta:
verbose_name = _("Domain")
def __str__(self): def __str__(self):
return self.name return self.name
...@@ -53,3 +56,4 @@ class Gateway(AssetUser): ...@@ -53,3 +56,4 @@ class Gateway(AssetUser):
class Meta: class Meta:
unique_together = [('name', 'org_id')] unique_together = [('name', 'org_id')]
verbose_name = _("Gateway")
...@@ -24,6 +24,9 @@ class Node(OrgModelMixin): ...@@ -24,6 +24,9 @@ class Node(OrgModelMixin):
is_node = True is_node = True
_full_value_cache_key_prefix = '_NODE_VALUE_{}' _full_value_cache_key_prefix = '_NODE_VALUE_{}'
class Meta:
verbose_name = _("Node")
def __str__(self): def __str__(self):
return self.full_value return self.full_value
......
...@@ -93,8 +93,8 @@ def update_assets_hardware_info_util(assets, task_name=None): ...@@ -93,8 +93,8 @@ def update_assets_hardware_info_util(assets, task_name=None):
""" """
from ops.utils import update_or_create_ansible_task from ops.utils import update_or_create_ansible_task
if task_name is None: if task_name is None:
# task_name = _("Update some assets hardware info") task_name = _("Update some assets hardware info")
task_name = _("更新资产硬件信息") # task_name = _("更新资产硬件信息")
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
hostname_list = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()] hostname_list = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
if not hostname_list: if not hostname_list:
...@@ -113,8 +113,8 @@ def update_assets_hardware_info_util(assets, task_name=None): ...@@ -113,8 +113,8 @@ def update_assets_hardware_info_util(assets, task_name=None):
@shared_task @shared_task
def update_asset_hardware_info_manual(asset): def update_asset_hardware_info_manual(asset):
# task_name = _("Update asset hardware info") task_name = _("Update asset hardware info")
task_name = _("更新资产硬件信息") # task_name = _("更新资产硬件信息")
return update_assets_hardware_info_util([asset], task_name=task_name) return update_assets_hardware_info_util([asset], task_name=task_name)
...@@ -132,8 +132,8 @@ def update_assets_hardware_info_period(): ...@@ -132,8 +132,8 @@ def update_assets_hardware_info_period():
return return
from ops.utils import update_or_create_ansible_task from ops.utils import update_or_create_ansible_task
# task_name = _("Update assets hardware info period") task_name = _("Update assets hardware info period")
task_name = _("定期更新资产硬件信息") # task_name = _("定期更新资产硬件信息")
hostname_list = [ hostname_list = [
asset.fullname for asset in Asset.objects.all() asset.fullname for asset in Asset.objects.all()
if asset.is_active and asset.is_unixlike() if asset.is_active and asset.is_unixlike()
...@@ -210,15 +210,15 @@ def test_admin_user_connectability_period(): ...@@ -210,15 +210,15 @@ def test_admin_user_connectability_period():
admin_users = AdminUser.objects.all() admin_users = AdminUser.objects.all()
for admin_user in admin_users: for admin_user in admin_users:
# task_name = _("Test admin user connectability period: {}".format(admin_user.name)) task_name = _("Test admin user connectability period: {}".format(admin_user.name))
task_name = _("定期测试管理账号可连接性: {}".format(admin_user.name)) # task_name = _("定期测试管理账号可连接性: {}".format(admin_user.name))
test_admin_user_connectability_util(admin_user, task_name) test_admin_user_connectability_util(admin_user, task_name)
@shared_task @shared_task
def test_admin_user_connectability_manual(admin_user): def test_admin_user_connectability_manual(admin_user):
# task_name = _("Test admin user connectability: {}").format(admin_user.name) task_name = _("Test admin user connectability: {}").format(admin_user.name)
task_name = _("测试管理行号可连接性: {}").format(admin_user.name) # task_name = _("测试管理行号可连接性: {}").format(admin_user.name)
return test_admin_user_connectability_util(admin_user, task_name) return test_admin_user_connectability_util(admin_user, task_name)
...@@ -227,8 +227,8 @@ def test_asset_connectability_util(assets, task_name=None): ...@@ -227,8 +227,8 @@ def test_asset_connectability_util(assets, task_name=None):
from ops.utils import update_or_create_ansible_task from ops.utils import update_or_create_ansible_task
if task_name is None: if task_name is None:
# task_name = _("Test assets connectability") task_name = _("Test assets connectability")
task_name = _("测试资产可连接性") # task_name = _("测试资产可连接性")
hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()] hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
if not hosts: if not hosts:
logger.info("No hosts, passed") logger.info("No hosts, passed")
...@@ -272,15 +272,16 @@ def set_system_user_connectablity_info(result, **kwargs): ...@@ -272,15 +272,16 @@ def set_system_user_connectablity_info(result, **kwargs):
@shared_task @shared_task
def test_system_user_connectability_util(system_user, task_name): def test_system_user_connectability_util(system_user, assets, task_name):
""" """
Test system cant connect his assets or not. Test system cant connect his assets or not.
:param system_user: :param system_user:
:param assets:
:param task_name: :param task_name:
:return: :return:
""" """
from ops.utils import update_or_create_ansible_task from ops.utils import update_or_create_ansible_task
assets = system_user.get_assets() # assets = system_user.get_assets()
hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()] hosts = [asset.fullname for asset in assets if asset.is_active and asset.is_unixlike()]
tasks = const.TEST_SYSTEM_USER_CONN_TASKS tasks = const.TEST_SYSTEM_USER_CONN_TASKS
if not hosts: if not hosts:
...@@ -299,7 +300,16 @@ def test_system_user_connectability_util(system_user, task_name): ...@@ -299,7 +300,16 @@ def test_system_user_connectability_util(system_user, task_name):
@shared_task @shared_task
def test_system_user_connectability_manual(system_user): def test_system_user_connectability_manual(system_user):
task_name = _("Test system user connectability: {}").format(system_user) task_name = _("Test system user connectability: {}").format(system_user)
return test_system_user_connectability_util(system_user, task_name) assets = system_user.get_assets()
return test_system_user_connectability_util(system_user, assets, task_name)
@shared_task
def test_system_user_connectability_a_asset(system_user, asset):
task_name = _("Test system user connectability: {} => {}").format(
system_user, asset
)
return test_system_user_connectability_util(system_user, [asset], task_name)
@shared_task @shared_task
...@@ -313,8 +323,8 @@ def test_system_user_connectability_period(): ...@@ -313,8 +323,8 @@ def test_system_user_connectability_period():
system_users = SystemUser.objects.all() system_users = SystemUser.objects.all()
for system_user in system_users: for system_user in system_users:
# task_name = _("Test system user connectability period: {}".format(system_user)) task_name = _("Test system user connectability period: {}".format(system_user))
task_name = _("定期测试系统用户可连接性: {}".format(system_user)) # task_name = _("定期测试系统用户可连接性: {}".format(system_user))
test_system_user_connectability_util(system_user, task_name) test_system_user_connectability_util(system_user, task_name)
...@@ -393,13 +403,23 @@ def push_system_user_util(system_users, assets, task_name): ...@@ -393,13 +403,23 @@ def push_system_user_util(system_users, assets, task_name):
@shared_task @shared_task
def push_system_user_to_assets_manual(system_user): def push_system_user_to_assets_manual(system_user):
assets = system_user.get_assets() assets = system_user.get_assets()
task_name = "推送系统用户到入资产: {}".format(system_user.name) # task_name = "推送系统用户到入资产: {}".format(system_user.name)
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util([system_user], assets, task_name=task_name) return push_system_user_util([system_user], assets, task_name=task_name)
@shared_task
def push_system_user_a_asset_manual(system_user, asset):
task_name = _("Push system users to asset: {} => {}").format(
system_user.name, asset.fullname
)
return push_system_user_util([system_user], [asset], task_name=task_name)
@shared_task @shared_task
def push_system_user_to_assets(system_user, assets): def push_system_user_to_assets(system_user, assets):
task_name = _("推送系统用户到入资产: {}").format(system_user.name) # task_name = _("推送系统用户到入资产: {}").format(system_user.name)
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util.delay([system_user], assets, task_name) return push_system_user_util.delay([system_user], assets, task_name)
......
...@@ -5,8 +5,11 @@ ...@@ -5,8 +5,11 @@
{% block help_message %} {% block help_message %}
<div class="alert alert-info help-message"> <div class="alert alert-info help-message">
管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。 {# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#}
Windows或其它硬件可以随意设置一个 {# Windows或其它硬件可以随意设置一个#}
{% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%}
{% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%}
{% trans 'You can set any one for Windows or other hardware.' %}
</div> </div>
{% endblock %} {% endblock %}
......
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
{% block form %} {% block form %}
<div class="ydxbd" id="formlists" style="display: block;"> <div class="ydxbd" id="formlists" style="display: block;">
<p id="tags_p" class="mgl-5 c02">选择需要修改属性</p> <p id="tags_p" class="mgl-5 c02">{% trans 'Select properties that need to be modified' %}</p>
<div class="tagBtnList"> <div class="tagBtnList">
<a class="label label-primary" id="change_all" value="1">全选</a> <a class="label label-primary" id="change_all" value="1">{% trans 'Select all' %}</a>
{% for field in form %} {% for field in form %}
{% if field.name != 'assets' %} {% if field.name != 'assets' %}
<a data-id="{{ field.id_for_label }}" class="label label-default label-primary field-tag" value="1">{{ field.label }}</a> <a data-id="{{ field.id_for_label }}" class="label label-default label-primary field-tag" value="1">{{ field.label }}</a>
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
{% block help_message %} {% block help_message %}
<div class="alert alert-info help-message"> <div class="alert alert-info help-message">
左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产 {# 左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,右侧是属于该节点下的资产#}
{% trans 'The left side is the asset tree, right click to create, delete, and change the tree node, authorization asset is also organized as a node, and the right side is the asset under that node' %}
</div> </div>
{% endblock %} {% endblock %}
...@@ -627,6 +628,7 @@ $(document).ready(function(){ ...@@ -627,6 +628,7 @@ $(document).ready(function(){
text: "{% trans 'This will delete the selected assets !!!' %}", text: "{% trans 'This will delete the selected assets !!!' %}",
type: "warning", type: "warning",
showCancelButton: true, showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#DD6B55", confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}", confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false closeOnConfirm: false
......
...@@ -120,8 +120,8 @@ $(document).ready(function(){ ...@@ -120,8 +120,8 @@ $(document).ready(function(){
APIUpdateAttr({ APIUpdateAttr({
url: the_url, url: the_url,
method: "GET", method: "GET",
success_message: "可连接", success_message: "{% trans 'Can be connected' %}",
fail_message: "连接失败" fail_message: "{% trans 'The connection fails' %}"
}) })
}); });
</script> </script>
......
...@@ -4,8 +4,11 @@ ...@@ -4,8 +4,11 @@
{% block help_message %} {% block help_message %}
<div class="alert alert-info help-message"> <div class="alert alert-info help-message">
网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登录。<br> {# 网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登录。<br>#}
JMS => 网域网关 => 目标资产 {# JMS => 网域网关 => 目标资产#}
{% trans 'The domain function is added to address the fact that some environments (such as the hybrid cloud) cannot be connected directly by jumping on the gateway server.' %}
<br>
{% trans 'JMS => Domain gateway => Target assets' %}
</div> </div>
{% endblock %} {% endblock %}
......
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
<th>{% trans 'IP' %}</th> <th>{% trans 'IP' %}</th>
<th>{% trans 'Port' %}</th> <th>{% trans 'Port' %}</th>
<th>{% trans 'Reachable' %}</th> <th>{% trans 'Reachable' %}</th>
<th>{% trans 'Action' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
...@@ -67,21 +68,59 @@ ...@@ -67,21 +68,59 @@
<table class="table"> <table class="table">
<tbody> <tbody>
<tr class="no-borders-tr"> <tr class="no-borders-tr">
<td width="50%">{% trans 'Push system user now' %}:</td> <td width="50%">{% trans 'Test assets connective' %}:</td>
<td> <td>
<span style="float: right"> <span style="float: right">
<button type="button" class="btn btn-primary btn-xs btn-push" style="width: 54px">{% trans 'Push' %}</button> <button type="button" class="btn btn-primary btn-xs btn-test-connective" style="width: 54px">{% trans 'Test' %}</button>
</span> </span>
</td> </td>
</tr> </tr>
{% if system_user.auto_push %}
<tr> <tr>
<td width="50%">{% trans 'Test assets connective' %}:</td> <td width="50%">{% trans 'Push system user now' %}:</td>
<td> <td>
<span style="float: right"> <span style="float: right">
<button type="button" class="btn btn-primary btn-xs btn-test-connective" style="width: 54px">{% trans 'Test' %}</button> <button type="button" class="btn btn-primary btn-xs btn-push" style="width: 54px">{% trans 'Push' %}</button>
</span> </span>
</td> </td>
</tr> </tr>
{% endif %}
</tbody>
</table>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
</div>
<div class="panel-body">
<table class="table node_edit" id="add-asset2group">
<tbody>
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for node in nodes_remain %}
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn-add-to-node">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for node in system_user.nodes.all %}
<tr>
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
...@@ -96,7 +135,7 @@ ...@@ -96,7 +135,7 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
function initAssetsTable() { function initAssetsTable() {
var unreachable = {{ system_user.unreachable_assets|safe}}; var unreachable = {{ system_user.unreachable_assets|safe }};
var options = { var options = {
ele: $('#system_user_list'), ele: $('#system_user_list'),
buttons: [], buttons: [],
...@@ -112,26 +151,63 @@ function initAssetsTable() { ...@@ -112,26 +151,63 @@ function initAssetsTable() {
} else { } else {
$(td).html('<i class="fa fa-check text-navy"></i>') $(td).html('<i class="fa fa-check text-navy"></i>')
} }
}},
{targets: 4, createdCell: function (td, cellData) {
var push_btn = '';
{% if system_user.auto_push %}
push_btn = '<a class="btn btn-xs btn-primary btn-push-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Push" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
{% endif %}
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
{#var unbound_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-asset-unbound" data-uid="{{ DEFAULT_PK }}">{% trans "Unbound" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);#}
$(td).html(push_btn + test_btn);
}} }}
], ],
ajax_url: '{% url "api-assets:asset-list" %}?system_user_id={{ system_user.id }}', ajax_url: '{% url "api-assets:system-user-assets" pk=system_user.id %}',
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }], columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }, {data: "id"}],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
jumpserver.initServerSideDataTable(options); jumpserver.initServerSideDataTable(options);
} }
function updateSystemUserNode(nodes) {
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
var body = {
nodes: Object.assign([], nodes)
};
var success = function(data) {
// remove all the selected groups from select > option and rendered ul element;
$('.select2-selection__rendered').empty();
$('#node_selected').val('');
$.map(jumpserver.nodes_selected, function(node_name, index) {
$('#opt_' + index).remove();
// change tr html of user groups.
$('.node_edit tbody').append(
'<tr>' +
'<td><b class="bdg_node" data-gid="' + index + '">' + node_name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
// clear jumpserver.nodes_selected
jumpserver.nodes_selected = {};
};
APIUpdateAttr({
url: the_url,
body: JSON.stringify(body),
success: success
});
}
jumpserver.nodes_selected = {};
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2() $('.select2').select2()
.on("select2:select", function (evt) { .on('select2:select', function(evt) {
var data = evt.params.data; var data = evt.params.data;
jumpserver.assets_selected[data.id] = data.text; jumpserver.nodes_selected[data.id] = data.text;
jumpserver.asset_groups_selected[data.id] = data.text;
}) })
.on('select2:unselect', function(evt) { .on('select2:unselect', function(evt) {
var data = evt.params.data; var data = evt.params.data;
delete jumpserver.assets_selected[data.id]; delete jumpserver.nodes_selected[data.id];
delete jumpserver.asset_groups_selected[data.id];
}); });
initAssetsTable(); initAssetsTable();
}) })
...@@ -140,11 +216,16 @@ $(document).ready(function () { ...@@ -140,11 +216,16 @@ $(document).ready(function () {
var error = function (data) { var error = function (data) {
alert(data) alert(data)
}; };
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({ APIUpdateAttr({
url: the_url, url: the_url,
error: error, error: error,
method: 'GET', method: 'GET',
success_message: "{% trans "Task has been send, Go to ops task list seen result" %}" success: success
}); });
}) })
.on('click', '.btn-test-connective', function () { .on('click', '.btn-test-connective', function () {
...@@ -152,15 +233,85 @@ $(document).ready(function () { ...@@ -152,15 +233,85 @@ $(document).ready(function () {
var error = function (data) { var error = function (data) {
alert(data) alert(data)
}; };
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({ APIUpdateAttr({
url: the_url, url: the_url,
error: error, error: error,
method: 'GET', method: 'GET',
success_message: "{% trans "Task has been send, seen left assets status" %}" success: success
}); });
}) })
.on('click', '.btn-remove-from-node', function() {
var $this = $(this);
var $tr = $this.closest('tr');
var $badge = $tr.find('.bdg_node');
var gid = $badge.data('gid');
var node_name = $badge.html() || $badge.text();
$('#groups_selected').append(
'<option value="' + gid + '" id="opt_' + gid + '">' + node_name + '</option>'
);
$tr.remove();
var nodes = $('.bdg_node').map(function () {
return $(this).data('gid');
}).get();
updateSystemUserNode(nodes);
})
.on('click', '#btn-add-to-node', function() {
if (Object.keys(jumpserver.nodes_selected).length === 0) {
return false;
}
var nodes = $('.bdg_node').map(function() {
return $(this).data('gid');
}).get();
$.map(jumpserver.nodes_selected, function(value, index) {
nodes.push(index);
});
updateSystemUserNode(nodes);
})
.on('click', '.btn-push-asset', function () {
var $this = $(this);
var asset_id = $this.data('uid');
var the_url = "{% url 'api-assets:system-user-push-to-asset' pk=object.id aid=DEFAULT_PK %}";
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
var error = function (data) {
alert(data)
};
APIUpdateAttr({
url: the_url,
method: 'GET',
success: success,
error: error
})
})
.on('click', '.btn-test-asset', function () {
var $this = $(this);
var asset_id = $this.data('uid');
var the_url = "{% url 'api-assets:system-user-test-to-asset' pk=object.id aid=DEFAULT_PK %}";
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
var error = function (data) {
alert(data)
};
APIUpdateAttr({
url: the_url,
method: 'GET',
success: success,
error: error
})
})
</script> </script>
{% endblock %} {% endblock %}
...@@ -17,11 +17,11 @@ ...@@ -17,11 +17,11 @@
<li class="active"> <li class="active">
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a> <a href="{% url 'assets:system-user-detail' pk=system_user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li> </li>
{# <li>#} <li>
{# <a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">#} <a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
{# <i class="fa fa-bar-chart-o"></i> {% trans 'Attached assets' %}#} <i class="fa fa-bar-chart-o"></i> {% trans 'Assets' %}
{# </a>#} </a>
{# </li>#} </li>
<li class="pull-right"> <li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:system-user-update' pk=system_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a> <a class="btn btn-outline btn-default" href="{% url 'assets:system-user-update' pk=system_user.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li> </li>
...@@ -152,63 +152,46 @@ ...@@ -152,63 +152,46 @@
</span> </span>
</td> </td>
</tr> </tr>
</tbody>
</table>
</div>
</div>
{# <div class="panel panel-info">#}
{# <div class="panel-heading">#}
{# <i class="fa fa-info-circle"></i> {% trans 'Nodes' %}#}
{# </div>#}
{# <div class="panel-body">#}
{# <table class="table node_edit" id="add-asset2group">#}
{# <tbody>#}
{# <form>#}
{# <tr>#} {# <tr>#}
{# <td width="50%">{% trans 'Clear auth' %}:</td>#} {# <td colspan="2" class="no-borders">#}
{# <td>#} {# <select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">#}
{# <span style="float: right">#} {# {% for node in nodes_remain %}#}
{# <button type="button" class="btn btn-primary btn-xs btn-clear-auth" style="width: 54px">{% trans 'Clear' %}</button>#} {# <option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>#}
{# </span>#} {# {% endfor %}#}
{# </select>#}
{# </td>#} {# </td>#}
{# </tr>#} {# </tr>#}
{# <tr>#} {# <tr>#}
{# <td width="50%">{% trans 'Change auth period' %}:</td>#} {# <td colspan="2" class="no-borders">#}
{# <button type="button" class="btn btn-info btn-sm" id="btn-add-to-node">{% trans 'Confirm' %}</button>#}
{# </td>#}
{# </tr>#}
{# </form>#}
{##}
{# {% for node in system_user.nodes.all %}#}
{# <tr>#}
{# <td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>#}
{# <td>#} {# <td>#}
{# <span style="float: right">#} {# <button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>#}
{# <button type="button" class="btn btn-primary btn-xs" style="width: 54px">{% trans 'Reset' %}</button>#}
{# </span>#}
{# </td>#} {# </td>#}
{# </tr>#} {# </tr>#}
</tbody> {# {% endfor %}#}
</table> {# </tbody>#}
</div> {# </table>#}
</div> {# </div>#}
<div class="panel panel-info"> {# </div>#}
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
</div>
<div class="panel-body">
<table class="table node_edit" id="add-asset2group">
<tbody>
<form>
<tr>
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for node in nodes_remain %}
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr>
<td colspan="2" class="no-borders">
<button type="button" class="btn btn-info btn-sm" id="btn-add-to-node">{% trans 'Confirm' %}</button>
</td>
</tr>
</form>
{% for node in system_user.nodes.all %}
<tr>
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
<td>
<button class="btn btn-danger pull-right btn-xs btn-remove-from-node" type="button"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -338,13 +321,13 @@ $(document).ready(function () { ...@@ -338,13 +321,13 @@ $(document).ready(function () {
var the_url = '{% url "api-assets:system-user-auth-info" pk=system_user.id %}'; var the_url = '{% url "api-assets:system-user-auth-info" pk=system_user.id %}';
var name = '{{ system_user.name }}'; var name = '{{ system_user.name }}';
swal({ swal({
title: '你确定清除该系统用户的认证信息吗 ?', title: "{% trans 'Are you sure to remove authentication information for the system user ?' %}",
text: " [" + name + "] ", text: " [" + name + "] ",
type: "warning", type: "warning",
showCancelButton: true, showCancelButton: true,
cancelButtonText: '取消', cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#ed5565", confirmButtonColor: "#ed5565",
confirmButtonText: '确认', confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: true closeOnConfirm: true
}, function () { }, function () {
APIUpdateAttr({ APIUpdateAttr({
......
...@@ -3,10 +3,13 @@ ...@@ -3,10 +3,13 @@
{% block help_message %} {% block help_message %}
<div class="alert alert-info help-message"> <div class="alert alert-info help-message">
系统用户是 Jumpserver跳转登录资产时使用的用户,可以理解为登录资产用户,如 web, sa, dba(`ssh web@some-host`), 而不是使用某个用户的用户名跳转登录服务器(`ssh xiaoming@some-host`); {# 系统用户是 Jumpserver跳转登录资产时使用的用户,可以理解为登录资产用户,如 web, sa, dba(`ssh web@some-host`), 而不是使用某个用户的用户名跳转登录服务器(`ssh xiaoming@some-host`);#}
简单来说是 用户使用自己的用户名登录Jumpserver, Jumpserver使用系统用户登录资产。 {# 简单来说是 用户使用自己的用户名登录Jumpserver, Jumpserver使用系统用户登录资产。#}
系统用户创建时,如果选择了自动推送 Jumpserver会使用ansible自动推送系统用户到资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。 {# 系统用户创建时,如果选择了自动推送 Jumpserver会使用ansible自动推送系统用户到资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。#}
目前还不支持Windows的自动推送 {# 目前还不支持Windows的自动推送#}
{% trans 'System user is Jumpserver jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); '%}
{% trans 'In simple terms, users log into Jumpserver using their own username, and Jumpserver uses system users to log into assets. '%}
{% trans 'When system users are created, if you choose auto push Jumpserver to use ansible push system users into the asset, if the asset (Switch, Windows) does not support ansible, please manually fill in the account password. Automatic push for Windows is not currently supported.' %}
</div> </div>
{% endblock %} {% endblock %}
...@@ -135,6 +138,7 @@ $(document).ready(function(){ ...@@ -135,6 +138,7 @@ $(document).ready(function(){
text: "{% trans 'This will delete the selected System Users !!!' %}", text: "{% trans 'This will delete the selected System Users !!!' %}",
type: "warning", type: "warning",
showCancelButton: true, showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#DD6B55", confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}", confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false closeOnConfirm: false
......
...@@ -19,6 +19,8 @@ urlpatterns = [ ...@@ -19,6 +19,8 @@ urlpatterns = [
path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'), path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'),
path('system-user/<uuid:pk>/auth-info/', path('system-user/<uuid:pk>/auth-info/',
api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'), api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
path('system-user/<uuid:pk>/assets/',
api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
path('assets/<uuid:pk>/refresh/', path('assets/<uuid:pk>/refresh/',
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'), api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
path('assets/<uuid:pk>/alive/', path('assets/<uuid:pk>/alive/',
...@@ -31,10 +33,16 @@ urlpatterns = [ ...@@ -31,10 +33,16 @@ urlpatterns = [
api.AdminUserAuthApi.as_view(), name='admin-user-auth'), api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
path('admin-user/<uuid:pk>/connective/', path('admin-user/<uuid:pk>/connective/',
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'), api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
path('system-user/<uuid:pk>/push/', path('system-user/<uuid:pk>/push/',
api.SystemUserPushApi.as_view(), name='system-user-push'), api.SystemUserPushApi.as_view(), name='system-user-push'),
path('system-user/<uuid:pk>/asset/<uuid:aid>/push/',
api.SystemUserPushToAssetApi.as_view(), name='system-user-push-to-asset'),
path('system-user/<uuid:pk>/asset/<uuid:aid>/test/',
api.SystemUserTestAssetConnectabilityApi.as_view(), name='system-user-test-to-asset'),
path('system-user/<uuid:pk>/connective/', path('system-user/<uuid:pk>/connective/',
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'), api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
path('nodes/<uuid:pk>/children/', path('nodes/<uuid:pk>/children/',
api.NodeChildrenApi.as_view(), name='node-children'), api.NodeChildrenApi.as_view(), name='node-children'),
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'), path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),
......
...@@ -94,6 +94,7 @@ class SystemUserAssetView(AdminUserRequiredMixin, DetailView): ...@@ -94,6 +94,7 @@ class SystemUserAssetView(AdminUserRequiredMixin, DetailView):
context = { context = {
'app': _('assets'), 'app': _('assets'),
'action': _('System user asset'), 'action': _('System user asset'),
'nodes_remain': Node.objects.exclude(systemuser=self.object)
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -3,3 +3,6 @@ from django.apps import AppConfig ...@@ -3,3 +3,6 @@ from django.apps import AppConfig
class AuditsConfig(AppConfig): class AuditsConfig(AppConfig):
name = 'audits' name = 'audits'
def ready(self):
from . import signals_handler
# -*- coding: utf-8 -*-
#
from users.models import LoginLog
\ No newline at end of file
...@@ -4,6 +4,11 @@ from django.db import models ...@@ -4,6 +4,11 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelMixin from orgs.mixins import OrgModelMixin
from .hands import LoginLog
__all__ = [
'FTPLog', 'OperateLog', 'PasswordChangeLog', 'UserLoginLog',
]
class FTPLog(OrgModelMixin): class FTPLog(OrgModelMixin):
...@@ -16,3 +21,40 @@ class FTPLog(OrgModelMixin): ...@@ -16,3 +21,40 @@ class FTPLog(OrgModelMixin):
filename = models.CharField(max_length=1024, verbose_name=_("Filename")) filename = models.CharField(max_length=1024, verbose_name=_("Filename"))
is_success = models.BooleanField(default=True, verbose_name=_("Success")) is_success = models.BooleanField(default=True, verbose_name=_("Success"))
date_start = models.DateTimeField(auto_now_add=True) date_start = models.DateTimeField(auto_now_add=True)
class OperateLog(OrgModelMixin):
ACTION_CREATE = 'create'
ACTION_UPDATE = 'update'
ACTION_DELETE = 'delete'
ACTION_CHOICES = (
(ACTION_CREATE, _("Create")),
(ACTION_UPDATE, _("Update")),
(ACTION_DELETE, _("Delete"))
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=128, verbose_name=_('User'))
action = models.CharField(max_length=16, choices=ACTION_CHOICES, verbose_name=_("Action"))
resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type"))
resource = models.CharField(max_length=128, verbose_name=_("Resource"))
remote_addr = models.CharField(max_length=15, verbose_name=_("Remote addr"), blank=True, null=True)
datetime = models.DateTimeField(auto_now=True)
def __str__(self):
return "<{}> {} <{}>".format(self.user, self.action, self.resource)
class PasswordChangeLog(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
user = models.CharField(max_length=128, verbose_name=_('User'))
change_by = models.CharField(max_length=128, verbose_name=_("Change by"))
remote_addr = models.CharField(max_length=15, verbose_name=_("Remote addr"), blank=True, null=True)
datetime = models.DateTimeField(auto_now=True)
def __str__(self):
return "{} change {}'s password".format(self.change_by, self.user)
class UserLoginLog(LoginLog):
class Meta:
proxy = True
# -*- coding: utf-8 -*-
#
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.db import transaction
from jumpserver.utils import current_request
from common.utils import get_request_ip
from users.models import User
from .models import OperateLog, PasswordChangeLog
MODELS_NEED_RECORD = (
'User', 'UserGroup', 'Asset', 'Node', 'AdminUser', 'SystemUser',
'Domain', 'Gateway', 'Organization', 'AssetPermission',
)
def create_operate_log(action, sender, resource):
user = current_request.user if current_request else None
if not user or not user.is_authenticated:
return
model_name = sender._meta.object_name
if model_name not in MODELS_NEED_RECORD:
return
resource_type = sender._meta.verbose_name
remote_addr = get_request_ip(current_request)
with transaction.atomic():
OperateLog.objects.create(
user=user, action=action, resource_type=resource_type,
resource=resource, remote_addr=remote_addr
)
@receiver(post_save, dispatch_uid="my_unique_identifier")
def on_object_created_or_update(sender, instance=None, created=False, **kwargs):
if created:
action = OperateLog.ACTION_CREATE
else:
action = OperateLog.ACTION_UPDATE
create_operate_log(action, sender, instance)
@receiver(post_delete, dispatch_uid="my_unique_identifier")
def on_object_delete(sender, instance=None, **kwargs):
create_operate_log(OperateLog.ACTION_DELETE, sender, instance)
@receiver(post_save, sender=User, dispatch_uid="my_unique_identifier")
def on_user_change_password(sender, instance=None, **kwargs):
if hasattr(instance, '_set_password'):
if not current_request or not current_request.user.is_authenticated:
return
with transaction.atomic():
PasswordChangeLog.objects.create(
user=instance, change_by=current_request.user,
remote_addr=get_request_ip(current_request),
)
...@@ -66,7 +66,6 @@ ...@@ -66,7 +66,6 @@
{% endblock %} {% endblock %}
{% block table_head %} {% block table_head %}
<th class="text-center"></th>
{# <th class="text-center">{% trans 'ID' %}</th>#} {# <th class="text-center">{% trans 'ID' %}</th>#}
<th class="text-center">{% trans 'User' %}</th> <th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'Asset' %}</th> <th class="text-center">{% trans 'Asset' %}</th>
...@@ -82,7 +81,6 @@ ...@@ -82,7 +81,6 @@
{% block table_body %} {% block table_body %}
{% for object in object_list %} {% for object in object_list %}
<tr class="gradeX"> <tr class="gradeX">
<td class="text-center"><input type="checkbox" value="{{ object.id }}"></td>
{# <td class="text-center">#} {# <td class="text-center">#}
{# <a href="{% url 'terminal:object-detail' pk=object.id %}">{{ forloop.counter }}</a>#} {# <a href="{% url 'terminal:object-detail' pk=object.id %}">{{ forloop.counter }}</a>#}
{# </td>#} {# </td>#}
......
{% extends '_base_list.html' %}
{% load i18n %}
{% load static %}
{% load terminal_tags %}
{% load common_tags %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<style>
#search_btn {
margin-bottom: 0;
}
</style>
{% endblock %}
{% block content_left_head %}
{% endblock %}
{% block table_search %}
<form id="search_form" method="get" action="" class="pull-right form-inline">
<div class="form-group" id="date">
<div class="input-daterange input-group" id="datepicker">
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from|date:'Y-m-d' }}">
<span class="input-group-addon">to</span>
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to|date:'Y-m-d' }}">
</div>
</div>
<div class="input-group">
<select class="select2 form-control" name="user">
<option value="">{% trans 'User' %}</option>
{% for u in user_list %}
<option value="{{ u }}" {% if u == user %} selected {% endif %}>{{ u }}</option>
{% endfor %}
</select>
</div>
<div class="input-group">
<select class="select2 form-control" name="asset">
<option value="">{% trans 'Action' %}</option>
{% for k, v in actions.items %}
<option value="{{ k }}" {% if k == action %} selected {% endif %}>{{ v }}</option>
{% endfor %}
</select>
</div>
<div class="input-group">
<select class="select2 form-control" name="system_user">
<option value="">{% trans 'Resource Type' %}</option>
{% for r in resource_type_list %}
<option value="{{ r }}" {% if r == resource_type %} selected {% endif %}>{{ r }}</option>
{% endfor %}
</select>
</div>
<div class="input-group">
<div class="input-group-btn">
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
{% trans 'Search' %}
</button>
</div>
</div>
</form>
{% endblock %}
{% block table_head %}
<th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
<th class="text-center">{% trans 'Resource Type' %}</th>
<th class="text-center">{% trans 'Resource' %}</th>
<th class="text-center">{% trans 'Remote addr' %}</th>
<th class="text-center">{% trans 'Datetime' %}</th>
{% endblock %}
{% block table_body %}
{% for object in object_list %}
<tr class="gradeX">
{# <td class="text-center">#}
{# <a href="{% url 'terminal:object-detail' pk=object.id %}">{{ forloop.counter }}</a>#}
{# </td>#}
<td class="text-center">{{ object.user }}</td>
<td class="text-center">{{ object.get_action_display }}</td>
<td class="text-center">{{ object.resource_type }}</td>
<td class="text-center">{{ object.resource }}</td>
<td class="text-center">{{ object.remote_addr }}</td>
<td class="text-center">{{ object.datetime }}</td>
</tr>
{% endfor %}
{% endblock %}
{% block content_bottom_left %}
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script>
$(document).ready(function() {
$('table').DataTable({
"searching": false,
"paging": false,
"bInfo" : false,
"order": [],
"language": jumpserver.language
});
$('.select2').select2({
dropdownAutoWidth: true,
width: "auto"
});
$('.input-daterange.input-group').datepicker({
format: "yyyy-mm-dd",
todayBtn: "linked",
keyboardNavigation: false,
forceParse: false,
calendarWeeks: true,
autoclose: true
});
})
</script>
{% endblock %}
{% extends '_base_list.html' %}
{% load i18n %}
{% load static %}
{% load terminal_tags %}
{% load common_tags %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/datepicker/datepicker3.css' %}" rel="stylesheet">
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
<style>
#search_btn {
margin-bottom: 0;
}
</style>
{% endblock %}
{% block content_left_head %}
{% endblock %}
{% block table_search %}
<form id="search_form" method="get" action="" class="pull-right form-inline">
<div class="form-group" id="date">
<div class="input-daterange input-group" id="datepicker">
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_from" value="{{ date_from|date:'Y-m-d' }}">
<span class="input-group-addon">to</span>
<input type="text" class="input-sm form-control" style="width: 100px;" name="date_to" value="{{ date_to|date:'Y-m-d' }}">
</div>
</div>
<div class="input-group">
<select class="select2 form-control" name="user">
<option value="">{% trans 'User' %}</option>
{% for u in user_list %}
<option value="{{ u }}" {% if u == user %} selected {% endif %}>{{ u }}</option>
{% endfor %}
</select>
</div>
<div class="input-group">
<div class="input-group-btn">
<button id='search_btn' type="submit" class="btn btn-sm btn-primary">
{% trans 'Search' %}
</button>
</div>
</div>
</form>
{% endblock %}
{% block table_head %}
<th class="text-center">{% trans 'User' %}</th>
<th class="text-center">{% trans 'Change by' %}</th>
<th class="text-center">{% trans 'Remote addr' %}</th>
<th class="text-center">{% trans 'Datetime' %}</th>
{% endblock %}
{% block table_body %}
{% for object in object_list %}
<tr class="gradeX">
<td class="text-center">{{ object.user }}</td>
<td class="text-center">{{ object.change_by }}</td>
<td class="text-center">{{ object.remote_addr }}</td>
<td class="text-center">{{ object.datetime }}</td>
</tr>
{% endfor %}
{% endblock %}
{% block content_bottom_left %}
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
<script>
$(document).ready(function() {
$('table').DataTable({
"searching": false,
"paging": false,
"bInfo" : false,
"order": [],
"language": jumpserver.language
});
$('.select2').select2({
dropdownAutoWidth: true,
width: "auto"
});
$('.input-daterange.input-group').datepicker({
format: "yyyy-mm-dd",
todayBtn: "linked",
keyboardNavigation: false,
forceParse: false,
calendarWeeks: true,
autoclose: true
});
})
</script>
{% endblock %}
...@@ -9,5 +9,8 @@ __all__ = ["urlpatterns"] ...@@ -9,5 +9,8 @@ __all__ = ["urlpatterns"]
app_name = "audits" app_name = "audits"
urlpatterns = [ urlpatterns = [
path('login-log/', views.LoginLogListView.as_view(), name='login-log-list'),
path('ftp-log/', views.FTPLogListView.as_view(), name='ftp-log-list'), path('ftp-log/', views.FTPLogListView.as_view(), name='ftp-log-list'),
path('operate-log/', views.OperateLogListView.as_view(), name='operate-log-list'),
path('password-change-log/', views.PasswordChangeLogList.as_view(), name='password-change-log-list'),
] ]
from django.conf import settings from django.conf import settings
from django.views.generic import ListView from django.views.generic import ListView
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.db.models import Q
from common.mixins import DatetimeSearchMixin from common.mixins import DatetimeSearchMixin
from common.permissions import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
from .models import FTPLog from orgs.utils import current_org
from .models import FTPLog, OperateLog, PasswordChangeLog, UserLoginLog
def get_resource_type_list():
from users.models import User, UserGroup
from assets.models import Asset, Node, AdminUser, SystemUser, Domain, Gateway
from orgs.models import Organization
from perms.models import AssetPermission
models = [
User, UserGroup, Asset, Node, AdminUser, SystemUser, Domain,
Gateway, Organization, AssetPermission
]
return [model._meta.verbose_name for model in models]
class FTPLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): class FTPLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
...@@ -53,3 +68,125 @@ class FTPLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): ...@@ -53,3 +68,125 @@ class FTPLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class OperateLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
model = OperateLog
template_name = 'audits/operate_log_list.html'
paginate_by = settings.DISPLAY_PER_PAGE
user = action = resource_type = ''
date_from = date_to = None
actions_dict = dict(OperateLog.ACTION_CHOICES)
def get_queryset(self):
self.queryset = super().get_queryset()
self.user = self.request.GET.get('user')
self.action = self.request.GET.get('action')
self.resource_type = self.request.GET.get('resource_type')
filter_kwargs = dict()
filter_kwargs['datetime__gt'] = self.date_from
filter_kwargs['datetime__lt'] = self.date_to
if self.user:
filter_kwargs['user'] = self.user
if self.action:
filter_kwargs['action'] = self.action
if self.resource_type:
filter_kwargs['resource_type'] = self.resource_type
if filter_kwargs:
self.queryset = self.queryset.filter(**filter_kwargs).order_by('-datetime')
return self.queryset
def get_context_data(self, **kwargs):
context = {
'user_list': current_org.get_org_users(),
'actions': self.actions_dict,
'resource_type_list': get_resource_type_list(),
'date_from': self.date_from,
'date_to': self.date_to,
'user': self.user,
'action': self.action,
'resource_type': self.resource_type,
"app": _("Audits"),
"action": _("Operate log"),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class PasswordChangeLogList(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
model = PasswordChangeLog
template_name = 'audits/password_change_log_list.html'
paginate_by = settings.DISPLAY_PER_PAGE
user = ''
date_from = date_to = None
def get_queryset(self):
self.queryset = super().get_queryset()
self.user = self.request.GET.get('user')
filter_kwargs = dict()
filter_kwargs['datetime__gt'] = self.date_from
filter_kwargs['datetime__lt'] = self.date_to
if self.user:
filter_kwargs['user'] = self.user
if filter_kwargs:
self.queryset = self.queryset.filter(**filter_kwargs).order_by('-datetime')
return self.queryset
def get_context_data(self, **kwargs):
context = {
'user_list': current_org.get_org_users(),
'date_from': self.date_from,
'date_to': self.date_to,
'user': self.user,
"app": _("Audits"),
"action": _("Password change log"),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
template_name = 'audits/login_log_list.html'
model = UserLoginLog
paginate_by = settings.DISPLAY_PER_PAGE
user = keyword = ""
date_to = date_from = None
@staticmethod
def get_org_users():
users = current_org.get_org_users().values_list('username', flat=True)
return users
def get_queryset(self):
users = self.get_org_users()
queryset = super().get_queryset().filter(username__in=users)
self.user = self.request.GET.get('user', '')
self.keyword = self.request.GET.get("keyword", '')
queryset = queryset.filter(
datetime__gt=self.date_from, datetime__lt=self.date_to
)
if self.user:
queryset = queryset.filter(username=self.user)
if self.keyword:
queryset = queryset.filter(
Q(ip__contains=self.keyword) |
Q(city__contains=self.keyword) |
Q(username__contains=self.keyword)
)
return queryset
def get_context_data(self, **kwargs):
context = {
'app': _('Users'),
'action': _('Login log'),
'date_from': self.date_from,
'date_to': self.date_to,
'user': self.user,
'keyword': self.keyword,
'user_list': self.get_org_users(),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
\ No newline at end of file
...@@ -13,7 +13,7 @@ from .utils import get_signer ...@@ -13,7 +13,7 @@ from .utils import get_signer
signer = get_signer() signer = get_signer()
class DictField(forms.Field): class FormDictField(forms.Field):
widget = forms.Textarea widget = forms.Textarea
def to_python(self, value): def to_python(self, value):
...@@ -23,6 +23,7 @@ class DictField(forms.Field): ...@@ -23,6 +23,7 @@ class DictField(forms.Field):
# RadioSelect will provide. Because bool("True") == bool('1') == True, # RadioSelect will provide. Because bool("True") == bool('1') == True,
# we don't need to handle that explicitly. # we don't need to handle that explicitly.
if isinstance(value, six.string_types): if isinstance(value, six.string_types):
value = value.replace("'", '"')
try: try:
value = json.loads(value) value = json.loads(value)
return value return value
...@@ -74,3 +75,14 @@ class EncryptCharField(EncryptMixin, models.CharField): ...@@ -74,3 +75,14 @@ class EncryptCharField(EncryptMixin, models.CharField):
kwargs['max_length'] = 2048 kwargs['max_length'] = 2048
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
class FormEncryptMixin:
pass
class FormEncryptCharField(FormEncryptMixin, forms.CharField):
pass
class FormEncryptDictField(FormEncryptMixin, FormDictField):
pass
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import json import json
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.html import escape
from django.db import transaction from django.db import transaction
from django.conf import settings from django.conf import settings
from .models import Setting from .models import Setting, common_settings
from .fields import DictField from .fields import FormDictField, FormEncryptCharField, \
FormEncryptMixin, FormEncryptDictField
def to_model_value(value):
try:
return json.dumps(value)
except json.JSONDecodeError:
return None
def to_form_value(value):
try:
data = json.loads(value)
if isinstance(data, dict):
data = value
return data
except json.JSONDecodeError:
return ""
class BaseForm(forms.Form): class BaseForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
db_settings = Setting.objects.all()
for name, field in self.fields.items(): for name, field in self.fields.items():
db_value = getattr(db_settings, name).value db_value = getattr(common_settings, name)
django_value = getattr(settings, name) if hasattr(settings, name) else None django_value = getattr(settings, name) if hasattr(settings, name) else None
if db_value is False or db_value: if db_value is False or db_value:
field.initial = to_form_value(db_value) if isinstance(db_value, dict):
db_value = json.dumps(db_value)
initial_value = db_value
elif django_value is False or django_value: elif django_value is False or django_value:
field.initial = to_form_value(to_model_value(django_value)) initial_value = django_value
else:
initial_value = ''
field.initial = initial_value
def save(self, category="default"): def save(self, category="default"):
if not self.is_bound: if not self.is_bound:
raise ValueError("Form is not bound") raise ValueError("Form is not bound")
db_settings = Setting.objects.all() # db_settings = Setting.objects.all()
if self.is_valid(): if not self.is_valid():
raise ValueError(self.errors)
with transaction.atomic(): with transaction.atomic():
for name, value in self.cleaned_data.items(): for name, value in self.cleaned_data.items():
field = self.fields[name] field = self.fields[name]
if isinstance(field.widget, forms.PasswordInput) and not value: if isinstance(field.widget, forms.PasswordInput) and not value:
continue continue
if value == to_form_value(getattr(db_settings, name).value): if value == getattr(common_settings, name):
continue continue
defaults = { encrypted = True if isinstance(field, FormEncryptMixin) else False
'name': name, try:
'category': category, setting = Setting.objects.get(name=name)
'value': to_model_value(value) except Setting.DoesNotExist:
} setting = Setting()
Setting.objects.update_or_create(defaults=defaults, name=name) setting.name = name
else: setting.category = category
raise ValueError(self.errors) setting.encrypted = encrypted
setting.cleaned_value = value
setting.save()
class BasicSettingForm(BaseForm): class BasicSettingForm(BaseForm):
...@@ -88,7 +79,7 @@ class EmailSettingForm(BaseForm): ...@@ -88,7 +79,7 @@ class EmailSettingForm(BaseForm):
EMAIL_HOST_USER = forms.CharField( EMAIL_HOST_USER = forms.CharField(
max_length=128, label=_("SMTP user"), initial='noreply@jumpserver.org' max_length=128, label=_("SMTP user"), initial='noreply@jumpserver.org'
) )
EMAIL_HOST_PASSWORD = forms.CharField( EMAIL_HOST_PASSWORD = FormEncryptCharField(
max_length=1024, label=_("SMTP password"), widget=forms.PasswordInput, max_length=1024, label=_("SMTP password"), widget=forms.PasswordInput,
required=False, help_text=_("Some provider use token except password") required=False, help_text=_("Some provider use token except password")
) )
...@@ -109,7 +100,7 @@ class LDAPSettingForm(BaseForm): ...@@ -109,7 +100,7 @@ class LDAPSettingForm(BaseForm):
AUTH_LDAP_BIND_DN = forms.CharField( AUTH_LDAP_BIND_DN = forms.CharField(
label=_("Bind DN"), initial='cn=admin,dc=jumpserver,dc=org' label=_("Bind DN"), initial='cn=admin,dc=jumpserver,dc=org'
) )
AUTH_LDAP_BIND_PASSWORD = forms.CharField( AUTH_LDAP_BIND_PASSWORD = FormEncryptCharField(
label=_("Password"), initial='', label=_("Password"), initial='',
widget=forms.PasswordInput, required=False widget=forms.PasswordInput, required=False
) )
...@@ -121,15 +112,12 @@ class LDAPSettingForm(BaseForm): ...@@ -121,15 +112,12 @@ class LDAPSettingForm(BaseForm):
label=_("User search filter"), initial='(cn=%(user)s)', label=_("User search filter"), initial='(cn=%(user)s)',
help_text=_("Choice may be (cn|uid|sAMAccountName)=%(user)s)") help_text=_("Choice may be (cn|uid|sAMAccountName)=%(user)s)")
) )
AUTH_LDAP_USER_ATTR_MAP = DictField( AUTH_LDAP_USER_ATTR_MAP = FormDictField(
label=_("User attr map"), label=_("User attr map"),
initial=json.dumps({
"username": "cn",
"name": "sn",
"email": "mail"
}),
help_text=_( help_text=_(
"User attr map present how to map LDAP user attr to jumpserver, username,name,email is jumpserver attr") "User attr map present how to map LDAP user attr to jumpserver, "
"username,name,email is jumpserver attr"
)
) )
# AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU # AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU
# AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER # AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
...@@ -156,13 +144,13 @@ class TerminalSettingForm(BaseForm): ...@@ -156,13 +144,13 @@ class TerminalSettingForm(BaseForm):
TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField( TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField(
initial=True, required=False, label=_("Public key auth") initial=True, required=False, label=_("Public key auth")
) )
TERMINAL_COMMAND_STORAGE = DictField( TERMINAL_COMMAND_STORAGE = FormEncryptDictField(
label=_("Command storage"), help_text=_( label=_("Command storage"), help_text=_(
"Set terminal storage setting, `default` is the using as default," "Set terminal storage setting, `default` is the using as default,"
"You can set other storage and some terminal using" "You can set other storage and some terminal using"
) )
) )
TERMINAL_REPLAY_STORAGE = DictField( TERMINAL_REPLAY_STORAGE = FormEncryptDictField(
label=_("Replay storage"), help_text=_( label=_("Replay storage"), help_text=_(
"Set replay storage setting, `default` is the using as default," "Set replay storage setting, `default` is the using as default,"
"You can set other storage and some terminal using" "You can set other storage and some terminal using"
...@@ -194,6 +182,14 @@ class SecuritySettingForm(BaseForm): ...@@ -194,6 +182,14 @@ class SecuritySettingForm(BaseForm):
"number of times, no login is allowed during this time interval." "number of times, no login is allowed during this time interval."
) )
) )
SECURITY_MAX_IDLE_TIME = forms.IntegerField(
initial=30, required=False,
label=_("Connection max idle time"),
help_text=_(
'If idle time more than it, disconnect connection(only ssh now) '
'Unit: minute'
),
)
# min length # min length
SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField( SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField(
initial=6, label=_("Password minimum length"), initial=6, label=_("Password minimum length"),
...@@ -223,9 +219,10 @@ class SecuritySettingForm(BaseForm): ...@@ -223,9 +219,10 @@ class SecuritySettingForm(BaseForm):
'and resets must contain numeric characters') 'and resets must contain numeric characters')
) )
# special char # special char
SECURITY_PASSWORD_SPECIAL_CHAR= forms.BooleanField( SECURITY_PASSWORD_SPECIAL_CHAR = forms.BooleanField(
initial=False, required=False, initial=False, required=False,
label=_("Must contain special characters"), label=_("Must contain special characters"),
help_text=_('After opening, the user password changes ' help_text=_('After opening, the user password changes '
'and resets must contain special characters') 'and resets must contain special characters')
) )
...@@ -7,6 +7,10 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -7,6 +7,10 @@ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
from .utils import get_signer
signer = get_signer()
class SettingQuerySet(models.QuerySet): class SettingQuerySet(models.QuerySet):
def __getattr__(self, item): def __getattr__(self, item):
...@@ -26,6 +30,7 @@ class Setting(models.Model): ...@@ -26,6 +30,7 @@ class Setting(models.Model):
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name")) name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
value = models.TextField(verbose_name=_("Value")) value = models.TextField(verbose_name=_("Value"))
category = models.CharField(max_length=128, default="default") category = models.CharField(max_length=128, default="default")
encrypted = models.BooleanField(default=False)
enabled = models.BooleanField(verbose_name=_("Enabled"), default=True) enabled = models.BooleanField(verbose_name=_("Enabled"), default=True)
comment = models.TextField(verbose_name=_("Comment")) comment = models.TextField(verbose_name=_("Comment"))
...@@ -34,10 +39,21 @@ class Setting(models.Model): ...@@ -34,10 +39,21 @@ class Setting(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
def __getattr__(self, item):
instances = self.__class__.objects.filter(name=item)
if len(instances) == 1:
return instances[0].cleaned_value
else:
return None
@property @property
def cleaned_value(self): def cleaned_value(self):
try: try:
return json.loads(self.value) value = self.value
if self.encrypted:
value = signer.unsign(value)
value = json.loads(value)
return value
except json.JSONDecodeError: except json.JSONDecodeError:
return None return None
...@@ -45,6 +61,8 @@ class Setting(models.Model): ...@@ -45,6 +61,8 @@ class Setting(models.Model):
def cleaned_value(self, item): def cleaned_value(self, item):
try: try:
v = json.dumps(item) v = json.dumps(item)
if self.encrypted:
v = signer.sign(v)
self.value = v self.value = v
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
raise ValueError("Json dump error: {}".format(str(e))) raise ValueError("Json dump error: {}".format(str(e)))
...@@ -59,11 +77,7 @@ class Setting(models.Model): ...@@ -59,11 +77,7 @@ class Setting(models.Model):
pass pass
def refresh_setting(self): def refresh_setting(self):
try: setattr(settings, self.name, self.cleaned_value)
value = json.loads(self.value)
except json.JSONDecodeError:
return
setattr(settings, self.name, value)
if self.name == "AUTH_LDAP": if self.name == "AUTH_LDAP":
if self.cleaned_value and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS: if self.cleaned_value and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
...@@ -81,3 +95,5 @@ class Setting(models.Model): ...@@ -81,3 +95,5 @@ class Setting(models.Model):
class Meta: class Meta:
db_table = "settings" db_table = "settings"
common_settings = Setting()
...@@ -92,3 +92,9 @@ class AdminUserRequiredMixin(UserPassesTestMixin): ...@@ -92,3 +92,9 @@ class AdminUserRequiredMixin(UserPassesTestMixin):
return redirect('orgs:switch-a-org') return redirect('orgs:switch-a-org')
return HttpResponseForbidden() return HttpResponseForbidden()
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
class SuperUserRequiredMixin(UserPassesTestMixin):
def test_func(self):
if self.request.user.is_authenticated and self.request.user.is_superuser:
return True
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.dispatch import receiver from django.dispatch import receiver
from django.db.models.signals import post_save from django.db.models.signals import post_save, pre_save
from django.conf import settings from django.conf import settings
from django.db.utils import ProgrammingError, OperationalError from django.db.utils import ProgrammingError, OperationalError
from jumpserver.utils import current_request
from .models import Setting from .models import Setting
from .utils import get_logger from .utils import get_logger
from .signals import django_ready, ldap_auth_enable from .signals import django_ready, ldap_auth_enable
...@@ -42,3 +43,9 @@ def ldap_auth_on_changed(sender, enabled=True, **kwargs): ...@@ -42,3 +43,9 @@ def ldap_auth_on_changed(sender, enabled=True, **kwargs):
if settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS: if settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS:
settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND) settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND)
@receiver(pre_save, dispatch_uid="my_unique_identifier")
def on_create_set_created_by(sender, instance=None, **kwargs):
if hasattr(instance, 'created_by') and not instance.created_by:
if current_request and current_request.user.is_authenticated:
instance.created_by = current_request.user.name
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
<h3>{% trans "User login settings" %}</h3> <h3>{% trans "User login settings" %}</h3>
{% for field in form %} {% for field in form %}
{% if forloop.counter == 4 %} {% if forloop.counter == 5 %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<h3>{% trans "Password check rule" %}</h3> <h3>{% trans "Password check rule" %}</h3>
{% endif %} {% endif %}
......
...@@ -378,6 +378,15 @@ def get_signer(): ...@@ -378,6 +378,15 @@ def get_signer():
return signer return signer
def get_request_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
if x_forwarded_for and x_forwarded_for[0]:
login_ip = x_forwarded_for[0]
else:
login_ip = request.META.get('REMOTE_ADDR', '')
return login_ip
class TeeObj: class TeeObj:
origin_stdout = sys.stdout origin_stdout = sys.stdout
......
...@@ -6,11 +6,11 @@ from django.conf import settings ...@@ -6,11 +6,11 @@ from django.conf import settings
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
TerminalSettingForm, SecuritySettingForm TerminalSettingForm, SecuritySettingForm
from common.permissions import AdminUserRequiredMixin from common.permissions import SuperUserRequiredMixin
from .signals import ldap_auth_enable from .signals import ldap_auth_enable
class BasicSettingView(AdminUserRequiredMixin, TemplateView): class BasicSettingView(SuperUserRequiredMixin, TemplateView):
form_class = BasicSettingForm form_class = BasicSettingForm
template_name = "common/basic_setting.html" template_name = "common/basic_setting.html"
...@@ -36,7 +36,7 @@ class BasicSettingView(AdminUserRequiredMixin, TemplateView): ...@@ -36,7 +36,7 @@ class BasicSettingView(AdminUserRequiredMixin, TemplateView):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class EmailSettingView(AdminUserRequiredMixin, TemplateView): class EmailSettingView(SuperUserRequiredMixin, TemplateView):
form_class = EmailSettingForm form_class = EmailSettingForm
template_name = "common/email_setting.html" template_name = "common/email_setting.html"
...@@ -62,7 +62,7 @@ class EmailSettingView(AdminUserRequiredMixin, TemplateView): ...@@ -62,7 +62,7 @@ class EmailSettingView(AdminUserRequiredMixin, TemplateView):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class LDAPSettingView(AdminUserRequiredMixin, TemplateView): class LDAPSettingView(SuperUserRequiredMixin, TemplateView):
form_class = LDAPSettingForm form_class = LDAPSettingForm
template_name = "common/ldap_setting.html" template_name = "common/ldap_setting.html"
...@@ -90,7 +90,7 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView): ...@@ -90,7 +90,7 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class TerminalSettingView(AdminUserRequiredMixin, TemplateView): class TerminalSettingView(SuperUserRequiredMixin, TemplateView):
form_class = TerminalSettingForm form_class = TerminalSettingForm
template_name = "common/terminal_setting.html" template_name = "common/terminal_setting.html"
...@@ -120,7 +120,7 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView): ...@@ -120,7 +120,7 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class SecuritySettingView(AdminUserRequiredMixin, TemplateView): class SecuritySettingView(SuperUserRequiredMixin, TemplateView):
form_class = SecuritySettingForm form_class = SecuritySettingForm
template_name = "common/security_setting.html" template_name = "common/security_setting.html"
......
...@@ -6,6 +6,8 @@ import pytz ...@@ -6,6 +6,8 @@ import pytz
from django.utils import timezone from django.utils import timezone
from django.shortcuts import HttpResponse from django.shortcuts import HttpResponse
from .utils import set_current_request
class TimezoneMiddleware: class TimezoneMiddleware:
def __init__(self, get_response): def __init__(self, get_response):
...@@ -45,3 +47,13 @@ class DemoMiddleware: ...@@ -45,3 +47,13 @@ class DemoMiddleware:
else: else:
response = self.get_response(request) response = self.get_response(request)
return response return response
class RequestMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
set_current_request(request)
response = self.get_response(request)
return response
...@@ -95,6 +95,7 @@ MIDDLEWARE = [ ...@@ -95,6 +95,7 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'jumpserver.middleware.TimezoneMiddleware', 'jumpserver.middleware.TimezoneMiddleware',
'jumpserver.middleware.DemoMiddleware', 'jumpserver.middleware.DemoMiddleware',
'jumpserver.middleware.RequestMiddleware',
'orgs.middleware.OrgMiddleware', 'orgs.middleware.OrgMiddleware',
] ]
...@@ -127,7 +128,6 @@ TEMPLATES = [ ...@@ -127,7 +128,6 @@ TEMPLATES = [
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
'jumpserver.context_processor.jumpserver_processor',
'django.template.context_processors.i18n', 'django.template.context_processors.i18n',
'django.template.context_processors.debug', 'django.template.context_processors.debug',
'django.template.context_processors.request', 'django.template.context_processors.request',
...@@ -136,6 +136,7 @@ TEMPLATES = [ ...@@ -136,6 +136,7 @@ TEMPLATES = [
'django.template.context_processors.static', 'django.template.context_processors.static',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.template.context_processors.media', 'django.template.context_processors.media',
'jumpserver.context_processor.jumpserver_processor',
'orgs.context_processor.org_processor', 'orgs.context_processor.org_processor',
*get_xpack_context_processor(), *get_xpack_context_processor(),
], ],
...@@ -214,7 +215,10 @@ LOGGING = { ...@@ -214,7 +215,10 @@ LOGGING = {
}, },
'file': { 'file': {
'level': 'DEBUG', 'level': 'DEBUG',
'class': 'logging.FileHandler', 'class': 'logging.handlers.TimedRotatingFileHandler',
'when': "D",
'interval': 1,
"backupCount": 7,
'formatter': 'main', 'formatter': 'main',
'filename': os.path.join(PROJECT_DIR, 'logs', 'jumpserver.log') 'filename': os.path.join(PROJECT_DIR, 'logs', 'jumpserver.log')
}, },
...@@ -270,7 +274,8 @@ LOGGING = { ...@@ -270,7 +274,8 @@ LOGGING = {
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/ # https://docs.djangoproject.com/en/1.10/topics/i18n/
LANGUAGE_CODE = 'en' # LANGUAGE_CODE = 'en'
LANGUAGE_CODE = 'zh'
TIME_ZONE = 'Asia/Shanghai' TIME_ZONE = 'Asia/Shanghai'
...@@ -281,7 +286,9 @@ USE_L10N = True ...@@ -281,7 +286,9 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
# I18N translation # I18N translation
LOCALE_PATHS = [os.path.join(BASE_DIR, 'i18n'), ] LOCALE_PATHS = [
os.path.join(BASE_DIR, 'locale'),
]
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/ # https://docs.djangoproject.com/en/1.10/howto/static-files/
...@@ -441,7 +448,8 @@ TERMINAL_REPLAY_STORAGE = { ...@@ -441,7 +448,8 @@ TERMINAL_REPLAY_STORAGE = {
DEFAULT_PASSWORD_MIN_LENGTH = 6 DEFAULT_PASSWORD_MIN_LENGTH = 6
DEFAULT_LOGIN_LIMIT_COUNT = 7 DEFAULT_LOGIN_LIMIT_COUNT = 7
DEFAULT_LOGIN_LIMIT_TIME = 30 DEFAULT_LOGIN_LIMIT_TIME = 30 # Unit: minute
DEFAULT_SECURITY_MAX_IDLE_TIME = 30 # Unit: minute
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html # Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3 = { BOOTSTRAP3 = {
......
...@@ -6,6 +6,8 @@ import os ...@@ -6,6 +6,8 @@ import os
from django.urls import path, include, re_path from django.urls import path, include, re_path
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from django.conf.urls.i18n import i18n_patterns
from django.views.i18n import JavaScriptCatalog
from rest_framework.response import Response from rest_framework.response import Response
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse from django.http import HttpResponse
...@@ -14,7 +16,7 @@ from rest_framework import permissions ...@@ -14,7 +16,7 @@ from rest_framework import permissions
from drf_yasg.views import get_schema_view from drf_yasg.views import get_schema_view
from drf_yasg import openapi from drf_yasg import openapi
from .views import IndexView, LunaView from .views import IndexView, LunaView, I18NView
schema_view = get_schema_view( schema_view = get_schema_view(
openapi.Info( openapi.Info(
...@@ -78,10 +80,14 @@ app_view_patterns = [ ...@@ -78,10 +80,14 @@ app_view_patterns = [
if settings.XPACK_ENABLED: if settings.XPACK_ENABLED:
app_view_patterns.append(path('xpack/', include('xpack.urls', namespace='xpack'))) app_view_patterns.append(path('xpack/', include('xpack.urls', namespace='xpack')))
js_i18n_patterns = i18n_patterns(
path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
)
urlpatterns = [ urlpatterns = [
path('', IndexView.as_view(), name='index'), path('', IndexView.as_view(), name='index'),
path('luna/', LunaView.as_view(), name='luna-error'), path('luna/', LunaView.as_view(), name='luna-error'),
path('i18n/<str:lang>/', I18NView.as_view(), name='i18n-switch'),
path('settings/', include('common.urls.view_urls', namespace='settings')), path('settings/', include('common.urls.view_urls', namespace='settings')),
path('common/', include('common.urls.view_urls', namespace='common')), path('common/', include('common.urls.view_urls', namespace='common')),
path('api/v1/', redirect_format_api), path('api/v1/', redirect_format_api),
...@@ -93,6 +99,7 @@ urlpatterns = [ ...@@ -93,6 +99,7 @@ urlpatterns = [
urlpatterns += app_view_patterns urlpatterns += app_view_patterns
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \
+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += js_i18n_patterns
if settings.DEBUG: if settings.DEBUG:
urlpatterns += [ urlpatterns += [
......
# -*- coding: utf-8 -*-
#
from functools import partial
from common.utils import LocalProxy
try:
from threading import local
except ImportError:
from django.utils._threading_local import local
_thread_locals = local()
def set_current_request(request):
setattr(_thread_locals, 'current_request', request)
def _find(attr):
return getattr(_thread_locals, attr, None)
def get_current_request():
return _find('current_request')
current_request = LocalProxy(partial(_find, 'current_request'))
import datetime import datetime
from django.http import HttpResponse from django.http import HttpResponse, HttpResponseRedirect
from django.conf import settings
from django.views.generic import TemplateView, View from django.views.generic import TemplateView, View
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.db.models import Count from django.db.models import Count
from django.shortcuts import redirect from django.shortcuts import redirect
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
...@@ -85,7 +87,7 @@ class IndexView(LoginRequiredMixin, TemplateView): ...@@ -85,7 +87,7 @@ class IndexView(LoginRequiredMixin, TemplateView):
return self.session_month.values('user').distinct().count() return self.session_month.values('user').distinct().count()
def get_month_inactive_user_total(self): def get_month_inactive_user_total(self):
return User.objects.all().count() - self.get_month_active_user_total() return current_org.get_org_users().count() - self.get_month_active_user_total()
def get_month_active_asset_total(self): def get_month_active_asset_total(self):
return self.session_month.values('asset').distinct().count() return self.session_month.values('asset').distinct().count()
...@@ -95,7 +97,7 @@ class IndexView(LoginRequiredMixin, TemplateView): ...@@ -95,7 +97,7 @@ class IndexView(LoginRequiredMixin, TemplateView):
@staticmethod @staticmethod
def get_user_disabled_total(): def get_user_disabled_total():
return User.objects.filter(is_active=False).count() return current_org.get_org_users().filter(is_active=False).count()
@staticmethod @staticmethod
def get_asset_disabled_total(): def get_asset_disabled_total():
...@@ -173,11 +175,14 @@ class IndexView(LoginRequiredMixin, TemplateView): ...@@ -173,11 +175,14 @@ class IndexView(LoginRequiredMixin, TemplateView):
class LunaView(View): class LunaView(View):
def get(self, request): def get(self, request):
msg = """ msg = _("<div>Luna is a separately deployed program, you need to deploy Luna, coco, configure nginx for url distribution,</div> "
Luna是单独部署的一个程序,你需要部署luna,coco,配置nginx做url分发, "</div>If you see this page, prove that you are not accessing the nginx listening port. Good luck.</div>")
如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运
"""
return HttpResponse(msg) return HttpResponse(msg)
class I18NView(View):
def get(self, request, lang):
referer_url = request.META.get('HTTP_REFERER', '/')
response = HttpResponseRedirect(referer_url)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang)
return response
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n" "Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-08-16 16:28+0800\n" "POT-Creation-Date: 2018-08-27 18:29+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n" "Language-Team: Jumpserver team<ibuler@qq.com>\n"
...@@ -18,32 +18,32 @@ msgstr "" ...@@ -18,32 +18,32 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: assets/api/node.py:58 #: assets/api/node.py:58
msgid "You cant update the root node name" msgid "You can't update the root node name"
msgstr "" msgstr "不能修改根节点名称"
#: assets/api/node.py:82 #: assets/api/node.py:82
msgid "New node {}" msgid "New node {}"
msgstr "新节点 {}" msgstr "新节点 {}"
#: assets/api/node.py:221 #: assets/api/node.py:222
msgid "更新节点资产硬件信息: {}" msgid "Update node asset hardware information: {}"
msgstr "" msgstr "更新节点资产硬件信息: {}"
#: assets/api/node.py:234 #: assets/api/node.py:236
msgid "测试节点下资产是否可连接: {}" msgid "Test if the assets under the node are connectable: {}"
msgstr "" msgstr "测试节点下资产是否可连接: {}"
#: assets/forms/asset.py:27 assets/models/asset.py:82 assets/models/user.py:113 #: assets/forms/asset.py:27 assets/models/asset.py:82 assets/models/user.py:113
#: assets/templates/assets/asset_detail.html:183 #: assets/templates/assets/asset_detail.html:183
#: assets/templates/assets/asset_detail.html:191 #: assets/templates/assets/asset_detail.html:191
#: assets/templates/assets/system_user_detail.html:178 perms/models.py:32 #: assets/templates/assets/system_user_asset.html:95 perms/models.py:32
msgid "Nodes" msgid "Nodes"
msgstr "节点管理" msgstr "节点管理"
#: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:112 #: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:112
#: assets/forms/asset.py:116 assets/models/asset.py:87 #: assets/forms/asset.py:116 assets/models/asset.py:87
#: assets/models/cluster.py:19 assets/models/user.py:73 #: assets/models/cluster.py:19 assets/models/user.py:73
#: assets/templates/assets/asset_detail.html:73 templates/_nav.html:25 #: assets/templates/assets/asset_detail.html:73 templates/_nav.html:24
#: xpack/plugins/orgs/templates/orgs/org_list.html:18 #: xpack/plugins/orgs/templates/orgs/org_list.html:18
msgid "Admin user" msgid "Admin user"
msgstr "管理用户" msgstr "管理用户"
...@@ -51,7 +51,7 @@ msgstr "管理用户" ...@@ -51,7 +51,7 @@ msgstr "管理用户"
#: assets/forms/asset.py:33 assets/forms/asset.py:72 assets/forms/asset.py:128 #: assets/forms/asset.py:33 assets/forms/asset.py:72 assets/forms/asset.py:128
#: assets/templates/assets/asset_create.html:36 #: assets/templates/assets/asset_create.html:36
#: assets/templates/assets/asset_create.html:38 #: assets/templates/assets/asset_create.html:38
#: assets/templates/assets/asset_list.html:80 #: assets/templates/assets/asset_list.html:81
#: assets/templates/assets/asset_update.html:41 #: assets/templates/assets/asset_update.html:41
#: assets/templates/assets/asset_update.html:43 #: assets/templates/assets/asset_update.html:43
#: assets/templates/assets/user_asset_list.html:34 #: assets/templates/assets/user_asset_list.html:34
...@@ -60,15 +60,17 @@ msgid "Label" ...@@ -60,15 +60,17 @@ msgid "Label"
msgstr "标签" msgstr "标签"
#: assets/forms/asset.py:37 assets/forms/asset.py:76 assets/models/asset.py:78 #: assets/forms/asset.py:37 assets/forms/asset.py:76 assets/models/asset.py:78
#: assets/models/domain.py:47 assets/templates/assets/user_asset_list.html:168 #: assets/models/domain.py:24 assets/models/domain.py:50
#: assets/templates/assets/user_asset_list.html:168
#: xpack/plugins/orgs/templates/orgs/org_list.html:17 #: xpack/plugins/orgs/templates/orgs/org_list.html:17
msgid "Domain" msgid "Domain"
msgstr "网域" msgstr "网域"
#: assets/forms/asset.py:41 assets/forms/asset.py:66 assets/forms/asset.py:80 #: assets/forms/asset.py:41 assets/forms/asset.py:66 assets/forms/asset.py:80
#: assets/forms/asset.py:131 assets/templates/assets/asset_create.html:30 #: assets/forms/asset.py:131 assets/models/node.py:28
#: assets/templates/assets/asset_update.html:35 perms/forms.py:50 #: assets/templates/assets/asset_create.html:30
#: perms/forms.py:57 perms/models.py:78 #: assets/templates/assets/asset_update.html:35 perms/forms.py:37
#: perms/forms.py:44 perms/models.py:79
#: perms/templates/perms/asset_permission_list.html:57 #: perms/templates/perms/asset_permission_list.html:57
#: perms/templates/perms/asset_permission_list.html:151 #: perms/templates/perms/asset_permission_list.html:151
msgid "Node" msgid "Node"
...@@ -94,7 +96,7 @@ msgid "Select assets" ...@@ -94,7 +96,7 @@ msgid "Select assets"
msgstr "选择资产" msgstr "选择资产"
#: assets/forms/asset.py:108 assets/models/asset.py:75 #: assets/forms/asset.py:108 assets/models/asset.py:75
#: assets/models/domain.py:45 assets/templates/assets/admin_user_assets.html:53 #: assets/models/domain.py:48 assets/templates/assets/admin_user_assets.html:53
#: assets/templates/assets/asset_detail.html:69 #: assets/templates/assets/asset_detail.html:69
#: assets/templates/assets/domain_gateway_list.html:58 #: assets/templates/assets/domain_gateway_list.html:58
#: assets/templates/assets/system_user_asset.html:51 #: assets/templates/assets/system_user_asset.html:51
...@@ -103,13 +105,13 @@ msgid "Port" ...@@ -103,13 +105,13 @@ msgid "Port"
msgstr "端口" msgstr "端口"
#: assets/forms/domain.py:15 assets/forms/label.py:13 #: assets/forms/domain.py:15 assets/forms/label.py:13
#: assets/models/asset.py:242 assets/templates/assets/admin_user_list.html:25 #: assets/models/asset.py:242 assets/templates/assets/admin_user_list.html:28
#: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:23 #: assets/templates/assets/domain_list.html:26
#: assets/templates/assets/label_list.html:16 #: assets/templates/assets/label_list.html:16
#: assets/templates/assets/system_user_list.html:30 audits/models.py:13 #: assets/templates/assets/system_user_list.html:33 audits/models.py:18
#: audits/templates/audits/ftp_log_list.html:41 #: audits/templates/audits/ftp_log_list.html:41
#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:47 #: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:34
#: perms/models.py:31 #: perms/models.py:31
#: perms/templates/perms/asset_permission_create_update.html:40 #: perms/templates/perms/asset_permission_create_update.html:40
#: perms/templates/perms/asset_permission_list.html:56 #: perms/templates/perms/asset_permission_list.html:56
...@@ -125,21 +127,21 @@ msgstr "资产" ...@@ -125,21 +127,21 @@ msgstr "资产"
#: assets/forms/domain.py:42 #: assets/forms/domain.py:42
msgid "Password should not contain special characters" msgid "Password should not contain special characters"
msgstr "密码不能包含特殊字符" msgstr "不能包含特殊字符"
#: assets/forms/domain.py:59 assets/forms/user.py:79 assets/forms/user.py:139 #: assets/forms/domain.py:59 assets/forms/user.py:79 assets/forms/user.py:139
#: assets/models/base.py:22 assets/models/cluster.py:18 #: assets/models/base.py:22 assets/models/cluster.py:18
#: assets/models/domain.py:18 assets/models/group.py:20 #: assets/models/domain.py:18 assets/models/group.py:20
#: assets/models/label.py:18 assets/templates/assets/admin_user_detail.html:56 #: assets/models/label.py:18 assets/templates/assets/admin_user_detail.html:56
#: assets/templates/assets/admin_user_list.html:23 #: assets/templates/assets/admin_user_list.html:26
#: assets/templates/assets/domain_detail.html:56 #: assets/templates/assets/domain_detail.html:56
#: assets/templates/assets/domain_gateway_list.html:56 #: assets/templates/assets/domain_gateway_list.html:56
#: assets/templates/assets/domain_list.html:22 #: assets/templates/assets/domain_list.html:25
#: assets/templates/assets/label_list.html:14 #: assets/templates/assets/label_list.html:14
#: assets/templates/assets/system_user_detail.html:58 #: assets/templates/assets/system_user_detail.html:58
#: assets/templates/assets/system_user_list.html:26 common/models.py:26 #: assets/templates/assets/system_user_list.html:29 common/models.py:26
#: common/templates/common/terminal_setting.html:72 #: common/templates/common/terminal_setting.html:72
#: common/templates/common/terminal_setting.html:90 ops/models/adhoc.py:36 #: common/templates/common/terminal_setting.html:90 ops/models/adhoc.py:37
#: ops/templates/ops/task_detail.html:59 ops/templates/ops/task_list.html:35 #: ops/templates/ops/task_detail.html:59 ops/templates/ops/task_list.html:35
#: orgs/models.py:12 perms/models.py:28 #: orgs/models.py:12 perms/models.py:28
#: perms/templates/perms/asset_permission_detail.html:62 #: perms/templates/perms/asset_permission_detail.html:62
...@@ -162,15 +164,15 @@ msgstr "名称" ...@@ -162,15 +164,15 @@ msgstr "名称"
#: assets/forms/domain.py:60 assets/forms/user.py:80 assets/forms/user.py:140 #: assets/forms/domain.py:60 assets/forms/user.py:80 assets/forms/user.py:140
#: assets/models/base.py:23 assets/templates/assets/admin_user_detail.html:60 #: assets/models/base.py:23 assets/templates/assets/admin_user_detail.html:60
#: assets/templates/assets/admin_user_list.html:24 #: assets/templates/assets/admin_user_list.html:27
#: assets/templates/assets/domain_gateway_list.html:60 #: assets/templates/assets/domain_gateway_list.html:60
#: assets/templates/assets/system_user_detail.html:62 #: assets/templates/assets/system_user_detail.html:62
#: assets/templates/assets/system_user_list.html:27 #: assets/templates/assets/system_user_list.html:30
#: audits/templates/audits/login_log_list.html:49
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:15 #: perms/templates/perms/asset_permission_user.html:55 users/forms.py:15
#: users/forms.py:33 users/models/authentication.py:70 users/models/user.py:49 #: users/forms.py:33 users/models/authentication.py:70 users/models/user.py:49
#: users/templates/users/_select_user_modal.html:14 #: users/templates/users/_select_user_modal.html:14
#: users/templates/users/login.html:60 #: users/templates/users/login.html:62
#: users/templates/users/login_log_list.html:49
#: users/templates/users/user_detail.html:67 #: users/templates/users/user_detail.html:67
#: users/templates/users/user_list.html:24 #: users/templates/users/user_list.html:24
#: users/templates/users/user_profile.html:47 #: users/templates/users/user_profile.html:47
...@@ -183,11 +185,11 @@ msgstr "密码或密钥密码" ...@@ -183,11 +185,11 @@ msgstr "密码或密钥密码"
#: assets/forms/user.py:25 assets/models/base.py:24 common/forms.py:113 #: assets/forms/user.py:25 assets/models/base.py:24 common/forms.py:113
#: users/forms.py:17 users/forms.py:35 users/forms.py:47 #: users/forms.py:17 users/forms.py:35 users/forms.py:47
#: users/templates/users/login.html:63 #: users/templates/users/login.html:65
#: users/templates/users/reset_password.html:53 #: users/templates/users/reset_password.html:53
#: users/templates/users/user_create.html:10 #: users/templates/users/user_create.html:10
#: users/templates/users/user_password_authentication.html:14 #: users/templates/users/user_password_authentication.html:18
#: users/templates/users/user_password_update.html:42 #: users/templates/users/user_password_update.html:43
#: users/templates/users/user_profile_update.html:40 #: users/templates/users/user_profile_update.html:40
#: users/templates/users/user_pubkey_update.html:40 #: users/templates/users/user_pubkey_update.html:40
msgid "Password" msgid "Password"
...@@ -225,17 +227,17 @@ msgid "" ...@@ -225,17 +227,17 @@ msgid ""
"password." "password."
msgstr "如果选择手动登录模式,用户名和密码则不需要填写" msgstr "如果选择手动登录模式,用户名和密码则不需要填写"
#: assets/models/asset.py:72 assets/models/domain.py:44 #: assets/models/asset.py:72 assets/models/domain.py:47
#: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:52 #: assets/templates/assets/admin_user_assets.html:52
#: assets/templates/assets/asset_detail.html:61 #: assets/templates/assets/asset_detail.html:61
#: assets/templates/assets/asset_list.html:92 #: assets/templates/assets/asset_list.html:93
#: assets/templates/assets/domain_gateway_list.html:57 #: assets/templates/assets/domain_gateway_list.html:57
#: assets/templates/assets/system_user_asset.html:50 #: assets/templates/assets/system_user_asset.html:50
#: assets/templates/assets/user_asset_list.html:46 #: assets/templates/assets/user_asset_list.html:46
#: assets/templates/assets/user_asset_list.html:162 common/forms.py:145 #: assets/templates/assets/user_asset_list.html:162
#: audits/templates/audits/login_log_list.html:52 common/forms.py:145
#: perms/templates/perms/asset_permission_asset.html:55 #: perms/templates/perms/asset_permission_asset.html:55
#: users/templates/users/login_log_list.html:52
#: users/templates/users/user_granted_asset.html:45 #: users/templates/users/user_granted_asset.html:45
#: users/templates/users/user_group_granted_asset.html:45 #: users/templates/users/user_group_granted_asset.html:45
msgid "IP" msgid "IP"
...@@ -244,7 +246,7 @@ msgstr "IP" ...@@ -244,7 +246,7 @@ msgstr "IP"
#: assets/models/asset.py:73 assets/templates/assets/_asset_list_modal.html:45 #: assets/models/asset.py:73 assets/templates/assets/_asset_list_modal.html:45
#: assets/templates/assets/admin_user_assets.html:51 #: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/asset_detail.html:57 #: assets/templates/assets/asset_detail.html:57
#: assets/templates/assets/asset_list.html:91 #: assets/templates/assets/asset_list.html:92
#: assets/templates/assets/system_user_asset.html:49 #: assets/templates/assets/system_user_asset.html:49
#: assets/templates/assets/user_asset_list.html:45 #: assets/templates/assets/user_asset_list.html:45
#: assets/templates/assets/user_asset_list.html:161 common/forms.py:144 #: assets/templates/assets/user_asset_list.html:161 common/forms.py:144
...@@ -254,11 +256,11 @@ msgstr "IP" ...@@ -254,11 +256,11 @@ msgstr "IP"
msgid "Hostname" msgid "Hostname"
msgstr "主机名" msgstr "主机名"
#: assets/models/asset.py:74 assets/models/domain.py:46 #: assets/models/asset.py:74 assets/models/domain.py:49
#: assets/models/user.py:116 #: assets/models/user.py:116
#: assets/templates/assets/domain_gateway_list.html:59 #: assets/templates/assets/domain_gateway_list.html:59
#: assets/templates/assets/system_user_detail.html:70 #: assets/templates/assets/system_user_detail.html:70
#: assets/templates/assets/system_user_list.html:28 #: assets/templates/assets/system_user_list.html:31
#: assets/templates/assets/user_asset_list.html:164 #: assets/templates/assets/user_asset_list.html:164
#: terminal/templates/terminal/session_list.html:75 #: terminal/templates/terminal/session_list.html:75
msgid "Protocol" msgid "Protocol"
...@@ -269,7 +271,7 @@ msgstr "协议" ...@@ -269,7 +271,7 @@ msgstr "协议"
msgid "Platform" msgid "Platform"
msgstr "系统平台" msgstr "系统平台"
#: assets/models/asset.py:83 assets/models/domain.py:49 #: assets/models/asset.py:83 assets/models/domain.py:52
#: assets/models/label.py:21 assets/templates/assets/asset_detail.html:105 #: assets/models/label.py:21 assets/templates/assets/asset_detail.html:105
#: assets/templates/assets/user_asset_list.html:169 #: assets/templates/assets/user_asset_list.html:169
msgid "Is active" msgid "Is active"
...@@ -308,10 +310,8 @@ msgid "CPU cores" ...@@ -308,10 +310,8 @@ msgid "CPU cores"
msgstr "CPU核数" msgstr "CPU核数"
#: assets/models/asset.py:105 #: assets/models/asset.py:105
#, fuzzy
#| msgid "CPU count"
msgid "CPU vcpus" msgid "CPU vcpus"
msgstr "CPU数量" msgstr "CPU总数"
#: assets/models/asset.py:107 assets/templates/assets/asset_detail.html:89 #: assets/models/asset.py:107 assets/templates/assets/asset_detail.html:89
msgid "Memory" msgid "Memory"
...@@ -344,7 +344,7 @@ msgstr "主机名原始" ...@@ -344,7 +344,7 @@ msgstr "主机名原始"
#: assets/models/asset.py:124 assets/templates/assets/asset_create.html:34 #: assets/models/asset.py:124 assets/templates/assets/asset_create.html:34
#: assets/templates/assets/asset_detail.html:220 #: assets/templates/assets/asset_detail.html:220
#: assets/templates/assets/asset_update.html:39 templates/_nav.html:27 #: assets/templates/assets/asset_update.html:39 templates/_nav.html:26
msgid "Labels" msgid "Labels"
msgstr "标签管理" msgstr "标签管理"
...@@ -355,7 +355,7 @@ msgstr "标签管理" ...@@ -355,7 +355,7 @@ msgstr "标签管理"
#: assets/templates/assets/domain_detail.html:72 #: assets/templates/assets/domain_detail.html:72
#: assets/templates/assets/system_user_detail.html:100 #: assets/templates/assets/system_user_detail.html:100
#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:37 #: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:37
#: perms/models.py:83 perms/templates/perms/asset_permission_detail.html:98 #: perms/models.py:84 perms/templates/perms/asset_permission_detail.html:98
#: users/models/user.py:92 users/templates/users/user_detail.html:111 #: users/models/user.py:92 users/templates/users/user_detail.html:111
msgid "Created by" msgid "Created by"
msgstr "创建者" msgstr "创建者"
...@@ -366,7 +366,7 @@ msgstr "创建者" ...@@ -366,7 +366,7 @@ msgstr "创建者"
#: assets/templates/assets/domain_detail.html:68 #: assets/templates/assets/domain_detail.html:68
#: assets/templates/assets/system_user_detail.html:96 #: assets/templates/assets/system_user_detail.html:96
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63 #: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63
#: orgs/models.py:16 perms/models.py:38 perms/models.py:84 #: orgs/models.py:16 perms/models.py:38 perms/models.py:85
#: perms/templates/perms/asset_permission_detail.html:94 #: 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:17
#: users/templates/users/user_group_detail.html:63 #: users/templates/users/user_group_detail.html:63
...@@ -376,18 +376,18 @@ msgstr "创建日期" ...@@ -376,18 +376,18 @@ msgstr "创建日期"
#: assets/models/asset.py:131 assets/models/base.py:27 #: assets/models/asset.py:131 assets/models/base.py:27
#: assets/models/cluster.py:29 assets/models/domain.py:19 #: assets/models/cluster.py:29 assets/models/domain.py:19
#: assets/models/domain.py:48 assets/models/group.py:23 #: assets/models/domain.py:51 assets/models/group.py:23
#: assets/models/label.py:22 assets/templates/assets/admin_user_detail.html:72 #: assets/models/label.py:22 assets/templates/assets/admin_user_detail.html:72
#: assets/templates/assets/admin_user_list.html:29 #: assets/templates/assets/admin_user_list.html:32
#: assets/templates/assets/asset_detail.html:125 #: assets/templates/assets/asset_detail.html:125
#: assets/templates/assets/domain_detail.html:76 #: assets/templates/assets/domain_detail.html:76
#: assets/templates/assets/domain_gateway_list.html:61 #: assets/templates/assets/domain_gateway_list.html:61
#: assets/templates/assets/domain_list.html:25 #: assets/templates/assets/domain_list.html:28
#: assets/templates/assets/system_user_detail.html:104 #: assets/templates/assets/system_user_detail.html:104
#: assets/templates/assets/system_user_list.html:34 #: assets/templates/assets/system_user_list.html:37
#: assets/templates/assets/user_asset_list.html:170 common/models.py:30 #: assets/templates/assets/user_asset_list.html:170 common/models.py:30
#: ops/models/adhoc.py:42 orgs/models.py:17 perms/models.py:39 #: ops/models/adhoc.py:43 orgs/models.py:17 perms/models.py:39
#: perms/models.py:85 perms/templates/perms/asset_permission_detail.html:102 #: perms/models.py:86 perms/templates/perms/asset_permission_detail.html:102
#: terminal/models.py:27 terminal/templates/terminal/terminal_detail.html:63 #: terminal/models.py:27 terminal/templates/terminal/terminal_detail.html:63
#: users/models/group.py:15 users/models/user.py:84 #: users/models/group.py:15 users/models/user.py:84
#: users/templates/users/user_detail.html:123 #: users/templates/users/user_detail.html:123
...@@ -442,7 +442,7 @@ msgid "Default" ...@@ -442,7 +442,7 @@ msgid "Default"
msgstr "默认" msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:14 #: assets/models/cluster.py:36 assets/models/label.py:14
#: users/models/user.py:360 #: users/models/user.py:363
msgid "System" msgid "System"
msgstr "系统" msgstr "系统"
...@@ -454,6 +454,25 @@ msgstr "默认Cluster" ...@@ -454,6 +454,25 @@ msgstr "默认Cluster"
msgid "Cluster" msgid "Cluster"
msgstr "集群" msgstr "集群"
#: assets/models/cluster.py:56
msgid "Beijing unicom"
msgstr "北京联通"
#: assets/models/cluster.py:56
msgid "Beijing telecom"
msgstr "北京电信"
#: assets/models/cluster.py:56
msgid "BGP full netcom"
msgstr "BGP全网通"
#: assets/models/domain.py:59 assets/templates/assets/domain_detail.html:21
#: assets/templates/assets/domain_detail.html:64
#: assets/templates/assets/domain_gateway_list.html:21
#: assets/templates/assets/domain_list.html:27
msgid "Gateway"
msgstr "网关"
#: assets/models/group.py:30 #: assets/models/group.py:30
msgid "Asset group" msgid "Asset group"
msgstr "资产组" msgstr "资产组"
...@@ -462,19 +481,23 @@ msgstr "资产组" ...@@ -462,19 +481,23 @@ msgstr "资产组"
msgid "Default asset group" msgid "Default asset group"
msgstr "默认资产组" msgstr "默认资产组"
#: assets/models/label.py:15 audits/models.py:11 #: assets/models/label.py:15 audits/models.py:16 audits/models.py:36
#: audits/templates/audits/ftp_log_list.html:33 #: audits/models.py:49 audits/templates/audits/ftp_log_list.html:33
#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:16 #: audits/templates/audits/ftp_log_list.html:70
#: perms/forms.py:41 perms/models.py:29 #: audits/templates/audits/operate_log_list.html:33
#: audits/templates/audits/operate_log_list.html:66
#: audits/templates/audits/password_change_log_list.html:33
#: audits/templates/audits/password_change_log_list.html:50 perms/forms.py:28
#: perms/models.py:29
#: perms/templates/perms/asset_permission_create_update.html:36 #: perms/templates/perms/asset_permission_create_update.html:36
#: perms/templates/perms/asset_permission_list.html:54 #: perms/templates/perms/asset_permission_list.html:54
#: perms/templates/perms/asset_permission_list.html:142 #: perms/templates/perms/asset_permission_list.html:142 templates/index.html:87
#: terminal/backends/command/models.py:12 terminal/models.py:127 #: terminal/backends/command/models.py:12 terminal/models.py:127
#: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:32
#: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:33
#: terminal/templates/terminal/session_list.html:71 users/forms.py:312 #: terminal/templates/terminal/session_list.html:71 users/forms.py:312
#: users/models/user.py:33 users/models/user.py:348 #: users/models/user.py:33 users/models/user.py:351
#: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:13 users/views/user.py:378 #: users/templates/users/user_group_list.html:13 users/views/user.py:378
#: xpack/plugins/orgs/forms.py:26 #: xpack/plugins/orgs/forms.py:26
...@@ -508,6 +531,7 @@ msgstr "手动登录" ...@@ -508,6 +531,7 @@ msgstr "手动登录"
#: assets/models/user.py:114 #: assets/models/user.py:114
#: assets/templates/assets/_asset_group_bulk_update_modal.html:11 #: assets/templates/assets/_asset_group_bulk_update_modal.html:11
#: assets/templates/assets/system_user_asset.html:21 #: assets/templates/assets/system_user_asset.html:21
#: assets/templates/assets/system_user_detail.html:22
#: assets/views/admin_user.py:29 assets/views/admin_user.py:47 #: assets/views/admin_user.py:29 assets/views/admin_user.py:47
#: assets/views/admin_user.py:63 assets/views/admin_user.py:78 #: assets/views/admin_user.py:63 assets/views/admin_user.py:78
#: assets/views/admin_user.py:102 assets/views/asset.py:53 #: assets/views/admin_user.py:102 assets/views/asset.py:53
...@@ -519,7 +543,7 @@ msgstr "手动登录" ...@@ -519,7 +543,7 @@ msgstr "手动登录"
#: assets/views/label.py:26 assets/views/label.py:42 assets/views/label.py:58 #: 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:28 assets/views/system_user.py:44
#: assets/views/system_user.py:60 assets/views/system_user.py:74 #: assets/views/system_user.py:60 assets/views/system_user.py:74
#: templates/_nav.html:20 #: templates/_nav.html:19
msgid "Assets" msgid "Assets"
msgstr "资产管理" msgstr "资产管理"
...@@ -542,17 +566,17 @@ msgid "Shell" ...@@ -542,17 +566,17 @@ msgid "Shell"
msgstr "Shell" msgstr "Shell"
#: assets/models/user.py:120 assets/templates/assets/system_user_detail.html:66 #: assets/models/user.py:120 assets/templates/assets/system_user_detail.html:66
#: assets/templates/assets/system_user_list.html:29 #: assets/templates/assets/system_user_list.html:32
msgid "Login mode" msgid "Login mode"
msgstr "登录模式" msgstr "登录模式"
#: assets/models/user.py:181 assets/templates/assets/user_asset_list.html:167 #: assets/models/user.py:181 assets/templates/assets/user_asset_list.html:167
#: audits/models.py:14 audits/templates/audits/ftp_log_list.html:49 #: audits/models.py:19 audits/templates/audits/ftp_log_list.html:49
#: audits/templates/audits/ftp_log_list.html:73 perms/forms.py:53 #: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:40
#: perms/models.py:33 perms/models.py:80 #: perms/models.py:33 perms/models.py:81
#: perms/templates/perms/asset_permission_detail.html:140 #: perms/templates/perms/asset_permission_detail.html:140
#: perms/templates/perms/asset_permission_list.html:58 #: perms/templates/perms/asset_permission_list.html:58
#: perms/templates/perms/asset_permission_list.html:154 templates/_nav.html:26 #: perms/templates/perms/asset_permission_list.html:154 templates/_nav.html:25
#: terminal/backends/command/models.py:14 terminal/models.py:129 #: terminal/backends/command/models.py:14 terminal/models.py:129
#: terminal/templates/terminal/command_list.html:48 #: terminal/templates/terminal/command_list.html:48
#: terminal/templates/terminal/command_list.html:74 #: terminal/templates/terminal/command_list.html:74
...@@ -567,37 +591,45 @@ msgstr "系统用户" ...@@ -567,37 +591,45 @@ msgstr "系统用户"
msgid "%(value)s is not an even number" msgid "%(value)s is not an even number"
msgstr "%(value)s is not an even number" msgstr "%(value)s is not an even number"
#: assets/tasks.py:97 assets/tasks.py:117 #: assets/tasks.py:96
msgid "更新资产硬件信息" msgid "Update some assets hardware info"
msgstr "" msgstr "更新资产硬件信息"
#: assets/tasks.py:136 #: assets/tasks.py:116
msgid "定期更新资产硬件信息" msgid "Update asset hardware info"
msgstr "" msgstr "更新资产硬件信息"
#: assets/tasks.py:214 #: assets/tasks.py:135
msgid "定期测试管理账号可连接性: {}" msgid "Update assets hardware info period"
msgstr "" msgstr "定期更新资产硬件信息"
#: assets/tasks.py:221 #: assets/tasks.py:213
msgid "测试管理行号可连接性: {}" msgid "Test admin user connectability period: {}"
msgstr "" msgstr "定期测试管理账号可连接性: {}"
#: assets/tasks.py:231 #: assets/tasks.py:220
msgid "测试资产可连接性" msgid "Test admin user connectability: {}"
msgstr "" msgstr "测试管理行号可连接性: {}"
#: assets/tasks.py:230
msgid "Test assets connectability"
msgstr "测试资产可连接性"
#: assets/tasks.py:301 #: assets/tasks.py:301
msgid "Test system user connectability: {}" msgid "Test system user connectability: {}"
msgstr "测试系统用户可连接性: {}" msgstr "测试系统用户可连接性: {}"
#: assets/tasks.py:317 #: assets/tasks.py:316
msgid "定期测试系统用户可连接性: {}" msgid "Test system user connectability period: {}"
msgstr "" msgstr "定期测试系统用户可连接性: {}"
#: assets/tasks.py:402 #: assets/tasks.py:397 assets/tasks.py:412
msgid "推送系统用户到入资产: {}" msgid "Push system users to assets: {}"
msgstr "" msgstr "推送系统用户到入资产: {}"
#: assets/tasks.py:403
msgid "Push system users to asset: {} => {}"
msgstr "推送系统用户到入资产: {} => {}"
#: assets/templates/assets/_asset_group_bulk_update_modal.html:5 #: assets/templates/assets/_asset_group_bulk_update_modal.html:5
msgid "Update asset group" msgid "Update asset group"
...@@ -648,7 +680,7 @@ msgid "If set id, will use this id update asset existed" ...@@ -648,7 +680,7 @@ msgid "If set id, will use this id update asset existed"
msgstr "如果设置了id,则会使用该行信息更新该id的资产" msgstr "如果设置了id,则会使用该行信息更新该id的资产"
#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:54 #: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:54
#: templates/_nav.html:23 #: templates/_nav.html:22
msgid "Asset list" msgid "Asset list"
msgstr "资产列表" msgstr "资产列表"
...@@ -698,8 +730,9 @@ msgstr "其它" ...@@ -698,8 +730,9 @@ msgstr "其它"
#: terminal/templates/terminal/terminal_update.html:47 #: terminal/templates/terminal/terminal_update.html:47
#: users/templates/users/_user.html:46 #: users/templates/users/_user.html:46
#: users/templates/users/user_bulk_update.html:23 #: users/templates/users/user_bulk_update.html:23
#: users/templates/users/user_password_update.html:70 #: users/templates/users/user_detail.html:172
#: users/templates/users/user_profile.html:188 #: users/templates/users/user_password_update.html:71
#: users/templates/users/user_profile.html:198
#: users/templates/users/user_profile_update.html:63 #: users/templates/users/user_profile_update.html:63
#: users/templates/users/user_pubkey_update.html:70 #: users/templates/users/user_pubkey_update.html:70
#: users/templates/users/user_pubkey_update.html:76 #: users/templates/users/user_pubkey_update.html:76
...@@ -710,7 +743,7 @@ msgstr "重置" ...@@ -710,7 +743,7 @@ msgstr "重置"
#: assets/templates/assets/admin_user_create_update.html:46 #: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24 #: assets/templates/assets/asset_bulk_update.html:24
#: assets/templates/assets/asset_create.html:68 #: assets/templates/assets/asset_create.html:68
#: assets/templates/assets/asset_list.html:113 #: assets/templates/assets/asset_list.html:114
#: assets/templates/assets/asset_update.html:72 #: assets/templates/assets/asset_update.html:72
#: assets/templates/assets/domain_create_update.html:17 #: assets/templates/assets/domain_create_update.html:17
#: assets/templates/assets/gateway_create_update.html:59 #: assets/templates/assets/gateway_create_update.html:59
...@@ -725,10 +758,10 @@ msgstr "重置" ...@@ -725,10 +758,10 @@ msgstr "重置"
#: terminal/templates/terminal/session_list.html:126 #: terminal/templates/terminal/session_list.html:126
#: terminal/templates/terminal/terminal_update.html:48 #: terminal/templates/terminal/terminal_update.html:48
#: users/templates/users/_user.html:47 #: users/templates/users/_user.html:47
#: users/templates/users/forgot_password.html:44 #: users/templates/users/forgot_password.html:45
#: users/templates/users/user_bulk_update.html:24 #: users/templates/users/user_bulk_update.html:24
#: users/templates/users/user_list.html:45 #: users/templates/users/user_list.html:45
#: users/templates/users/user_password_update.html:71 #: users/templates/users/user_password_update.html:72
#: users/templates/users/user_profile_update.html:64 #: users/templates/users/user_profile_update.html:64
#: users/templates/users/user_pubkey_update.html:77 #: users/templates/users/user_pubkey_update.html:77
msgid "Submit" msgid "Submit"
...@@ -770,15 +803,15 @@ msgid "Asset list of " ...@@ -770,15 +803,15 @@ msgid "Asset list of "
msgstr "资产列表" msgstr "资产列表"
#: assets/templates/assets/admin_user_assets.html:54 #: assets/templates/assets/admin_user_assets.html:54
#: assets/templates/assets/admin_user_list.html:26 #: assets/templates/assets/admin_user_list.html:29
#: assets/templates/assets/system_user_asset.html:52 #: assets/templates/assets/system_user_asset.html:52
#: assets/templates/assets/system_user_list.html:31 #: assets/templates/assets/system_user_list.html:34
#: users/templates/users/user_group_granted_asset.html:47 #: users/templates/users/user_group_granted_asset.html:47
msgid "Reachable" msgid "Reachable"
msgstr "可连接" msgstr "可连接"
#: assets/templates/assets/admin_user_assets.html:66 #: assets/templates/assets/admin_user_assets.html:66
#: assets/templates/assets/system_user_asset.html:64 #: assets/templates/assets/system_user_asset.html:65
#: assets/templates/assets/system_user_detail.html:116 #: assets/templates/assets/system_user_detail.html:116
#: perms/templates/perms/asset_permission_detail.html:114 #: perms/templates/perms/asset_permission_detail.html:114
msgid "Quick update" msgid "Quick update"
...@@ -791,22 +824,22 @@ msgstr "测试可连接性" ...@@ -791,22 +824,22 @@ msgstr "测试可连接性"
#: assets/templates/assets/admin_user_assets.html:75 #: assets/templates/assets/admin_user_assets.html:75
#: assets/templates/assets/asset_detail.html:171 #: assets/templates/assets/asset_detail.html:171
#: assets/templates/assets/system_user_asset.html:81 #: assets/templates/assets/system_user_asset.html:74
#: assets/templates/assets/system_user_detail.html:151 #: assets/templates/assets/system_user_detail.html:151
msgid "Test" msgid "Test"
msgstr "测试" msgstr "测试"
#: assets/templates/assets/admin_user_detail.html:24 #: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:85 #: assets/templates/assets/admin_user_list.html:88
#: assets/templates/assets/asset_detail.html:24 #: assets/templates/assets/asset_detail.html:24
#: assets/templates/assets/asset_list.html:170 #: assets/templates/assets/asset_list.html:171
#: assets/templates/assets/domain_detail.html:24 #: assets/templates/assets/domain_detail.html:24
#: assets/templates/assets/domain_detail.html:103 #: assets/templates/assets/domain_detail.html:103
#: assets/templates/assets/domain_gateway_list.html:85 #: assets/templates/assets/domain_gateway_list.html:85
#: assets/templates/assets/domain_list.html:50 #: assets/templates/assets/domain_list.html:53
#: assets/templates/assets/label_list.html:38 #: assets/templates/assets/label_list.html:38
#: assets/templates/assets/system_user_detail.html:26 #: assets/templates/assets/system_user_detail.html:26
#: assets/templates/assets/system_user_list.html:89 #: assets/templates/assets/system_user_list.html:92 audits/models.py:32
#: perms/templates/perms/asset_permission_detail.html:30 #: perms/templates/perms/asset_permission_detail.html:30
#: perms/templates/perms/asset_permission_list.html:200 #: perms/templates/perms/asset_permission_list.html:200
#: terminal/templates/terminal/terminal_detail.html:16 #: terminal/templates/terminal/terminal_detail.html:16
...@@ -816,7 +849,8 @@ msgstr "测试" ...@@ -816,7 +849,8 @@ msgstr "测试"
#: users/templates/users/user_group_list.html:43 #: users/templates/users/user_group_list.html:43
#: users/templates/users/user_list.html:77 #: users/templates/users/user_list.html:77
#: users/templates/users/user_profile.html:151 #: users/templates/users/user_profile.html:151
#: users/templates/users/user_profile.html:180 #: users/templates/users/user_profile.html:181
#: users/templates/users/user_profile.html:190
#: xpack/plugins/orgs/templates/orgs/org_detail.html:25 #: xpack/plugins/orgs/templates/orgs/org_detail.html:25
#: xpack/plugins/orgs/templates/orgs/org_list.html:85 #: xpack/plugins/orgs/templates/orgs/org_list.html:85
#: xpack/templates/orgs/org_list.html:43 #: xpack/templates/orgs/org_list.html:43
...@@ -824,16 +858,16 @@ msgid "Update" ...@@ -824,16 +858,16 @@ msgid "Update"
msgstr "更新" msgstr "更新"
#: assets/templates/assets/admin_user_detail.html:28 #: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:86 #: assets/templates/assets/admin_user_list.html:89
#: assets/templates/assets/asset_detail.html:28 #: assets/templates/assets/asset_detail.html:28
#: assets/templates/assets/asset_list.html:171 #: assets/templates/assets/asset_list.html:172
#: assets/templates/assets/domain_detail.html:28 #: assets/templates/assets/domain_detail.html:28
#: assets/templates/assets/domain_detail.html:104 #: assets/templates/assets/domain_detail.html:104
#: assets/templates/assets/domain_gateway_list.html:86 #: assets/templates/assets/domain_gateway_list.html:86
#: assets/templates/assets/domain_list.html:51 #: assets/templates/assets/domain_list.html:54
#: assets/templates/assets/label_list.html:39 #: assets/templates/assets/label_list.html:39
#: assets/templates/assets/system_user_detail.html:30 #: assets/templates/assets/system_user_detail.html:30
#: assets/templates/assets/system_user_list.html:90 #: assets/templates/assets/system_user_list.html:93 audits/models.py:33
#: ops/templates/ops/task_list.html:72 #: ops/templates/ops/task_list.html:72
#: perms/templates/perms/asset_permission_detail.html:34 #: perms/templates/perms/asset_permission_detail.html:34
#: perms/templates/perms/asset_permission_list.html:201 #: perms/templates/perms/asset_permission_list.html:201
...@@ -860,46 +894,67 @@ msgstr "选择节点" ...@@ -860,46 +894,67 @@ msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100 #: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:200 #: assets/templates/assets/asset_detail.html:200
#: assets/templates/assets/asset_list.html:631 #: assets/templates/assets/asset_list.html:633
#: assets/templates/assets/system_user_detail.html:195 #: assets/templates/assets/system_user_asset.html:112
#: assets/templates/assets/system_user_list.html:139 templates/_modal.html:22 #: assets/templates/assets/system_user_detail.html:330
#: assets/templates/assets/system_user_list.html:143 templates/_modal.html:22
#: terminal/templates/terminal/session_detail.html:108 #: terminal/templates/terminal/session_detail.html:108
#: users/templates/users/user_detail.html:374 #: users/templates/users/user_detail.html:382
#: users/templates/users/user_detail.html:399 #: users/templates/users/user_detail.html:408
#: users/templates/users/user_detail.html:422 #: users/templates/users/user_detail.html:431
#: users/templates/users/user_detail.html:466 #: users/templates/users/user_detail.html:476
#: users/templates/users/user_group_create_update.html:32 #: users/templates/users/user_group_create_update.html:32
#: users/templates/users/user_group_list.html:86 #: users/templates/users/user_group_list.html:87
#: users/templates/users/user_list.html:200 #: users/templates/users/user_list.html:201
#: users/templates/users/user_profile.html:222 #: users/templates/users/user_profile.html:232
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:33
#: xpack/templates/orgs/org_list.html:86 #: xpack/templates/orgs/org_list.html:86
msgid "Confirm" msgid "Confirm"
msgstr "确认" msgstr "确认"
#: assets/templates/assets/admin_user_list.html:15 #: assets/templates/assets/admin_user_list.html:10
msgid ""
"Admin users are asset (charged server) on the root, or have NOPASSWD: ALL "
"sudo permissions users, "
msgstr ""
"管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,"
#: assets/templates/assets/admin_user_list.html:11
msgid ""
"Jumpserver users of the system using the user to `push system user`, `get "
"assets hardware information`, etc. "
msgstr "Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。"
#: assets/templates/assets/admin_user_list.html:12
msgid "You can set any one for Windows or other hardware."
msgstr "Windows或其它硬件可以随意设置一个"
#: assets/templates/assets/admin_user_list.html:18
#: assets/views/admin_user.py:48 #: assets/views/admin_user.py:48
msgid "Create admin user" msgid "Create admin user"
msgstr "创建管理用户" msgstr "创建管理用户"
#: assets/templates/assets/admin_user_list.html:27 #: assets/templates/assets/admin_user_list.html:30
#: assets/templates/assets/system_user_list.html:32 #: assets/templates/assets/system_user_list.html:35
msgid "Unreachable" msgid "Unreachable"
msgstr "不可达" msgstr "不可达"
#: assets/templates/assets/admin_user_list.html:28 #: assets/templates/assets/admin_user_list.html:31
#: assets/templates/assets/system_user_list.html:33 #: assets/templates/assets/system_user_list.html:36
#: ops/templates/ops/adhoc_history.html:54 #: ops/templates/ops/adhoc_history.html:54
#: ops/templates/ops/task_history.html:60 #: ops/templates/ops/task_history.html:60
msgid "Ratio" msgid "Ratio"
msgstr "比例" msgstr "比例"
#: assets/templates/assets/admin_user_list.html:30 #: assets/templates/assets/admin_user_list.html:33
#: assets/templates/assets/asset_list.html:96 #: assets/templates/assets/asset_list.html:97
#: assets/templates/assets/domain_gateway_list.html:62 #: assets/templates/assets/domain_gateway_list.html:62
#: assets/templates/assets/domain_list.html:26 #: assets/templates/assets/domain_list.html:29
#: assets/templates/assets/label_list.html:17 #: assets/templates/assets/label_list.html:17
#: assets/templates/assets/system_user_list.html:35 #: assets/templates/assets/system_user_asset.html:53
#: assets/templates/assets/system_user_list.html:38 audits/models.py:37
#: audits/templates/audits/operate_log_list.html:41
#: audits/templates/audits/operate_log_list.html:67
#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64 #: 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 #: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:42
#: perms/templates/perms/asset_permission_list.html:60 #: perms/templates/perms/asset_permission_list.html:60
...@@ -912,6 +967,16 @@ msgstr "比例" ...@@ -912,6 +967,16 @@ msgstr "比例"
msgid "Action" msgid "Action"
msgstr "动作" msgstr "动作"
#: assets/templates/assets/asset_bulk_update.html:8
#: users/templates/users/user_bulk_update.html:8
msgid "Select properties that need to be modified"
msgstr "选择需要修改属性"
#: assets/templates/assets/asset_bulk_update.html:10
#: users/templates/users/user_bulk_update.html:10
msgid "Select all"
msgstr "全选"
#: assets/templates/assets/asset_detail.html:85 #: assets/templates/assets/asset_detail.html:85
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
...@@ -934,9 +999,9 @@ msgid "Quick modify" ...@@ -934,9 +999,9 @@ msgid "Quick modify"
msgstr "快速修改" msgstr "快速修改"
#: assets/templates/assets/asset_detail.html:143 #: assets/templates/assets/asset_detail.html:143
#: assets/templates/assets/asset_list.html:94 #: assets/templates/assets/asset_list.html:95
#: assets/templates/assets/user_asset_list.html:47 perms/models.py:34 #: assets/templates/assets/user_asset_list.html:47 perms/models.py:34
#: perms/models.py:81 #: perms/models.py:82
#: perms/templates/perms/asset_permission_create_update.html:47 #: perms/templates/perms/asset_permission_create_update.html:47
#: perms/templates/perms/asset_permission_detail.html:120 #: perms/templates/perms/asset_permission_detail.html:120
#: perms/templates/perms/asset_permission_list.html:59 #: perms/templates/perms/asset_permission_list.html:59
...@@ -959,134 +1024,156 @@ msgid "Refresh" ...@@ -959,134 +1024,156 @@ msgid "Refresh"
msgstr "刷新" msgstr "刷新"
#: assets/templates/assets/asset_detail.html:300 #: assets/templates/assets/asset_detail.html:300
#: users/templates/users/user_detail.html:294 #: users/templates/users/user_detail.html:301
#: users/templates/users/user_detail.html:321 #: users/templates/users/user_detail.html:328
msgid "Update successfully!" msgid "Update successfully!"
msgstr "更新成功" msgstr "更新成功"
#: assets/templates/assets/asset_list.html:68 assets/views/asset.py:93 #: assets/templates/assets/asset_list.html:8
msgid ""
"The left side is the asset tree, right click to create, delete, and change "
"the tree node, authorization asset is also organized as a node, and the "
"right side is the asset under that node"
msgstr ""
"左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的,"
"右侧是属于该节点下的资产"
#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:93
msgid "Create asset" msgid "Create asset"
msgstr "创建资产" msgstr "创建资产"
#: assets/templates/assets/asset_list.html:72 #: assets/templates/assets/asset_list.html:73
#: users/templates/users/user_list.html:7 #: users/templates/users/user_list.html:7
msgid "Import" msgid "Import"
msgstr "导入" msgstr "导入"
#: assets/templates/assets/asset_list.html:75 #: assets/templates/assets/asset_list.html:76
#: users/templates/users/user_list.html:10 #: users/templates/users/user_list.html:10
msgid "Export" msgid "Export"
msgstr "导出" msgstr "导出"
#: assets/templates/assets/asset_list.html:93 #: assets/templates/assets/asset_list.html:94
msgid "Hardware" msgid "Hardware"
msgstr "硬件" msgstr "硬件"
#: assets/templates/assets/asset_list.html:105 #: assets/templates/assets/asset_list.html:106
#: users/templates/users/user_list.html:38 #: users/templates/users/user_list.html:38
msgid "Delete selected" msgid "Delete selected"
msgstr "批量删除" msgstr "批量删除"
#: assets/templates/assets/asset_list.html:106 #: assets/templates/assets/asset_list.html:107
#: users/templates/users/user_list.html:39 #: users/templates/users/user_list.html:39
msgid "Update selected" msgid "Update selected"
msgstr "批量更新" msgstr "批量更新"
#: assets/templates/assets/asset_list.html:107 #: assets/templates/assets/asset_list.html:108
msgid "Remove from this node" msgid "Remove from this node"
msgstr "从节点移除" msgstr "从节点移除"
#: assets/templates/assets/asset_list.html:108 #: assets/templates/assets/asset_list.html:109
#: users/templates/users/user_list.html:40 #: users/templates/users/user_list.html:40
msgid "Deactive selected" msgid "Deactive selected"
msgstr "禁用所选" msgstr "禁用所选"
#: assets/templates/assets/asset_list.html:109 #: assets/templates/assets/asset_list.html:110
#: users/templates/users/user_list.html:41 #: users/templates/users/user_list.html:41
msgid "Active selected" msgid "Active selected"
msgstr "激活所选" msgstr "激活所选"
#: assets/templates/assets/asset_list.html:126 #: assets/templates/assets/asset_list.html:127
msgid "Add node" msgid "Add node"
msgstr "新建节点" msgstr "新建节点"
#: assets/templates/assets/asset_list.html:127 #: assets/templates/assets/asset_list.html:128
msgid "Rename node" msgid "Rename node"
msgstr "重命名节点" msgstr "重命名节点"
#: assets/templates/assets/asset_list.html:128 #: assets/templates/assets/asset_list.html:129
msgid "Delete node" msgid "Delete node"
msgstr "删除节点" msgstr "删除节点"
#: assets/templates/assets/asset_list.html:130 #: assets/templates/assets/asset_list.html:131
msgid "Add assets to node" msgid "Add assets to node"
msgstr "添加资产到节点" msgstr "添加资产到节点"
#: assets/templates/assets/asset_list.html:131 #: assets/templates/assets/asset_list.html:132
msgid "Move assets to node" msgid "Move assets to node"
msgstr "移动资产到节点" msgstr "移动资产到节点"
#: assets/templates/assets/asset_list.html:133 #: assets/templates/assets/asset_list.html:134
msgid "Refresh node hardware info" msgid "Refresh node hardware info"
msgstr "更新节点资产硬件信息" msgstr "更新节点资产硬件信息"
#: assets/templates/assets/asset_list.html:134 #: assets/templates/assets/asset_list.html:135
msgid "Test node connective" msgid "Test node connective"
msgstr "测试节点资产可连接性" msgstr "测试节点资产可连接性"
#: assets/templates/assets/asset_list.html:136 #: assets/templates/assets/asset_list.html:137
msgid "Display only current node assets" msgid "Display only current node assets"
msgstr "仅显示当前节点资产" msgstr "仅显示当前节点资产"
#: assets/templates/assets/asset_list.html:137 #: assets/templates/assets/asset_list.html:138
msgid "Displays all child node assets" msgid "Displays all child node assets"
msgstr "显示所有子节点资产" msgstr "显示所有子节点资产"
#: assets/templates/assets/asset_list.html:207 #: assets/templates/assets/asset_list.html:208
msgid "Create node failed" msgid "Create node failed"
msgstr "创建节点失败" msgstr "创建节点失败"
#: assets/templates/assets/asset_list.html:219 #: assets/templates/assets/asset_list.html:220
msgid "Have child node, cancel" msgid "Have child node, cancel"
msgstr "存在子节点,不能删除" msgstr "存在子节点,不能删除"
#: assets/templates/assets/asset_list.html:221 #: assets/templates/assets/asset_list.html:222
msgid "Have assets, cancel" msgid "Have assets, cancel"
msgstr "存在资产,不能删除" msgstr "存在资产,不能删除"
#: assets/templates/assets/asset_list.html:292 #: assets/templates/assets/asset_list.html:293
msgid "Rename success" msgid "Rename success"
msgstr "重命名成功" msgstr "重命名成功"
#: assets/templates/assets/asset_list.html:293 #: assets/templates/assets/asset_list.html:294
msgid "Rename failed, do not change the root node name" msgid "Rename failed, do not change the root node name"
msgstr "重命名失败,不可以更改根节点名称" msgstr "重命名失败,不能更改root节点的名称"
#: assets/templates/assets/asset_list.html:626 #: assets/templates/assets/asset_list.html:627
#: assets/templates/assets/system_user_list.html:134 #: assets/templates/assets/system_user_list.html:137
#: users/templates/users/user_detail.html:369 #: users/templates/users/user_detail.html:376
#: users/templates/users/user_detail.html:394 #: users/templates/users/user_detail.html:402
#: users/templates/users/user_detail.html:461 #: users/templates/users/user_detail.html:470
#: users/templates/users/user_group_list.html:81 #: users/templates/users/user_group_list.html:81
#: users/templates/users/user_list.html:195 #: users/templates/users/user_list.html:195
#: xpack/templates/orgs/org_list.html:81 #: xpack/templates/orgs/org_list.html:81
msgid "Are you sure?" msgid "Are you sure?"
msgstr "你确认吗?" msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:627 #: assets/templates/assets/asset_list.html:628
msgid "This will delete the selected assets !!!" msgid "This will delete the selected assets !!!"
msgstr "删除选择资产" msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:635 #: assets/templates/assets/asset_list.html:631
#: assets/templates/assets/system_user_detail.html:328
#: assets/templates/assets/system_user_list.html:141
#: users/templates/users/user_detail.html:380
#: users/templates/users/user_detail.html:406
#: users/templates/users/user_detail.html:474
#: users/templates/users/user_group_create_update.html:31
#: users/templates/users/user_group_list.html:85
#: users/templates/users/user_list.html:199
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:32
msgid "Cancel"
msgstr "取消"
#: assets/templates/assets/asset_list.html:637
msgid "Asset Deleted." msgid "Asset Deleted."
msgstr "已被删除" msgstr "已被删除"
#: assets/templates/assets/asset_list.html:636 #: assets/templates/assets/asset_list.html:638
#: assets/templates/assets/asset_list.html:641 #: assets/templates/assets/asset_list.html:643
msgid "Asset Delete" msgid "Asset Delete"
msgstr "删除" msgstr "删除"
#: assets/templates/assets/asset_list.html:640 #: assets/templates/assets/asset_list.html:642
msgid "Asset Deleting failed." msgid "Asset Deleting failed."
msgstr "删除失败" msgstr "删除失败"
...@@ -1104,13 +1191,6 @@ msgstr "确认删除" ...@@ -1104,13 +1191,6 @@ msgstr "确认删除"
msgid "Are you sure delete" msgid "Are you sure delete"
msgstr "您确定删除吗?" msgstr "您确定删除吗?"
#: assets/templates/assets/domain_detail.html:21
#: assets/templates/assets/domain_detail.html:64
#: assets/templates/assets/domain_gateway_list.html:21
#: assets/templates/assets/domain_list.html:24
msgid "Gateway"
msgstr "网关"
#: assets/templates/assets/domain_gateway_list.html:31 #: assets/templates/assets/domain_gateway_list.html:31
msgid "Gateway list" msgid "Gateway list"
msgstr "网关列表" msgstr "网关列表"
...@@ -1127,7 +1207,28 @@ msgstr "创建网关" ...@@ -1127,7 +1207,28 @@ msgstr "创建网关"
msgid "Test connection" msgid "Test connection"
msgstr "测试连接" msgstr "测试连接"
#: assets/templates/assets/domain_list.html:14 assets/views/domain.py:46 #: assets/templates/assets/domain_gateway_list.html:123
msgid "Can be connected"
msgstr "可连接"
#: assets/templates/assets/domain_gateway_list.html:124
msgid "The connection fails"
msgstr "连接失败"
#: assets/templates/assets/domain_list.html:9
msgid ""
"The domain function is added to address the fact that some environments "
"(such as the hybrid cloud) cannot be connected directly by jumping on the "
"gateway server."
msgstr ""
"网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过"
"网关服务器进行跳转登录。"
#: assets/templates/assets/domain_list.html:11
msgid "JMS => Domain gateway => Target assets"
msgstr "JMS => 网域网关 => 目标资产"
#: assets/templates/assets/domain_list.html:17 assets/views/domain.py:46
msgid "Create domain" msgid "Create domain"
msgstr "创建网域" msgstr "创建网域"
...@@ -1139,26 +1240,32 @@ msgstr "创建标签" ...@@ -1139,26 +1240,32 @@ msgstr "创建标签"
msgid "Assets of " msgid "Assets of "
msgstr "资产" msgstr "资产"
#: assets/templates/assets/system_user_asset.html:70 #: assets/templates/assets/system_user_asset.html:71
#: assets/templates/assets/system_user_detail.html:148
msgid "Test assets connective"
msgstr "测试资产可连接性"
#: assets/templates/assets/system_user_asset.html:80
#: assets/templates/assets/system_user_detail.html:139 #: assets/templates/assets/system_user_detail.html:139
msgid "Push system user now" msgid "Push system user now"
msgstr "立刻推送系统" msgstr "立刻推送系统"
#: assets/templates/assets/system_user_asset.html:73 #: assets/templates/assets/system_user_asset.html:83
#: assets/templates/assets/system_user_asset.html:159
#: assets/templates/assets/system_user_detail.html:142 #: assets/templates/assets/system_user_detail.html:142
msgid "Push" msgid "Push"
msgstr "推送" msgstr "推送"
#: assets/templates/assets/system_user_asset.html:78 #: assets/templates/assets/system_user_asset.html:103
#: assets/templates/assets/system_user_detail.html:148 msgid "Add to node"
msgid "Test assets connective" msgstr "添加到节点"
msgstr "测试资产可连接性"
#: assets/templates/assets/system_user_asset.html:147 #: assets/templates/assets/system_user_asset.html:223
msgid "Task has been send, Go to ops task list seen result" msgid "Task has been send, Go to ops task list seen result"
msgstr "任务已下发,查看ops任务列表" msgstr "任务已下发,查看ops任务列表"
#: assets/templates/assets/system_user_asset.html:159 #: assets/templates/assets/system_user_asset.html:235
#: assets/templates/assets/system_user_asset.html:273
msgid "Task has been send, seen left assets status" msgid "Task has been send, seen left assets status"
msgstr "任务已下发,查看左侧资产状态" msgstr "任务已下发,查看左侧资产状态"
...@@ -1170,37 +1277,67 @@ msgstr "家目录" ...@@ -1170,37 +1277,67 @@ msgstr "家目录"
msgid "Uid" msgid "Uid"
msgstr "Uid" msgstr "Uid"
#: assets/templates/assets/system_user_detail.html:186 #: assets/templates/assets/system_user_detail.html:324
msgid "Add to node" msgid "Are you sure to remove authentication information for the system user ?"
msgstr "添加到节点" msgstr "你确定清除该系统用户的认证信息吗 ?"
#: assets/templates/assets/system_user_detail.html:353 #: assets/templates/assets/system_user_detail.html:336
msgid "Clear auth" msgid "Clear auth"
msgstr "清除认证信息" msgstr "清除认证信息"
#: assets/templates/assets/system_user_detail.html:353 #: assets/templates/assets/system_user_detail.html:336
msgid "success" msgid "success"
msgstr "成功" msgstr "成功"
#: assets/templates/assets/system_user_list.html:18 #: assets/templates/assets/system_user_list.html:10
msgid ""
"System user is Jumpserver jump login assets used by the users, can be "
"understood as the user login assets, such as web, sa, the dba (` ssh "
"web@some-host `), rather than using a user the username login server jump (` "
"ssh xiaoming@some-host `); "
msgstr ""
"系统用户是 Jumpserver跳转登录资产时使用的用户,可以理解为登录资产用户,如 "
"web, sa, dba(`ssh web@some-host`), 而不是使用某个用户的用户名跳转登录服务器"
"(`ssh xiaoming@some-host`);"
#: assets/templates/assets/system_user_list.html:11
msgid ""
"In simple terms, users log into Jumpserver using their own username, and "
"Jumpserver uses system users to log into assets. "
msgstr ""
"简单来说是 用户使用自己的用户名登录Jumpserver, Jumpserver使用系统用户登录资"
"产。"
#: assets/templates/assets/system_user_list.html:12
msgid ""
"When system users are created, if you choose auto push Jumpserver to use "
"ansible push system users into the asset, if the asset (Switch, Windows) "
"does not support ansible, please manually fill in the account password. "
"Automatic push for Windows is not currently supported."
msgstr ""
"系统用户创建时,如果选择了自动推送 Jumpserver会使用ansible自动推送系统用户到"
"资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。目前还不"
"支持Windows的自动推送"
#: assets/templates/assets/system_user_list.html:21
#: assets/views/system_user.py:45 #: assets/views/system_user.py:45
msgid "Create system user" msgid "Create system user"
msgstr "创建系统用户" msgstr "创建系统用户"
#: assets/templates/assets/system_user_list.html:135 #: assets/templates/assets/system_user_list.html:138
msgid "This will delete the selected System Users !!!" msgid "This will delete the selected System Users !!!"
msgstr "删除选择系统用户" msgstr "删除选择系统用户"
#: assets/templates/assets/system_user_list.html:143 #: assets/templates/assets/system_user_list.html:147
msgid "System Users Deleted." msgid "System Users Deleted."
msgstr "已被删除" msgstr "已被删除"
#: assets/templates/assets/system_user_list.html:144 #: assets/templates/assets/system_user_list.html:148
#: assets/templates/assets/system_user_list.html:149 #: assets/templates/assets/system_user_list.html:153
msgid "System Users Delete" msgid "System Users Delete"
msgstr "删除系统用户" msgstr "删除系统用户"
#: assets/templates/assets/system_user_list.html:148 #: assets/templates/assets/system_user_list.html:152
msgid "System Users Deleting failed." msgid "System Users Deleting failed."
msgstr "系统用户删除失败" msgstr "系统用户删除失败"
...@@ -1236,7 +1373,7 @@ msgstr "更新资产" ...@@ -1236,7 +1373,7 @@ msgstr "更新资产"
msgid "already exists" msgid "already exists"
msgstr "已经存在" msgstr "已经存在"
#: assets/views/domain.py:30 templates/_nav.html:24 #: assets/views/domain.py:30 templates/_nav.html:23
msgid "Domain list" msgid "Domain list"
msgstr "网域列表" msgstr "网域列表"
...@@ -1284,28 +1421,48 @@ msgstr "资产管理" ...@@ -1284,28 +1421,48 @@ msgstr "资产管理"
msgid "System user asset" msgid "System user asset"
msgstr "系统用户集群资产" msgstr "系统用户集群资产"
#: audits/models.py:12 audits/templates/audits/ftp_log_list.html:74 #: audits/models.py:17 audits/models.py:40 audits/models.py:51
#: audits/templates/audits/ftp_log_list.html:73
#: audits/templates/audits/operate_log_list.html:70
#: audits/templates/audits/password_change_log_list.html:52
#: terminal/models.py:131 terminal/templates/terminal/session_list.html:74 #: terminal/models.py:131 terminal/templates/terminal/session_list.html:74
#: terminal/templates/terminal/terminal_detail.html:47 #: terminal/templates/terminal/terminal_detail.html:47
msgid "Remote addr" msgid "Remote addr"
msgstr "远端地址" msgstr "远端地址"
#: audits/models.py:15 audits/templates/audits/ftp_log_list.html:75 #: audits/models.py:20 audits/templates/audits/ftp_log_list.html:74
msgid "Operate" msgid "Operate"
msgstr "操作" msgstr "操作"
#: audits/models.py:16 audits/templates/audits/ftp_log_list.html:56 #: audits/models.py:21 audits/templates/audits/ftp_log_list.html:56
#: audits/templates/audits/ftp_log_list.html:76 #: audits/templates/audits/ftp_log_list.html:75
msgid "Filename" msgid "Filename"
msgstr "文件名" msgstr "文件名"
#: audits/models.py:17 audits/templates/audits/ftp_log_list.html:77 #: audits/models.py:22 audits/templates/audits/ftp_log_list.html:76
#: ops/templates/ops/task_list.html:39 users/models/authentication.py:66 #: ops/templates/ops/task_list.html:39 users/models/authentication.py:66
#: users/templates/users/user_detail.html:443 #: users/templates/users/user_detail.html:452
msgid "Success" msgid "Success"
msgstr "成功" msgstr "成功"
#: audits/templates/audits/ftp_log_list.html:78 #: audits/models.py:31
msgid "Create"
msgstr "创建"
#: audits/models.py:38 audits/templates/audits/operate_log_list.html:49
#: audits/templates/audits/operate_log_list.html:68
msgid "Resource Type"
msgstr "资源类型"
#: audits/models.py:39 audits/templates/audits/operate_log_list.html:69
msgid "Resource"
msgstr "资源"
#: audits/models.py:50 audits/templates/audits/password_change_log_list.html:51
msgid "Change by"
msgstr "修改者"
#: audits/templates/audits/ftp_log_list.html:77
#: ops/templates/ops/adhoc_history.html:52 #: ops/templates/ops/adhoc_history.html:52
#: ops/templates/ops/adhoc_history_detail.html:61 #: ops/templates/ops/adhoc_history_detail.html:61
#: ops/templates/ops/task_history.html:58 perms/models.py:35 #: ops/templates/ops/task_history.html:58 perms/models.py:35
...@@ -1314,14 +1471,102 @@ msgstr "成功" ...@@ -1314,14 +1471,102 @@ msgstr "成功"
msgid "Date start" msgid "Date start"
msgstr "开始日期" msgstr "开始日期"
#: audits/views.py:51 templates/_nav.html:68 #: audits/templates/audits/login_log_list.html:28
#: perms/templates/perms/asset_permission_user.html:88
msgid "Select user"
msgstr "选择用户"
#: audits/templates/audits/login_log_list.html:35
#: audits/templates/audits/login_log_list.html:40
#: audits/templates/audits/operate_log_list.html:58
#: audits/templates/audits/password_change_log_list.html:42
#: ops/templates/ops/task_list.html:21 ops/templates/ops/task_list.html:26
#: templates/_base_list.html:43 templates/_header_bar.html:8
#: terminal/templates/terminal/command_list.html:60
#: terminal/templates/terminal/session_list.html:61
msgid "Search"
msgstr "搜索"
#: audits/templates/audits/login_log_list.html:48
#: ops/templates/ops/adhoc_detail.html:49
#: ops/templates/ops/adhoc_history_detail.html:49
#: ops/templates/ops/task_detail.html:55
#: terminal/templates/terminal/session_list.html:70
msgid "ID"
msgstr "ID"
#: audits/templates/audits/login_log_list.html:50
#: common/templates/common/terminal_setting.html:73
#: common/templates/common/terminal_setting.html:91
msgid "Type"
msgstr "类型"
#: audits/templates/audits/login_log_list.html:51
msgid "UA"
msgstr "Agent"
#: audits/templates/audits/login_log_list.html:53
msgid "City"
msgstr "城市"
#: audits/templates/audits/login_log_list.html:54 users/forms.py:169
#: users/models/authentication.py:75 users/models/user.py:73
#: users/templates/users/first_login.html:45
msgid "MFA"
msgstr "MFA"
#: audits/templates/audits/login_log_list.html:55
#: users/models/authentication.py:76
msgid "Reason"
msgstr "原因"
#: audits/templates/audits/login_log_list.html:56
#: users/models/authentication.py:77
msgid "Status"
msgstr "状态"
#: audits/templates/audits/login_log_list.html:57
#: ops/templates/ops/task_list.html:40
msgid "Date"
msgstr "日期"
#: audits/templates/audits/operate_log_list.html:71
#: audits/templates/audits/password_change_log_list.html:53
#: ops/templates/ops/task_adhoc.html:63
#: terminal/templates/terminal/command_list.html:76
#: terminal/templates/terminal/session_detail.html:50
msgid "Datetime"
msgstr "日期"
#: audits/views.py:66 audits/views.py:110 audits/views.py:143
#: templates/_nav.html:67
msgid "Audits" msgid "Audits"
msgstr "日志审计" msgstr "日志审计"
#: audits/views.py:52 templates/_nav.html:71 #: audits/views.py:67 templates/_nav.html:71
msgid "FTP log" msgid "FTP log"
msgstr "FTP日志" msgstr "FTP日志"
#: audits/views.py:111 templates/_nav.html:72
msgid "Operate log"
msgstr "操作日志"
#: audits/views.py:144 templates/_nav.html:73
msgid "Password change log"
msgstr "改密日志"
#: audits/views.py:183 templates/_nav.html:10 users/views/group.py:28
#: users/views/group.py:44 users/views/group.py:60 users/views/group.py:76
#: users/views/group.py:92 users/views/login.py:327 users/views/user.py:67
#: users/views/user.py:82 users/views/user.py:110 users/views/user.py:192
#: users/views/user.py:347 users/views/user.py:397 users/views/user.py:432
msgid "Users"
msgstr "用户管理"
#: audits/views.py:184 templates/_nav.html:70
msgid "Login log"
msgstr "登录日志"
#: common/api.py:18 #: common/api.py:18
msgid "Test mail sent to {}, please check" msgid "Test mail sent to {}, please check"
msgstr "邮件已经发送{}, 请检查" msgstr "邮件已经发送{}, 请检查"
...@@ -1332,7 +1577,7 @@ msgstr "连接LDAP成功" ...@@ -1332,7 +1577,7 @@ msgstr "连接LDAP成功"
#: common/api.py:72 #: common/api.py:72
msgid "Search no entry matched in ou {}" msgid "Search no entry matched in ou {}"
msgstr "" msgstr "在ou:{}中没有匹配条目"
#: common/api.py:81 #: common/api.py:81
msgid "Match {} s users" msgid "Match {} s users"
...@@ -1426,7 +1671,7 @@ msgstr "用户OU" ...@@ -1426,7 +1671,7 @@ msgstr "用户OU"
#: common/forms.py:118 #: common/forms.py:118
msgid "Use | split User OUs" msgid "Use | split User OUs"
msgstr "" msgstr "使用|分隔各OU"
#: common/forms.py:121 #: common/forms.py:121
msgid "User search filter" msgid "User search filter"
...@@ -1461,7 +1706,7 @@ msgstr "资产列表排序" ...@@ -1461,7 +1706,7 @@ msgstr "资产列表排序"
msgid "Heartbeat interval" msgid "Heartbeat interval"
msgstr "心跳间隔" msgstr "心跳间隔"
#: common/forms.py:151 ops/models/adhoc.py:37 #: common/forms.py:151 ops/models/adhoc.py:38
msgid "Units: seconds" msgid "Units: seconds"
msgstr "单位: 秒" msgstr "单位: 秒"
...@@ -1626,18 +1871,12 @@ msgstr "用户登录设置" ...@@ -1626,18 +1871,12 @@ msgstr "用户登录设置"
msgid "Password check rule" msgid "Password check rule"
msgstr "密码校验规则" 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 #: common/validators.py:7
msgid "Special char not allowed" msgid "Special char not allowed"
msgstr "不能包含特殊字符" msgstr "不能包含特殊字符"
#: common/views.py:19 common/views.py:45 common/views.py:71 common/views.py:101 #: common/views.py:19 common/views.py:45 common/views.py:71 common/views.py:101
#: common/views.py:129 templates/_nav.html:98 #: common/views.py:129 templates/_nav.html:100
msgid "Settings" msgid "Settings"
msgstr "系统设置" msgstr "系统设置"
...@@ -1646,93 +1885,103 @@ msgstr "系统设置" ...@@ -1646,93 +1885,103 @@ msgstr "系统设置"
msgid "Update setting successfully, please restart program" msgid "Update setting successfully, please restart program"
msgstr "更新设置成功, 请手动重启程序" msgstr "更新设置成功, 请手动重启程序"
#: jumpserver/views.py:178
msgid ""
"<div>Luna is a separately deployed program, you need to deploy Luna, coco, "
"configure nginx for url distribution,</div> </div>If you see this page, "
"prove that you are not accessing the nginx listening port. Good luck.</div>"
msgstr ""
"<div>Luna是单独部署的一个程序,你需要部署luna,coco,配置nginx做url分发, </"
"div><div>如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运</"
"div>"
#: ops/api.py:81 #: ops/api.py:81
msgid "Waiting ..." msgid "Waiting ..."
msgstr "" msgstr ""
#: ops/models/adhoc.py:37 #: ops/models/adhoc.py:38
msgid "Interval" msgid "Interval"
msgstr "间隔" msgstr "间隔"
#: ops/models/adhoc.py:38 #: ops/models/adhoc.py:39
msgid "Crontab" msgid "Crontab"
msgstr "Crontab" msgstr "Crontab"
#: ops/models/adhoc.py:38 #: ops/models/adhoc.py:39
msgid "5 * * * *" msgid "5 * * * *"
msgstr "" msgstr ""
#: ops/models/adhoc.py:40 #: ops/models/adhoc.py:41
msgid "Callback" msgid "Callback"
msgstr "回调" msgstr "回调"
#: ops/models/adhoc.py:154 ops/templates/ops/adhoc_detail.html:114 #: ops/models/adhoc.py:155 ops/templates/ops/adhoc_detail.html:114
msgid "Tasks" msgid "Tasks"
msgstr "任务" msgstr "任务"
#: ops/models/adhoc.py:155 ops/templates/ops/adhoc_detail.html:57 #: ops/models/adhoc.py:156 ops/templates/ops/adhoc_detail.html:57
#: ops/templates/ops/task_adhoc.html:60 #: ops/templates/ops/task_adhoc.html:60
msgid "Pattern" msgid "Pattern"
msgstr "" msgstr ""
#: ops/models/adhoc.py:156 ops/templates/ops/adhoc_detail.html:61 #: ops/models/adhoc.py:157 ops/templates/ops/adhoc_detail.html:61
msgid "Options" msgid "Options"
msgstr "选项" msgstr "选项"
#: ops/models/adhoc.py:157 ops/templates/ops/adhoc_detail.html:53 #: ops/models/adhoc.py:158 ops/templates/ops/adhoc_detail.html:53
#: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:38 #: ops/templates/ops/task_adhoc.html:59 ops/templates/ops/task_list.html:38
msgid "Hosts" msgid "Hosts"
msgstr "主机" msgstr "主机"
#: ops/models/adhoc.py:158 #: ops/models/adhoc.py:159
msgid "Run as admin" msgid "Run as admin"
msgstr "再次执行" msgstr "再次执行"
#: ops/models/adhoc.py:159 ops/templates/ops/adhoc_detail.html:72 #: ops/models/adhoc.py:160 ops/templates/ops/adhoc_detail.html:72
#: ops/templates/ops/adhoc_detail.html:77 ops/templates/ops/task_adhoc.html:61 #: ops/templates/ops/adhoc_detail.html:77 ops/templates/ops/task_adhoc.html:61
msgid "Run as" msgid "Run as"
msgstr "用户" msgstr "用户"
#: ops/models/adhoc.py:160 ops/templates/ops/adhoc_detail.html:82 #: ops/models/adhoc.py:161 ops/templates/ops/adhoc_detail.html:82
#: ops/templates/ops/task_adhoc.html:62 #: ops/templates/ops/task_adhoc.html:62
msgid "Become" msgid "Become"
msgstr "Become" msgstr "Become"
#: ops/models/adhoc.py:161 users/templates/users/user_group_detail.html:59 #: ops/models/adhoc.py:162 users/templates/users/user_group_detail.html:59
#: xpack/plugins/orgs/templates/orgs/org_detail.html:56 #: xpack/plugins/orgs/templates/orgs/org_detail.html:56
msgid "Create by" msgid "Create by"
msgstr "创建者" msgstr "创建者"
#: ops/models/adhoc.py:324 #: ops/models/adhoc.py:326
msgid "Start time" msgid "Start time"
msgstr "开始时间" msgstr "开始时间"
#: ops/models/adhoc.py:325 #: ops/models/adhoc.py:327
msgid "End time" msgid "End time"
msgstr "完成时间" msgstr "完成时间"
#: ops/models/adhoc.py:326 ops/templates/ops/adhoc_history.html:57 #: ops/models/adhoc.py:328 ops/templates/ops/adhoc_history.html:57
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:41 #: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:41
msgid "Time" msgid "Time"
msgstr "时间" msgstr "时间"
#: ops/models/adhoc.py:327 ops/templates/ops/adhoc_detail.html:106 #: ops/models/adhoc.py:329 ops/templates/ops/adhoc_detail.html:106
#: ops/templates/ops/adhoc_history.html:55 #: ops/templates/ops/adhoc_history.html:55
#: ops/templates/ops/adhoc_history_detail.html:69 #: ops/templates/ops/adhoc_history_detail.html:69
#: ops/templates/ops/task_detail.html:83 ops/templates/ops/task_history.html:61 #: ops/templates/ops/task_detail.html:83 ops/templates/ops/task_history.html:61
msgid "Is finished" msgid "Is finished"
msgstr "是否完成" msgstr "是否完成"
#: ops/models/adhoc.py:328 ops/templates/ops/adhoc_history.html:56 #: ops/models/adhoc.py:330 ops/templates/ops/adhoc_history.html:56
#: ops/templates/ops/task_history.html:62 #: ops/templates/ops/task_history.html:62
msgid "Is success" msgid "Is success"
msgstr "是否成功" msgstr "是否成功"
#: ops/models/adhoc.py:329 #: ops/models/adhoc.py:331
msgid "Adhoc raw result" msgid "Adhoc raw result"
msgstr "结果" msgstr "结果"
#: ops/models/adhoc.py:330 #: ops/models/adhoc.py:332
msgid "Adhoc result summary" msgid "Adhoc result summary"
msgstr "汇总" msgstr "汇总"
...@@ -1746,14 +1995,6 @@ msgstr "版本详情" ...@@ -1746,14 +1995,6 @@ msgstr "版本详情"
msgid "Version run history" msgid "Version run history"
msgstr "执行历史" msgstr "执行历史"
#: ops/templates/ops/adhoc_detail.html:49
#: ops/templates/ops/adhoc_history_detail.html:49
#: ops/templates/ops/task_detail.html:55
#: terminal/templates/terminal/session_list.html:70
#: users/templates/users/login_log_list.html:48
msgid "ID"
msgstr "ID"
#: ops/templates/ops/adhoc_detail.html:94 ops/templates/ops/task_list.html:36 #: ops/templates/ops/adhoc_detail.html:94 ops/templates/ops/task_list.html:36
msgid "Run times" msgid "Run times"
msgstr "执行次数" msgstr "执行次数"
...@@ -1861,12 +2102,6 @@ msgstr "输出" ...@@ -1861,12 +2102,6 @@ msgstr "输出"
msgid "Versions of " msgid "Versions of "
msgstr "版本" msgstr "版本"
#: ops/templates/ops/task_adhoc.html:63
#: terminal/templates/terminal/command_list.html:76
#: terminal/templates/terminal/session_detail.html:50
msgid "Datetime"
msgstr "日期"
#: ops/templates/ops/task_detail.html:67 #: ops/templates/ops/task_detail.html:67
msgid "Total versions" msgid "Total versions"
msgstr "版本数量" msgstr "版本数量"
...@@ -1879,24 +2114,10 @@ msgstr "最新版本" ...@@ -1879,24 +2114,10 @@ msgstr "最新版本"
msgid "Contents" msgid "Contents"
msgstr "内容" msgstr "内容"
#: ops/templates/ops/task_list.html:21 ops/templates/ops/task_list.html:26
#: templates/_base_list.html:43 templates/_header_bar.html:8
#: terminal/templates/terminal/command_list.html:60
#: terminal/templates/terminal/session_list.html:61
#: users/templates/users/login_log_list.html:35
#: users/templates/users/login_log_list.html:40
msgid "Search"
msgstr "搜索"
#: ops/templates/ops/task_list.html:37 #: ops/templates/ops/task_list.html:37
msgid "Versions" msgid "Versions"
msgstr "版本" msgstr "版本"
#: ops/templates/ops/task_list.html:40
#: users/templates/users/login_log_list.html:57
msgid "Date"
msgstr "日期"
#: ops/templates/ops/task_list.html:71 #: ops/templates/ops/task_list.html:71
msgid "Run" msgid "Run"
msgstr "执行" msgstr "执行"
...@@ -1910,7 +2131,7 @@ msgstr "任务开始: " ...@@ -1910,7 +2131,7 @@ msgstr "任务开始: "
msgid "Ops" msgid "Ops"
msgstr "作业中心" msgstr "作业中心"
#: ops/views.py:37 templates/_nav.html:62 #: ops/views.py:37 templates/_nav.html:61
msgid "Task list" msgid "Task list"
msgstr "任务列表" msgstr "任务列表"
...@@ -1918,42 +2139,37 @@ msgstr "任务列表" ...@@ -1918,42 +2139,37 @@ msgstr "任务列表"
msgid "Task run history" msgid "Task run history"
msgstr "执行历史" msgstr "执行历史"
#: orgs/mixins.py:79 #: orgs/mixins.py:79 orgs/models.py:24
msgid "Organization" msgid "Organization"
msgstr "组织" msgstr "组织管理"
#: perms/forms.py:20 users/forms.py:265 users/forms.py:270 users/forms.py:316
#: xpack/plugins/orgs/forms.py:30
msgid "Select users"
msgstr "选择用户"
#: perms/forms.py:44 perms/models.py:30 perms/models.py:79 #: perms/forms.py:31 perms/models.py:30 perms/models.py:80
#: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:55
#: perms/templates/perms/asset_permission_list.html:145 templates/_nav.html:14 #: perms/templates/perms/asset_permission_list.html:145 templates/_nav.html:14
#: users/forms.py:282 users/models/group.py:26 users/models/user.py:57 #: users/forms.py:282 users/models/group.py:26 users/models/user.py:57
#: users/templates/users/_select_user_modal.html:16 #: users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:200 #: users/templates/users/user_detail.html:207
#: users/templates/users/user_list.html:26 #: users/templates/users/user_list.html:26
#: xpack/plugins/orgs/templates/orgs/org_list.html:15 #: xpack/plugins/orgs/templates/orgs/org_list.html:15
msgid "User group" msgid "User group"
msgstr "用户组" msgstr "用户组"
#: perms/forms.py:66 #: perms/forms.py:53
msgid "User or group at least one required" msgid "User or group at least one required"
msgstr "" msgstr "用户和用户组至少选一个"
#: perms/forms.py:75 #: perms/forms.py:62
msgid "Asset or group at least one required" msgid "Asset or group at least one required"
msgstr "" msgstr "资产和节点至少选一个"
#: perms/models.py:36 perms/models.py:82 #: perms/models.py:36 perms/models.py:83
#: perms/templates/perms/asset_permission_detail.html:90 #: perms/templates/perms/asset_permission_detail.html:90
#: users/models/user.py:89 users/templates/users/user_detail.html:107 #: users/models/user.py:89 users/templates/users/user_detail.html:107
#: users/templates/users/user_profile.html:112 #: users/templates/users/user_profile.html:112
msgid "Date expired" msgid "Date expired"
msgstr "失效日期" msgstr "失效日期"
#: perms/models.py:91 templates/_nav.html:34 #: perms/models.py:45 perms/models.py:92 templates/_nav.html:33
msgid "Asset permission" msgid "Asset permission"
msgstr "资产授权" msgstr "资产授权"
...@@ -1988,7 +2204,7 @@ msgid "Add node to this permission" ...@@ -1988,7 +2204,7 @@ msgid "Add node to this permission"
msgstr "添加节点" msgstr "添加节点"
#: perms/templates/perms/asset_permission_asset.html:125 #: perms/templates/perms/asset_permission_asset.html:125
#: users/templates/users/user_detail.html:217 #: users/templates/users/user_detail.html:224
msgid "Join" msgid "Join"
msgstr "加入" msgstr "加入"
...@@ -2032,11 +2248,6 @@ msgstr "用户列表" ...@@ -2032,11 +2248,6 @@ msgstr "用户列表"
msgid "Add user to asset permission" msgid "Add user to asset permission"
msgstr "添加用户" msgstr "添加用户"
#: perms/templates/perms/asset_permission_user.html:88
#: users/templates/users/login_log_list.html:28
msgid "Select user"
msgstr "选择用户"
#: perms/templates/perms/asset_permission_user.html:108 #: perms/templates/perms/asset_permission_user.html:108
msgid "Add user group to asset permission" msgid "Add user group to asset permission"
msgstr "添加用户组" msgstr "添加用户组"
...@@ -2046,7 +2257,7 @@ msgid "Select user groups" ...@@ -2046,7 +2257,7 @@ msgid "Select user groups"
msgstr "选择用户组" msgstr "选择用户组"
#: perms/views.py:23 perms/views.py:53 perms/views.py:68 perms/views.py:83 #: perms/views.py:23 perms/views.py:53 perms/views.py:68 perms/views.py:83
#: perms/views.py:118 perms/views.py:150 templates/_nav.html:31 #: perms/views.py:118 perms/views.py:150 templates/_nav.html:30
#: xpack/plugins/orgs/templates/orgs/org_list.html:21 #: xpack/plugins/orgs/templates/orgs/org_list.html:21
msgid "Perms" msgid "Perms"
msgstr "权限管理" msgstr "权限管理"
...@@ -2071,18 +2282,26 @@ msgstr "资产授权用户列表" ...@@ -2071,18 +2282,26 @@ msgstr "资产授权用户列表"
msgid "Asset permission asset list" msgid "Asset permission asset list"
msgstr "资产授权资产列表" msgstr "资产授权资产列表"
#: templates/_header_bar.html:18 #: templates/_copyright.html:2 templates/_footer.html:8
msgid "Supports" msgid " Beijing Duizhan Tech, Inc. "
msgstr "商业支持" msgstr " 北京堆栈科技有限公司 "
#: templates/_header_bar.html:23 #: templates/_header_bar.html:31
msgid "Help"
msgstr "帮助"
#: templates/_header_bar.html:38 users/templates/users/_base_otp.html:29
msgid "Docs" msgid "Docs"
msgstr "文档" msgstr "文档"
#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:148 #: templates/_header_bar.html:44
msgid "Commercial support"
msgstr "商业支持"
#: templates/_header_bar.html:89 templates/_nav_user.html:9 users/forms.py:148
#: users/templates/users/_user.html:39 #: users/templates/users/_user.html:39
#: users/templates/users/first_login.html:39 #: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:39 #: users/templates/users/user_password_update.html:40
#: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:57 #: users/templates/users/user_profile_update.html:57
...@@ -2090,24 +2309,24 @@ msgstr "文档" ...@@ -2090,24 +2309,24 @@ msgstr "文档"
msgid "Profile" msgid "Profile"
msgstr "个人信息" msgstr "个人信息"
#: templates/_header_bar.html:40 #: templates/_header_bar.html:92
msgid "Admin page" msgid "Admin page"
msgstr "管理页面" msgstr "管理页面"
#: templates/_header_bar.html:42 #: templates/_header_bar.html:94
msgid "User page" msgid "User page"
msgstr "用户页面" msgstr "用户页面"
#: templates/_header_bar.html:45 #: templates/_header_bar.html:97
msgid "Logout" msgid "Logout"
msgstr "注销登录" msgstr "注销登录"
#: templates/_header_bar.html:49 users/templates/users/login.html:44 #: templates/_header_bar.html:101 users/templates/users/login.html:46
#: users/templates/users/login.html:68 #: users/templates/users/login.html:70
msgid "Login" msgid "Login"
msgstr "登录" msgstr "登录"
#: templates/_header_bar.html:62 templates/_nav.html:4 #: templates/_header_bar.html:114 templates/_nav.html:4
msgid "Dashboard" msgid "Dashboard"
msgstr "仪表盘" msgstr "仪表盘"
...@@ -2137,57 +2356,50 @@ msgstr "" ...@@ -2137,57 +2356,50 @@ msgstr ""
"\"%(user_pubkey_update)s\"> 链接 </a> 更新\n" "\"%(user_pubkey_update)s\"> 链接 </a> 更新\n"
" " " "
#: templates/_nav.html:10 users/views/group.py:27 users/views/group.py:43
#: users/views/group.py:61 users/views/group.py:78 users/views/group.py:94
#: users/views/login.py:333 users/views/login.py:397 users/views/user.py:67
#: users/views/user.py:82 users/views/user.py:110 users/views/user.py:192
#: users/views/user.py:347 users/views/user.py:397 users/views/user.py:432
msgid "Users"
msgstr "用户管理"
#: templates/_nav.html:13 users/views/user.py:68 #: templates/_nav.html:13 users/views/user.py:68
msgid "User list" msgid "User list"
msgstr "用户列表" msgstr "用户列表"
#: templates/_nav.html:15 #: templates/_nav.html:39
msgid "Login logs"
msgstr "登录日志"
#: templates/_nav.html:40
msgid "Sessions" msgid "Sessions"
msgstr "会话管理" msgstr "会话管理"
#: templates/_nav.html:43 #: templates/_nav.html:42
msgid "Session online" msgid "Session online"
msgstr "在线会话" msgstr "在线会话"
#: templates/_nav.html:44 #: templates/_nav.html:43
msgid "Session offline" msgid "Session offline"
msgstr "历史会话" msgstr "历史会话"
#: templates/_nav.html:45 #: templates/_nav.html:44
msgid "Commands" msgid "Commands"
msgstr "命令记录" msgstr "命令记录"
#: templates/_nav.html:48 templates/_nav_user.html:14 #: templates/_nav.html:47 templates/_nav_user.html:14
msgid "Web terminal" msgid "Web terminal"
msgstr "Web终端" msgstr "Web终端"
#: templates/_nav.html:52 terminal/views/command.py:50 #: templates/_nav.html:51 terminal/views/command.py:50
#: terminal/views/session.py:75 terminal/views/session.py:93 #: terminal/views/session.py:75 terminal/views/session.py:93
#: terminal/views/session.py:115 terminal/views/terminal.py:31 #: terminal/views/session.py:115 terminal/views/terminal.py:31
#: terminal/views/terminal.py:46 terminal/views/terminal.py:58 #: terminal/views/terminal.py:46 terminal/views/terminal.py:58
msgid "Terminal" msgid "Terminal"
msgstr "终端管理" msgstr "终端管理"
#: templates/_nav.html:59 #: templates/_nav.html:58
msgid "Job Center" msgid "Job Center"
msgstr "作业中心" msgstr "作业中心"
#: templates/_nav.html:86 #: templates/_nav.html:88
msgid "XPack" msgid "XPack"
msgstr "" msgstr ""
#: templates/_pagination.html:59
msgid ""
"Displays the results of items _START_ to _END_; A total of _TOTAL_ entries"
msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项"
#: templates/captcha/image.html:3 #: templates/captcha/image.html:3
msgid "Play CAPTCHA as audio file" msgid "Play CAPTCHA as audio file"
msgstr "语言播放验证码" msgstr "语言播放验证码"
...@@ -2196,6 +2408,164 @@ msgstr "语言播放验证码" ...@@ -2196,6 +2408,164 @@ msgstr "语言播放验证码"
msgid "Captcha" msgid "Captcha"
msgstr "验证码" msgstr "验证码"
#: templates/flash_message_standalone.html:45
msgid "Return"
msgstr "返回"
#: templates/index.html:11
msgid "Total users"
msgstr "用户总数"
#: templates/index.html:23
msgid "Total hosts"
msgstr "主机总数"
#: templates/index.html:36
msgid "Online users"
msgstr "在线用户"
#: templates/index.html:49
msgid "Online sessions"
msgstr "在线会话"
#: templates/index.html:61
msgid " Top 5 Active user"
msgstr "活跃用户Top5"
#: templates/index.html:62
msgid "In the past week, a total of "
msgstr "过去一周, 共有 "
#: templates/index.html:62
msgid " users have logged in "
msgstr " 位用户登录 "
#: templates/index.html:62
msgid " times asset."
msgstr " 次资产."
#: templates/index.html:67
msgid " times/week"
msgstr " 次/周"
#: templates/index.html:78
msgid "Active user asset ratio"
msgstr "活跃用户资产占比"
#: templates/index.html:81
msgid ""
"The following graphs describe the percentage of active users per month and "
"assets per user host per month, respectively."
msgstr "以下图形分别描述一个月活跃用户和资产占所有用户主机的百分比"
#: templates/index.html:91
msgid "Host"
msgstr "主机"
#: templates/index.html:106 templates/index.html:121
msgid "Top 10 assets in a week"
msgstr "一周Top10资产"
#: templates/index.html:122
msgid "Login frequency and last login record."
msgstr "登录次数及最近一次登录记录."
#: templates/index.html:133 templates/index.html:221
msgid " times"
msgstr " 次"
#: templates/index.html:136
msgid "The last time a user logged in"
msgstr "最近一次登录用户"
#: templates/index.html:138 templates/index.html:226
msgid "At "
msgstr "于"
#: templates/index.html:144 templates/index.html:183 templates/index.html:232
msgid "(No)"
msgstr "(暂无)"
#: templates/index.html:152
msgid "Last 10 login"
msgstr "最近十次登录"
#: templates/index.html:158
msgid "Login record"
msgstr "登录记录"
#: templates/index.html:159
msgid "Last 10 login records."
msgstr "最近十次登录记录."
#: templates/index.html:172 templates/index.html:174
msgid "Before"
msgstr "前"
#: templates/index.html:176
msgid "Login in "
msgstr "登录了"
#: templates/index.html:194 templates/index.html:209
msgid "Top 10 users in a week"
msgstr "一周Top10用户"
#: templates/index.html:210
msgid "User login frequency and last login record."
msgstr "用户登录次数及最近一次登录记录"
#: templates/index.html:224
msgid "The last time logged on to the host"
msgstr "最近一次登录主机"
#: templates/index.html:268
msgid "Monthly data overview"
msgstr "月数据总览"
#: templates/index.html:269
msgid "History summary in one month"
msgstr "一个月内历史汇总"
#: templates/index.html:277 templates/index.html:301
msgid "Login count"
msgstr "登陆次数"
#: templates/index.html:277 templates/index.html:308
msgid "Active users"
msgstr "活跃用户"
#: templates/index.html:277 templates/index.html:315
msgid "Active assets"
msgstr "活跃资产"
#: templates/index.html:342 templates/index.html:392
msgid "Monthly active users"
msgstr "月活跃用户"
#: templates/index.html:342 templates/index.html:393
msgid "Disable user"
msgstr "禁用用户"
#: templates/index.html:342 templates/index.html:394
msgid "Month not logged in user"
msgstr "月未登陆用户"
#: templates/index.html:368 templates/index.html:444
msgid "Access to the source"
msgstr "访问来源"
#: templates/index.html:418 templates/index.html:468
msgid "Month is logged into the host"
msgstr "月被登陆主机"
#: templates/index.html:418 templates/index.html:469
msgid "Disable host"
msgstr "禁用主机"
#: templates/index.html:418 templates/index.html:470
msgid "Month not logged on host"
msgstr "月未登陆主机"
#: templates/rest_framework/base.html:128 #: templates/rest_framework/base.html:128
msgid "Filters" msgid "Filters"
msgstr "过滤" msgstr "过滤"
...@@ -2412,10 +2782,26 @@ msgid "" ...@@ -2412,10 +2782,26 @@ msgid ""
"You should use your ssh client tools connect terminal: {} <br /> <br />{}" "You should use your ssh client tools connect terminal: {} <br /> <br />{}"
msgstr "你可以使用ssh客户端工具连接终端" msgstr "你可以使用ssh客户端工具连接终端"
#: users/api.py:235 users/templates/users/login.html:50 #: users/api/auth.py:38 users/templates/users/login.html:52
msgid "Log in frequently and try again later" msgid "Log in frequently and try again later"
msgstr "登录频繁, 稍后重试" msgstr "登录频繁, 稍后重试"
#: users/api/auth.py:77
msgid "Please carry seed value and conduct MFA secondary certification"
msgstr "请携带seed值, 进行MFA二次认证"
#: users/api/auth.py:190
msgid "Please verify the user name and password first"
msgstr "请先进行用户名和密码验证"
#: users/api/auth.py:202
msgid "MFA certification failed"
msgstr "MFA认证失败"
#: users/api/user.py:134
msgid "Could not reset self otp, use profile reset instead"
msgstr ""
#: users/authentication.py:56 #: users/authentication.py:56
msgid "Invalid signature header. No credentials provided." msgid "Invalid signature header. No credentials provided."
msgstr "" msgstr ""
...@@ -2491,7 +2877,7 @@ msgstr "" ...@@ -2491,7 +2877,7 @@ msgstr ""
msgid "Paste user id_rsa.pub here." msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里" msgstr "复制用户公钥到这里"
#: users/forms.py:76 users/templates/users/user_detail.html:208 #: users/forms.py:76 users/templates/users/user_detail.html:215
msgid "Join user groups" msgid "Join user groups"
msgstr "添加到用户组" msgstr "添加到用户组"
...@@ -2516,12 +2902,6 @@ msgstr "" ...@@ -2516,12 +2902,6 @@ msgstr ""
msgid "* Enable MFA authentication to make the account more secure." msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全." msgstr "* 启用MFA认证,使账号更加安全."
#: users/forms.py:169 users/models/authentication.py:75 users/models/user.py:73
#: users/templates/users/first_login.html:45
#: users/templates/users/login_log_list.html:54
msgid "MFA"
msgstr "MFA"
#: users/forms.py:174 #: users/forms.py:174
msgid "" msgid ""
"In order to protect you and your company, please keep your account, password " "In order to protect you and your company, please keep your account, password "
...@@ -2567,13 +2947,18 @@ msgstr "复制你的公钥到这里" ...@@ -2567,13 +2947,18 @@ msgstr "复制你的公钥到这里"
#: users/forms.py:258 users/models/user.py:81 #: users/forms.py:258 users/models/user.py:81
#: users/templates/users/first_login.html:42 #: users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:45 #: users/templates/users/user_password_update.html:46
#: users/templates/users/user_profile.html:68 #: users/templates/users/user_profile.html:68
#: users/templates/users/user_profile_update.html:43 #: users/templates/users/user_profile_update.html:43
#: users/templates/users/user_pubkey_update.html:43 #: users/templates/users/user_pubkey_update.html:43
msgid "Public key" msgid "Public key"
msgstr "ssh公钥" msgstr "ssh公钥"
#: users/forms.py:265 users/forms.py:270 users/forms.py:316
#: xpack/plugins/orgs/forms.py:30
msgid "Select users"
msgstr "选择用户"
#: users/models/authentication.py:36 #: users/models/authentication.py:36
msgid "Private Token" msgid "Private Token"
msgstr "ssh密钥" msgstr "ssh密钥"
...@@ -2614,21 +2999,11 @@ msgstr "登录城市" ...@@ -2614,21 +2999,11 @@ msgstr "登录城市"
msgid "User agent" msgid "User agent"
msgstr "Agent" msgstr "Agent"
#: 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 #: users/models/authentication.py:78
msgid "Date login" msgid "Date login"
msgstr "登录日期" msgstr "登录日期"
#: users/models/user.py:32 users/models/user.py:356 #: users/models/user.py:32 users/models/user.py:359
msgid "Administrator" msgid "Administrator"
msgstr "管理员" msgstr "管理员"
...@@ -2670,10 +3045,27 @@ msgstr "微信" ...@@ -2670,10 +3045,27 @@ msgstr "微信"
msgid "Source" msgid "Source"
msgstr "用户来源" msgstr "用户来源"
#: users/models/user.py:359 #: users/models/user.py:362
msgid "Administrator is the super user of system" msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员" msgstr "Administrator是初始的超级管理员"
#: users/templates/users/_base_otp.html:27
msgid "Home page"
msgstr "首页"
#: users/templates/users/_base_otp.html:44
msgid "Security token validation"
msgstr "安全令牌验证"
#: users/templates/users/_base_otp.html:44 users/templates/users/_user.html:13
#: users/templates/users/user_profile_update.html:51
msgid "Account"
msgstr "账户"
#: users/templates/users/_base_otp.html:44
msgid "Follow these steps to complete the binding operation"
msgstr "请按照以下步骤完成绑定操作"
#: users/templates/users/_select_user_modal.html:5 #: users/templates/users/_select_user_modal.html:5
msgid "Please Select User" msgid "Please Select User"
msgstr "选择用户" msgstr "选择用户"
...@@ -2682,11 +3074,6 @@ msgstr "选择用户" ...@@ -2682,11 +3074,6 @@ msgstr "选择用户"
msgid "Asset num" msgid "Asset num"
msgstr "资产数量" msgstr "资产数量"
#: users/templates/users/_user.html:13
#: users/templates/users/user_profile_update.html:51
msgid "Account"
msgstr "账户"
#: users/templates/users/_user.html:26 #: users/templates/users/_user.html:26
msgid "Security and Role" msgid "Security and Role"
msgstr "角色安全" msgstr "角色安全"
...@@ -2729,11 +3116,11 @@ msgid "Previous" ...@@ -2729,11 +3116,11 @@ msgid "Previous"
msgstr "上一步" msgstr "上一步"
#: users/templates/users/first_login.html:105 #: users/templates/users/first_login.html:105
#: users/templates/users/login_otp.html:66 #: users/templates/users/login_otp.html:67
#: users/templates/users/user_otp_authentication.html:22 #: users/templates/users/user_otp_authentication.html:26
#: users/templates/users/user_otp_enable_bind.html:19 #: users/templates/users/user_otp_enable_bind.html:23
#: users/templates/users/user_otp_enable_install_app.html:22 #: users/templates/users/user_otp_enable_install_app.html:26
#: users/templates/users/user_password_authentication.html:17 #: users/templates/users/user_password_authentication.html:21
msgid "Next" msgid "Next"
msgstr "下一步" msgstr "下一步"
...@@ -2749,56 +3136,129 @@ msgstr "向导" ...@@ -2749,56 +3136,129 @@ msgstr "向导"
msgid " for more information" msgid " for more information"
msgstr "获取更多信息" msgstr "获取更多信息"
#: users/templates/users/forgot_password.html:26 #: users/templates/users/forgot_password.html:11
#: users/templates/users/login.html:77 #: users/templates/users/forgot_password.html:27
#: users/templates/users/login.html:79
msgid "Forgot password" msgid "Forgot password"
msgstr "忘记密码" msgstr "忘记密码"
#: users/templates/users/forgot_password.html:33 #: users/templates/users/forgot_password.html:34
msgid "Input your email, that will send a mail to your" msgid "Input your email, that will send a mail to your"
msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中" msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中"
#: users/templates/users/login.html:53 #: users/templates/users/login.html:27 users/templates/users/login_otp.html:27
msgid "Captcha invalid" #: users/templates/users/reset_password.html:25
msgstr "验证码错误" msgid "Welcome to the Jumpserver open source fortress"
msgstr "欢迎使用Jumpserver开源堡垒机"
#: users/templates/users/login_log_list.html:51 #: users/templates/users/login.html:29 users/templates/users/login_otp.html:29
msgid "UA" msgid ""
msgstr "Agent" "The world's first fully open source fortress, using the GNU GPL v2.0 open "
"source protocol, is a professional operation and maintenance audit system in "
"compliance with 4A."
msgstr ""
"全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计"
"系统。"
#: users/templates/users/login_log_list.html:53 #: users/templates/users/login.html:32 users/templates/users/login_otp.html:32
msgid "City" msgid ""
msgstr "城市" "Developed using Python/Django, following the Web 2.0 specification and "
"equipped with industry-leading Web Terminal solutions, with beautiful "
"interactive interface and good user experience."
msgstr ""
"使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web "
"Terminal 解决方案,交互界面美观、用户体验好。"
#: users/templates/users/login.html:35 users/templates/users/login_otp.html:35
msgid ""
"Distributed architecture is adopted to support multi-machine room deployment "
"across regions, central node provides API, and each machine room deploys "
"login node, which can be extended horizontally and without concurrent access "
"restrictions."
msgstr ""
"采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,"
"可横向扩展、无并发访问限制。"
#: users/templates/users/login_otp.html:45 #: users/templates/users/login.html:38 users/templates/users/login_otp.html:38
msgid "Changes the world, starting with a little bit."
msgstr "改变世界,从一点点开始。"
#: users/templates/users/login.html:55
msgid "Captcha invalid"
msgstr "验证码错误"
#: users/templates/users/login_otp.html:46
#: users/templates/users/user_detail.html:91 #: users/templates/users/user_detail.html:91
#: users/templates/users/user_profile.html:85 #: users/templates/users/user_profile.html:85
msgid "MFA certification" msgid "MFA certification"
msgstr "MFA认证" msgstr "MFA认证"
#: users/templates/users/login_otp.html:64 #: users/templates/users/login_otp.html:51
#: users/templates/users/user_otp_authentication.html:19 #: users/templates/users/user_otp_authentication.html:11
#: users/templates/users/user_otp_enable_bind.html:16 msgid ""
"The account protection has been opened, please complete the following "
"operations according to the prompts"
msgstr "账号保护已开启,请根据提示完成以下操作"
#: users/templates/users/login_otp.html:55
#: users/templates/users/user_otp_authentication.html:13
msgid "Open Authenticator and enter the 6-bit dynamic code"
msgstr "请打开手机Google Authenticator应用,输入6位动态码"
#: users/templates/users/login_otp.html:65
#: users/templates/users/user_otp_authentication.html:23
#: users/templates/users/user_otp_enable_bind.html:20
msgid "Six figures" msgid "Six figures"
msgstr "6位数字" msgstr "6位数字"
#: users/templates/users/login_otp.html:69 #: users/templates/users/login_otp.html:70
msgid "Can't provide security? Please contact the administrator!" msgid "Can't provide security? Please contact the administrator!"
msgstr "如果不能提供MFA验证码,请联系管理员!" msgstr "如果不能提供MFA验证码,请联系管理员!"
#: users/templates/users/reset_password.html:28
msgid ""
"Jumpserver is an open source desktop system developed using Python and "
"Django that helps Internet businesses with efficient users, assets, "
"permissions, and audit management"
msgstr ""
"Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互联网企业高效 用"
"户、资产、权限、审计 管理"
#: users/templates/users/reset_password.html:32
msgid ""
"We are from all over the world, we have great admiration and worship for the "
"spirit of open source, we have endless pursuit for perfection, neatness and "
"elegance"
msgstr ""
"我们自五湖四海,我们对开源精神无比敬仰和崇拜,我们对完美、整洁、优雅 无止境的"
"追求"
#: users/templates/users/reset_password.html:36
msgid ""
"We focus on automatic operation and maintenance, and strive to build an easy-"
"to-use, stable, safe and automatic board hopping machine, which is our "
"unremitting pursuit and power"
msgstr ""
"专注自动化运维,努力打造 易用、稳定、安全、自动化 的跳板机, 这是我们的不懈的"
"追求和动力"
#: users/templates/users/reset_password.html:40
msgid "Always young, always with tears in my eyes. Stay foolish Stay hungry"
msgstr "永远年轻,永远热泪盈眶 stay foolish stay hungry"
#: users/templates/users/reset_password.html:46 #: users/templates/users/reset_password.html:46
#: users/templates/users/user_detail.html:360 users/utils.py:80 #: users/templates/users/user_detail.html:367 users/utils.py:80
msgid "Reset password" msgid "Reset password"
msgstr "重置密码" msgstr "重置密码"
#: users/templates/users/reset_password.html:59 #: users/templates/users/reset_password.html:59
#: users/templates/users/user_password_update.html:60 #: users/templates/users/user_password_update.html:61
#: users/templates/users/user_update.html:12 #: users/templates/users/user_update.html:12
msgid "Your password must satisfy" msgid "Your password must satisfy"
msgstr "您的密码必须满足:" msgstr "您的密码必须满足:"
#: users/templates/users/reset_password.html:60 #: users/templates/users/reset_password.html:60
#: users/templates/users/user_password_update.html:61 #: users/templates/users/user_password_update.html:62
#: users/templates/users/user_update.html:13 #: users/templates/users/user_update.html:13
msgid "Password strength" msgid "Password strength"
msgstr "密码强度:" msgstr "密码强度:"
...@@ -2813,37 +3273,37 @@ msgid "Setting" ...@@ -2813,37 +3273,37 @@ msgid "Setting"
msgstr "设置" msgstr "设置"
#: users/templates/users/reset_password.html:105 #: users/templates/users/reset_password.html:105
#: users/templates/users/user_password_update.html:98 #: users/templates/users/user_password_update.html:99
#: users/templates/users/user_update.html:34 #: users/templates/users/user_update.html:34
msgid "Very weak" msgid "Very weak"
msgstr "很弱" msgstr "很弱"
#: users/templates/users/reset_password.html:106 #: users/templates/users/reset_password.html:106
#: users/templates/users/user_password_update.html:99 #: users/templates/users/user_password_update.html:100
#: users/templates/users/user_update.html:35 #: users/templates/users/user_update.html:35
msgid "Weak" msgid "Weak"
msgstr "弱" msgstr "弱"
#: users/templates/users/reset_password.html:107 #: users/templates/users/reset_password.html:107
#: users/templates/users/user_password_update.html:100 #: users/templates/users/user_password_update.html:101
#: users/templates/users/user_update.html:36 #: users/templates/users/user_update.html:36
msgid "Normal" msgid "Normal"
msgstr "正常" msgstr "正常"
#: users/templates/users/reset_password.html:108 #: users/templates/users/reset_password.html:108
#: users/templates/users/user_password_update.html:101 #: users/templates/users/user_password_update.html:102
#: users/templates/users/user_update.html:37 #: users/templates/users/user_update.html:37
msgid "Medium" msgid "Medium"
msgstr "一般" msgstr "一般"
#: users/templates/users/reset_password.html:109 #: users/templates/users/reset_password.html:109
#: users/templates/users/user_password_update.html:102 #: users/templates/users/user_password_update.html:103
#: users/templates/users/user_update.html:38 #: users/templates/users/user_update.html:38
msgid "Strong" msgid "Strong"
msgstr "强" msgstr "强"
#: users/templates/users/reset_password.html:110 #: users/templates/users/reset_password.html:110
#: users/templates/users/user_password_update.html:103 #: users/templates/users/user_password_update.html:104
#: users/templates/users/user_update.html:39 #: users/templates/users/user_update.html:39
msgid "Very strong" msgid "Very strong"
msgstr "很强" msgstr "很强"
...@@ -2878,82 +3338,87 @@ msgstr "强制启用" ...@@ -2878,82 +3338,87 @@ msgstr "强制启用"
msgid "Last login" msgid "Last login"
msgstr "最后登录" msgstr "最后登录"
#: users/templates/users/user_detail.html:155 #: users/templates/users/user_detail.html:154
msgid "Force enabled MFA" msgid "Force enabled MFA"
msgstr "强制启用MFA" msgstr "强制启用MFA"
#: users/templates/users/user_detail.html:170 #: users/templates/users/user_detail.html:169
msgid "Reset MFA"
msgstr "重置MFA"
#: users/templates/users/user_detail.html:177
msgid "Send reset password mail" msgid "Send reset password mail"
msgstr "发送重置密码邮件" msgstr "发送重置密码邮件"
#: users/templates/users/user_detail.html:173 #: users/templates/users/user_detail.html:180
#: users/templates/users/user_detail.html:181 #: users/templates/users/user_detail.html:188
msgid "Send" msgid "Send"
msgstr "发送" msgstr "发送"
#: users/templates/users/user_detail.html:178 #: users/templates/users/user_detail.html:185
msgid "Send reset ssh key mail" msgid "Send reset ssh key mail"
msgstr "发送重置密钥邮件" msgstr "发送重置密钥邮件"
#: users/templates/users/user_detail.html:186 #: users/templates/users/user_detail.html:193
#: users/templates/users/user_detail.html:446 #: users/templates/users/user_detail.html:455
msgid "Unblock user" msgid "Unblock user"
msgstr "解除登录限制" msgstr "解除登录限制"
#: users/templates/users/user_detail.html:189 #: users/templates/users/user_detail.html:196
msgid "Unblock" msgid "Unblock"
msgstr "解除" msgstr "解除"
#: users/templates/users/user_detail.html:303 #: users/templates/users/user_detail.html:310
msgid "Goto profile page enable MFA" msgid "Goto profile page enable MFA"
msgstr "请去个人信息页面启用自己的MFA" msgstr "请去个人信息页面启用自己的MFA"
#: users/templates/users/user_detail.html:359 #: users/templates/users/user_detail.html:366
msgid "An e-mail has been sent to the user`s mailbox." msgid "An e-mail has been sent to the user`s mailbox."
msgstr "已发送邮件到用户邮箱" msgstr "已发送邮件到用户邮箱"
#: users/templates/users/user_detail.html:370 #: users/templates/users/user_detail.html:377
msgid "This will reset the user password and send a reset mail" msgid "This will reset the user password and send a reset mail"
msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱" msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱"
#: users/templates/users/user_detail.html:384 #: users/templates/users/user_detail.html:392
msgid "" msgid ""
"The reset-ssh-public-key E-mail has been sent successfully. Please inform " "The reset-ssh-public-key E-mail has been sent successfully. Please inform "
"the user to update his new ssh public key." "the user to update his new ssh public key."
msgstr "重设密钥邮件将会发送到用户邮箱" msgstr "重设密钥邮件将会发送到用户邮箱"
#: users/templates/users/user_detail.html:385 #: users/templates/users/user_detail.html:393
msgid "Reset SSH public key" msgid "Reset SSH public key"
msgstr "重置SSH密钥" msgstr "重置SSH密钥"
#: users/templates/users/user_detail.html:395 #: users/templates/users/user_detail.html:403
msgid "This will reset the user public key and send a reset mail" msgid "This will reset the user public key and send a reset mail"
msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱" msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱"
#: users/templates/users/user_detail.html:412 #: users/templates/users/user_detail.html:421
#: users/templates/users/user_profile.html:211 #: users/templates/users/user_profile.html:221
msgid "Successfully updated the SSH public key." msgid "Successfully updated the SSH public key."
msgstr "更新ssh密钥成功" msgstr "更新ssh密钥成功"
#: users/templates/users/user_detail.html:413 #: users/templates/users/user_detail.html:422
#: users/templates/users/user_detail.html:417 #: users/templates/users/user_detail.html:426
#: users/templates/users/user_profile.html:212 #: users/templates/users/user_profile.html:222
#: users/templates/users/user_profile.html:217 #: users/templates/users/user_profile.html:227
msgid "User SSH public key update" msgid "User SSH public key update"
msgstr "ssh密钥" msgstr "ssh密钥"
#: users/templates/users/user_detail.html:462 #: users/templates/users/user_detail.html:471
msgid "After unlocking the user, the user can log in normally." msgid "After unlocking the user, the user can log in normally."
msgstr "解除用户登录限制后,此用户即可正常登录" msgstr "解除用户登录限制后,此用户即可正常登录"
#: users/templates/users/user_group_create_update.html:31 #: users/templates/users/user_detail.html:485
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:32 #, fuzzy
msgid "Cancel" #| msgid "Reset password success"
msgstr "取消" msgid "Reset user MFA success"
msgstr "重置密码成功"
#: users/templates/users/user_group_detail.html:22 #: users/templates/users/user_group_detail.html:22
#: users/templates/users/user_group_granted_asset.html:18 #: users/templates/users/user_group_granted_asset.html:18
#: users/views/group.py:79 #: users/views/group.py:77
msgid "User group detail" msgid "User group detail"
msgstr "用户组详情" msgstr "用户组详情"
...@@ -2962,7 +3427,7 @@ msgstr "用户组详情" ...@@ -2962,7 +3427,7 @@ msgstr "用户组详情"
msgid "Add user" msgid "Add user"
msgstr "添加用户" msgstr "添加用户"
#: users/templates/users/user_group_list.html:5 users/views/group.py:44 #: users/templates/users/user_group_list.html:5 users/views/group.py:45
#: xpack/templates/orgs/org_list.html:5 #: xpack/templates/orgs/org_list.html:5
msgid "Create user group" msgid "Create user group"
msgstr "创建用户组" msgstr "创建用户组"
...@@ -2972,18 +3437,18 @@ msgstr "创建用户组" ...@@ -2972,18 +3437,18 @@ msgstr "创建用户组"
msgid "This will delete the selected groups !!!" msgid "This will delete the selected groups !!!"
msgstr "删除选择组" msgstr "删除选择组"
#: users/templates/users/user_group_list.html:90 #: users/templates/users/user_group_list.html:91
#: xpack/templates/orgs/org_list.html:90 #: xpack/templates/orgs/org_list.html:90
msgid "UserGroups Deleted." msgid "UserGroups Deleted."
msgstr "用户组删除" msgstr "用户组删除"
#: users/templates/users/user_group_list.html:91 #: users/templates/users/user_group_list.html:92
#: users/templates/users/user_group_list.html:96 #: users/templates/users/user_group_list.html:97
#: xpack/templates/orgs/org_list.html:91 xpack/templates/orgs/org_list.html:96 #: xpack/templates/orgs/org_list.html:91 xpack/templates/orgs/org_list.html:96
msgid "UserGroups Delete" msgid "UserGroups Delete"
msgstr "用户组删除" msgstr "用户组删除"
#: users/templates/users/user_group_list.html:95 #: users/templates/users/user_group_list.html:96
#: xpack/templates/orgs/org_list.html:95 #: xpack/templates/orgs/org_list.html:95
msgid "UserGroup Deleting failed." msgid "UserGroup Deleting failed."
msgstr "用户组删除失败" msgstr "用户组删除失败"
...@@ -2992,19 +3457,60 @@ msgstr "用户组删除失败" ...@@ -2992,19 +3457,60 @@ msgstr "用户组删除失败"
msgid "This will delete the selected users !!!" msgid "This will delete the selected users !!!"
msgstr "删除选中用户 !!!" msgstr "删除选中用户 !!!"
#: users/templates/users/user_list.html:204 #: users/templates/users/user_list.html:205
msgid "User Deleted." msgid "User Deleted."
msgstr "已被删除" msgstr "已被删除"
#: users/templates/users/user_list.html:205 #: users/templates/users/user_list.html:206
#: users/templates/users/user_list.html:210 #: users/templates/users/user_list.html:211
msgid "User Delete" msgid "User Delete"
msgstr "删除" msgstr "删除"
#: users/templates/users/user_list.html:209 #: users/templates/users/user_list.html:210
msgid "User Deleting failed." msgid "User Deleting failed."
msgstr "用户删除失败" msgstr "用户删除失败"
#: users/templates/users/user_otp_authentication.html:6
#: users/templates/users/user_password_authentication.html:6
msgid "Authenticate"
msgstr "验证身份"
#: users/templates/users/user_otp_authentication.html:32
msgid "Unbind"
msgstr "解绑 MFA"
#: users/templates/users/user_otp_enable_bind.html:6
msgid "Bind"
msgstr "绑定 MFA"
#: users/templates/users/user_otp_enable_bind.html:12
msgid ""
"Use the mobile Google Authenticator application to scan the following qr "
"code for a 6-bit verification code"
msgstr "使用手机 Google Authenticator 应用扫描以下二维码,获取6位验证码"
#: users/templates/users/user_otp_enable_install_app.html:6
msgid "Install"
msgstr "安装应用"
#: users/templates/users/user_otp_enable_install_app.html:11
msgid "Download and install the Google Authenticator application on your phone"
msgstr "请在手机端下载并安装 Google Authenticator 应用"
#: users/templates/users/user_otp_enable_install_app.html:14
msgid "Android downloads"
msgstr "Android手机下载"
#: users/templates/users/user_otp_enable_install_app.html:19
msgid "iPhone downloads"
msgstr "iPhone手机下载"
#: users/templates/users/user_otp_enable_install_app.html:23
msgid ""
"After installation, click the next step to enter the binding page (if "
"installed, go to the next step directly)."
msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接进入下一步"
#: users/templates/users/user_profile.html:95 #: users/templates/users/user_profile.html:95
msgid "Administrator Settings force MFA login" msgid "Administrator Settings force MFA login"
msgstr "管理员设置强制使用MFA登录" msgstr "管理员设置强制使用MFA登录"
...@@ -3019,18 +3525,22 @@ msgid "Update password" ...@@ -3019,18 +3525,22 @@ msgid "Update password"
msgstr "更改密码" msgstr "更改密码"
#: users/templates/users/user_profile.html:156 #: users/templates/users/user_profile.html:156
msgid "Update MFA settings" msgid "Set MFA"
msgstr "更改MFA设置" msgstr "设置MFA"
#: users/templates/users/user_profile.html:177 #: users/templates/users/user_profile.html:178
msgid "Update MFA"
msgstr "更改MFA"
#: users/templates/users/user_profile.html:187
msgid "Update SSH public key" msgid "Update SSH public key"
msgstr "更改SSH密钥" msgstr "更改SSH密钥"
#: users/templates/users/user_profile.html:185 #: users/templates/users/user_profile.html:195
msgid "Reset public key and download" msgid "Reset public key and download"
msgstr "重置并下载SSH密钥" msgstr "重置并下载SSH密钥"
#: users/templates/users/user_profile.html:215 #: users/templates/users/user_profile.html:225
msgid "Failed to update SSH public key." msgid "Failed to update SSH public key."
msgstr "更新密钥失败" msgstr "更新密钥失败"
...@@ -3194,79 +3704,75 @@ msgstr "禁用或失效" ...@@ -3194,79 +3704,75 @@ msgstr "禁用或失效"
msgid "Password or SSH public key invalid" msgid "Password or SSH public key invalid"
msgstr "密码或密钥不合法" msgstr "密码或密钥不合法"
#: users/utils.py:289 users/utils.py:299 #: users/utils.py:286 users/utils.py:296
msgid "Bit" msgid "Bit"
msgstr " 位" msgstr " 位"
#: users/views/group.py:28 #: users/views/group.py:29
msgid "User group list" msgid "User group list"
msgstr "用户组列表" msgstr "用户组列表"
#: users/views/group.py:62 #: users/views/group.py:61
msgid "Update user group" msgid "Update user group"
msgstr "更新用户组" msgstr "更新用户组"
#: users/views/group.py:95 #: users/views/group.py:93
msgid "User group granted asset" msgid "User group granted asset"
msgstr "用户组授权资产" msgstr "用户组授权资产"
#: users/views/login.py:77 #: users/views/login.py:69
msgid "Please enable cookies and try again." msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie" msgstr "设置你的浏览器支持cookie"
#: users/views/login.py:181 users/views/user.py:517 users/views/user.py:542 #: users/views/login.py:175 users/views/user.py:517 users/views/user.py:542
msgid "MFA code invalid" msgid "MFA code invalid, or ntp sync server time"
msgstr "MFA码认证失败" msgstr "MFA验证码不正确,或者服务器端时间不对"
#: users/views/login.py:210 #: users/views/login.py:204
msgid "Logout success" msgid "Logout success"
msgstr "退出登录成功" msgstr "退出登录成功"
#: users/views/login.py:211 #: users/views/login.py:205
msgid "Logout success, return login page" msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面" msgstr "退出登录成功,返回到登录页面"
#: users/views/login.py:227 #: users/views/login.py:221
msgid "Email address invalid, please input again" msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入" msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:240 #: users/views/login.py:234
msgid "Send reset password message" msgid "Send reset password message"
msgstr "发送重置密码邮件" msgstr "发送重置密码邮件"
#: users/views/login.py:241 #: users/views/login.py:235
msgid "Send reset password mail success, login your mail box and follow it " msgid "Send reset password mail success, login your mail box and follow it "
msgstr "" msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/login.py:254 #: users/views/login.py:248
msgid "Reset password success" msgid "Reset password success"
msgstr "重置密码成功" msgstr "重置密码成功"
#: users/views/login.py:255 #: users/views/login.py:249
msgid "Reset password success, return to login page" msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面" msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:276 users/views/login.py:289 #: users/views/login.py:270 users/views/login.py:283
msgid "Token invalid or expired" msgid "Token invalid or expired"
msgstr "Token错误或失效" msgstr "Token错误或失效"
#: users/views/login.py:285 #: users/views/login.py:279
msgid "Password not same" msgid "Password not same"
msgstr "密码不一致" msgstr "密码不一致"
#: users/views/login.py:295 users/views/user.py:126 users/views/user.py:415 #: users/views/login.py:289 users/views/user.py:126 users/views/user.py:415
msgid "* Your password does not meet the requirements" msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求" msgstr "* 您的密码不符合要求"
#: users/views/login.py:333 #: users/views/login.py:327
msgid "First login" msgid "First login"
msgstr "首次登陆" msgstr "首次登陆"
#: users/views/login.py:398
msgid "Login log list"
msgstr "登录日志"
#: users/views/user.py:143 #: users/views/user.py:143
msgid "Bulk update user success" msgid "Bulk update user success"
msgstr "批量更新用户成功" msgstr "批量更新用户成功"
...@@ -3299,19 +3805,19 @@ msgstr "密钥更新" ...@@ -3299,19 +3805,19 @@ msgstr "密钥更新"
msgid "Password invalid" msgid "Password invalid"
msgstr "用户名或密码无效" msgstr "用户名或密码无效"
#: users/views/user.py:568 #: users/views/user.py:572
msgid "MFA enable success" msgid "MFA enable success"
msgstr "MFA 绑定成功" msgstr "MFA 绑定成功"
#: users/views/user.py:569 #: users/views/user.py:573
msgid "MFA enable success, return login page" msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面" msgstr "MFA 绑定成功,返回到登录页面"
#: users/views/user.py:571 #: users/views/user.py:575
msgid "MFA disable success" msgid "MFA disable success"
msgstr "MFA 解绑成功" msgstr "MFA 解绑成功"
#: users/views/user.py:572 #: users/views/user.py:576
msgid "MFA disable success, return login page" msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面" msgstr "MFA 解绑成功,返回登录页面"
...@@ -3363,6 +3869,18 @@ msgstr "创建组织" ...@@ -3363,6 +3869,18 @@ msgstr "创建组织"
msgid "Update org" msgid "Update org"
msgstr "更新组织" msgstr "更新组织"
#~ msgid "OK"
#~ msgstr "完成"
#~ msgid "Update MFA settings"
#~ msgstr "更改MFA设置"
#~ msgid "MFA code invalid"
#~ msgstr "MFA码认证失败"
#~ msgid "Chinese"
#~ msgstr "中文"
#~ msgid "* required Must set exact system platform, Windows, Linux ..." #~ msgid "* required Must set exact system platform, Windows, Linux ..."
#~ msgstr "* required 必须准确设置操作系统平台,如Windows, Linux ..." #~ msgstr "* required 必须准确设置操作系统平台,如Windows, Linux ..."
......
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-08-08 14:48+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: static/js/jumpserver.js:158
msgid "Update is successful!"
msgstr "更新成功"
#: static/js/jumpserver.js:160
msgid "An unknown error occurred while updating.."
msgstr "更新时发生未知错误"
#: static/js/jumpserver.js:205 static/js/jumpserver.js:247
#: static/js/jumpserver.js:252
msgid "Error"
msgstr "错误"
#: static/js/jumpserver.js:205
msgid "Being used by the asset, please unbind the asset first."
msgstr "正在被资产使用中,请先解除资产绑定"
#: static/js/jumpserver.js:212 static/js/jumpserver.js:260
msgid "Delete the success"
msgstr "删除成功"
#: static/js/jumpserver.js:219
msgid "Are you sure about deleting it?"
msgstr "你确定删除吗 ?"
#: static/js/jumpserver.js:224 static/js/jumpserver.js:273
msgid "Cancel"
msgstr "取消"
#: static/js/jumpserver.js:227 static/js/jumpserver.js:276
msgid "Confirm"
msgstr "确认"
#: static/js/jumpserver.js:247
msgid ""
"The organization contains undeleted information. Please try again after "
"deleting"
msgstr "组织中包含未删除信息,请删除后重试"
#: static/js/jumpserver.js:252
msgid ""
"Do not perform this operation under this organization. Try again after "
"switching to another organization"
msgstr "请勿在此组织下执行此操作,切换到其他组织后重试"
#: static/js/jumpserver.js:267
msgid ""
"Please ensure that the following information in the organization has been "
"deleted"
msgstr "请确保组织内的以下信息已删除"
#: static/js/jumpserver.js:269
msgid ""
"User list、User group、Asset list、Domain list、Admin user、System user、"
"Labels、Asset permission"
msgstr ""
"用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权"
"规则"
#: static/js/jumpserver.js:311
msgid "Loading ..."
msgstr "加载中 ..."
#: static/js/jumpserver.js:313
msgid "Search"
msgstr "搜索"
#: static/js/jumpserver.js:317
#, javascript-format
msgid "Selected item %d"
msgstr "选中 %d 项"
#: static/js/jumpserver.js:322
msgid "Per page _MENU_"
msgstr "每页 _MENU_"
#: static/js/jumpserver.js:324
msgid ""
"Displays the results of items _START_ to _END_; A total of _TOTAL_ entries"
msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项"
#: static/js/jumpserver.js:328
msgid "No match"
msgstr "没有匹配项"
#: static/js/jumpserver.js:330
msgid "No record"
msgstr "没有记录"
...@@ -207,6 +207,7 @@ class AdHoc(models.Model): ...@@ -207,6 +207,7 @@ class AdHoc(models.Model):
return {} return {}
def run(self, record=True): def run(self, record=True):
set_to_root_org()
if record: if record:
return self._run_and_record() return self._run_and_record()
else: else:
......
...@@ -6,10 +6,10 @@ from django.views.generic import ListView, DetailView, TemplateView ...@@ -6,10 +6,10 @@ from django.views.generic import ListView, DetailView, TemplateView
from common.mixins import DatetimeSearchMixin from common.mixins import DatetimeSearchMixin
from .models import Task, AdHoc, AdHocRunHistory, CeleryTask from .models import Task, AdHoc, AdHocRunHistory, CeleryTask
from common.permissions import AdminUserRequiredMixin from common.permissions import SuperUserRequiredMixin
class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): class TaskListView(SuperUserRequiredMixin, DatetimeSearchMixin, ListView):
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
model = Task model = Task
ordering = ('-date_created',) ordering = ('-date_created',)
...@@ -43,7 +43,7 @@ class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): ...@@ -43,7 +43,7 @@ class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class TaskDetailView(AdminUserRequiredMixin, DetailView): class TaskDetailView(SuperUserRequiredMixin, DetailView):
model = Task model = Task
template_name = 'ops/task_detail.html' template_name = 'ops/task_detail.html'
...@@ -56,7 +56,7 @@ class TaskDetailView(AdminUserRequiredMixin, DetailView): ...@@ -56,7 +56,7 @@ class TaskDetailView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class TaskAdhocView(AdminUserRequiredMixin, DetailView): class TaskAdhocView(SuperUserRequiredMixin, DetailView):
model = Task model = Task
template_name = 'ops/task_adhoc.html' template_name = 'ops/task_adhoc.html'
...@@ -69,7 +69,7 @@ class TaskAdhocView(AdminUserRequiredMixin, DetailView): ...@@ -69,7 +69,7 @@ class TaskAdhocView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class TaskHistoryView(AdminUserRequiredMixin, DetailView): class TaskHistoryView(SuperUserRequiredMixin, DetailView):
model = Task model = Task
template_name = 'ops/task_history.html' template_name = 'ops/task_history.html'
...@@ -82,7 +82,7 @@ class TaskHistoryView(AdminUserRequiredMixin, DetailView): ...@@ -82,7 +82,7 @@ class TaskHistoryView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AdHocDetailView(AdminUserRequiredMixin, DetailView): class AdHocDetailView(SuperUserRequiredMixin, DetailView):
model = AdHoc model = AdHoc
template_name = 'ops/adhoc_detail.html' template_name = 'ops/adhoc_detail.html'
...@@ -95,7 +95,7 @@ class AdHocDetailView(AdminUserRequiredMixin, DetailView): ...@@ -95,7 +95,7 @@ class AdHocDetailView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AdHocHistoryView(AdminUserRequiredMixin, DetailView): class AdHocHistoryView(SuperUserRequiredMixin, DetailView):
model = AdHoc model = AdHoc
template_name = 'ops/adhoc_history.html' template_name = 'ops/adhoc_history.html'
...@@ -108,7 +108,7 @@ class AdHocHistoryView(AdminUserRequiredMixin, DetailView): ...@@ -108,7 +108,7 @@ class AdHocHistoryView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AdHocHistoryDetailView(AdminUserRequiredMixin, DetailView): class AdHocHistoryDetailView(SuperUserRequiredMixin, DetailView):
model = AdHocRunHistory model = AdHocRunHistory
template_name = 'ops/adhoc_history_detail.html' template_name = 'ops/adhoc_history_detail.html'
...@@ -121,6 +121,6 @@ class AdHocHistoryDetailView(AdminUserRequiredMixin, DetailView): ...@@ -121,6 +121,6 @@ class AdHocHistoryDetailView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class CeleryTaskLogView(AdminUserRequiredMixin, DetailView): class CeleryTaskLogView(SuperUserRequiredMixin, DetailView):
template_name = 'ops/celery_task_log.html' template_name = 'ops/celery_task_log.html'
model = CeleryTask model = CeleryTask
...@@ -20,6 +20,9 @@ class Organization(models.Model): ...@@ -20,6 +20,9 @@ class Organization(models.Model):
ROOT_ID_NAME = 'ROOT' ROOT_ID_NAME = 'ROOT'
DEFAULT_ID_NAME = 'DEFAULT' DEFAULT_ID_NAME = 'DEFAULT'
class Meta:
verbose_name = _("Organization")
def __str__(self): def __str__(self):
return self.name return self.name
...@@ -63,14 +66,17 @@ class Organization(models.Model): ...@@ -63,14 +66,17 @@ class Organization(models.Model):
org = cls.default() if default else None org = cls.default() if default else None
return org return org
def get_org_users(self): def get_org_users(self, include_app=False):
from users.models import User from users.models import User
if self.is_default(): if self.is_default():
users = User.objects.filter(orgs__isnull=True) users = User.objects.filter(orgs__isnull=True)
elif not self.is_real(): elif not self.is_real():
users = User.objects.all() users = User.objects.all()
elif self.is_root():
users = User.objects.all()
else: else:
users = self.users.all() users = self.users.all()
if not include_app:
users = users.exclude(role=User.ROLE_APP) users = users.exclude(role=User.ROLE_APP)
return users return users
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.db.models.signals import m2m_changed
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from .models import Organization from .models import Organization
from .hands import set_current_org, current_org, Node from .hands import set_current_org, current_org, Node
from perms.models import AssetPermission
from users.models import UserGroup
@receiver(post_save, sender=Organization) @receiver(post_save, sender=Organization)
...@@ -21,3 +24,20 @@ def on_org_create_or_update(sender, instance=None, created=False, **kwargs): ...@@ -21,3 +24,20 @@ def on_org_create_or_update(sender, instance=None, created=False, **kwargs):
if instance and not created: if instance and not created:
instance.expire_cache() instance.expire_cache()
@receiver(m2m_changed, sender=Organization.users.through)
def on_org_user_changed(sender, instance=None, **kwargs):
if isinstance(instance, Organization):
old_org = current_org
set_current_org(instance)
if kwargs['action'] == 'pre_remove':
users = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
for user in users:
perms = AssetPermission.objects.filter(users=user)
user_groups = UserGroup.objects.filter(users=user)
for perm in perms:
perm.users.remove(user)
for user_group in user_groups:
user_group.users.remove(user)
set_current_org(old_org)
...@@ -40,5 +40,3 @@ def get_current_org(): ...@@ -40,5 +40,3 @@ def get_current_org():
current_org = LocalProxy(partial(_find, 'current_org')) current_org = LocalProxy(partial(_find, 'current_org'))
current_user = LocalProxy(partial(_find, 'current_user'))
current_request = LocalProxy(partial(_find, 'current_request'))
...@@ -6,23 +6,10 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -6,23 +6,10 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgModelForm from orgs.mixins import OrgModelForm
from orgs.utils import current_org from orgs.utils import current_org
from .hands import User
from .models import AssetPermission from .models import AssetPermission
class AssetPermissionForm(OrgModelForm): class AssetPermissionForm(OrgModelForm):
users = forms.ModelMultipleChoiceField(
queryset=User.objects.exclude(role=User.ROLE_APP),
label=_("User"),
widget=forms.SelectMultiple(
attrs={
'class': 'select2',
'data-placeholder': _('Select users')
}
),
required=False,
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if 'initial' not in kwargs: if 'initial' not in kwargs:
......
...@@ -42,6 +42,7 @@ class AssetPermission(OrgModelMixin): ...@@ -42,6 +42,7 @@ class AssetPermission(OrgModelMixin):
class Meta: class Meta:
unique_together = [('org_id', 'name')] unique_together = [('org_id', 'name')]
verbose_name = _("Asset permission")
def __str__(self): def __str__(self):
return self.name return self.name
......
...@@ -154,8 +154,8 @@ function activeNav() { ...@@ -154,8 +154,8 @@ function activeNav() {
function APIUpdateAttr(props) { function APIUpdateAttr(props) {
// props = {url: .., body: , success: , error: , method: ,} // props = {url: .., body: , success: , error: , method: ,}
props = props || {}; props = props || {};
var success_message = props.success_message || '更新成功!'; var success_message = props.success_message || gettext('Update is successful!');
var fail_message = props.fail_message || '更新时发生未知错误.'; var fail_message = props.fail_message || gettext('An unknown error occurred while updating..');
var flash_message = props.flash_message || true; var flash_message = props.flash_message || true;
if (props.flash_message === false){ if (props.flash_message === false){
flash_message = false; flash_message = false;
...@@ -199,25 +199,25 @@ function objectDelete(obj, name, url, redirectTo) { ...@@ -199,25 +199,25 @@ function objectDelete(obj, name, url, redirectTo) {
}; };
var fail = function() { var fail = function() {
// swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error"); // swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error");
swal("错误", "[ "+name+" ]"+"正在被资产使用中,请先解除资产绑定", "error"); swal(gettext('Error'), "[ "+name+" ]" + gettext("Being used by the asset, please unbind the asset first."), "error");
}; };
APIUpdateAttr({ APIUpdateAttr({
url: url, url: url,
body: JSON.stringify(body), body: JSON.stringify(body),
method: 'DELETE', method: 'DELETE',
success_message: "删除成功", success_message: gettext("Delete the success"),
success: success, success: success,
error: fail error: fail
}); });
} }
swal({ swal({
title: '你确定删除吗 ?', title: gettext('Are you sure about deleting it?'),
text: " [" + name + "] ", text: " [" + name + "] ",
type: "warning", type: "warning",
showCancelButton: true, showCancelButton: true,
cancelButtonText: '取消', cancelButtonText: gettext('Cancel'),
confirmButtonColor: "#ed5565", confirmButtonColor: "#ed5565",
confirmButtonText: '确认', confirmButtonText: gettext('Confirm'),
closeOnConfirm: true, closeOnConfirm: true,
}, function () { }, function () {
doDelete() doDelete()
...@@ -236,29 +236,29 @@ function orgDelete(obj, name, url, redirectTo){ ...@@ -236,29 +236,29 @@ function orgDelete(obj, name, url, redirectTo){
}; };
var fail = function(responseText, status) { var fail = function(responseText, status) {
if (status === 400){ if (status === 400){
swal("错误", "[ " + name + " ] 组织中包含未删除信息,请删除后重试", "error"); swal(gettext("Error"), "[ " + name + " ] " + gettext("The organization contains undeleted information. Please try again after deleting"), "error");
} }
else if (status === 405){ else if (status === 405){
swal("错误", "请勿在组织 [ "+ name + " ] 下执行此操作,切换到其他组织后重试", "error"); swal(gettext("Error"), " [ "+ name + " ] " + gettext("Do not perform this operation under this organization. Try again after switching to another organization"), "error");
} }
}; };
APIUpdateAttr({ APIUpdateAttr({
url: url, url: url,
body: JSON.stringify(body), body: JSON.stringify(body),
method: 'DELETE', method: 'DELETE',
success_message: "删除成功", success_message: gettext("Delete the success"),
success: success, success: success,
error: fail error: fail
}); });
} }
swal({ swal({
title: "请确保组织内的以下信息已删除", title: gettext("Please ensure that the following information in the organization has been deleted"),
text: "用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权规则", text: gettext("User list、User group、Asset list、Domain list、Admin user、System user、Labels、Asset permission"),
type: "warning", type: "warning",
showCancelButton: true, showCancelButton: true,
cancelButtonText: '取消', cancelButtonText: gettext('Cancel'),
confirmButtonColor: "#ed5565", confirmButtonColor: "#ed5565",
confirmButtonText: '确认', confirmButtonText: gettext('Confirm'),
closeOnConfirm: true closeOnConfirm: true
}, function () { }, function () {
doDelete(); doDelete();
...@@ -292,20 +292,20 @@ var jumpserver = {}; ...@@ -292,20 +292,20 @@ var jumpserver = {};
jumpserver.checked = false; jumpserver.checked = false;
jumpserver.selected = {}; jumpserver.selected = {};
jumpserver.language = { jumpserver.language = {
processing: "加载中", processing: gettext('Loading ...'),
search: "搜索", search: gettext('Search'),
select: { select: {
rows: { rows: {
_: "选中 %d 项", _: gettext("Selected item %d"),
0: "" 0: ""
} }
}, },
lengthMenu: "每页 _MENU_", lengthMenu: gettext("Per page _MENU_"),
info: "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项", info: gettext('Displays the results of items _START_ to _END_; A total of _TOTAL_ entries'),
infoFiltered: "", infoFiltered: "",
infoEmpty: "", infoEmpty: "",
zeroRecords: "没有匹配项", zeroRecords: gettext("No match"),
emptyTable: "没有记录", emptyTable: gettext('No record'),
paginate: { paginate: {
first: "«", first: "«",
previous: "‹", previous: "‹",
......
<strong>Copyright</strong> 北京堆栈科技有限公司 &copy; 2014-2018 {% load i18n %}
\ No newline at end of file <strong>Copyright</strong> {% trans ' Beijing Duizhan Tech, Inc. ' %} &copy; 2014-2018
\ No newline at end of file
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
<!-- Custom and plugin javascript --> <!-- Custom and plugin javascript -->
<script src="{% static "js/plugins/toastr/toastr.min.js" %}"></script> <script src="{% static "js/plugins/toastr/toastr.min.js" %}"></script>
<script src="{% static "js/inspinia.js" %}"></script> <script src="{% static "js/inspinia.js" %}"></script>
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script> <script src="{% static "js/jumpserver.js" %}"></script>
<script> <script>
activeNav(); activeNav();
......
{% load i18n %}
<div class="footer fixed"> <div class="footer fixed">
<div class="pull-right"> <div class="pull-right">
Version <strong>1.4.0-{% include '_build.html' %}</strong> GPLv2. Version <strong>1.4.1-{% include '_build.html' %}</strong> GPLv2.
<!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">--> <!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
</div> </div>
<div> <div>
<strong>Copyright</strong> 北京堆栈科技有限公司 &copy; 2014-2018 <strong>Copyright</strong> {% trans ' Beijing Duizhan Tech, Inc. ' %}&copy; 2014-2018
</div> </div>
</div> </div>
...@@ -13,16 +13,68 @@ ...@@ -13,16 +13,68 @@
{# <li>#} {# <li>#}
{# <span class="m-r-sm text-muted welcome-message">{% trans 'Welcome to use Jumpserver system' %}</span>#} {# <span class="m-r-sm text-muted welcome-message">{% trans 'Welcome to use Jumpserver system' %}</span>#}
{# </li>#} {# </li>#}
{# <li class="dropdown">#}
{# <a class="count-info" href="https://market.aliyun.com/products/53690006/cmgj026011.html?spm=5176.730005.0.0.cY2io1" target="_blank">#}
{# <span class="m-r-sm text-muted welcome-message">{% trans 'Supports' %}</span>#}
{# </a>#}
{# </li>#}
{# <li class="dropdown">#}
{# <a class="count-info" href="http://docs.jumpserver.org/" target="_blank">#}
{# <span class="m-r-sm text-muted welcome-message">{% trans 'Docs' %}</span>#}
{# </a>#}
{# </li>#}
<li class="dropdown"> <li class="dropdown">
<a class="count-info dropdown-toggle" data-toggle="dropdown" href="#" target="_blank">
<i class="fa fa-handshake-o"></i>
<span class="m-r-sm text-muted welcome-message">{% trans 'Help' %} <b class="caret"></b></span>
</a>
<ul class="dropdown-menu animated fadeInRight m-t-xs profile-dropdown">
<li>
<a class="count-info" href="http://docs.jumpserver.org/" target="_blank">
<i class="fa fa-file-text"></i>
<span class="m-r-sm text-muted welcome-message">{% trans 'Docs' %}</span>
</a>
</li>
<li>
<a class="count-info" href="https://market.aliyun.com/products/53690006/cmgj026011.html?spm=5176.730005.0.0.cY2io1" target="_blank"> <a class="count-info" href="https://market.aliyun.com/products/53690006/cmgj026011.html?spm=5176.730005.0.0.cY2io1" target="_blank">
<span class="m-r-sm text-muted welcome-message">{% trans 'Supports' %}</span> <i class="fa fa-suitcase"></i>
<span class="m-r-sm text-muted welcome-message">{% trans 'Commercial support' %}</span>
</a> </a>
</li> </li>
</ul>
</li>
<li class="dropdown"> <li class="dropdown">
<a class="count-info" href="http://docs.jumpserver.org/" target="_blank"> <a class="count-info dropdown-toggle" data-toggle="dropdown" href="#" target="_blank">
<span class="m-r-sm text-muted welcome-message">{% trans 'Docs' %}</span> <i class="fa fa-globe"></i>
{% ifequal request.COOKIES.django_language 'en' %}
<span class="m-r-sm text-muted welcome-message">English<b class="caret"></b></span>
{% else %}
<span class="m-r-sm text-muted welcome-message">中文<b class="caret"></b></span>
{% endifequal %}
</a>
<ul class="dropdown-menu animated fadeInRight m-t-xs profile-dropdown">
<li>
<a id="switch_cn" href="{% url 'i18n-switch' lang='zh-hans' %}">
<i class="fa fa-flag"></i>
<span> 中文</span>
</a> </a>
</li> </li>
<li>
<a id="switch_en" href="{% url 'i18n-switch' lang='en' %}">
<i class="fa fa-flag-checkered"></i>
<span> English</span>
</a>
</li>
</ul>
</li>
<li class="dropdown"> <li class="dropdown">
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<a data-toggle="dropdown" class="dropdown-toggle" href="#"> <a data-toggle="dropdown" class="dropdown-toggle" href="#">
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
<ul class="nav nav-second-level active"> <ul class="nav nav-second-level active">
<li id="user"><a href="{% url 'users:user-list' %}">{% trans 'User list' %}</a></li> <li id="user"><a href="{% url 'users:user-list' %}">{% trans 'User list' %}</a></li>
<li id="user-group"><a href="{% url 'users:user-group-list' %}">{% trans 'User group' %}</a></li> <li id="user-group"><a href="{% url 'users:user-group-list' %}">{% trans 'User group' %}</a></li>
<li id="login-log"><a href="{% url 'users:login-log-list' %}">{% trans 'Login logs' %}</a></li>
</ul> </ul>
</li> </li>
<li id="assets"> <li id="assets">
...@@ -68,7 +67,10 @@ ...@@ -68,7 +67,10 @@
<i class="fa fa-history" style="width: 14px"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span> <i class="fa fa-history" style="width: 14px"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span>
</a> </a>
<ul class="nav nav-second-level"> <ul class="nav nav-second-level">
<li id="login-log"><a href="{% url 'audits:login-log-list' %}">{% trans 'Login log' %}</a></li>
<li id="ftp-log"><a href="{% url 'audits:ftp-log-list' %}">{% trans 'FTP log' %}</a></li> <li id="ftp-log"><a href="{% url 'audits:ftp-log-list' %}">{% trans 'FTP log' %}</a></li>
<li id="operate-log"><a href="{% url 'audits:operate-log-list' %}">{% trans 'Operate log' %}</a></li>
<li id="password-change-log"><a href="{% url 'audits:password-change-log-list' %}">{% trans 'Password change log' %}</a></li>
</ul> </ul>
</li> </li>
{#<li id="">#} {#<li id="">#}
......
{% load i18n %}
{% load common_tags %} {% load common_tags %}
{% if is_paginated %} {% if is_paginated %}
<div class="col-sm-4"> <div class="col-sm-4">
<div class="dataTables_info text-center" id="editable_info" role="status" aria-live="polite"> <div class="dataTables_info text-center" id="editable_info" role="status" aria-live="polite">
显示第 {{ page_obj.start_index }} 至 {{ page_obj.end_index }} 项结果,共 {{ paginator.count }} 项 {# 显示第 {{ page_obj.start_index }} 至 {{ page_obj.end_index }} 项结果,共 {{ paginator.count }} 项#}
</div> </div>
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
...@@ -10,7 +11,7 @@ ...@@ -10,7 +11,7 @@
<ul class="pagination" style="margin-top: 0; float: right"> <ul class="pagination" style="margin-top: 0; float: right">
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<li class="paginate_button previous" aria-controls="editable" tabindex="0" id="previous"> <li class="paginate_button previous" aria-controls="editable" tabindex="0" id="previous">
<a data-page="next" href="?page={{ page_obj.previous_page_number}}"></a> <a data-page="next" class="page" href="?page={{ page_obj.previous_page_number}}"></a>
</li> </li>
{% endif %} {% endif %}
...@@ -26,7 +27,7 @@ ...@@ -26,7 +27,7 @@
{% if page_obj.has_next %} {% if page_obj.has_next %}
<li class="paginate_button next" aria-controls="editable" tabindex="0" id="next"> <li class="paginate_button next" aria-controls="editable" tabindex="0" id="next">
<a data-page="next" href="?page={{ page_obj.next_page_number }}"></a> <a data-page="next" class="page" href="?page={{ page_obj.next_page_number }}"></a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
...@@ -40,7 +41,7 @@ ...@@ -40,7 +41,7 @@
var old_href = $(this).attr('href').replace('?', ''); var old_href = $(this).attr('href').replace('?', '');
var searchArray = searchStr.split('&'); var searchArray = searchStr.split('&');
if (searchStr == '') { if (searchStr === '') {
searchStr = '?page=1' searchStr = '?page=1'
} }
...@@ -53,6 +54,13 @@ ...@@ -53,6 +54,13 @@
$(this).attr('href', searchArray.join('&')); $(this).attr('href', searchArray.join('&'));
} }
}) })
$('#editable_info').html(
"{% trans 'Displays the results of items _START_ to _END_; A total of _TOTAL_ entries' %}"
.replace('_START_', {{ page_obj.start_index }})
.replace('_END_', {{ page_obj.end_index }})
.replace('_TOTAL_', {{ paginator.count }})
)
}); });
</script> </script>
{% load i18n %}
{% load static %} {% load static %}
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
...@@ -10,6 +11,7 @@ ...@@ -10,6 +11,7 @@
{% include '_head_css_js.html' %} {% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet"> <link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script> <script src="{% static "js/jumpserver.js" %}"></script>
</head> </head>
...@@ -40,7 +42,7 @@ ...@@ -40,7 +42,7 @@
{% endif %} {% endif %}
<div class="row"> <div class="row">
<div class="col-lg-3"> <div class="col-lg-3">
<a href="{{ redirect_url }}" class="btn btn-primary block full-width m-b">返回</a> <a href="{{ redirect_url }}" class="btn btn-primary block full-width m-b">{% trans 'Return' %}</a>
</div> </div>
</div> </div>
</div> </div>
......
{% extends 'base.html' %} {% extends 'base.html' %}
{% load i18n %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="wrapper wrapper-content"> <div class="wrapper wrapper-content">
...@@ -7,7 +8,7 @@ ...@@ -7,7 +8,7 @@
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<span class="label label-success pull-right">Users</span> <span class="label label-success pull-right">Users</span>
<h5>用户总数</h5> <h5>{% trans 'Total users' %}</h5>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
<h1 class="no-margins"><a href="{% url 'users:user-list' %}">{{ users_count }}</a></h1> <h1 class="no-margins"><a href="{% url 'users:user-list' %}">{{ users_count }}</a></h1>
...@@ -19,7 +20,7 @@ ...@@ -19,7 +20,7 @@
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<span class="label label-info pull-right">Hosts</span> <span class="label label-info pull-right">Hosts</span>
<h5>主机总数</h5> <h5>{% trans 'Total hosts' %}</h5>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
<h1 class="no-margins"><a href="{% url 'assets:asset-list' %}">{{ assets_count }}</a></h1> <h1 class="no-margins"><a href="{% url 'assets:asset-list' %}">{{ assets_count }}</a></h1>
...@@ -32,7 +33,7 @@ ...@@ -32,7 +33,7 @@
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<span class="label label-primary pull-right">Online</span> <span class="label label-primary pull-right">Online</span>
<h5>在线用户</h5> <h5>{% trans 'Online users' %}</h5>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
<h1 class="no-margins"><a href="{% url 'terminal:session-online-list' %}"> <span id="online_users"></span>{{ online_user_count }}</a></h1> <h1 class="no-margins"><a href="{% url 'terminal:session-online-list' %}"> <span id="online_users"></span>{{ online_user_count }}</a></h1>
...@@ -45,7 +46,8 @@ ...@@ -45,7 +46,8 @@
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<span class="label label-danger pull-right">Connected</span> <span class="label label-danger pull-right">Connected</span>
<h5>在线会话</h5> <h5>{% trans 'Online sessions' %}</h5>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
<h1 class="no-margins"><a href="{% url 'terminal:session-online-list' %}"> <span id="online_hosts"></span>{{ online_asset_count }}</a></h1> <h1 class="no-margins"><a href="{% url 'terminal:session-online-list' %}"> <span id="online_hosts"></span>{{ online_asset_count }}</a></h1>
...@@ -56,13 +58,13 @@ ...@@ -56,13 +58,13 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-2 border-bottom white-bg dashboard-header" style="margin-left:15px;height: 346px"> <div class="col-sm-2 border-bottom white-bg dashboard-header" style="margin-left:15px;height: 346px">
<h2>活跃用户TOP5</h2> <h2>{% trans ' Top 5 Active user' %}</h2>
<small>过去一周共有<span class="text-info">{{ user_visit_count_weekly }}</span>位用户登录<span class="text-success">{{ asset_visit_count_weekly }}</span>次资产.</small> <small>{% trans 'In the past week, a total of ' %}<span class="text-info">{{ user_visit_count_weekly }}</span>{% trans ' users have logged in ' %}<span class="text-success">{{ asset_visit_count_weekly }}</span>{% trans ' times asset.' %}</small>
<ul class="list-group clear-list m-t"> <ul class="list-group clear-list m-t">
{% for data in user_visit_count_top_five %} {% for data in user_visit_count_top_five %}
<li class="list-group-item fist-item"> <li class="list-group-item fist-item">
<span class="pull-right"> <span class="pull-right">
{{ data.total }}次/周 {{ data.total }}{% trans ' times/week' %}
</span> </span>
<span class="label ">{{ forloop.counter }}</span> {{ data.user }} <span class="label ">{{ forloop.counter }}</span> {{ data.user }}
</li> </li>
...@@ -73,21 +75,20 @@ ...@@ -73,21 +75,20 @@
<div class="col-sm-3 white-bg" id="top1" style="margin-left: -15px;height: 346px"> <div class="col-sm-3 white-bg" id="top1" style="margin-left: -15px;height: 346px">
<div class="statistic-box"> <div class="statistic-box">
<h4> <h4>
活跃用户资产占比 {% trans 'Active user asset ratio' %}
</h4> </h4>
<p> <p>
以下图形分别描述一个月活跃用户和资产占所有用户主机的百分比 {% trans 'The following graphs describe the percentage of active users per month and assets per user host per month, respectively.' %}
</p> </p>
<div class="row text-center"> <div class="row text-center">
<div class="col-sm-6"> <div class="col-sm-6">
<div id="activeUser" style="width: 140px; height: 140px;"> <div id="activeUser" style="width: 140px; height: 140px;">
</div> </div>
<h5>用户</h5> <h5>{% trans 'User' %}</h5>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<div id="activeAsset" style="width: 140px; height: 140px;"></div> <div id="activeAsset" style="width: 140px; height: 140px;"></div>
<h5>主机</h5> <h5>{% trans 'Host' %}</h5>
</div> </div>
</div> </div>
<div class="m-t"> <div class="m-t">
...@@ -102,7 +103,7 @@ ...@@ -102,7 +103,7 @@
<div class="col-sm-4"> <div class="col-sm-4">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<h5>一周Top10资产</h5> <h5>{% trans 'Top 10 assets in a week' %}</h5>
<div class="ibox-tools"> <div class="ibox-tools">
<a class="collapse-link"> <a class="collapse-link">
<i class="fa fa-chevron-up"></i> <i class="fa fa-chevron-up"></i>
...@@ -117,8 +118,8 @@ ...@@ -117,8 +118,8 @@
</div> </div>
</div> </div>
<div class="ibox-content ibox-heading"> <div class="ibox-content ibox-heading">
<h3><i class="fa fa-inbox"></i> 一周Top10资产 </h3> <h3><i class="fa fa-inbox"></i>{% trans 'Top 10 assets in a week'%}</h3>
<small><i class="fa fa-map-marker"></i> 登录次数及最近一次登录记录. </small> <small><i class="fa fa-map-marker"></i>{% trans 'Login frequency and last login record.' %}</small>
</div> </div>
<div class="ibox-content inspinia-timeline"> <div class="ibox-content inspinia-timeline">
{% if week_asset_hot_ten %} {% if week_asset_hot_ten %}
...@@ -129,18 +130,18 @@ ...@@ -129,18 +130,18 @@
<i class="fa fa-info-circle"></i> <i class="fa fa-info-circle"></i>
<strong data-toggle="tooltip" title="{{ data.asset }}">{{ data.asset }}</strong> <strong data-toggle="tooltip" title="{{ data.asset }}">{{ data.asset }}</strong>
<br/> <br/>
<small class="text-navy">{{ data.total }}</small> <small class="text-navy">{{ data.total }}{% trans ' times' %}</small>
</div> </div>
<div class="col-xs-7 content no-top-border"> <div class="col-xs-7 content no-top-border">
<p class="m-b-xs">最近一次登录用户</p> <p class="m-b-xs">{% trans 'The last time a user logged in' %}</p>
<p>{{ data.last.user }}</p> <p>{{ data.last.user }}</p>
<p>{{ data.last.date_start |date:"Y-m-d H:i:s" }}</p> <p>{% trans 'At ' %}{{ data.last.date_start |date:"Y-m-d H:i:s" }}</p>
</div> </div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
{% else %} {% else %}
<p class="text-center">(暂无)</p> <p class="text-center">{% trans '(No)' %}</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
...@@ -148,14 +149,14 @@ ...@@ -148,14 +149,14 @@
<div class="col-sm-4"> <div class="col-sm-4">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<h5>最近十次登录</h5> <h5>{% trans 'Last 10 login' %}</h5>
<div class="ibox-tools"> <div class="ibox-tools">
<span class="label label-info-light">10 Messages</span> <span class="label label-info-light">10 Messages</span>
</div> </div>
</div> </div>
<div class="ibox-content ibox-heading"> <div class="ibox-content ibox-heading">
<h3><i class="fa fa-paper-plane-o"></i> 登录记录 </h3> <h3><i class="fa fa-paper-plane-o"></i> {% trans 'Login record' %}</h3>
<small><i class="fa fa-map-marker"></i> 最近十次登录记录. </small> <small><i class="fa fa-map-marker"></i>{% trans 'Last 10 login records.' %}</small>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
<div> <div>
...@@ -168,18 +169,18 @@ ...@@ -168,18 +169,18 @@
</a> </a>
<div class="media-body "> <div class="media-body ">
{% ifequal login.is_finished 0 %} {% ifequal login.is_finished 0 %}
<small class="pull-right text-navy">{{ login.date_start|timesince }} </small> <small class="pull-right text-navy">{{ login.date_start|timesince }} {% trans 'Before' %}</small>
{% else %} {% else %}
<small class="pull-right">{{ login.date_start|timesince }} </small> <small class="pull-right">{{ login.date_start|timesince }} {% trans 'Before' %}</small>
{% endifequal %} {% endifequal %}
<strong>{{ login.user }}</strong> 登录了{{ login.asset }} <br> <strong>{{ login.user }}</strong> {% trans 'Login in ' %}{{ login.asset }} <br>
<small class="text-muted">{{ login.date_start }}</small> <small class="text-muted">{{ login.date_start }}</small>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
{% else %} {% else %}
<p class="text-center">(暂无)</p> <p class="text-center">{% trans '(No)' %}</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
...@@ -190,7 +191,7 @@ ...@@ -190,7 +191,7 @@
<div class="col-sm-4"> <div class="col-sm-4">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<h5>一周Top10用户</h5> <h5>{% trans 'Top 10 users in a week' %}</h5>
<div class="ibox-tools"> <div class="ibox-tools">
<a class="collapse-link"> <a class="collapse-link">
<i class="fa fa-chevron-up"></i> <i class="fa fa-chevron-up"></i>
...@@ -205,8 +206,8 @@ ...@@ -205,8 +206,8 @@
</div> </div>
</div> </div>
<div class="ibox-content ibox-heading"> <div class="ibox-content ibox-heading">
<h3><i class="fa fa-user"></i> 一周Top10用户 </h3> <h3><i class="fa fa-user"></i>{% trans 'Top 10 users in a week' %}</h3>
<small><i class="fa fa-map-marker"></i> 用户登录次数及最近一次登录记录. </small> <small><i class="fa fa-map-marker"></i>{% trans 'User login frequency and last login record.' %}</small>
</div> </div>
<div class="ibox-content inspinia-timeline"> <div class="ibox-content inspinia-timeline">
{% if week_user_hot_ten %} {% if week_user_hot_ten %}
...@@ -217,18 +218,18 @@ ...@@ -217,18 +218,18 @@
<i class="fa fa-info-circle"></i> <i class="fa fa-info-circle"></i>
<strong data-toggle="tooltip" title="{{ data.user }}">{{ data.user }}</strong> <strong data-toggle="tooltip" title="{{ data.user }}">{{ data.user }}</strong>
<br/> <br/>
<small class="text-navy">{{ data.total }}</small> <small class="text-navy">{{ data.total }}{% trans ' times' %}</small>
</div> </div>
<div class="col-xs-7 content no-top-border"> <div class="col-xs-7 content no-top-border">
<p class="m-b-xs">最近一次登录主机</p> <p class="m-b-xs">{% trans 'The last time logged on to the host' %}</p>
<p>{{ data.last.asset }}</p> <p>{{ data.last.asset }}</p>
<p>{{ data.last.date_start |date:"Y-m-d H:i:s" }}</p> <p>{% trans 'At ' %}{{ data.last.date_start |date:"Y-m-d H:i:s" }}</p>
</div> </div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
{% else %} {% else %}
<p class="text-center">(暂无)</p> <p class="text-center">{% trans '(No)' %}</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
...@@ -264,8 +265,8 @@ $(document).ready(function(){ ...@@ -264,8 +265,8 @@ $(document).ready(function(){
var top10Chart = ec.init(document.getElementById('top10')); var top10Chart = ec.init(document.getElementById('top10'));
var option = { var option = {
title : { title : {
text: '月数据总览', text: "{% trans 'Monthly data overview' %}",
subtext: '一个月内历史汇总', subtext: "{% trans 'History summary in one month' %}",
x: 'center' x: 'center'
}, },
tooltip : { tooltip : {
...@@ -273,7 +274,7 @@ $(document).ready(function(){ ...@@ -273,7 +274,7 @@ $(document).ready(function(){
}, },
backgroundColor: '#fff', backgroundColor: '#fff',
legend: { legend: {
data:['登陆次数', '活跃用户','活跃资产'], data:["{% trans 'Login count' %}", "{% trans 'Active users' %}", "{% trans 'Active assets' %}"],
y: 'bottom' y: 'bottom'
}, },
toolbox: { toolbox: {
...@@ -297,21 +298,21 @@ $(document).ready(function(){ ...@@ -297,21 +298,21 @@ $(document).ready(function(){
], ],
series : [ series : [
{ {
name:'登陆次数', name:"{% trans 'Login count' %}",
type:'line', type:'line',
smooth:true, smooth:true,
itemStyle: {normal: {areaStyle: {type: 'default'}}}, itemStyle: {normal: {areaStyle: {type: 'default'}}},
data: {{ month_total_visit_count|safe}} data: {{ month_total_visit_count|safe}}
}, },
{ {
name:'活跃用户', name:"{% trans 'Active users' %}",
type:'line', type:'line',
smooth:true, smooth:true,
itemStyle: {normal: {areaStyle: {type: 'default'}}}, itemStyle: {normal: {areaStyle: {type: 'default'}}},
data: {{ month_user|safe }} data: {{ month_user|safe }}
}, },
{ {
name:'活跃资产', name:"{% trans 'Active assets' %}",
type:'line', type:'line',
smooth:true, smooth:true,
itemStyle: {normal: {areaStyle: {type: 'default'}}}, itemStyle: {normal: {areaStyle: {type: 'default'}}},
...@@ -338,7 +339,7 @@ $(document).ready(function(){ ...@@ -338,7 +339,7 @@ $(document).ready(function(){
show: false, show: false,
orient : 'vertical', orient : 'vertical',
x : 'left', x : 'left',
data:['月活跃用户','禁用用户','月未登陆用户'] data:["{% trans 'Monthly active users' %}", "{% trans 'Disable user' %}", "{% trans 'Month not logged in user' %}"]
}, },
toolbox: { toolbox: {
show : false, show : false,
...@@ -364,7 +365,7 @@ $(document).ready(function(){ ...@@ -364,7 +365,7 @@ $(document).ready(function(){
calculable : true, calculable : true,
series : [ series : [
{ {
name:'访问来源', name:"{% trans 'Access to the source' %}",
type:'pie', type:'pie',
radius : ['50%', '70%'], radius : ['50%', '70%'],
itemStyle : { itemStyle : {
...@@ -388,9 +389,9 @@ $(document).ready(function(){ ...@@ -388,9 +389,9 @@ $(document).ready(function(){
} }
}, },
data:[ data:[
{value:{{ month_user_active }}, name:'月活跃用户'}, {value:{{ month_user_active }}, name:"{% trans 'Monthly active users' %}"},
{value:{{ month_user_disabled }}, name:'禁用用户'}, {value:{{ month_user_disabled }}, name:"{% trans 'Disable user' %}"},
{value:{{ month_user_inactive }}, name:'月未登陆用户'} {value:{{ month_user_inactive }}, name:"{% trans 'Month not logged in user' %}"}
] ]
} }
] ]
...@@ -414,7 +415,7 @@ $(document).ready(function(){ ...@@ -414,7 +415,7 @@ $(document).ready(function(){
show: false, show: false,
orient : 'vertical', orient : 'vertical',
x : 'left', x : 'left',
data:['月被登陆主机','禁用主机','月未登陆主机'] data:["{% trans 'Month is logged into the host' %}", "{% trans 'Disable host' %}", "{% trans 'Month not logged on host' %}"]
}, },
toolbox: { toolbox: {
show : false, show : false,
...@@ -440,7 +441,7 @@ $(document).ready(function(){ ...@@ -440,7 +441,7 @@ $(document).ready(function(){
calculable : true, calculable : true,
series : [ series : [
{ {
name:'访问来源', name:"{% trans 'Access to the source' %}",
type:'pie', type:'pie',
radius : ['50%', '70%'], radius : ['50%', '70%'],
itemStyle : { itemStyle : {
...@@ -464,9 +465,9 @@ $(document).ready(function(){ ...@@ -464,9 +465,9 @@ $(document).ready(function(){
} }
}, },
data:[ data:[
{value:{{ month_asset_active }}, name:'月被登陆主机'}, {value:{{ month_asset_active }}, name:"{% trans 'Month is logged into the host' %}"},
{value:{{ month_asset_disabled }}, name:'禁用主机'}, {value:{{ month_asset_disabled }}, name:"{% trans 'Disable host' %}"},
{value:{{ month_asset_inactive }}, name:'月未登陆主机'} {value:{{ month_asset_inactive }}, name:"{% trans 'Month not logged on host' %}"}
] ]
} }
] ]
......
...@@ -9,6 +9,7 @@ from django.conf import settings ...@@ -9,6 +9,7 @@ from django.conf import settings
from users.models import User from users.models import User
from orgs.mixins import OrgModelMixin from orgs.mixins import OrgModelMixin
from common.models import common_settings
from .backends.command.models import AbstractSessionCommand from .backends.command.models import AbstractSessionCommand
...@@ -62,6 +63,10 @@ class Terminal(models.Model): ...@@ -62,6 +63,10 @@ class Terminal(models.Model):
configs[k] = getattr(settings, k) configs[k] = getattr(settings, k)
configs.update(self.get_common_storage()) configs.update(self.get_common_storage())
configs.update(self.get_replay_storage()) configs.update(self.get_replay_storage())
configs.update({
'SECURITY_MAX_IDLE_TIME': common_settings.SECURITY_MAX_IDLE_TIME or
settings.DEFAULT_SECURITY_MAX_IDLE_TIME,
})
return configs return configs
def create_app_user(self): def create_app_user(self):
......
...@@ -10,7 +10,7 @@ from django.urls import reverse_lazy, reverse ...@@ -10,7 +10,7 @@ from django.urls import reverse_lazy, reverse
from common.mixins import JSONResponseMixin from common.mixins import JSONResponseMixin
from ..models import Terminal from ..models import Terminal
from ..forms import TerminalForm from ..forms import TerminalForm
from common.permissions import AdminUserRequiredMixin from common.permissions import SuperUserRequiredMixin
__all__ = [ __all__ = [
...@@ -20,7 +20,7 @@ __all__ = [ ...@@ -20,7 +20,7 @@ __all__ = [
] ]
class TerminalListView(AdminUserRequiredMixin, ListView): class TerminalListView(SuperUserRequiredMixin, ListView):
model = Terminal model = Terminal
template_name = 'terminal/terminal_list.html' template_name = 'terminal/terminal_list.html'
form_class = TerminalForm form_class = TerminalForm
...@@ -35,7 +35,7 @@ class TerminalListView(AdminUserRequiredMixin, ListView): ...@@ -35,7 +35,7 @@ class TerminalListView(AdminUserRequiredMixin, ListView):
return context return context
class TerminalUpdateView(AdminUserRequiredMixin, UpdateView): class TerminalUpdateView(SuperUserRequiredMixin, UpdateView):
model = Terminal model = Terminal
form_class = TerminalForm form_class = TerminalForm
template_name = 'terminal/terminal_update.html' template_name = 'terminal/terminal_update.html'
...@@ -47,7 +47,7 @@ class TerminalUpdateView(AdminUserRequiredMixin, UpdateView): ...@@ -47,7 +47,7 @@ class TerminalUpdateView(AdminUserRequiredMixin, UpdateView):
return context return context
class TerminalDetailView(LoginRequiredMixin, DetailView): class TerminalDetailView(LoginRequiredMixin, SuperUserRequiredMixin, DetailView):
model = Terminal model = Terminal
template_name = 'terminal/terminal_detail.html' template_name = 'terminal/terminal_detail.html'
context_object_name = 'terminal' context_object_name = 'terminal'
...@@ -61,13 +61,13 @@ class TerminalDetailView(LoginRequiredMixin, DetailView): ...@@ -61,13 +61,13 @@ class TerminalDetailView(LoginRequiredMixin, DetailView):
return context return context
class TerminalDeleteView(AdminUserRequiredMixin, DeleteView): class TerminalDeleteView(SuperUserRequiredMixin, DeleteView):
model = Terminal model = Terminal
template_name = 'delete_confirm.html' template_name = 'delete_confirm.html'
success_url = reverse_lazy('terminal:terminal-list') success_url = reverse_lazy('terminal:terminal-list')
class TerminalAcceptView(AdminUserRequiredMixin, JSONResponseMixin, UpdateView): class TerminalAcceptView(SuperUserRequiredMixin, JSONResponseMixin, UpdateView):
model = Terminal model = Terminal
form_class = TerminalForm form_class = TerminalForm
template_name = 'terminal/terminal_modal_accept.html' template_name = 'terminal/terminal_modal_accept.html'
...@@ -92,7 +92,7 @@ class TerminalAcceptView(AdminUserRequiredMixin, JSONResponseMixin, UpdateView): ...@@ -92,7 +92,7 @@ class TerminalAcceptView(AdminUserRequiredMixin, JSONResponseMixin, UpdateView):
return self.render_json_response(data) return self.render_json_response(data)
class TerminalConnectView(LoginRequiredMixin, DetailView): class TerminalConnectView(LoginRequiredMixin, SuperUserRequiredMixin, DetailView):
template_name = 'flash_message_standalone.html' template_name = 'flash_message_standalone.html'
model = Terminal model = Terminal
...@@ -118,6 +118,6 @@ class TerminalConnectView(LoginRequiredMixin, DetailView): ...@@ -118,6 +118,6 @@ class TerminalConnectView(LoginRequiredMixin, DetailView):
return super(TerminalConnectView, self).get_context_data(**kwargs) return super(TerminalConnectView, self).get_context_data(**kwargs)
class WebTerminalView(LoginRequiredMixin, View): class WebTerminalView(LoginRequiredMixin, SuperUserRequiredMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return redirect('/luna/?' + request.GET.urlencode()) return redirect('/luna/?' + request.GET.urlencode())
# -*- coding: utf-8 -*-
#
from .user import *
from .auth import *
from .group import *
# ~*~ coding: utf-8 ~*~ # -*- coding: utf-8 -*-
#
import uuid import uuid
from django.core.cache import cache from django.core.cache import cache
...@@ -6,233 +7,36 @@ from django.urls import reverse ...@@ -6,233 +7,36 @@ from django.urls import reverse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from rest_framework import generics from rest_framework.permissions import AllowAny
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework_bulk import BulkModelViewSet
from .serializers import UserSerializer, UserGroupSerializer, \
UserGroupUpdateMemeberSerializer, UserPKUpdateSerializer, \
UserUpdateGroupSerializer, ChangeUserPasswordSerializer
from .tasks import write_login_log_async
from .models import User, UserGroup, LoginLog
from .utils import check_user_valid, generate_token, get_login_ip, \
check_otp_code, set_user_login_failed_count_to_cache, is_block_login
from .hands import Asset, SystemUser
from orgs.utils import current_org
from common.permissions import IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser
from .hands import Asset, SystemUser
from common.mixins import IDInFilterMixin
from common.utils import get_logger
from common.utils import get_logger, get_request_ip
from ..serializers import UserSerializer
from ..tasks import write_login_log_async
from ..models import User, LoginLog
from ..utils import check_user_valid, generate_token, \
check_otp_code, increase_login_failed_count, is_block_login, clean_failed_count
from common.permissions import IsOrgAdminOrAppUser
from ..hands import Asset, SystemUser
logger = get_logger(__name__)
class UserViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = User.objects.exclude(role="App")
serializer_class = UserSerializer
permission_classes = (IsOrgAdmin,)
filter_fields = ('username', 'email', 'name', 'id')
def get_queryset(self):
queryset = super().get_queryset()
org_users = current_org.get_org_users().values_list('id', flat=True)
queryset = queryset.filter(id__in=org_users)
return queryset
def get_permissions(self):
if self.action == "retrieve":
self.permission_classes = (IsOrgAdminOrAppUser,)
return super().get_permissions()
class ChangeUserPasswordApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsOrgAdmin,)
queryset = User.objects.all()
serializer_class = ChangeUserPasswordSerializer
def perform_update(self, serializer):
user = self.get_object()
user.password_raw = serializer.validated_data["password"]
user.save()
class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserUpdateGroupSerializer
permission_classes = (IsOrgAdmin,)
class UserResetPasswordApi(generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsAuthenticated,)
def perform_update(self, serializer):
# Note: we are not updating the user object here.
# We just do the reset-password stuff.
from .utils import send_reset_password_mail
user = self.get_object()
user.password_raw = str(uuid.uuid4())
user.save()
send_reset_password_mail(user)
class UserResetPKApi(generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsAuthenticated,)
def perform_update(self, serializer):
from .utils import send_reset_ssh_key_mail
user = self.get_object()
user.is_public_key_valid = False
user.save()
send_reset_ssh_key_mail(user)
class UserUpdatePKApi(generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserPKUpdateSerializer
permission_classes = (IsCurrentUserOrReadOnly,)
def perform_update(self, serializer):
user = self.get_object()
user.public_key = serializer.validated_data['_public_key']
user.save()
class UserUnblockPKApi(generics.UpdateAPIView):
queryset = User.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = UserSerializer
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
key_prefix_block = "_LOGIN_BLOCK_{}"
def perform_update(self, serializer):
user = self.get_object()
username = user.username if user else ''
key_limit = self.key_prefix_limit.format(username, '*')
key_block = self.key_prefix_block.format(username)
cache.delete_pattern(key_limit)
cache.delete(key_block)
class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = UserGroup.objects.all()
serializer_class = UserGroupSerializer
permission_classes = (IsOrgAdmin,)
class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
queryset = UserGroup.objects.all()
serializer_class = UserGroupUpdateMemeberSerializer
permission_classes = (IsOrgAdmin,)
class UserToken(APIView):
permission_classes = (AllowAny,)
def post(self, request):
if not request.user.is_authenticated:
username = request.data.get('username', '')
email = request.data.get('email', '')
password = request.data.get('password', '')
public_key = request.data.get('public_key', '')
user, msg = check_user_valid(
username=username, email=email,
password=password, public_key=public_key)
else:
user = request.user
msg = None
if user:
token = generate_token(request, user)
return Response({'Token': token, 'Keyword': 'Bearer'}, status=200)
else:
return Response({'error': msg}, status=406)
class UserProfile(generics.RetrieveAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = UserSerializer
def get_object(self):
return self.request.user
class UserOtpAuthApi(APIView):
permission_classes = (AllowAny,)
serializer_class = UserSerializer
def post(self, request):
otp_code = request.data.get('otp_code', '')
seed = request.data.get('seed', '')
user = cache.get(seed, None)
if not user:
return Response({'msg': '请先进行用户名和密码验证'}, status=401)
if not check_otp_code(user.otp_secret_key, otp_code):
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_MFA,
'status': False
}
self.write_login_log(request, data)
return Response({'msg': 'MFA认证失败'}, status=401)
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(request, data)
token = generate_token(request, user)
return Response(
{
'token': token,
'user': self.serializer_class(user).data
}
)
@staticmethod logger = get_logger(__name__)
def write_login_log(request, data):
login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '')
user_agent = request.data.get('HTTP_USER_AGENT', '')
if not login_ip:
login_ip = get_login_ip(request)
tmp_data = {
'ip': login_ip,
'type': login_type,
'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
class UserAuthApi(APIView): class UserAuthApi(APIView):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
serializer_class = UserSerializer serializer_class = UserSerializer
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
key_prefix_block = "_LOGIN_BLOCK_{}"
def post(self, request): def post(self, request):
# limit login # limit login
username = request.data.get('username') username = request.data.get('username')
ip = request.data.get('remote_addr', None) ip = request.data.get('remote_addr', None)
ip = ip if ip else get_login_ip(request) ip = ip or get_request_ip(request)
key_limit = self.key_prefix_limit.format(username, ip)
key_block = self.key_prefix_block.format(username) if is_block_login(username, ip):
if is_block_login(key_limit):
msg = _("Log in frequently and try again later") msg = _("Log in frequently and try again later")
logger.warn(msg + ': ' + username + ':' + ip)
return Response({'msg': msg}, status=401) return Response({'msg': msg}, status=401)
user, msg = self.check_user_valid(request) user, msg = self.check_user_valid(request)
...@@ -244,8 +48,7 @@ class UserAuthApi(APIView): ...@@ -244,8 +48,7 @@ class UserAuthApi(APIView):
'status': False 'status': False
} }
self.write_login_log(request, data) self.write_login_log(request, data)
increase_login_failed_count(username, ip)
set_user_login_failed_count_to_cache(key_limit, key_block)
return Response({'msg': msg}, status=401) return Response({'msg': msg}, status=401)
if not user.otp_enabled: if not user.otp_enabled:
...@@ -256,6 +59,8 @@ class UserAuthApi(APIView): ...@@ -256,6 +59,8 @@ class UserAuthApi(APIView):
'status': True 'status': True
} }
self.write_login_log(request, data) self.write_login_log(request, data)
# 登陆成功,清除原来的缓存计数
clean_failed_count(username, ip)
token = generate_token(request, user) token = generate_token(request, user)
return Response( return Response(
{ {
...@@ -269,7 +74,8 @@ class UserAuthApi(APIView): ...@@ -269,7 +74,8 @@ class UserAuthApi(APIView):
return Response( return Response(
{ {
'code': 101, 'code': 101,
'msg': '请携带seed值,进行MFA二次认证', 'msg': _('Please carry seed value and '
'conduct MFA secondary certification'),
'otp_url': reverse('api-users:user-otp-auth'), 'otp_url': reverse('api-users:user-otp-auth'),
'seed': seed, 'seed': seed,
'user': self.serializer_class(user).data 'user': self.serializer_class(user).data
...@@ -294,7 +100,7 @@ class UserAuthApi(APIView): ...@@ -294,7 +100,7 @@ class UserAuthApi(APIView):
user_agent = request.data.get('HTTP_USER_AGENT', '') user_agent = request.data.get('HTTP_USER_AGENT', '')
if not login_ip: if not login_ip:
login_ip = get_login_ip(request) login_ip = get_request_ip(request)
tmp_data = { tmp_data = {
'ip': login_ip, 'ip': login_ip,
...@@ -345,3 +151,84 @@ class UserConnectionTokenApi(APIView): ...@@ -345,3 +151,84 @@ class UserConnectionTokenApi(APIView):
if self.request.query_params.get('user-only', None): if self.request.query_params.get('user-only', None):
self.permission_classes = (AllowAny,) self.permission_classes = (AllowAny,)
return super().get_permissions() return super().get_permissions()
class UserToken(APIView):
permission_classes = (AllowAny,)
def post(self, request):
if not request.user.is_authenticated:
username = request.data.get('username', '')
email = request.data.get('email', '')
password = request.data.get('password', '')
public_key = request.data.get('public_key', '')
user, msg = check_user_valid(
username=username, email=email,
password=password, public_key=public_key)
else:
user = request.user
msg = None
if user:
token = generate_token(request, user)
return Response({'Token': token, 'Keyword': 'Bearer'}, status=200)
else:
return Response({'error': msg}, status=406)
class UserOtpAuthApi(APIView):
permission_classes = (AllowAny,)
serializer_class = UserSerializer
def post(self, request):
otp_code = request.data.get('otp_code', '')
seed = request.data.get('seed', '')
user = cache.get(seed, None)
if not user:
return Response(
{'msg': _('Please verify the user name and password first')},
status=401
)
if not check_otp_code(user.otp_secret_key, otp_code):
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_MFA,
'status': False
}
self.write_login_log(request, data)
return Response({'msg': _('MFA certification failed')}, status=401)
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(request, data)
token = generate_token(request, user)
return Response(
{
'token': token,
'user': self.serializer_class(user).data
}
)
@staticmethod
def write_login_log(request, data):
login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '')
user_agent = request.data.get('HTTP_USER_AGENT', '')
if not login_ip:
login_ip = get_request_ip(request)
tmp_data = {
'ip': login_ip,
'type': login_type,
'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
# -*- coding: utf-8 -*-
#
from rest_framework import generics
from rest_framework_bulk import BulkModelViewSet
from ..serializers import UserGroupSerializer, \
UserGroupUpdateMemeberSerializer
from ..models import UserGroup
from common.permissions import IsOrgAdmin
from common.mixins import IDInFilterMixin
__all__ = ['UserGroupViewSet', 'UserGroupUpdateUserApi']
class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = UserGroup.objects.all()
serializer_class = UserGroupSerializer
permission_classes = (IsOrgAdmin,)
class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
queryset = UserGroup.objects.all()
serializer_class = UserGroupUpdateMemeberSerializer
permission_classes = (IsOrgAdmin,)
# ~*~ coding: utf-8 ~*~
import uuid
from django.core.cache import cache
from django.contrib.auth import logout
from rest_framework import generics
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework_bulk import BulkModelViewSet
from ..serializers import UserSerializer, UserPKUpdateSerializer, \
UserUpdateGroupSerializer, ChangeUserPasswordSerializer
from ..models import User
from orgs.utils import current_org
from common.permissions import IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser
from common.mixins import IDInFilterMixin
from common.utils import get_logger
logger = get_logger(__name__)
__all__ = [
'UserViewSet', 'UserChangePasswordApi', 'UserUpdateGroupApi',
'UserResetPasswordApi', 'UserResetPKApi', 'UserUpdatePKApi',
'UserUnblockPKApi', 'UserProfileApi', 'UserResetOTPApi',
]
class UserViewSet(IDInFilterMixin, BulkModelViewSet):
queryset = User.objects.exclude(role="App")
serializer_class = UserSerializer
permission_classes = (IsOrgAdmin,)
filter_fields = ('username', 'email', 'name', 'id')
def get_queryset(self):
queryset = super().get_queryset()
org_users = current_org.get_org_users()
queryset = queryset.filter(id__in=org_users)
return queryset
def get_permissions(self):
if self.action == "retrieve":
self.permission_classes = (IsOrgAdminOrAppUser,)
return super().get_permissions()
class UserChangePasswordApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsOrgAdmin,)
queryset = User.objects.all()
serializer_class = ChangeUserPasswordSerializer
def perform_update(self, serializer):
user = self.get_object()
user.password_raw = serializer.validated_data["password"]
user.save()
class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserUpdateGroupSerializer
permission_classes = (IsOrgAdmin,)
class UserResetPasswordApi(generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsAuthenticated,)
def perform_update(self, serializer):
# Note: we are not updating the user object here.
# We just do the reset-password stuff.
from ..utils import send_reset_password_mail
user = self.get_object()
user.password_raw = str(uuid.uuid4())
user.save()
send_reset_password_mail(user)
class UserResetPKApi(generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsAuthenticated,)
def perform_update(self, serializer):
from ..utils import send_reset_ssh_key_mail
user = self.get_object()
user.is_public_key_valid = False
user.save()
send_reset_ssh_key_mail(user)
class UserUpdatePKApi(generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserPKUpdateSerializer
permission_classes = (IsCurrentUserOrReadOnly,)
def perform_update(self, serializer):
user = self.get_object()
user.public_key = serializer.validated_data['_public_key']
user.save()
class UserUnblockPKApi(generics.UpdateAPIView):
queryset = User.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = UserSerializer
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
key_prefix_block = "_LOGIN_BLOCK_{}"
def perform_update(self, serializer):
user = self.get_object()
username = user.username if user else ''
key_limit = self.key_prefix_limit.format(username, '*')
key_block = self.key_prefix_block.format(username)
cache.delete_pattern(key_limit)
cache.delete(key_block)
class UserProfileApi(generics.RetrieveAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = UserSerializer
def get_object(self):
return self.request.user
class UserResetOTPApi(generics.RetrieveAPIView):
queryset = User.objects.all()
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
user = self.get_object() if kwargs.get('pk') else request.user
if user == request.user:
msg = _("Could not reset self otp, use profile reset instead")
return Response({"msg": msg}, status=401)
if user.otp_enabled and user.otp_secret_key:
user.otp_secret_key = ''
user.save()
logout(request)
return Response({"msg": "success"})
...@@ -308,7 +308,7 @@ def user_limit_to(): ...@@ -308,7 +308,7 @@ def user_limit_to():
class UserGroupForm(OrgModelForm): class UserGroupForm(OrgModelForm):
users = forms.ModelMultipleChoiceField( users = forms.ModelMultipleChoiceField(
queryset=User.objects.exclude(role=User.ROLE_APP), queryset=User.objects.all(),
label=_("User"), label=_("User"),
widget=forms.SelectMultiple( widget=forms.SelectMultiple(
attrs={ attrs={
...@@ -349,12 +349,5 @@ class UserGroupForm(OrgModelForm): ...@@ -349,12 +349,5 @@ class UserGroupForm(OrgModelForm):
} }
class OrgUserField(forms.ModelMultipleChoiceField):
def get_limit_choices_to(self):
return {"orgs"}
class FileForm(forms.Form): class FileForm(forms.Form):
file = forms.FileField() file = forms.FileField()
...@@ -14,7 +14,7 @@ from django.utils import timezone ...@@ -14,7 +14,7 @@ from django.utils import timezone
from django.shortcuts import reverse from django.shortcuts import reverse
from common.utils import get_signer, date_expired_default from common.utils import get_signer, date_expired_default
from common.models import Setting from common.models import common_settings
from orgs.mixins import OrgManager from orgs.mixins import OrgManager
from orgs.utils import current_org from orgs.utils import current_org
...@@ -112,6 +112,10 @@ class User(AbstractUser): ...@@ -112,6 +112,10 @@ class User(AbstractUser):
def password_raw(self, password_raw_): def password_raw(self, password_raw_):
self.set_password(password_raw_) self.set_password(password_raw_)
def set_password(self, raw_password):
self._set_password = True
return super().set_password(raw_password)
@property @property
def otp_secret_key(self): def otp_secret_key(self):
return signer.unsign(self._otp_secret_key) return signer.unsign(self._otp_secret_key)
...@@ -278,8 +282,7 @@ class User(AbstractUser): ...@@ -278,8 +282,7 @@ class User(AbstractUser):
@property @property
def otp_force_enabled(self): def otp_force_enabled(self):
mfa_setting = Setting.objects.filter(name='SECURITY_MFA_AUTH').first() if common_settings.SECURITY_MFA_AUTH:
if mfa_setting and mfa_setting.cleaned_value:
return True return True
return self.otp_level == 2 return self.otp_level == 2
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
<link rel="stylesheet" href="{% static 'css/otp.css' %}" /> <link rel="stylesheet" href="{% static 'css/otp.css' %}" />
<script src="{% static 'js/jquery-2.1.1.js' %}"></script> <script src="{% static 'js/jquery-2.1.1.js' %}"></script>
<script src="{% static "js/plugins/qrcode/qrcode.min.js" %}"></script> <script src="{% static "js/plugins/qrcode/qrcode.min.js" %}"></script>
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
</head> </head>
<body> <body>
...@@ -23,9 +24,9 @@ ...@@ -23,9 +24,9 @@
<a href="{% url 'index' %}">Jumpserver</a> <a href="{% url 'index' %}">Jumpserver</a>
</div> </div>
<div> <div>
<a href="{% url 'index' %}">首页</a> <a href="{% url 'index' %}">{% trans 'Home page' %}</a>
<b></b> <b></b>
<a href="http://docs.jumpserver.org/zh/docs/">文档</a> <a href="http://docs.jumpserver.org/zh/docs/">{% trans 'Docs' %}</a>
<b></b> <b></b>
<a href="https://www.github.com/jumpserver/">GitHub</a> <a href="https://www.github.com/jumpserver/">GitHub</a>
</div> </div>
...@@ -33,39 +34,14 @@ ...@@ -33,39 +34,14 @@
<!--内容--> <!--内容-->
<article> <article>
<div class="clearfix"> <div class="" style="text-align: center; margin-bottom: 50px">
<ul class="change-color"> <h2>
<li> {% block small_title %}
<div> {% endblock %}
<i class="iconfont icon-step active"></i> </h2>
<span></span>
</div>
<div class="back">验证身份</div>
</li>
<li>
<div>
<i class="iconfont icon-step2"></i>
<span></span>
</div>
<div class="back">安装应用</div>
</li>
<li>
<div>
<i class="iconfont icon-step1"></i>
<span></span>
</div>
<div class="back">绑定MFA</div>
</li>
<li>
<div>
<i class="iconfont icon-duigou"></i>
</div>
<div>完成</div>
</li>
</ul>
</div> </div>
<div > <div >
<div class="verify">安全令牌验证&nbsp;&nbsp;账户&nbsp;<span>{{ user.username }}</span>&nbsp;&nbsp;请按照以下步骤完成绑定操作</div> <div class="verify">{% trans 'Security token validation' %}&nbsp;&nbsp;{% trans 'Account' %}&nbsp;<span>{{ user.username }}</span>&nbsp;&nbsp;{% trans 'Follow these steps to complete the binding operation' %}</div>
<div class="line"></div> <div class="line"></div>
{% block content %} {% block content %}
......
...@@ -8,10 +8,11 @@ ...@@ -8,10 +8,11 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon"> <link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon">
<title>忘记密码</title> <title>{% trans 'Forgot password' %}</title>
{% include '_head_css_js.html' %} {% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet"> <link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script> <script src="{% static "js/jumpserver.js" %}"></script>
</head> </head>
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
<link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon"> <link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon">
{% include '_head_css_js.html' %} {% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet"> <link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script> <script src="{% static "js/jumpserver.js" %}"></script>
<style> <style>
.captcha { .captcha {
...@@ -22,18 +23,19 @@ ...@@ -22,18 +23,19 @@
<div class="loginColumns animated fadeInDown"> <div class="loginColumns animated fadeInDown">
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<h2 class="font-bold">欢迎使用Jumpserver开源堡垒机</h2>
<h2 class="font-bold">{% trans 'Welcome to the Jumpserver open source fortress' %}</h2>
<p> <p>
全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。 {% trans "The world's first fully open source fortress, using the GNU GPL v2.0 open source protocol, is a professional operation and maintenance audit system in compliance with 4A." %}
</p> </p>
<p> <p>
使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。 {% trans "Developed using Python/Django, following the Web 2.0 specification and equipped with industry-leading Web Terminal solutions, with beautiful interactive interface and good user experience." %}
</p> </p>
<p> <p>
采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发访问限制。 {% trans 'Distributed architecture is adopted to support multi-machine room deployment across regions, central node provides API, and each machine room deploys login node, which can be extended horizontally and without concurrent access restrictions.' %}
</p> </p>
<p> <p>
改变世界,从一点点开始。 {% trans "Changes the world, starting with a little bit." %}
</p> </p>
</div> </div>
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
<link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon"> <link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon">
{% include '_head_css_js.html' %} {% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet"> <link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script> <script src="{% static "js/jumpserver.js" %}"></script>
<script src="{% static "js/plugins/qrcode/qrcode.min.js" %}"></script> <script src="{% static "js/plugins/qrcode/qrcode.min.js" %}"></script>
<style> <style>
...@@ -23,18 +24,18 @@ ...@@ -23,18 +24,18 @@
<div class="loginColumns animated fadeInDown"> <div class="loginColumns animated fadeInDown">
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<h2 class="font-bold">欢迎使用Jumpserver开源堡垒机</h2> <h2 class="font-bold">{% trans 'Welcome to the Jumpserver open source fortress' %}</h2>
<p> <p>
全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。 {% trans "The world's first fully open source fortress, using the GNU GPL v2.0 open source protocol, is a professional operation and maintenance audit system in compliance with 4A." %}
</p> </p>
<p> <p>
使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。 {% trans "Developed using Python/Django, following the Web 2.0 specification and equipped with industry-leading Web Terminal solutions, with beautiful interactive interface and good user experience." %}
</p> </p>
<p> <p>
采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发访问限制。 {% trans 'Distributed architecture is adopted to support multi-machine room deployment across regions, central node provides API, and each machine room deploys login node, which can be extended horizontally and without concurrent access restrictions.' %}
</p> </p>
<p> <p>
改变世界,从一点点开始。 {% trans "Changes the world, starting with a little bit." %}
</p> </p>
</div> </div>
...@@ -47,11 +48,11 @@ ...@@ -47,11 +48,11 @@
<div class="m-t"> <div class="m-t">
<div class="form-group"> <div class="form-group">
<p style="margin:30px auto;" class="text-center"><strong style="color:#000000">账号保护已开启,请根据提示完成以下操作</strong></p> <p style="margin:30px auto;" class="text-center"><strong style="color:#000000">{% trans 'The account protection has been opened, please complete the following operations according to the prompts' %}</strong></p>
<div class="text-center"> <div class="text-center">
<img src="{% static 'img/otp_auth.png' %}" alt="" width="72px" height="117"> <img src="{% static 'img/otp_auth.png' %}" alt="" width="72px" height="117">
</div> </div>
<p style="margin: 30px auto">&nbsp;请打开手机Google Authenticator应用,输入6位动态码</p> <p style="margin: 30px auto">&nbsp;{% trans 'Open Authenticator and enter the 6-bit dynamic code' %}</p>
</div> </div>
<form class="m-t" role="form" method="post" action=""> <form class="m-t" role="form" method="post" action="">
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
<link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon"> <link rel="shortcut icon" href="{% static "img/facio.ico" %}" type="image/x-icon">
{% include '_head_css_js.html' %} {% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet"> <link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script> <script src="{% static "js/jumpserver.js" %}"></script>
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script> <script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
...@@ -21,23 +22,22 @@ ...@@ -21,23 +22,22 @@
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<h2 class="font-bold">{% trans 'Welcome to the Jumpserver open source fortress' %}</h2>
<h2 class="font-bold">欢迎使用Jumpserver开源跳板机</h2>
<p> <p>
Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互联网企业高效 用户、资产、权限、审计 管理 {% trans 'Jumpserver is an open source desktop system developed using Python and Django that helps Internet businesses with efficient users, assets, permissions, and audit management' %}
</p> </p>
<p> <p>
我们自五湖四海,我们对开源精神无比敬仰和崇拜,我们对完美、整洁、优雅 无止境的追求 {% trans 'We are from all over the world, we have great admiration and worship for the spirit of open source, we have endless pursuit for perfection, neatness and elegance' %}
</p> </p>
<p> <p>
专注自动化运维,努力打造 易用、稳定、安全、自动化 的跳板机, 这是我们的不懈的追求和动力 {% trans 'We focus on automatic operation and maintenance, and strive to build an easy-to-use, stable, safe and automatic board hopping machine, which is our unremitting pursuit and power' %}
</p> </p>
<p> <p>
<small>永远年轻,永远热泪盈眶 stay foolish stay hungry</small> <small>{% trans 'Always young, always with tears in my eyes. Stay foolish Stay hungry' %}</small>
</p> </p>
</div> </div>
......
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
{% block form %} {% block form %}
<div class="ydxbd" id="formlists" style="display: block;"> <div class="ydxbd" id="formlists" style="display: block;">
<p id="tags_p" class="mgl-5 c02">选择需要修改属性</p> <p id="tags_p" class="mgl-5 c02">{% trans 'Select properties that need to be modified' %}</p>
<div class="tagBtnList"> <div class="tagBtnList">
<a class="label label-primary" id="change_all" value="1">全选</a> <a class="label label-primary" id="change_all" value="1">{% trans 'Select all' %}</a>
{% for field in form %} {% for field in form %}
{% if field.name != 'users' %} {% if field.name != 'users' %}
<a data-id="{{ field.id_for_label }}" class="label label-default label-primary field-tag" value="1">{{ field.label }}</a> <a data-id="{{ field.id_for_label }}" class="label label-default label-primary field-tag" value="1">{{ field.label }}</a>
......
...@@ -149,7 +149,6 @@ ...@@ -149,7 +149,6 @@
</div> </div>
</div> </div>
</span></td> </span></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Force enabled MFA' %}:</td> <td>{% trans 'Force enabled MFA' %}:</td>
...@@ -166,6 +165,14 @@ ...@@ -166,6 +165,14 @@
</div> </div>
</span></td> </span></td>
</tr> </tr>
<tr>
<td>{% trans 'Reset MFA' %}:</td>
<td>
<span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" id="btn-reset-mfa" style="width: 54px">{% trans 'Reset' %}</button>
</span>
</td>
</tr>
<tr> <tr>
<td>{% trans 'Send reset password mail' %}:</td> <td>{% trans 'Send reset password mail' %}:</td>
<td> <td>
...@@ -370,6 +377,7 @@ $(document).ready(function() { ...@@ -370,6 +377,7 @@ $(document).ready(function() {
text: "{% trans "This will reset the user password and send a reset mail"%}", text: "{% trans "This will reset the user password and send a reset mail"%}",
type: "warning", type: "warning",
showCancelButton: true, showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#DD6B55", confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}", confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false closeOnConfirm: false
...@@ -395,6 +403,7 @@ $(document).ready(function() { ...@@ -395,6 +403,7 @@ $(document).ready(function() {
text: "{% trans 'This will reset the user public key and send a reset mail' %}", text: "{% trans 'This will reset the user public key and send a reset mail' %}",
type: "warning", type: "warning",
showCancelButton: true, showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#DD6B55", confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}", confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false closeOnConfirm: false
...@@ -462,12 +471,19 @@ $(document).ready(function() { ...@@ -462,12 +471,19 @@ $(document).ready(function() {
text: "{% trans "After unlocking the user, the user can log in normally."%}", text: "{% trans "After unlocking the user, the user can log in normally."%}",
type: "warning", type: "warning",
showCancelButton: true, showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#DD6B55", confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}", confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false closeOnConfirm: false
}, function() { }, function() {
doReset(); doReset();
}); });
}).on('click', '#btn-reset-mfa', function () {
APIUpdateAttr({
url: "{% url 'api-users:user-reset-otp' pk=user_object.id %}",
method: "GET",
success_message: "{% trans 'Reset user MFA success' %}"
})
}) })
</script> </script>
{% endblock %} {% endblock %}
...@@ -82,6 +82,7 @@ $(document).ready(function() { ...@@ -82,6 +82,7 @@ $(document).ready(function() {
text: "{% trans 'This will delete the selected groups !!!' %}", text: "{% trans 'This will delete the selected groups !!!' %}",
type: "warning", type: "warning",
showCancelButton: true, showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#DD6B55", confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}", confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false closeOnConfirm: false
......
...@@ -196,6 +196,7 @@ $(document).ready(function(){ ...@@ -196,6 +196,7 @@ $(document).ready(function(){
text: "{% trans 'This will delete the selected users !!!' %}", text: "{% trans 'This will delete the selected users !!!' %}",
type: "warning", type: "warning",
showCancelButton: true, showCancelButton: true,
cancelButtonText: "{% trans 'Cancel' %}",
confirmButtonColor: "#DD6B55", confirmButtonColor: "#DD6B55",
confirmButtonText: "{% trans 'Confirm' %}", confirmButtonText: "{% trans 'Confirm' %}",
closeOnConfirm: false closeOnConfirm: false
......
...@@ -2,11 +2,15 @@ ...@@ -2,11 +2,15 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% block small_title %}
{% trans 'Authenticate' %}
{% endblock %}
{% block content %} {% block content %}
<div class="verify"> <div class="verify">
<p style="margin: 20px auto;"><strong style="color: #000000">账号保护已开启,请根据提示完成以下操作</strong></p> <p style="margin: 20px auto;"><strong style="color: #000000">{% trans 'The account protection has been opened, please complete the following operations according to the prompts' %}</strong></p>
<img src="{% static 'img/otp_auth.png' %}" alt="" width="72px" height="117"> <img src="{% static 'img/otp_auth.png' %}" alt="" width="72px" height="117">
<p style="margin: 20px auto;">请在手机中打开Google Authenticator应用,输入6位动态码</p> <p style="margin: 20px auto;">{% trans 'Open Authenticator and enter the 6-bit dynamic code' %}</p>
</div> </div>
<form class="" role="form" method="post" action=""> <form class="" role="form" method="post" action="">
...@@ -22,14 +26,11 @@ ...@@ -22,14 +26,11 @@
<button type="submit" class="next">{% trans 'Next' %}</button> <button type="submit" class="next">{% trans 'Next' %}</button>
</form> </form>
<script> <script>
$(function(){ $(function(){
$('.change-color li').eq(2).remove(); $('.change-color li').eq(2).remove();
$('.change-color li:eq(1) div').eq(1).html('解绑MFA') $('.change-color li:eq(1) div').eq(1).html("{% trans 'Unbind' %}")
}) })
</script> </script>
{% endblock %} {% endblock %}
......
...@@ -2,10 +2,14 @@ ...@@ -2,10 +2,14 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% block small_title %}
{% trans 'Bind' %}
{% endblock %}
{% block content %} {% block content %}
<div class="verify"> <div class="verify">
<p style="margin:20px auto;"><strong style="color: #000000">使用手机 Google Authenticator 应用扫描以下二维码,获取6位验证码</strong></p> <p style="margin:20px auto;"><strong style="color: #000000">{% trans 'Use the mobile Google Authenticator application to scan the following qr code for a 6-bit verification code' %}</strong></p>
<div id="qr_code"></div> <div id="qr_code"></div>
......
...@@ -2,21 +2,25 @@ ...@@ -2,21 +2,25 @@
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% block small_title %}
{% trans 'Install' %}
{% endblock %}
{% block content %} {% block content %}
<div class="verify"> <div class="verify">
<p style="margin: 20px auto;"><strong style="color: #000000">请在手机端下载并安装 Google Authenticator 应用</strong></p> <p style="margin: 20px auto;"><strong style="color: #000000">{% trans 'Download and install the Google Authenticator application on your phone' %}</strong></p>
<div> <div>
<img src="{% static 'img/authenticator_android.png' %}" width="128" height="128" alt=""> <img src="{% static 'img/authenticator_android.png' %}" width="128" height="128" alt="">
<p>Android手机下载</p> <p>{% trans 'Android downloads' %}</p>
</div> </div>
<div> <div>
<img src="{% static 'img/authenticator_iphone.png' %}" width="128" height="128" alt=""> <img src="{% static 'img/authenticator_iphone.png' %}" width="128" height="128" alt="">
<p>iPhone手机下载</p> <p>{% trans 'iPhone downloads' %}</p>
</div> </div>
<p style="margin: 20px auto;"></p> <p style="margin: 20px auto;"></p>
<p style="margin: 20px auto;"><strong style="color: #000000">安装完成后点击下一步进入绑定页面(如已安装,直接进入下一步)</strong></p> <p style="margin: 20px auto;"><strong style="color: #000000">{% trans 'After installation, click the next step to enter the binding page (if installed, go to the next step directly).' %}</strong></p>
</div> </div>
<a href="{% url 'users:user-otp-enable-bind' %}" class="next">{% trans 'Next' %}</a> <a href="{% url 'users:user-otp-enable-bind' %}" class="next">{% trans 'Next' %}</a>
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% block small_title %}
{% trans 'Authenticate' %}
{% endblock %}
{% block content %} {% block content %}
<form class="" role="form" method="post" action=""> <form class="" role="form" method="post" action="">
{% csrf_token %} {% csrf_token %}
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script> <script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script> <script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
<script src="{% static "js/jumpserver.js" %}"></script> <script src="{% static "js/jumpserver.js" %}"></script>
<style> <style>
......
...@@ -153,7 +153,7 @@ ...@@ -153,7 +153,7 @@
</td> </td>
</tr> </tr>
<tr class="no-borders-tr"> <tr class="no-borders-tr">
<td>{% trans 'Update MFA settings' %}:</td> <td>{% trans 'Set MFA' %}:</td>
<td> <td>
<span class="pull-right"> <span class="pull-right">
<a type="button" class="btn btn-primary btn-xs" style="width: 54px" id="" <a type="button" class="btn btn-primary btn-xs" style="width: 54px" id=""
...@@ -173,6 +173,16 @@ ...@@ -173,6 +173,16 @@
</span> </span>
</td> </td>
</tr> </tr>
{% if request.user.otp_enabled and request.user.otp_secret_key %}
<tr>
<td>{% trans 'Update MFA' %}:</td>
<td>
<span class="pull-right">
<a type="button" class="btn btn-primary btn-xs" style="width: 54px" href="{% url 'users:user-otp-update' %}">{% trans 'Update' %}</a>
</span>
</td>
</tr>
{% endif %}
<tr> <tr>
<td>{% trans 'Update SSH public key' %}:</td> <td>{% trans 'Update SSH public key' %}:</td>
<td> <td>
......
...@@ -18,10 +18,12 @@ urlpatterns = [ ...@@ -18,10 +18,12 @@ urlpatterns = [
# path(r'', api.UserListView.as_view()), # path(r'', api.UserListView.as_view()),
path('token/', api.UserToken.as_view(), name='user-token'), path('token/', api.UserToken.as_view(), name='user-token'),
path('connection-token/', api.UserConnectionTokenApi.as_view(), name='connection-token'), path('connection-token/', api.UserConnectionTokenApi.as_view(), name='connection-token'),
path('profile/', api.UserProfile.as_view(), name='user-profile'), path('profile/', api.UserProfileApi.as_view(), name='user-profile'),
path('auth/', api.UserAuthApi.as_view(), name='user-auth'), path('auth/', api.UserAuthApi.as_view(), name='user-auth'),
path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'), path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
path('users/<uuid:pk>/password/', api.ChangeUserPasswordApi.as_view(), name='change-user-password'), path('otp/reset/', api.UserResetOTPApi.as_view(), name='my-otp-reset'),
path('users/<uuid:pk>/otp/reset/', api.UserResetOTPApi.as_view(), name='user-reset-otp'),
path('users/<uuid:pk>/password/', api.UserChangePasswordApi.as_view(), name='change-user-password'),
path('users/<uuid:pk>/password/reset/', api.UserResetPasswordApi.as_view(), name='user-reset-password'), path('users/<uuid:pk>/password/reset/', api.UserResetPasswordApi.as_view(), name='user-reset-password'),
path('users/<uuid:pk>/pubkey/reset/', api.UserResetPKApi.as_view(), name='user-public-key-reset'), path('users/<uuid:pk>/pubkey/reset/', api.UserResetPKApi.as_view(), name='user-public-key-reset'),
path('users/<uuid:pk>/pubkey/update/', api.UserUpdatePKApi.as_view(), name='user-public-key-update'), path('users/<uuid:pk>/pubkey/update/', api.UserUpdatePKApi.as_view(), name='user-public-key-update'),
......
...@@ -26,6 +26,7 @@ urlpatterns = [ ...@@ -26,6 +26,7 @@ urlpatterns = [
path('profile/otp/enable/install-app/', views.UserOtpEnableInstallAppView.as_view(), name='user-otp-enable-install-app'), path('profile/otp/enable/install-app/', views.UserOtpEnableInstallAppView.as_view(), name='user-otp-enable-install-app'),
path('profile/otp/enable/bind/', views.UserOtpEnableBindView.as_view(), name='user-otp-enable-bind'), path('profile/otp/enable/bind/', views.UserOtpEnableBindView.as_view(), name='user-otp-enable-bind'),
path('profile/otp/disable/authentication/', views.UserOtpDisableAuthenticationView.as_view(), name='user-otp-disable-authentication'), path('profile/otp/disable/authentication/', views.UserOtpDisableAuthenticationView.as_view(), name='user-otp-disable-authentication'),
path('profile/otp/update/', views.UserOtpUpdateView.as_view(), name='user-otp-update'),
path('profile/otp/settings-success/', views.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'), path('profile/otp/settings-success/', views.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'),
# User view # User view
...@@ -48,5 +49,6 @@ urlpatterns = [ ...@@ -48,5 +49,6 @@ urlpatterns = [
path('user-group/<uuid:pk>/assets/', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'), path('user-group/<uuid:pk>/assets/', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'),
# Login log # Login log
# Abandon
path('login-log/', views.LoginLogListView.as_view(), name='login-log-list'), path('login-log/', views.LoginLogListView.as_view(), name='login-log-list'),
] ]
...@@ -19,7 +19,7 @@ from django.core.cache import cache ...@@ -19,7 +19,7 @@ from django.core.cache import cache
from common.tasks import send_mail_async from common.tasks import send_mail_async
from common.utils import reverse, get_object_or_none from common.utils import reverse, get_object_or_none
from common.models import Setting from common.models import common_settings, Setting
from common.forms import SecuritySettingForm from common.forms import SecuritySettingForm
from .models import User, LoginLog from .models import User, LoginLog
...@@ -190,16 +190,6 @@ def validate_ip(ip): ...@@ -190,16 +190,6 @@ def validate_ip(ip):
return False return False
def get_login_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
if x_forwarded_for and x_forwarded_for[0]:
login_ip = x_forwarded_for[0]
else:
login_ip = request.META.get('REMOTE_ADDR', '')
return login_ip
def write_login_log(*args, **kwargs): def write_login_log(*args, **kwargs):
ip = kwargs.get('ip', '') ip = kwargs.get('ip', '')
if not (ip and validate_ip(ip)): if not (ip and validate_ip(ip)):
...@@ -225,7 +215,12 @@ def get_ip_city(ip, timeout=10): ...@@ -225,7 +215,12 @@ def get_ip_city(ip, timeout=10):
try: try:
data = r.json() data = r.json()
if not isinstance(data, int) and data['code'] == 0: if not isinstance(data, int) and data['code'] == 0:
city = data['data']['country'] + ' ' + data['data']['city'] country = data['data']['country']
_city = data['data']['city']
if country == 'XX':
city = _city
else:
city = ' '.join([country, _city])
except ValueError: except ValueError:
pass pass
return city return city
...@@ -272,6 +267,8 @@ def generate_otp_uri(request, issuer="Jumpserver"): ...@@ -272,6 +267,8 @@ def generate_otp_uri(request, issuer="Jumpserver"):
def check_otp_code(otp_secret_key, otp_code): def check_otp_code(otp_secret_key, otp_code):
if not otp_secret_key or not otp_code:
return False
totp = pyotp.TOTP(otp_secret_key) totp = pyotp.TOTP(otp_secret_key)
return totp.verify(otp_code) return totp.verify(otp_code)
...@@ -310,8 +307,8 @@ def check_password_rules(password): ...@@ -310,8 +307,8 @@ def check_password_rules(password):
lower_field_name = 'SECURITY_PASSWORD_LOWER_CASE' lower_field_name = 'SECURITY_PASSWORD_LOWER_CASE'
number_field_name = 'SECURITY_PASSWORD_NUMBER' number_field_name = 'SECURITY_PASSWORD_NUMBER'
special_field_name = 'SECURITY_PASSWORD_SPECIAL_CHAR' special_field_name = 'SECURITY_PASSWORD_SPECIAL_CHAR'
min_length_setting = Setting.objects.filter(name=min_field_name).first() min_length = getattr(common_settings, min_field_name) or \
min_length = min_length_setting.value if min_length_setting else settings.DEFAULT_PASSWORD_MIN_LENGTH settings.DEFAULT_PASSWORD_MIN_LENGTH
password_setting = Setting.objects.filter(name__startswith='SECURITY_PASSWORD') password_setting = Setting.objects.filter(name__startswith='SECURITY_PASSWORD')
if not password_setting: if not password_setting:
...@@ -333,37 +330,40 @@ def check_password_rules(password): ...@@ -333,37 +330,40 @@ def check_password_rules(password):
return bool(match_obj) return bool(match_obj)
def set_user_login_failed_count_to_cache(key_limit, key_block): key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
key_prefix_block = "_LOGIN_BLOCK_{}"
# def increase_login_failed_count(key_limit, key_block):
def increase_login_failed_count(username, ip):
key_limit = key_prefix_limit.format(username, ip)
count = cache.get(key_limit) count = cache.get(key_limit)
count = count + 1 if count else 1 count = count + 1 if count else 1
setting_limit_time = Setting.objects.filter( limit_time = common_settings.SECURITY_LOGIN_LIMIT_TIME or \
name='SECURITY_LOGIN_LIMIT_TIME' settings.DEFAULT_LOGIN_LIMIT_TIME
).first() cache.set(key_limit, count, int(limit_time)*60)
limit_time = setting_limit_time.cleaned_value if setting_limit_time \
else settings.DEFAULT_LOGIN_LIMIT_TIME
setting_limit_count = Setting.objects.filter(
name='SECURITY_LOGIN_LIMIT_COUNT'
).first()
limit_count = setting_limit_count.cleaned_value if setting_limit_count \
else settings.DEFAULT_LOGIN_LIMIT_COUNT
if count >= limit_count:
cache.set(key_block, 1, int(limit_time)*60)
cache.set(key_limit, count, int(limit_time)*60) def clean_failed_count(username, ip):
key_limit = key_prefix_limit.format(username, ip)
key_block = key_prefix_block.format(username)
cache.delete(key_limit)
cache.delete(key_block)
def is_block_login(key_limit): def is_block_login(username, ip):
count = cache.get(key_limit) key_limit = key_prefix_limit.format(username, ip)
key_block = key_prefix_block.format(username)
count = cache.get(key_limit, 0)
setting_limit_count = Setting.objects.filter( limit_count = common_settings.SECURITY_LOGIN_LIMIT_COUNT or \
name='SECURITY_LOGIN_LIMIT_COUNT' settings.DEFAULT_LOGIN_LIMIT_COUNT
).first() limit_time = common_settings.SECURITY_LOGIN_LIMIT_TIME or \
limit_count = setting_limit_count.cleaned_value if setting_limit_count \ settings.DEFAULT_LOGIN_LIMIT_TIME
else settings.DEFAULT_LOGIN_LIMIT_COUNT
if count >= limit_count:
cache.set(key_block, 1, int(limit_time)*60)
if count and count >= limit_count: if count and count >= limit_count:
return True return True
......
...@@ -11,6 +11,7 @@ from django.contrib.messages.views import SuccessMessageMixin ...@@ -11,6 +11,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from common.utils import get_logger from common.utils import get_logger
from common.const import create_success_msg, update_success_msg from common.const import create_success_msg, update_success_msg
from common.permissions import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
from orgs.utils import current_org
from ..models import User, UserGroup from ..models import User, UserGroup
from .. import forms from .. import forms
...@@ -55,13 +56,10 @@ class UserGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateVie ...@@ -55,13 +56,10 @@ class UserGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateVie
success_message = update_success_msg success_message = update_success_msg
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
users = User.objects.all()
group_users = [user.id for user in self.object.users.all()]
context = { context = {
'app': _('Users'), 'app': _('Users'),
'action': _('Update user group'), 'action': _('Update user group'),
'users': users,
'group_users': group_users
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -73,7 +71,7 @@ class UserGroupDetailView(AdminUserRequiredMixin, DetailView): ...@@ -73,7 +71,7 @@ class UserGroupDetailView(AdminUserRequiredMixin, DetailView):
template_name = 'users/user_group_detail.html' template_name = 'users/user_group_detail.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
users = User.objects.exclude(id__in=self.object.users.all()).exclude(role=User.ROLE_APP) users = current_org.get_org_users().exclude(id__in=self.object.users.all())
context = { context = {
'app': _('Users'), 'app': _('Users'),
'action': _('User group detail'), 'action': _('User group detail'),
......
...@@ -8,7 +8,6 @@ from django.contrib.auth import login as auth_login, logout as auth_logout ...@@ -8,7 +8,6 @@ from django.contrib.auth import login as auth_login, logout as auth_logout
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView from django.views.generic import ListView
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.db.models import Q
from django.http import HttpResponseRedirect, HttpResponse from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import reverse, redirect from django.shortcuts import reverse, redirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
...@@ -21,15 +20,12 @@ from django.views.generic.edit import FormView ...@@ -21,15 +20,12 @@ from django.views.generic.edit import FormView
from formtools.wizard.views import SessionWizardView from formtools.wizard.views import SessionWizardView
from django.conf import settings from django.conf import settings
from common.utils import get_object_or_none from common.utils import get_object_or_none, get_request_ip
from common.mixins import DatetimeSearchMixin
from common.permissions import AdminUserRequiredMixin
from orgs.utils import current_org
from ..models import User, LoginLog from ..models import User, LoginLog
from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, \ from ..utils import send_reset_password_mail, check_otp_code, \
redirect_user_first_login_or_index, get_user_or_tmp_user, \ redirect_user_first_login_or_index, get_user_or_tmp_user, \
set_tmp_user_to_cache, get_password_check_rules, check_password_rules, \ set_tmp_user_to_cache, get_password_check_rules, check_password_rules, \
is_block_login, set_user_login_failed_count_to_cache is_block_login, increase_login_failed_count, clean_failed_count
from ..tasks import write_login_log_async from ..tasks import write_login_log_async
from .. import forms from .. import forms
...@@ -51,8 +47,6 @@ class UserLoginView(FormView): ...@@ -51,8 +47,6 @@ class UserLoginView(FormView):
form_class_captcha = forms.UserLoginCaptchaForm form_class_captcha = forms.UserLoginCaptchaForm
redirect_field_name = 'next' redirect_field_name = 'next'
key_prefix_captcha = "_LOGIN_INVALID_{}" key_prefix_captcha = "_LOGIN_INVALID_{}"
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
key_prefix_block = "_LOGIN_BLOCK_{}"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if request.user.is_staff: if request.user.is_staff:
...@@ -64,12 +58,10 @@ class UserLoginView(FormView): ...@@ -64,12 +58,10 @@ class UserLoginView(FormView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
# limit login authentication # limit login authentication
ip = get_login_ip(request) ip = get_request_ip(request)
username = self.request.POST.get('username') username = self.request.POST.get('username')
key_limit = self.key_prefix_limit.format(username, ip) if is_block_login(username, ip):
if is_block_login(key_limit):
return self.render_to_response(self.get_context_data(block_login=True)) return self.render_to_response(self.get_context_data(block_login=True))
return super().post(request, *args, **kwargs) return super().post(request, *args, **kwargs)
def form_valid(self, form): def form_valid(self, form):
...@@ -77,6 +69,10 @@ class UserLoginView(FormView): ...@@ -77,6 +69,10 @@ class UserLoginView(FormView):
return HttpResponse(_("Please enable cookies and try again.")) return HttpResponse(_("Please enable cookies and try again."))
set_tmp_user_to_cache(self.request, form.get_user()) set_tmp_user_to_cache(self.request, form.get_user())
username = form.cleaned_data.get('username')
ip = get_request_ip(self.request)
# 登陆成功,清除缓存计数
clean_failed_count(username, ip)
return redirect(self.get_success_url()) return redirect(self.get_success_url())
def form_invalid(self, form): def form_invalid(self, form):
...@@ -91,10 +87,8 @@ class UserLoginView(FormView): ...@@ -91,10 +87,8 @@ class UserLoginView(FormView):
self.write_login_log(data) self.write_login_log(data)
# limit user login failed count # limit user login failed count
ip = get_login_ip(self.request) ip = get_request_ip(self.request)
key_limit = self.key_prefix_limit.format(username, ip) increase_login_failed_count(username, ip)
key_block = self.key_prefix_block.format(username)
set_user_login_failed_count_to_cache(key_limit, key_block)
# show captcha # show captcha
cache.set(self.key_prefix_captcha.format(ip), 1, 3600) cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
...@@ -104,7 +98,7 @@ class UserLoginView(FormView): ...@@ -104,7 +98,7 @@ class UserLoginView(FormView):
return super().form_invalid(form) return super().form_invalid(form)
def get_form_class(self): def get_form_class(self):
ip = get_login_ip(self.request) ip = get_request_ip(self.request)
if cache.get(self.key_prefix_captcha.format(ip)): if cache.get(self.key_prefix_captcha.format(ip)):
return self.form_class_captcha return self.form_class_captcha
else: else:
...@@ -139,7 +133,7 @@ class UserLoginView(FormView): ...@@ -139,7 +133,7 @@ class UserLoginView(FormView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def write_login_log(self, data): def write_login_log(self, data):
login_ip = get_login_ip(self.request) login_ip = get_request_ip(self.request)
user_agent = self.request.META.get('HTTP_USER_AGENT', '') user_agent = self.request.META.get('HTTP_USER_AGENT', '')
tmp_data = { tmp_data = {
'ip': login_ip, 'ip': login_ip,
...@@ -178,14 +172,14 @@ class UserLoginOtpView(FormView): ...@@ -178,14 +172,14 @@ class UserLoginOtpView(FormView):
'status': False 'status': False
} }
self.write_login_log(data) self.write_login_log(data)
form.add_error('otp_code', _('MFA code invalid')) form.add_error('otp_code', _('MFA code invalid, or ntp sync server time'))
return super().form_invalid(form) return super().form_invalid(form)
def get_success_url(self): def get_success_url(self):
return redirect_user_first_login_or_index(self.request, self.redirect_field_name) return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
def write_login_log(self, data): def write_login_log(self, data):
login_ip = get_login_ip(self.request) login_ip = get_request_ip(self.request)
user_agent = self.request.META.get('HTTP_USER_AGENT', '') user_agent = self.request.META.get('HTTP_USER_AGENT', '')
tmp_data = { tmp_data = {
'ip': login_ip, 'ip': login_ip,
...@@ -361,46 +355,6 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView): ...@@ -361,46 +355,6 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
return form return form
class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): class LoginLogListView(ListView):
template_name = 'users/login_log_list.html' def get(self, request, *args, **kwargs):
model = LoginLog return redirect(reverse('audits:login-log-list'))
paginate_by = settings.DISPLAY_PER_PAGE
user = keyword = ""
date_to = date_from = None
@staticmethod
def get_org_users():
users = current_org.get_org_users().values_list('username', flat=True)
return users
def get_queryset(self):
users = self.get_org_users()
queryset = super().get_queryset().filter(username__in=users)
self.user = self.request.GET.get('user', '')
self.keyword = self.request.GET.get("keyword", '')
queryset = queryset.filter(
datetime__gt=self.date_from, datetime__lt=self.date_to
)
if self.user:
queryset = queryset.filter(username=self.user)
if self.keyword:
queryset = queryset.filter(
Q(ip__contains=self.keyword) |
Q(city__contains=self.keyword) |
Q(username__contains=self.keyword)
)
return queryset
def get_context_data(self, **kwargs):
context = {
'app': _('Users'),
'action': _('Login log list'),
'date_from': self.date_from,
'date_to': self.date_to,
'user': self.user,
'keyword': self.keyword,
'user_list': self.get_org_users(),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
\ No newline at end of file
...@@ -33,8 +33,9 @@ from django.contrib.auth import logout as auth_logout ...@@ -33,8 +33,9 @@ from django.contrib.auth import logout as auth_logout
from common.const import create_success_msg, update_success_msg from common.const import create_success_msg, update_success_msg
from common.mixins import JSONResponseMixin from common.mixins import JSONResponseMixin
from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
from common.models import Setting from common.models import Setting, common_settings
from common.permissions import AdminUserRequiredMixin from common.permissions import AdminUserRequiredMixin
from orgs.utils import current_org
from .. import forms from .. import forms
from ..models import User, UserGroup from ..models import User, UserGroup
from ..utils import generate_otp_uri, check_otp_code, \ from ..utils import generate_otp_uri, check_otp_code, \
...@@ -52,7 +53,7 @@ __all__ = [ ...@@ -52,7 +53,7 @@ __all__ = [
'UserPublicKeyGenerateView', 'UserPublicKeyGenerateView',
'UserOtpEnableAuthenticationView', 'UserOtpEnableInstallAppView', 'UserOtpEnableAuthenticationView', 'UserOtpEnableInstallAppView',
'UserOtpEnableBindView', 'UserOtpSettingsSuccessView', 'UserOtpEnableBindView', 'UserOtpSettingsSuccessView',
'UserOtpDisableAuthenticationView', 'UserOtpDisableAuthenticationView', 'UserOtpUpdateView'
] ]
logger = get_logger(__name__) logger = get_logger(__name__)
...@@ -197,6 +198,12 @@ class UserDetailView(AdminUserRequiredMixin, DetailView): ...@@ -197,6 +198,12 @@ class UserDetailView(AdminUserRequiredMixin, DetailView):
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def get_queryset(self):
queryset = super().get_queryset()
org_users = current_org.get_org_users().values_list('id', flat=True)
queryset = queryset.filter(id__in=org_users)
return queryset
@method_decorator(csrf_exempt, name='dispatch') @method_decorator(csrf_exempt, name='dispatch')
class UserExportView(View): class UserExportView(View):
...@@ -355,10 +362,10 @@ class UserProfileView(LoginRequiredMixin, TemplateView): ...@@ -355,10 +362,10 @@ class UserProfileView(LoginRequiredMixin, TemplateView):
template_name = 'users/user_profile.html' template_name = 'users/user_profile.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
mfa_setting = Setting.objects.filter(name='SECURITY_MFA_AUTH').first() mfa_setting = common_settings.SECURITY_MFA_AUTH
context = { context = {
'action': _('Profile'), 'action': _('Profile'),
'mfa_setting': mfa_setting.cleaned_value if mfa_setting else False, 'mfa_setting': mfa_setting if mfa_setting is not None else False,
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -514,7 +521,7 @@ class UserOtpEnableBindView(TemplateView, FormView): ...@@ -514,7 +521,7 @@ class UserOtpEnableBindView(TemplateView, FormView):
return super().form_valid(form) return super().form_valid(form)
else: else:
form.add_error("otp_code", _("MFA code invalid")) form.add_error("otp_code", _("MFA code invalid, or ntp sync server time"))
return self.form_invalid(form) return self.form_invalid(form)
def save_otp(self, otp_secret_key): def save_otp(self, otp_secret_key):
...@@ -539,10 +546,14 @@ class UserOtpDisableAuthenticationView(FormView): ...@@ -539,10 +546,14 @@ class UserOtpDisableAuthenticationView(FormView):
user.save() user.save()
return super().form_valid(form) return super().form_valid(form)
else: else:
form.add_error('otp_code', _('MFA code invalid')) form.add_error('otp_code', _('MFA code invalid, or ntp sync server time'))
return super().form_invalid(form) return super().form_invalid(form)
class UserOtpUpdateView(UserOtpDisableAuthenticationView):
success_url = reverse_lazy('users:user-otp-enable-bind')
class UserOtpSettingsSuccessView(TemplateView): class UserOtpSettingsSuccessView(TemplateView):
template_name = 'flash_message_standalone.html' template_name = 'flash_message_standalone.html'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment