From e108aae3c01da04c4ac238ed770ae3037f8b0bf4 Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Tue, 14 May 2019 21:39:34 +0800 Subject: [PATCH 01/36] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8DAssetUserMan?= =?UTF-8?q?ager.get=E8=8E=B7=E5=8F=96username=E4=B8=BA""=E7=9A=84AuthBook?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E6=97=B6=EF=BC=8C=E8=BF=94=E5=9B=9E=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E7=BB=93=E6=9E=9C=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/backends/external/db.py | 2 +- apps/assets/backends/internal/admin_user.py | 2 +- apps/assets/backends/internal/system_user.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/assets/backends/external/db.py b/apps/assets/backends/external/db.py index f3f6c16d6..eedafd336 100644 --- a/apps/assets/backends/external/db.py +++ b/apps/assets/backends/external/db.py @@ -11,7 +11,7 @@ class AuthBookBackend(BaseBackend): @classmethod def filter(cls, username=None, asset=None, latest=True): queryset = AuthBook.objects.all() - if username: + if username is not None: queryset = queryset.filter(username=username) if asset: queryset = queryset.filter(asset=asset) diff --git a/apps/assets/backends/internal/admin_user.py b/apps/assets/backends/internal/admin_user.py index e67b41fc9..abd32b5ae 100644 --- a/apps/assets/backends/internal/admin_user.py +++ b/apps/assets/backends/internal/admin_user.py @@ -27,7 +27,7 @@ class AdminUserBackend(BaseBackend): instances = [] assets = cls._get_assets(asset) for asset in assets: - if username and asset.admin_user.username != username: + if username is not None and asset.admin_user.username != username: continue instance = construct_authbook_object(asset.admin_user, asset) instances.append(instance) diff --git a/apps/assets/backends/internal/system_user.py b/apps/assets/backends/internal/system_user.py index 52b22215c..b1413fedb 100644 --- a/apps/assets/backends/internal/system_user.py +++ b/apps/assets/backends/internal/system_user.py @@ -30,7 +30,7 @@ class SystemUserBackend(BaseBackend): @classmethod def _filter_system_users_by_username(cls, system_users, username): _system_users = cls._distinct_system_users_by_username(system_users) - if username: + if username is not None: _system_users = [su for su in _system_users if username == su.username] return _system_users From 54a9070c58f307d6bca03e4031e4e517d17f81aa Mon Sep 17 00:00:00 2001 From: BaiJiangJie Date: Thu, 16 May 2019 10:36:47 +0800 Subject: [PATCH 02/36] =?UTF-8?q?[Update]=20=E8=B5=84=E4=BA=A7=E8=8A=82?= =?UTF-8?q?=E7=82=B9API=E6=B7=BB=E5=8A=A0search=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/node.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 51e830efc..f772a2ace 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -39,6 +39,8 @@ __all__ = [ class NodeViewSet(viewsets.ModelViewSet): + filter_fields = ('value', 'key', ) + search_fields = filter_fields queryset = Node.objects.all() permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeSerializer From 9e52579ca681c5daa6c3f9702fdcf9651ba1faaa Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 16 May 2019 14:41:11 +0800 Subject: [PATCH 03/36] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9api=20meta?= =?UTF-8?q?=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drfmetadata.py | 111 ++++++++++++++++++++++++++++++++++++ apps/jumpserver/settings.py | 1 + 2 files changed, 112 insertions(+) create mode 100644 apps/common/drfmetadata.py diff --git a/apps/common/drfmetadata.py b/apps/common/drfmetadata.py new file mode 100644 index 000000000..e29ac8641 --- /dev/null +++ b/apps/common/drfmetadata.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# +from __future__ import unicode_literals + +from collections import OrderedDict + +from django.core.exceptions import PermissionDenied +from django.http import Http404 +from django.utils.encoding import force_text + +from rest_framework.metadata import SimpleMetadata +from rest_framework import exceptions, serializers +from rest_framework.request import clone_request + + +class SimpleMetadataWithFilters(SimpleMetadata): + """Override SimpleMetadata, adding info about filters""" + + methods = {"PUT", "POST", "GET"} + attrs = [ + 'read_only', 'label', 'help_text', + 'min_length', 'max_length', + 'min_value', 'max_value', "write_only" + ] + + def determine_actions(self, request, view): + """ + For generic class based views we return information about + the fields that are accepted for 'PUT' and 'POST' methods. + """ + actions = {} + for method in self.methods & set(view.allowed_methods): + view.request = clone_request(request, method) + try: + # Test global permissions + if hasattr(view, 'check_permissions'): + view.check_permissions(view.request) + # Test object permissions + if method == 'PUT' and hasattr(view, 'get_object'): + view.get_object() + except (exceptions.APIException, PermissionDenied, Http404): + pass + else: + # If user has appropriate permissions for the view, include + # appropriate metadata about the fields that should be supplied. + serializer = view.get_serializer() + actions[method] = self.get_serializer_info(serializer) + finally: + view.request = request + return actions + + def get_field_info(self, field): + """ + Given an instance of a serializer field, return a dictionary + of metadata about it. + """ + field_info = OrderedDict() + field_info['type'] = self.label_lookup[field] + field_info['required'] = getattr(field, 'required', False) + + for attr in self.attrs: + value = getattr(field, attr, None) + if value is not None and value != '': + field_info[attr] = force_text(value, strings_only=True) + + if getattr(field, 'child', None): + field_info['child'] = self.get_field_info(field.child) + elif getattr(field, 'fields', None): + field_info['children'] = self.get_serializer_info(field) + + if (not field_info.get('read_only') and + not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and + hasattr(field, 'choices')): + field_info['choices'] = [ + { + 'value': choice_value, + 'display_name': force_text(choice_name, strings_only=True) + } + for choice_value, choice_name in field.choices.items() + ] + + return field_info + + def get_filters_fields(self, request, view): + fields = [] + if hasattr(view, 'get_filter_fields'): + fields = view.get_filter_fields(request) + elif hasattr(view, 'filter_fields'): + fields = view.filter_fields + return fields + + def get_ordering_fields(self, request, view): + fields = [] + if hasattr(view, 'get_ordering_fields'): + fields = view.get_filter_fields(request) + elif hasattr(view, 'ordering_fields'): + fields = view.filter_fields + return fields + + def determine_metadata(self, request, view): + metadata = super(SimpleMetadataWithFilters, self).determine_metadata(request, view) + filter_fields = self.get_filters_fields(request, view) + order_fields = self.get_ordering_fields(request, view) + + meta_get = metadata.get("actions", {}).get("GET", {}) + for k, v in meta_get.items(): + if k in filter_fields: + v["filter"] = True + if k in order_fields: + v["order"] = True + return metadata diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index cc8a730c1..d97acb361 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -374,6 +374,7 @@ REST_FRAMEWORK = { 'rest_framework.filters.SearchFilter', 'rest_framework.filters.OrderingFilter', ), + 'DEFAULT_METADATA_CLASS': 'common.drfmetadata.SimpleMetadataWithFilters', 'ORDERING_PARAM': "order", 'SEARCH_PARAM': "search", 'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z', From 3855fecc69e4286efc793724460e11b86e88e5fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Mon, 20 May 2019 12:30:55 +0800 Subject: [PATCH 04/36] Password message (#2702) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 密码信封 * [Update] 查看密码 * [Update] 支持查看密码 * [Update] 修改语言翻译 * [Update] 迁移ansible到2.8版本 * [Update] 修改auth book的可连接性 * [Update] 删除不使用的方法 --- apps/assets/models/authbook.py | 2 +- .../assets/_asset_user_view_auth_modal.html | 140 +++++++++ .../templates/assets/admin_user_assets.html | 14 +- .../assets/asset_asset_user_list.html | 34 +- .../templates/assets/system_user_asset.html | 12 +- apps/authentication/api/auth.py | 24 +- apps/authentication/serializers.py | 4 + apps/authentication/urls/api_urls.py | 1 + apps/common/permissions.py | 1 + apps/common/utils/common.py | 1 + apps/jumpserver/conf.py | 2 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 71420 -> 71684 bytes apps/locale/zh/LC_MESSAGES/django.po | 295 ++++++++++-------- apps/ops/ansible/callback.py | 3 + apps/ops/ansible/runner.py | 38 +-- apps/ops/ansible/test_runner.py | 2 +- apps/ops/inventory.py | 1 - apps/static/js/jumpserver.js | 7 +- .../js/plugins/clipboard/clipboard.min.js | 7 + apps/users/models/user.py | 4 + requirements/requirements.txt | 16 +- 21 files changed, 416 insertions(+), 192 deletions(-) create mode 100644 apps/assets/templates/assets/_asset_user_view_auth_modal.html create mode 100755 apps/static/js/plugins/clipboard/clipboard.min.js diff --git a/apps/assets/models/authbook.py b/apps/assets/models/authbook.py index a040bbd0d..94e4f4cf4 100644 --- a/apps/assets/models/authbook.py +++ b/apps/assets/models/authbook.py @@ -78,7 +78,7 @@ class AuthBook(AssetUser): if host == self.asset.hostname: _connectivity = self.UNREACHABLE - for host in value.get('contacted', {}).keys(): + for host in value.get('contacted', []): if host == self.asset.hostname: _connectivity = self.REACHABLE diff --git a/apps/assets/templates/assets/_asset_user_view_auth_modal.html b/apps/assets/templates/assets/_asset_user_view_auth_modal.html new file mode 100644 index 000000000..07ac34ce5 --- /dev/null +++ b/apps/assets/templates/assets/_asset_user_view_auth_modal.html @@ -0,0 +1,140 @@ +{% extends '_modal.html' %} +{% load i18n %} +{% load static %} +{% block modal_id %}asset_user_auth_view{% endblock %} +{% block modal_title%}{% trans "Asset user auth" %}{% endblock %} +{% block modal_body %} + +
+
+ +
+ + {% trans "Need otp auth for view auth" %} +
+ +
+ +
+ + +{% endblock %} +{% block modal_button %} + +{% endblock %} diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html index c893ead80..328b21579 100644 --- a/apps/assets/templates/assets/admin_user_assets.html +++ b/apps/assets/templates/assets/admin_user_assets.html @@ -85,6 +85,7 @@ {% include 'assets/_asset_user_auth_modal.html' %} + {% include 'assets/_asset_user_view_auth_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/assets/templates/assets/asset_asset_user_list.html b/apps/assets/templates/assets/asset_asset_user_list.html index 9a330ce77..ef192d790 100644 --- a/apps/assets/templates/assets/asset_asset_user_list.html +++ b/apps/assets/templates/assets/asset_asset_user_list.html @@ -2,10 +2,6 @@ {% load common_tags %} {% load static %} {% load i18n %} - -{% block custom_head_css_js %} -{% endblock %} - {% block content %}
@@ -87,6 +83,7 @@
{% include 'assets/_asset_user_auth_modal.html' %} + {% include 'assets/_asset_user_view_auth_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/system_user_asset.html b/apps/assets/templates/assets/system_user_asset.html index 082e13fd8..f5df32bf5 100644 --- a/apps/assets/templates/assets/system_user_asset.html +++ b/apps/assets/templates/assets/system_user_asset.html @@ -133,6 +133,7 @@ {% include 'assets/_asset_user_auth_modal.html' %} + {% include 'assets/_asset_user_view_auth_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/authentication/api/auth.py b/apps/authentication/api/auth.py index 2d9cfe367..42c196c51 100644 --- a/apps/authentication/api/auth.py +++ b/apps/authentication/api/auth.py @@ -2,6 +2,7 @@ # import uuid +import time from django.core.cache import cache from django.urls import reverse @@ -10,10 +11,11 @@ from django.utils.translation import ugettext as _ from rest_framework.permissions import AllowAny from rest_framework.response import Response +from rest_framework.generics import CreateAPIView from rest_framework.views import APIView from common.utils import get_logger, get_request_ip -from common.permissions import IsOrgAdminOrAppUser +from common.permissions import IsOrgAdminOrAppUser, IsValidUser from orgs.mixins import RootOrgViewMixin from users.serializers import UserSerializer from users.models import User @@ -23,12 +25,13 @@ from users.utils import ( check_user_valid, check_otp_code, increase_login_failed_count, is_block_login, clean_failed_count ) - +from ..serializers import OtpVerifySerializer from ..signals import post_auth_success, post_auth_failed logger = get_logger(__name__) __all__ = [ 'UserAuthApi', 'UserConnectionTokenApi', 'UserOtpAuthApi', + 'UserOtpVerifyApi', ] @@ -179,3 +182,20 @@ class UserOtpAuthApi(RootOrgViewMixin, APIView): sender=self.__class__, username=username, request=self.request, reason=reason ) + + +class UserOtpVerifyApi(CreateAPIView): + permission_classes = (IsValidUser,) + serializer_class = OtpVerifySerializer + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + code = serializer.validated_data["code"] + + if request.user.check_otp(code): + request.session["OTP_LAST_VERIFY_TIME"] = int(time.time()) + return Response({"ok": "1"}) + else: + return Response({"error": "Code not valid"}, status=400) + diff --git a/apps/authentication/serializers.py b/apps/authentication/serializers.py index cf4968a56..2033b3e44 100644 --- a/apps/authentication/serializers.py +++ b/apps/authentication/serializers.py @@ -14,3 +14,7 @@ class AccessKeySerializer(serializers.ModelSerializer): model = AccessKey fields = ['id', 'secret'] read_only_fields = ['id', 'secret'] + + +class OtpVerifySerializer(serializers.Serializer): + code = serializers.CharField(max_length=6, min_length=6) diff --git a/apps/authentication/urls/api_urls.py b/apps/authentication/urls/api_urls.py index b22c49884..85f3aa522 100644 --- a/apps/authentication/urls/api_urls.py +++ b/apps/authentication/urls/api_urls.py @@ -16,5 +16,6 @@ urlpatterns = [ path('connection-token/', api.UserConnectionTokenApi.as_view(), name='connection-token'), path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'), + path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'), ] diff --git a/apps/common/permissions.py b/apps/common/permissions.py index 689444131..025d44ba3 100644 --- a/apps/common/permissions.py +++ b/apps/common/permissions.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +import time from rest_framework import permissions from django.contrib.auth.mixins import UserPassesTestMixin diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index dcd7daf16..79146c039 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -144,6 +144,7 @@ def is_uuid(seq): 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: diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index ee6dcdbfd..35254d1ca 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -194,7 +194,7 @@ class Config(dict): filename = os.path.join(self.root_path, filename) try: with open(filename, 'rt', encoding='utf8') as f: - obj = yaml.load(f) + obj = yaml.safe_load(f) except IOError as e: if silent and e.errno in (errno.ENOENT, errno.EISDIR): return False diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 52ca4190e25b6f8f01a33ddea5e4b8414831c36f..4d188c93e46ee615c0be0c00ea39a0646ea361be 100644 GIT binary patch delta 21270 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 delta 21011 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\n" "Language-Team: Jumpserver team\n" @@ -21,15 +21,15 @@ msgstr "" msgid "Please select assets that need to be updated" msgstr "请选择需要更新的资产" -#: assets/api/node.py:58 +#: assets/api/node.py:60 msgid "You can't update the root node name" msgstr "不能修改根节点名称" -#: assets/api/node.py:282 +#: assets/api/node.py:285 msgid "Update node asset hardware information: {}" msgstr "更新节点资产硬件信息: {}" -#: assets/api/node.py:296 +#: assets/api/node.py:299 msgid "Test if the assets under the node are connectable: {}" msgstr "测试节点下资产是否可连接: {}" @@ -45,7 +45,7 @@ msgstr "节点管理" #: assets/models/cluster.py:19 assets/models/user.py:91 #: assets/templates/assets/asset_detail.html:80 templates/_nav.html:24 #: xpack/plugins/cloud/models.py:124 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:67 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:65 #: xpack/plugins/orgs/templates/orgs/org_list.html:18 msgid "Admin user" msgstr "管理用户" @@ -81,8 +81,8 @@ msgstr "网域" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:55 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:15 #: xpack/plugins/cloud/models.py:123 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:63 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:61 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64 msgid "Node" msgstr "节点" @@ -136,7 +136,7 @@ msgstr "选择资产" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:14 #: xpack/plugins/cloud/models.py:187 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 #: xpack/plugins/orgs/templates/orgs/org_list.html:16 msgid "Asset" msgstr "资产" @@ -188,9 +188,9 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 #: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:52 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:50 #: xpack/plugins/cloud/templates/cloud/account_list.html:12 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:55 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12 #: xpack/plugins/orgs/templates/orgs/org_detail.html:52 #: xpack/plugins/orgs/templates/orgs/org_list.html:12 @@ -200,9 +200,10 @@ msgstr "名称" #: assets/forms/domain.py:74 assets/forms/user.py:85 assets/forms/user.py:147 #: assets/models/base.py:27 #: assets/templates/assets/_asset_user_auth_modal.html:15 +#: assets/templates/assets/_asset_user_view_auth_modal.html:31 #: assets/templates/assets/admin_user_detail.html:60 #: assets/templates/assets/admin_user_list.html:27 -#: assets/templates/assets/asset_asset_user_list.html:48 +#: assets/templates/assets/asset_asset_user_list.html:44 #: assets/templates/assets/domain_gateway_list.html:71 #: assets/templates/assets/system_user_detail.html:62 #: assets/templates/assets/system_user_list.html:30 audits/models.py:94 @@ -233,6 +234,7 @@ msgstr "密码或密钥密码" #: assets/forms/user.py:26 assets/models/base.py:28 #: assets/serializers/asset_user.py:19 #: assets/templates/assets/_asset_user_auth_modal.html:21 +#: assets/templates/assets/_asset_user_view_auth_modal.html:37 #: authentication/forms.py:13 #: authentication/templates/authentication/login.html:67 #: authentication/templates/authentication/new_login.html:93 @@ -313,6 +315,7 @@ msgstr "IP" #: assets/models/asset.py:75 assets/templates/assets/_asset_list_modal.html:45 #: assets/templates/assets/_asset_user_auth_modal.html:9 +#: assets/templates/assets/_asset_user_view_auth_modal.html:25 #: assets/templates/assets/admin_user_assets.html:48 #: assets/templates/assets/asset_detail.html:60 #: assets/templates/assets/asset_list.html:92 @@ -461,8 +464,8 @@ msgstr "创建者" #: users/templates/users/user_group_detail.html:63 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105 #: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:128 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:68 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:79 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:77 #: xpack/plugins/orgs/templates/orgs/org_detail.html:60 msgid "Date created" msgstr "创建日期" @@ -495,9 +498,9 @@ msgstr "创建日期" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 #: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:72 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:70 #: xpack/plugins/cloud/templates/cloud/account_list.html:15 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:71 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16 #: xpack/plugins/orgs/templates/orgs/org_detail.html:64 #: xpack/plugins/orgs/templates/orgs/org_list.html:22 @@ -513,7 +516,7 @@ msgstr "不可达" #: assets/models/asset.py:118 assets/models/base.py:39 #: assets/templates/assets/admin_user_assets.html:51 #: assets/templates/assets/admin_user_list.html:29 -#: assets/templates/assets/asset_asset_user_list.html:50 +#: assets/templates/assets/asset_asset_user_list.html:46 #: assets/templates/assets/asset_list.html:95 #: assets/templates/assets/system_user_asset.html:53 #: assets/templates/assets/system_user_list.html:34 @@ -584,7 +587,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:475 +#: users/models/user.py:479 msgid "System" msgstr "系统" @@ -665,7 +668,7 @@ msgstr "每行一个命令" #: assets/models/cmd_filter.py:54 #: assets/templates/assets/admin_user_assets.html:52 #: assets/templates/assets/admin_user_list.html:33 -#: assets/templates/assets/asset_asset_user_list.html:52 +#: assets/templates/assets/asset_asset_user_list.html:48 #: assets/templates/assets/asset_list.html:96 #: assets/templates/assets/cmd_filter_list.html:28 #: assets/templates/assets/cmd_filter_rule_list.html:63 @@ -735,7 +738,7 @@ msgstr "默认资产组" #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:71 users/forms.py:283 -#: users/models/user.py:36 users/models/user.py:463 +#: users/models/user.py:36 users/models/user.py:467 #: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_list.html:13 users/views/user.py:395 #: xpack/plugins/orgs/forms.py:26 @@ -757,7 +760,7 @@ msgstr "分类" msgid "Key" msgstr "键" -#: assets/models/node.py:128 +#: assets/models/node.py:133 msgid "New node" msgstr "新节点" @@ -975,6 +978,61 @@ msgstr "更新资产用户认证信息" msgid "Please input password" msgstr "请输入密码" +#: assets/templates/assets/_asset_user_view_auth_modal.html:5 +msgid "Asset user auth" +msgstr "资产用户信息" + +#: assets/templates/assets/_asset_user_view_auth_modal.html:14 +#: assets/templates/assets/_otp_verify_modal.html:8 audits/models.py:99 +#: audits/templates/audits/login_log_list.html:56 users/forms.py:142 +#: users/models/user.py:83 users/templates/users/first_login.html:45 +msgid "MFA" +msgstr "MFA" + +#: assets/templates/assets/_asset_user_view_auth_modal.html:17 +msgid "Need otp auth for view auth" +msgstr "需要二次认证来查看账号信息" + +#: assets/templates/assets/_asset_user_view_auth_modal.html:20 +#: assets/templates/assets/admin_user_detail.html:100 +#: assets/templates/assets/asset_detail.html:211 +#: assets/templates/assets/asset_list.html:637 +#: assets/templates/assets/cmd_filter_detail.html:106 +#: assets/templates/assets/system_user_asset.html:112 +#: assets/templates/assets/system_user_detail.html:182 +#: assets/templates/assets/system_user_list.html:144 +#: settings/templates/settings/terminal_setting.html:165 +#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108 +#: users/templates/users/user_detail.html:388 +#: users/templates/users/user_detail.html:414 +#: users/templates/users/user_detail.html:437 +#: users/templates/users/user_detail.html:482 +#: users/templates/users/user_group_create_update.html:32 +#: users/templates/users/user_group_list.html:90 +#: users/templates/users/user_list.html:215 +#: users/templates/users/user_profile.html:238 +#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 +#: xpack/plugins/interface/templates/interface/interface.html:103 +#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 +msgid "Confirm" +msgstr "确认" + +#: assets/templates/assets/_asset_user_view_auth_modal.html:63 +msgid "Copy success" +msgstr "复制成功" + +#: assets/templates/assets/_asset_user_view_auth_modal.html:79 +msgid "Get auth info error" +msgstr "获取认证信息错误" + +#: assets/templates/assets/_asset_user_view_auth_modal.html:139 +#: assets/templates/assets/_user_asset_detail_modal.html:23 +#: settings/templates/settings/_ldap_list_users_modal.html:96 +#: templates/_modal.html:22 +msgid "Close" +msgstr "关闭" + #: assets/templates/assets/_gateway_test_modal.html:4 msgid "Test gateway test connection" msgstr "测试连接网关" @@ -987,6 +1045,10 @@ msgstr "SSH端口" msgid "If use nat, set the ssh real port" msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口" +#: assets/templates/assets/_otp_verify_modal.html:4 +msgid "MFA Confirm" +msgstr "确认" + #: assets/templates/assets/_system_user.html:37 #: assets/templates/assets/asset_create.html:16 #: assets/templates/assets/asset_update.html:21 @@ -1088,17 +1150,11 @@ msgid "Submit" msgstr "提交" #: assets/templates/assets/_user_asset_detail_modal.html:11 -#: assets/templates/assets/asset_asset_user_list.html:17 +#: assets/templates/assets/asset_asset_user_list.html:13 #: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:187 msgid "Asset detail" msgstr "资产详情" -#: assets/templates/assets/_user_asset_detail_modal.html:23 -#: settings/templates/settings/_ldap_list_users_modal.html:96 -#: templates/_modal.html:22 -msgid "Close" -msgstr "关闭" - #: assets/templates/assets/admin_user_assets.html:18 #: assets/templates/assets/admin_user_detail.html:18 #: assets/templates/assets/cmd_filter_detail.html:19 @@ -1140,41 +1196,47 @@ msgid "Quick update" msgstr "快速更新" #: assets/templates/assets/admin_user_assets.html:70 -#: assets/templates/assets/asset_asset_user_list.html:71 +#: assets/templates/assets/asset_asset_user_list.html:67 #: assets/templates/assets/asset_detail.html:179 msgid "Test connective" msgstr "测试可连接性" #: assets/templates/assets/admin_user_assets.html:73 -#: assets/templates/assets/admin_user_assets.html:115 -#: assets/templates/assets/asset_asset_user_list.html:74 -#: assets/templates/assets/asset_asset_user_list.html:122 +#: assets/templates/assets/admin_user_assets.html:118 +#: assets/templates/assets/asset_asset_user_list.html:70 +#: assets/templates/assets/asset_asset_user_list.html:119 #: assets/templates/assets/asset_detail.html:182 #: assets/templates/assets/system_user_asset.html:75 -#: assets/templates/assets/system_user_asset.html:165 +#: assets/templates/assets/system_user_asset.html:166 #: assets/templates/assets/system_user_detail.html:151 msgid "Test" msgstr "测试" #: assets/templates/assets/admin_user_assets.html:116 -#: assets/templates/assets/asset_asset_user_list.html:120 -#: assets/templates/assets/system_user_asset.html:167 +#: assets/templates/assets/asset_asset_user_list.html:117 +#: assets/templates/assets/system_user_asset.html:169 msgid "Update auth" msgstr "更新认证" -#: assets/templates/assets/admin_user_assets.html:192 -#: assets/templates/assets/asset_asset_user_list.html:176 +#: assets/templates/assets/admin_user_assets.html:117 +#: assets/templates/assets/asset_asset_user_list.html:118 +#: assets/templates/assets/system_user_asset.html:167 +msgid "View auth" +msgstr "查看认证" + +#: assets/templates/assets/admin_user_assets.html:196 +#: assets/templates/assets/asset_asset_user_list.html:162 #: assets/templates/assets/asset_detail.html:311 -#: assets/templates/assets/system_user_asset.html:351 +#: assets/templates/assets/system_user_asset.html:353 #: users/templates/users/user_detail.html:307 #: users/templates/users/user_detail.html:334 #: xpack/plugins/interface/views.py:34 msgid "Update successfully!" msgstr "更新成功" -#: assets/templates/assets/admin_user_assets.html:195 -#: assets/templates/assets/asset_asset_user_list.html:179 -#: assets/templates/assets/system_user_asset.html:354 +#: assets/templates/assets/admin_user_assets.html:199 +#: assets/templates/assets/asset_asset_user_list.html:165 +#: assets/templates/assets/system_user_asset.html:356 msgid "Update failed!" msgstr "更新失败" @@ -1205,11 +1267,11 @@ msgstr "更新失败" #: users/templates/users/user_profile.html:187 #: users/templates/users/user_profile.html:196 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:54 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:25 -#: xpack/plugins/cloud/templates/cloud/account_list.html:38 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:55 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:23 +#: xpack/plugins/cloud/templates/cloud/account_list.html:39 #: 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:87 msgid "Update" msgstr "更新" @@ -1239,13 +1301,13 @@ msgstr "更新" #: users/templates/users/user_list.html:91 #: users/templates/users/user_list.html:95 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:56 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:29 -#: xpack/plugins/cloud/templates/cloud/account_list.html:40 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:32 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:54 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:57 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:27 +#: xpack/plugins/cloud/templates/cloud/account_list.html:41 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55 #: xpack/plugins/orgs/templates/orgs/org_detail.html:29 -#: xpack/plugins/orgs/templates/orgs/org_list.html:87 +#: xpack/plugins/orgs/templates/orgs/org_list.html:89 msgid "Delete" msgstr "删除" @@ -1260,30 +1322,6 @@ msgstr "替换资产的管理员" msgid "Select nodes" msgstr "选择节点" -#: assets/templates/assets/admin_user_detail.html:100 -#: assets/templates/assets/asset_detail.html:211 -#: assets/templates/assets/asset_list.html:637 -#: assets/templates/assets/cmd_filter_detail.html:106 -#: assets/templates/assets/system_user_asset.html:112 -#: assets/templates/assets/system_user_detail.html:182 -#: assets/templates/assets/system_user_list.html:144 -#: settings/templates/settings/terminal_setting.html:165 -#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108 -#: users/templates/users/user_detail.html:388 -#: users/templates/users/user_detail.html:414 -#: users/templates/users/user_detail.html:437 -#: users/templates/users/user_detail.html:482 -#: users/templates/users/user_group_create_update.html:32 -#: users/templates/users/user_group_list.html:90 -#: users/templates/users/user_list.html:215 -#: users/templates/users/user_profile.html:238 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 -#: xpack/plugins/interface/templates/interface/interface.html:103 -#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 -msgid "Confirm" -msgstr "确认" - #: assets/templates/assets/admin_user_list.html:10 msgid "" "Admin users are asset (charged server) on the root, or have NOPASSWD: ALL " @@ -1313,26 +1351,26 @@ msgstr "创建管理用户" msgid "Ratio" msgstr "比例" -#: assets/templates/assets/asset_asset_user_list.html:20 +#: assets/templates/assets/asset_asset_user_list.html:16 #: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:68 msgid "Asset user list" msgstr "资产用户列表" -#: assets/templates/assets/asset_asset_user_list.html:28 +#: assets/templates/assets/asset_asset_user_list.html:24 msgid "Asset users of" msgstr "资产用户" -#: assets/templates/assets/asset_asset_user_list.html:49 +#: assets/templates/assets/asset_asset_user_list.html:45 msgid "Password version" msgstr "密码版本" -#: assets/templates/assets/asset_asset_user_list.html:51 +#: assets/templates/assets/asset_asset_user_list.html:47 #: assets/templates/assets/cmd_filter_detail.html:73 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109 msgid "Date updated" msgstr "更新日期" -#: assets/templates/assets/asset_asset_user_list.html:64 +#: assets/templates/assets/asset_asset_user_list.html:60 #: assets/templates/assets/asset_detail.html:148 #: terminal/templates/terminal/session_detail.html:81 #: users/templates/users/user_detail.html:138 @@ -1672,7 +1710,7 @@ msgid "Push system user now" msgstr "立刻推送系统" #: assets/templates/assets/system_user_asset.html:84 -#: assets/templates/assets/system_user_asset.html:163 +#: assets/templates/assets/system_user_asset.html:164 #: assets/templates/assets/system_user_detail.html:142 msgid "Push" msgstr "推送" @@ -1948,12 +1986,6 @@ msgstr "登录城市" msgid "User agent" msgstr "Agent" -#: audits/models.py:99 audits/templates/audits/login_log_list.html:56 -#: users/forms.py:142 users/models/user.py:83 -#: users/templates/users/first_login.html:45 -msgid "MFA" -msgstr "MFA" - #: audits/models.py:100 audits/templates/audits/login_log_list.html:57 #: xpack/plugins/change_auth_plan/models.py:413 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15 @@ -1965,7 +1997,7 @@ msgstr "原因" #: audits/models.py:101 audits/templates/audits/login_log_list.html:58 #: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:188 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 msgid "Status" msgstr "状态" @@ -2003,7 +2035,7 @@ msgstr "选择用户" #: terminal/templates/terminal/command_list.html:60 #: terminal/templates/terminal/session_list.html:61 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:52 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:50 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:48 msgid "Search" msgstr "搜索" @@ -2013,7 +2045,7 @@ msgstr "搜索" #: ops/templates/ops/task_detail.html:56 #: terminal/templates/terminal/session_list.html:70 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:64 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:60 msgid "ID" msgstr "ID" @@ -2063,25 +2095,25 @@ msgstr "登录日志" msgid "Command execution log" msgstr "命令执行" -#: authentication/api/auth.py:46 +#: authentication/api/auth.py:49 #: authentication/templates/authentication/login.html:52 #: authentication/templates/authentication/new_login.html:77 msgid "Log in frequently and try again later" msgstr "登录频繁, 稍后重试" -#: authentication/api/auth.py:64 +#: authentication/api/auth.py:67 msgid "The user {} password has expired, please update." msgstr "用户 {} 密码已经过期,请更新。" -#: authentication/api/auth.py:83 +#: authentication/api/auth.py:86 msgid "Please carry seed value and conduct MFA secondary certification" msgstr "请携带seed值, 进行MFA二次认证" -#: authentication/api/auth.py:163 +#: authentication/api/auth.py:166 msgid "Please verify the user name and password first" msgstr "请先进行用户名和密码验证" -#: authentication/api/auth.py:168 +#: authentication/api/auth.py:171 msgid "MFA certification failed" msgstr "MFA认证失败" @@ -2415,7 +2447,7 @@ msgid "Become" msgstr "Become" #: ops/models/adhoc.py:166 users/templates/users/user_group_detail.html:59 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:64 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:62 #: xpack/plugins/orgs/templates/orgs/org_detail.html:56 msgid "Create by" msgstr "创建者" @@ -2661,8 +2693,8 @@ msgstr "版本" #: ops/templates/ops/task_list.html:63 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:137 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:52 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:52 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:53 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:53 msgid "Run" msgstr "执行" @@ -2828,7 +2860,7 @@ msgstr "创建授权规则" #: perms/templates/perms/asset_permission_list.html:59 #: perms/templates/perms/asset_permission_list.html:73 #: users/templates/users/user_list.html:28 xpack/plugins/cloud/models.py:53 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:60 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:58 #: xpack/plugins/cloud/templates/cloud/account_list.html:14 msgid "Validity" msgstr "有效" @@ -3283,8 +3315,8 @@ msgstr "端点后缀" #: settings/templates/settings/replay_storage_create.html:136 #: xpack/plugins/cloud/models.py:186 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:81 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62 msgid "Region" msgstr "地域" @@ -3999,7 +4031,7 @@ msgstr "复制你的公钥到这里" msgid "Select users" msgstr "选择用户" -#: users/models/user.py:35 users/models/user.py:471 +#: users/models/user.py:35 users/models/user.py:475 msgid "Administrator" msgstr "管理员" @@ -4045,7 +4077,7 @@ msgstr "最后更新密码日期" msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" -#: users/models/user.py:474 +#: users/models/user.py:478 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -4064,7 +4096,7 @@ msgstr "安全令牌验证" #: users/templates/users/_base_otp.html:44 users/templates/users/_user.html:13 #: users/templates/users/user_profile_update.html:51 #: xpack/plugins/cloud/models.py:120 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:59 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:57 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:13 msgid "Account" msgstr "账户" @@ -4947,7 +4979,7 @@ msgid "Run plan manually" msgstr "手动执行计划" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:179 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:101 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:102 msgid "Execute failed" msgstr "执行失败" @@ -5033,7 +5065,7 @@ msgid "Unavailable" msgstr "无效" #: xpack/plugins/cloud/models.py:50 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:56 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:54 #: xpack/plugins/cloud/templates/cloud/account_list.html:13 msgid "Provider" msgstr "云服务商" @@ -5059,7 +5091,7 @@ msgid "Instances" msgstr "实例" #: xpack/plugins/cloud/models.py:126 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:75 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:73 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:17 msgid "Date last sync" msgstr "最后同步日期" @@ -5078,7 +5110,7 @@ msgstr "" #: xpack/plugins/cloud/models.py:173 xpack/plugins/cloud/models.py:189 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:71 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:68 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66 msgid "Date sync" msgstr "同步日期" @@ -5095,8 +5127,8 @@ msgid "Sync instance task history" msgstr "同步实例任务历史" #: xpack/plugins/cloud/models.py:185 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:91 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:89 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:61 msgid "Instance" msgstr "实例" @@ -5116,7 +5148,7 @@ msgstr "AWS (国际)" msgid "Qcloud" msgstr "腾讯云" -#: xpack/plugins/cloud/templates/cloud/account_detail.html:22 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:20 #: xpack/plugins/cloud/views.py:72 msgid "Account detail" msgstr "账户详情" @@ -5134,23 +5166,23 @@ msgstr "加载中..." msgid "Load failed" msgstr "加载失败" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:22 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:20 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:25 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:23 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:21 #: xpack/plugins/cloud/views.py:122 msgid "Sync task detail" msgstr "同步任务详情" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:25 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:23 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:28 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:26 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:24 #: xpack/plugins/cloud/views.py:137 msgid "Sync task history" msgstr "同步历史列表" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:28 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:26 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:31 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:29 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:27 #: xpack/plugins/cloud/views.py:188 msgid "Sync instance list" msgstr "同步实例列表" @@ -5183,7 +5215,7 @@ msgstr "执行次数" msgid "Instance count" msgstr "实例个数" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:92 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:93 msgid "Sync success" msgstr "同步成功" @@ -5267,6 +5299,7 @@ msgid "This will restore default Settings of the interface !!!" msgstr "您确定要恢复默认初始化吗?" #: xpack/plugins/interface/templates/interface/interface.html:107 +#: xpack/plugins/interface/views.py:53 msgid "Restore default successfully." msgstr "恢复默认成功!" @@ -5279,13 +5312,9 @@ msgid "Interface" msgstr "界面" #: xpack/plugins/interface/views.py:49 -msgid "It is already in the default setting state!" +msgid "It is already in the default setting state!" msgstr "当前已经是初始化状态!" -#: xpack/plugins/interface/views.py:53 -msgid "Restore default successfully!" -msgstr "恢复默认成功!" - #: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94 #: xpack/plugins/license/templates/license/license_detail.html:50 #: xpack/plugins/license/templates/license/license_detail.html:55 @@ -5423,6 +5452,22 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" +#~ msgid "Monitor" +#~ msgstr "监控" + +#, fuzzy +#~| msgid "Select user" +#~ msgid "Select time" +#~ msgstr "选择用户" + +#, fuzzy +#~| msgid "Select Asset" +#~ msgid "Select host" +#~ msgstr "选择资产" + +#~ msgid "Restore default successfully!" +#~ msgstr "恢复默认成功!" + #~ msgid "Beijing Duizhan Tech, Inc." #~ msgstr "北京堆栈科技有限公司" @@ -5453,9 +5498,6 @@ msgstr "更新组织" #~ "object has no attribute 'keys'" #~ msgstr "导入 {} 个用户成功; 导入 {} 这些用户失败,因为对象没有属性'keys'" -#~ msgid "Monitor" -#~ msgstr "监控" - #~ msgid "Invalid private key" #~ msgstr "ssh密钥不合法" @@ -5549,9 +5591,6 @@ msgstr "更新组织" #~ "Are you sure to remove authentication information for the system user ?" #~ msgstr "你确定清除该系统用户的认证信息吗 ?" -#~ msgid "Clear auth" -#~ msgstr "清除认证信息" - #~ msgid "success" #~ msgstr "成功" diff --git a/apps/ops/ansible/callback.py b/apps/ops/ansible/callback.py index 02004285a..694bb9e27 100644 --- a/apps/ops/ansible/callback.py +++ b/apps/ops/ansible/callback.py @@ -124,6 +124,9 @@ class AdHocResultCallback(CallbackMixin, CallbackModule, CMDCallBackModule): def display_ok_hosts(self): pass + def display_failed_stderr(self): + pass + class CommandResultCallback(AdHocResultCallback): """ diff --git a/apps/ops/ansible/runner.py b/apps/ops/ansible/runner.py index 4c8f87888..5f25ee2c4 100644 --- a/apps/ops/ansible/runner.py +++ b/apps/ops/ansible/runner.py @@ -1,8 +1,11 @@ # ~*~ coding: utf-8 ~*~ import os +import shutil from collections import namedtuple +from ansible import context +from ansible.module_utils.common.collections import ImmutableDict from ansible.executor.task_queue_manager import TaskQueueManager from ansible.vars.manager import VariableManager from ansible.parsing.dataloader import DataLoader @@ -33,29 +36,18 @@ Options = namedtuple('Options', [ def get_default_options(): - options = Options( - listtags=False, - listtasks=False, - listhosts=False, + options = dict( syntax=False, timeout=30, connection='ssh', - module_path='', forks=10, remote_user='root', private_key_file=None, - ssh_common_args="", - ssh_extra_args="", - sftp_extra_args="", - scp_extra_args="", become=None, become_method=None, become_user=None, - verbosity=None, - extra_vars=[], + verbosity=1, check=False, - playbook_path='/etc/ansible/', - passwords=None, diff=False, gathering='implicit', remote_tmp='/tmp/.ansible' @@ -108,9 +100,9 @@ class PlayBookRunner: inventory=self.inventory, variable_manager=self.variable_manager, loader=self.loader, - options=self.options, - passwords=self.passwords + passwords={"conn_pass": self.passwords} ) + context.CLIARGS = ImmutableDict(self.options) if executor._tqm: executor._tqm._stdout_callback = self.results_callback @@ -185,11 +177,10 @@ class AdHocRunner: return cleaned_tasks def update_options(self, options): + _options = {k: v for k, v in self.default_options.items()} if options and isinstance(options, dict): - options = self.__class__.default_options._replace(**options) - else: - options = self.__class__.default_options - return options + _options.update(options) + return _options def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no'): """ @@ -202,6 +193,7 @@ class AdHocRunner: self.check_pattern(pattern) self.results_callback = self.get_result_callback() cleaned_tasks = self.clean_tasks(tasks) + context.CLIARGS = ImmutableDict(self.options) play_source = dict( name=play_name, @@ -220,9 +212,8 @@ class AdHocRunner: inventory=self.inventory, variable_manager=self.variable_manager, loader=self.loader, - options=self.options, stdout_callback=self.results_callback, - passwords=self.options.passwords, + passwords={"conn_pass": self.options.get("password", "")} ) try: tqm.run(play) @@ -230,8 +221,9 @@ class AdHocRunner: except Exception as e: raise AnsibleError(e) finally: - tqm.cleanup() - self.loader.cleanup_all_tmp_files() + if tqm is not None: + tqm.cleanup() + shutil.rmtree(C.DEFAULT_LOCAL_TMP, True) class CommandRunner(AdHocRunner): diff --git a/apps/ops/ansible/test_runner.py b/apps/ops/ansible/test_runner.py index 07c9051f3..e38168a6c 100644 --- a/apps/ops/ansible/test_runner.py +++ b/apps/ops/ansible/test_runner.py @@ -15,7 +15,7 @@ class TestAdHocRunner(unittest.TestCase): host_data = [ { "hostname": "testserver", - "ip": "192.168.244.168", + "ip": "192.168.244.185", "port": 22, "username": "root", "password": "redhat", diff --git a/apps/ops/inventory.py b/apps/ops/inventory.py index e4f11bec1..002b6fbda 100644 --- a/apps/ops/inventory.py +++ b/apps/ops/inventory.py @@ -35,7 +35,6 @@ class JMSBaseInventory(BaseInventory): info["vars"].update({ label.name: label.value }) - info["groups"].append("{}:{}".format(label.name, label.value)) if asset.domain: info["vars"].update({ "domain": asset.domain.name, diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 50f78187c..b3a2bd35e 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -715,9 +715,12 @@ String.prototype.format = function(args) { return result; }; -function setCookie(key, value) { +function setCookie(key, value, time) { var expires = new Date(); - expires.setTime(expires.getTime() + (24 * 60 * 60 * 1000)); + if (!time) { + time = expires.getTime() + (24 * 60 * 60 * 1000); + } + expires.setTime(time); document.cookie = key + '=' + value + ';expires=' + expires.toUTCString() + ';path=/'; } diff --git a/apps/static/js/plugins/clipboard/clipboard.min.js b/apps/static/js/plugins/clipboard/clipboard.min.js new file mode 100755 index 000000000..4e2a012a6 --- /dev/null +++ b/apps/static/js/plugins/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v1.5.5 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Clipboard=t()}}(function(){var t,e,n;return function t(e,n,r){function o(a,c){if(!n[a]){if(!e[a]){var s="function"==typeof require&&require;if(!c&&s)return s(a,!0);if(i)return i(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var l=n[a]={exports:{}};e[a][0].call(l.exports,function(t){var n=e[a][1][t];return o(n?n:t)},l,l.exports,t,e,n,r)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;ar;r++)n[r].fn.apply(n[r].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),r=n[t],o=[];if(r&&e)for(var i=0,a=r.length;a>i;i++)r[i].fn!==e&&r[i].fn._!==e&&o.push(r[i]);return o.length?n[t]=o:delete n[t],this}},e.exports=r},{}],8:[function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}n.__esModule=!0;var i=function(){function t(t,e){for(var n=0;n Date: Mon, 20 May 2019 12:36:35 +0800 Subject: [PATCH 05/36] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E6=8F=90=E7=A4=BA=E9=A2=9C=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/templates/assets/_asset_user_view_auth_modal.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/templates/assets/_asset_user_view_auth_modal.html b/apps/assets/templates/assets/_asset_user_view_auth_modal.html index 07ac34ce5..05f3bf619 100644 --- a/apps/assets/templates/assets/_asset_user_view_auth_modal.html +++ b/apps/assets/templates/assets/_asset_user_view_auth_modal.html @@ -122,7 +122,7 @@ $(document).ready(function () { showAuth() }; var error = function () { - $("#mfa_error").addClass("error").html("Code error"); + $("#mfa_error").addClass("text-danger").html("Code error"); }; APIUpdateAttr({ url: url, From 1eca517978828d8e7ca70bd79e502d297702d6d2 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Mon, 20 May 2019 19:39:53 +0800 Subject: [PATCH 06/36] =?UTF-8?q?[Feature]=20=E6=B7=BB=E5=8A=A0=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=20RemoteApp=20(#2706)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Feature] RemoteApp添加Model * [Feature] RemoteApp添加ViewSet API * [Feature] RemoteApp添加获取connection-info API * [Feature] Perms模块修改目录结构 * [Feature] RemoteAppPermission添加Model * [Feature] RemoteAppPermission添加ViewSet API * [Feature] RemoteAppPermission添加用户/用户组获取被授权的RemoteApp API * [Feature] RemoteAppPermission添加校验用户对RemoteApp的权限 API * [Feature] RemoteAppPermission添加获取用户授权的RemoteApp树 API * [Feature] RemoteAppPermission添加<添加/移除>所授权的<用户/RemoteApp> API * [Feature] RemoteApp添加创建、更新、详情、删除、用户RemoteApp等页面 * [Feature] RemoteAppPermission添加创建、更新、详情、删除、授权用户、授权RemoteApp等页面 * [Feature] RemoteApp从assets模块迁移到新添加的applications模块 * [Feature] RemoteApp/RemoteAppPermission添加迁移文件 * [Feature] RemoteApp/RemoteAppPermission修改小细节 * [Feature] RemoteApp/RemoteAppPermission修改小细节2 * [Feature] RemoteApp/RemoteAppPermission修改小细节3 * [Feature] RemoteApp更新迁移文件 * [Feature] RemoteApp/RemoteAppPermission添加翻译信息 * [Feature] RemoteApp/RemoteAppPermission删除迁移文件 * [Feature] RemoteApp/RemoteAppPermission添加迁移文件 * [Feature] RemoteApp/RemoteAppPermission修改代码风格 --- apps/applications/__init__.py | 0 apps/applications/admin.py | 3 + apps/applications/api/__init__.py | 1 + apps/applications/api/remote_app.py | 31 + apps/applications/apps.py | 7 + apps/applications/const.py | 68 + apps/applications/forms/__init__.py | 1 + apps/applications/forms/remote_app.py | 111 ++ apps/applications/hands.py | 16 + apps/applications/migrations/0001_initial.py | 42 + apps/applications/migrations/__init__.py | 0 apps/applications/models/__init__.py | 1 + apps/applications/models/remote_app.py | 89 ++ apps/applications/serializers/__init__.py | 1 + apps/applications/serializers/remote_app.py | 103 ++ .../remote_app_create_update.html | 91 ++ .../applications/remote_app_detail.html | 109 ++ .../applications/remote_app_list.html | 90 ++ .../applications/user_remote_app_list.html | 79 ++ apps/applications/tests.py | 3 + apps/applications/urls/__init__.py | 7 + apps/applications/urls/api_urls.py | 20 + apps/applications/urls/views_urls.py | 16 + apps/applications/views/__init__.py | 1 + apps/applications/views/remote_app.py | 99 ++ apps/assets/const.py | 4 + apps/assets/models/__init__.py | 1 + apps/common/fields/model.py | 5 +- apps/jumpserver/settings.py | 1 + apps/jumpserver/urls.py | 2 + apps/locale/zh/LC_MESSAGES/django.mo | Bin 71684 -> 74215 bytes apps/locale/zh/LC_MESSAGES/django.po | 1211 ++++++++++------- apps/perms/api/__init__.py | 3 +- .../{permission.py => asset_permission.py} | 0 apps/perms/api/remote_app_permission.py | 101 ++ apps/perms/api/user_group_permission.py | 24 +- apps/perms/api/user_permission.py | 86 +- apps/perms/forms/__init__.py | 5 + .../{forms.py => forms/asset_permission.py} | 6 +- apps/perms/forms/remote_app_permission.py | 49 + apps/perms/hands.py | 7 +- .../migrations/0005_auto_20190520_1904.py | 45 + apps/perms/mixins.py | 40 + apps/perms/models/__init__.py | 5 + .../{models.py => models/asset_permission.py} | 7 +- apps/perms/models/remote_app_permission.py | 75 + apps/perms/serializers/__init__.py | 5 + .../asset_permission.py} | 4 +- .../serializers/remote_app_permission.py | 36 + .../remote_app_permission_create_update.html | 120 ++ .../perms/remote_app_permission_detail.html | 169 +++ .../perms/remote_app_permission_list.html | 93 ++ .../remote_app_permission_remote_app.html | 164 +++ .../perms/remote_app_permission_user.html | 256 ++++ apps/perms/urls/api_urls.py | 47 +- apps/perms/urls/views_urls.py | 9 + apps/perms/utils/__init__.py | 5 + .../{utils.py => utils/asset_permission.py} | 18 +- apps/perms/utils/remote_app_permission.py | 81 ++ apps/perms/views/__init__.py | 5 + .../{views.py => views/asset_permission.py} | 19 +- apps/perms/views/remote_app_permission.py | 144 ++ apps/templates/_nav.html | 11 + apps/templates/_nav_user.html | 12 + 64 files changed, 3355 insertions(+), 509 deletions(-) create mode 100644 apps/applications/__init__.py create mode 100644 apps/applications/admin.py create mode 100644 apps/applications/api/__init__.py create mode 100644 apps/applications/api/remote_app.py create mode 100644 apps/applications/apps.py create mode 100644 apps/applications/const.py create mode 100644 apps/applications/forms/__init__.py create mode 100644 apps/applications/forms/remote_app.py create mode 100644 apps/applications/hands.py create mode 100644 apps/applications/migrations/0001_initial.py create mode 100644 apps/applications/migrations/__init__.py create mode 100644 apps/applications/models/__init__.py create mode 100644 apps/applications/models/remote_app.py create mode 100644 apps/applications/serializers/__init__.py create mode 100644 apps/applications/serializers/remote_app.py create mode 100644 apps/applications/templates/applications/remote_app_create_update.html create mode 100644 apps/applications/templates/applications/remote_app_detail.html create mode 100644 apps/applications/templates/applications/remote_app_list.html create mode 100644 apps/applications/templates/applications/user_remote_app_list.html create mode 100644 apps/applications/tests.py create mode 100644 apps/applications/urls/__init__.py create mode 100644 apps/applications/urls/api_urls.py create mode 100644 apps/applications/urls/views_urls.py create mode 100644 apps/applications/views/__init__.py create mode 100644 apps/applications/views/remote_app.py rename apps/perms/api/{permission.py => asset_permission.py} (100%) create mode 100644 apps/perms/api/remote_app_permission.py create mode 100644 apps/perms/forms/__init__.py rename apps/perms/{forms.py => forms/asset_permission.py} (97%) create mode 100644 apps/perms/forms/remote_app_permission.py create mode 100644 apps/perms/migrations/0005_auto_20190520_1904.py create mode 100644 apps/perms/models/__init__.py rename apps/perms/{models.py => models/asset_permission.py} (97%) create mode 100644 apps/perms/models/remote_app_permission.py create mode 100644 apps/perms/serializers/__init__.py rename apps/perms/{serializers.py => serializers/asset_permission.py} (97%) create mode 100644 apps/perms/serializers/remote_app_permission.py create mode 100644 apps/perms/templates/perms/remote_app_permission_create_update.html create mode 100644 apps/perms/templates/perms/remote_app_permission_detail.html create mode 100644 apps/perms/templates/perms/remote_app_permission_list.html create mode 100644 apps/perms/templates/perms/remote_app_permission_remote_app.html create mode 100644 apps/perms/templates/perms/remote_app_permission_user.html create mode 100644 apps/perms/utils/__init__.py rename apps/perms/{utils.py => utils/asset_permission.py} (98%) create mode 100644 apps/perms/utils/remote_app_permission.py create mode 100644 apps/perms/views/__init__.py rename apps/perms/{views.py => views/asset_permission.py} (90%) create mode 100644 apps/perms/views/remote_app_permission.py diff --git a/apps/applications/__init__.py b/apps/applications/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/applications/admin.py b/apps/applications/admin.py new file mode 100644 index 000000000..8c38f3f3d --- /dev/null +++ b/apps/applications/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/applications/api/__init__.py b/apps/applications/api/__init__.py new file mode 100644 index 000000000..e6bc7adb4 --- /dev/null +++ b/apps/applications/api/__init__.py @@ -0,0 +1 @@ +from .remote_app import * diff --git a/apps/applications/api/remote_app.py b/apps/applications/api/remote_app.py new file mode 100644 index 000000000..c41c5d100 --- /dev/null +++ b/apps/applications/api/remote_app.py @@ -0,0 +1,31 @@ +# coding: utf-8 +# + + +from rest_framework import generics +from rest_framework.pagination import LimitOffsetPagination +from rest_framework_bulk import BulkModelViewSet + +from ..hands import IsOrgAdmin, IsAppUser +from ..models import RemoteApp +from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer + + +__all__ = [ + 'RemoteAppViewSet', 'RemoteAppConnectionInfoApi', +] + + +class RemoteAppViewSet(BulkModelViewSet): + filter_fields = ('name',) + search_fields = filter_fields + permission_classes = (IsOrgAdmin,) + queryset = RemoteApp.objects.all() + serializer_class = RemoteAppSerializer + pagination_class = LimitOffsetPagination + + +class RemoteAppConnectionInfoApi(generics.RetrieveAPIView): + queryset = RemoteApp.objects.all() + permission_classes = (IsAppUser, ) + serializer_class = RemoteAppConnectionInfoSerializer diff --git a/apps/applications/apps.py b/apps/applications/apps.py new file mode 100644 index 000000000..3c22ddedc --- /dev/null +++ b/apps/applications/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class ApplicationsConfig(AppConfig): + name = 'applications' diff --git a/apps/applications/const.py b/apps/applications/const.py new file mode 100644 index 000000000..b64b1a14b --- /dev/null +++ b/apps/applications/const.py @@ -0,0 +1,68 @@ +# coding: utf-8 +# + +from django.utils.translation import ugettext_lazy as _ + + +# RemoteApp +REMOTE_APP_BOOT_PROGRAM_NAME = '||jmservisor' + +REMOTE_APP_TYPE_CHROME = 'chrome' +REMOTE_APP_TYPE_MYSQL_WORKBENCH = 'mysql_workbench' +REMOTE_APP_TYPE_VMWARE_CLIENT = 'vmware_client' +REMOTE_APP_TYPE_CUSTOM = 'custom' + +REMOTE_APP_TYPE_CHOICES = ( + ( + _('Browser'), + ( + (REMOTE_APP_TYPE_CHROME, 'Chrome'), + ) + ), + ( + _('Database tools'), + ( + (REMOTE_APP_TYPE_MYSQL_WORKBENCH, 'MySQL Workbench'), + ) + ), + ( + _('Virtualization tools'), + ( + (REMOTE_APP_TYPE_VMWARE_CLIENT, 'VMware Client'), + ) + ), + (REMOTE_APP_TYPE_CUSTOM, _('Custom')), + +) + +# Fields attribute write_only default => False + +REMOTE_APP_TYPE_CHROME_FIELDS = [ + {'name': 'chrome_target'}, + {'name': 'chrome_username'}, + {'name': 'chrome_password', 'write_only': True} +] +REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS = [ + {'name': 'mysql_workbench_ip'}, + {'name': 'mysql_workbench_name'}, + {'name': 'mysql_workbench_username'}, + {'name': 'mysql_workbench_password', 'write_only': True} +] +REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS = [ + {'name': 'vmware_target'}, + {'name': 'vmware_username'}, + {'name': 'vmware_password', 'write_only': True} +] +REMOTE_APP_TYPE_CUSTOM_FIELDS = [ + {'name': 'custom_cmdline'}, + {'name': 'custom_target'}, + {'name': 'custom_username'}, + {'name': 'custom_password', 'write_only': True} +] + +REMOTE_APP_TYPE_MAP_FIELDS = { + REMOTE_APP_TYPE_CHROME: REMOTE_APP_TYPE_CHROME_FIELDS, + REMOTE_APP_TYPE_MYSQL_WORKBENCH: REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS, + REMOTE_APP_TYPE_VMWARE_CLIENT: REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS, + REMOTE_APP_TYPE_CUSTOM: REMOTE_APP_TYPE_CUSTOM_FIELDS +} diff --git a/apps/applications/forms/__init__.py b/apps/applications/forms/__init__.py new file mode 100644 index 000000000..e6bc7adb4 --- /dev/null +++ b/apps/applications/forms/__init__.py @@ -0,0 +1 @@ +from .remote_app import * diff --git a/apps/applications/forms/remote_app.py b/apps/applications/forms/remote_app.py new file mode 100644 index 000000000..88f1912e0 --- /dev/null +++ b/apps/applications/forms/remote_app.py @@ -0,0 +1,111 @@ +# coding: utf-8 +# + +from django.utils.translation import ugettext as _ +from django import forms + +from orgs.mixins import OrgModelForm +from assets.models import Asset, SystemUser + +from ..models import RemoteApp + + +__all__ = [ + 'RemoteAppCreateUpdateForm', +] + + +class RemoteAppTypeChromeForm(forms.ModelForm): + chrome_target = forms.CharField( + max_length=128, label=_('Target URL'), required=False + ) + chrome_username = forms.CharField( + max_length=128, label=_('Login username'), required=False + ) + chrome_password = forms.CharField( + widget=forms.PasswordInput, strip=True, + max_length=128, label=_('Login password'), required=False + ) + + +class RemoteAppTypeMySQLWorkbenchForm(forms.ModelForm): + mysql_workbench_ip = forms.CharField( + max_length=128, label=_('Database IP'), required=False + ) + mysql_workbench_name = forms.CharField( + max_length=128, label=_('Database name'), required=False + ) + mysql_workbench_username = forms.CharField( + max_length=128, label=_('Database username'), required=False + ) + mysql_workbench_password = forms.CharField( + widget=forms.PasswordInput, strip=True, + max_length=128, label=_('Database password'), required=False + ) + + +class RemoteAppTypeVMwareForm(forms.ModelForm): + vmware_target = forms.CharField( + max_length=128, label=_('Target address'), required=False + ) + vmware_username = forms.CharField( + max_length=128, label=_('Login username'), required=False + ) + vmware_password = forms.CharField( + widget=forms.PasswordInput, strip=True, + max_length=128, label=_('Login password'), required=False + ) + + +class RemoteAppTypeCustomForm(forms.ModelForm): + custom_cmdline = forms.CharField( + max_length=128, label=_('Operating parameter'), required=False + ) + custom_target = forms.CharField( + max_length=128, label=_('Target address'), required=False + ) + custom_username = forms.CharField( + max_length=128, label=_('Login username'), required=False + ) + custom_password = forms.CharField( + widget=forms.PasswordInput, strip=True, + max_length=128, label=_('Login password'), required=False + ) + + +class RemoteAppTypeForms( + RemoteAppTypeChromeForm, + RemoteAppTypeMySQLWorkbenchForm, + RemoteAppTypeVMwareForm, + RemoteAppTypeCustomForm +): + pass + + +class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm): + def __init__(self, *args, **kwargs): + # 过滤RDP资产和系统用户 + super().__init__(*args, **kwargs) + field_asset = self.fields['asset'] + field_asset.queryset = field_asset.queryset.filter( + protocol=Asset.PROTOCOL_RDP + ) + field_system_user = self.fields['system_user'] + field_system_user.queryset = field_system_user.queryset.filter( + protocol=SystemUser.PROTOCOL_RDP + ) + + class Meta: + model = RemoteApp + fields = [ + 'name', 'asset', 'system_user', 'type', 'path', 'comment' + ] + widgets = { + 'asset': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('Asset') + }), + 'system_user': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('System user') + }) + } + diff --git a/apps/applications/hands.py b/apps/applications/hands.py new file mode 100644 index 000000000..ffe1e35c5 --- /dev/null +++ b/apps/applications/hands.py @@ -0,0 +1,16 @@ +""" + jumpserver.__app__.hands.py + ~~~~~~~~~~~~~~~~~ + + This app depends other apps api, function .. should be import or write mack here. + + Other module of this app shouldn't connect with other app. + + :copyright: (c) 2014-2018 by Jumpserver Team. + :license: GPL v2, see LICENSE for more details. +""" + + +from common.permissions import AdminUserRequiredMixin +from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser +from users.models import User, UserGroup diff --git a/apps/applications/migrations/0001_initial.py b/apps/applications/migrations/0001_initial.py new file mode 100644 index 000000000..35d6dc103 --- /dev/null +++ b/apps/applications/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# Generated by Django 2.1.7 on 2019-05-20 11:04 + +import common.fields.model +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('assets', '0026_auto_20190325_2035'), + ] + + operations = [ + migrations.CreateModel( + name='RemoteApp', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('type', models.CharField(choices=[('Browser', (('chrome', 'Chrome'),)), ('Database tools', (('mysql_workbench', 'MySQL Workbench'),)), ('Virtualization tools', (('vmware_client', 'VMware Client'),)), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type')), + ('path', models.CharField(max_length=128, verbose_name='App path')), + ('params', common.fields.model.EncryptJsonDictTextField(blank=True, default={}, max_length=4096, null=True, verbose_name='Parameters')), + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')), + ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')), + ('system_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser', verbose_name='System user')), + ], + options={ + 'verbose_name': 'RemoteApp', + 'ordering': ('name',), + }, + ), + migrations.AlterUniqueTogether( + name='remoteapp', + unique_together={('org_id', 'name')}, + ), + ] diff --git a/apps/applications/migrations/__init__.py b/apps/applications/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/applications/models/__init__.py b/apps/applications/models/__init__.py new file mode 100644 index 000000000..e6bc7adb4 --- /dev/null +++ b/apps/applications/models/__init__.py @@ -0,0 +1 @@ +from .remote_app import * diff --git a/apps/applications/models/remote_app.py b/apps/applications/models/remote_app.py new file mode 100644 index 000000000..772d39834 --- /dev/null +++ b/apps/applications/models/remote_app.py @@ -0,0 +1,89 @@ +# coding: utf-8 +# + +import uuid +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from orgs.mixins import OrgModelMixin +from common.fields.model import EncryptJsonDictTextField + +from .. import const + + +__all__ = [ + 'RemoteApp', +] + + +class RemoteApp(OrgModelMixin): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, verbose_name=_('Name')) + asset = models.ForeignKey( + 'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset') + ) + system_user = models.ForeignKey( + 'assets.SystemUser', on_delete=models.CASCADE, + verbose_name=_('System user') + ) + type = models.CharField( + default=const.REMOTE_APP_TYPE_CHROME, + choices=const.REMOTE_APP_TYPE_CHOICES, + max_length=128, verbose_name=_('App type') + ) + path = models.CharField( + max_length=128, blank=False, null=False, + verbose_name=_('App path') + ) + params = EncryptJsonDictTextField( + max_length=4096, default={}, blank=True, null=True, + verbose_name=_('Parameters') + ) + created_by = models.CharField( + max_length=32, null=True, blank=True, verbose_name=_('Created by') + ) + date_created = models.DateTimeField( + auto_now_add=True, null=True, blank=True, verbose_name=_('Date created') + ) + comment = models.TextField( + max_length=128, default='', blank=True, verbose_name=_('Comment') + ) + + class Meta: + verbose_name = _("RemoteApp") + unique_together = [('org_id', 'name')] + ordering = ('name', ) + + def __str__(self): + return self.name + + @property + def parameters(self): + """ + 返回Guacamole需要的RemoteApp配置参数信息中的parameters参数 + """ + _parameters = list() + _parameters.append(self.type) + path = '\"%s\"' % self.path + _parameters.append(path) + for field in const.REMOTE_APP_TYPE_MAP_FIELDS[self.type]: + value = self.params.get(field['name']) + if value is None: + continue + _parameters.append(value) + _parameters = ' '.join(_parameters) + return _parameters + + @property + def asset_info(self): + return { + 'id': self.asset.id, + 'hostname': self.asset.hostname + } + + @property + def system_user_info(self): + return { + 'id': self.system_user.id, + 'name': self.system_user.name + } diff --git a/apps/applications/serializers/__init__.py b/apps/applications/serializers/__init__.py new file mode 100644 index 000000000..e6bc7adb4 --- /dev/null +++ b/apps/applications/serializers/__init__.py @@ -0,0 +1 @@ +from .remote_app import * diff --git a/apps/applications/serializers/remote_app.py b/apps/applications/serializers/remote_app.py new file mode 100644 index 000000000..6957d11d5 --- /dev/null +++ b/apps/applications/serializers/remote_app.py @@ -0,0 +1,103 @@ +# coding: utf-8 +# + + +from rest_framework import serializers + +from common.mixins import BulkSerializerMixin +from common.serializers import AdaptedBulkListSerializer + +from .. import const +from ..models import RemoteApp + + +__all__ = [ + 'RemoteAppSerializer', 'RemoteAppConnectionInfoSerializer', +] + + +class RemoteAppParamsDictField(serializers.DictField): + """ + RemoteApp field => params + """ + @staticmethod + def filter_attribute(attribute, instance): + """ + 过滤掉params字段值中write_only特性的key-value值 + For example, the chrome_password field is not returned when serializing + { + 'chrome_target': 'http://www.jumpserver.org/', + 'chrome_username': 'admin', + 'chrome_password': 'admin', + } + """ + for field in const.REMOTE_APP_TYPE_MAP_FIELDS[instance.type]: + if field.get('write_only', False): + attribute.pop(field['name'], None) + return attribute + + def get_attribute(self, instance): + """ + 序列化时调用 + """ + attribute = super().get_attribute(instance) + attribute = self.filter_attribute(attribute, instance) + return attribute + + @staticmethod + def filter_value(dictionary, value): + """ + 过滤掉不属于当前app_type所包含的key-value值 + """ + app_type = dictionary.get('type', const.REMOTE_APP_TYPE_CHROME) + fields = const.REMOTE_APP_TYPE_MAP_FIELDS[app_type] + fields_names = [field['name'] for field in fields] + no_need_keys = [k for k in value.keys() if k not in fields_names] + for k in no_need_keys: + value.pop(k) + return value + + def get_value(self, dictionary): + """ + 反序列化时调用 + """ + value = super().get_value(dictionary) + value = self.filter_value(dictionary, value) + return value + + +class RemoteAppSerializer(BulkSerializerMixin, serializers.ModelSerializer): + params = RemoteAppParamsDictField() + + class Meta: + model = RemoteApp + list_serializer_class = AdaptedBulkListSerializer + fields = [ + 'id', 'name', 'asset', 'system_user', 'type', 'path', 'params', + 'comment', 'created_by', 'date_created', 'asset_info', + 'system_user_info', 'get_type_display', + ] + read_only_fields = [ + 'created_by', 'date_created', 'asset_info', + 'system_user_info', 'get_type_display' + ] + + +class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer): + parameter_remote_app = serializers.SerializerMethodField() + + class Meta: + model = RemoteApp + fields = [ + 'id', 'name', 'asset', 'system_user', 'parameter_remote_app', + ] + read_only_fields = ['parameter_remote_app'] + + @staticmethod + def get_parameter_remote_app(obj): + parameter = { + 'program': const.REMOTE_APP_BOOT_PROGRAM_NAME, + 'working_directory': '', + 'parameters': obj.parameters, + } + return parameter diff --git a/apps/applications/templates/applications/remote_app_create_update.html b/apps/applications/templates/applications/remote_app_create_update.html new file mode 100644 index 000000000..e64b8994c --- /dev/null +++ b/apps/applications/templates/applications/remote_app_create_update.html @@ -0,0 +1,91 @@ +{% extends '_base_create_update.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} + +{% block form %} +
+ {% if form.non_field_errors %} +
+ {{ form.non_field_errors }} +
+ {% endif %} + {% csrf_token %} + {% bootstrap_field form.name layout="horizontal" %} + {% bootstrap_field form.asset layout="horizontal" %} + {% bootstrap_field form.system_user layout="horizontal" %} + {% bootstrap_field form.type layout="horizontal" %} + {% bootstrap_field form.path layout="horizontal" %} + +
+ + {# chrome #} +
+ {% bootstrap_field form.chrome_target layout="horizontal" %} + {% bootstrap_field form.chrome_username layout="horizontal" %} + {% bootstrap_field form.chrome_password layout="horizontal" %} +
+ + {# mysql workbench #} +
+ {% bootstrap_field form.mysql_workbench_ip layout="horizontal" %} + {% bootstrap_field form.mysql_workbench_name layout="horizontal" %} + {% bootstrap_field form.mysql_workbench_username layout="horizontal" %} + {% bootstrap_field form.mysql_workbench_password layout="horizontal" %} +
+ + {# vmware #} +
+ {% bootstrap_field form.vmware_target layout="horizontal" %} + {% bootstrap_field form.vmware_username layout="horizontal" %} + {% bootstrap_field form.vmware_password layout="horizontal" %} +
+ + {# custom #} +
+ {% bootstrap_field form.custom_cmdline layout="horizontal" %} + {% bootstrap_field form.custom_target layout="horizontal" %} + {% bootstrap_field form.custom_username layout="horizontal" %} + {% bootstrap_field form.custom_password layout="horizontal" %} +
+ + {% bootstrap_field form.comment layout="horizontal" %} +
+
+
+ + + +
+
+ +
+{% endblock %} + +{% block custom_foot_js %} + +{% endblock %} \ No newline at end of file diff --git a/apps/applications/templates/applications/remote_app_detail.html b/apps/applications/templates/applications/remote_app_detail.html new file mode 100644 index 000000000..d006bb51a --- /dev/null +++ b/apps/applications/templates/applications/remote_app_detail.html @@ -0,0 +1,109 @@ +{% extends 'base.html' %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} + + +{% endblock %} + +{% block content %} +
+
+
+
+ +
+
+
+
+ {{ remote_app.name }} +
+ + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans 'Name' %}:{{ remote_app.name }}
{% trans 'Asset' %}:{{ remote_app.asset.hostname }}
{% trans 'System user' %}:{{ remote_app.system_user.name }}
{% trans 'App type' %}:{{ remote_app.get_type_display }}
{% trans 'App path' %}:{{ remote_app.path }}
{% trans 'Date created' %}:{{ remote_app.date_created }}
{% trans 'Created by' %}:{{ remote_app.created_by }}
{% trans 'Comment' %}:{{ remote_app.comment }}
+
+
+
+
+
+
+
+
+{% endblock %} +{% block custom_foot_js %} + +{% endblock %} \ No newline at end of file diff --git a/apps/applications/templates/applications/remote_app_list.html b/apps/applications/templates/applications/remote_app_list.html new file mode 100644 index 000000000..c2f64235e --- /dev/null +++ b/apps/applications/templates/applications/remote_app_list.html @@ -0,0 +1,90 @@ +{% extends '_base_list.html' %} +{% load i18n static %} +{% block help_message %} +
+ {% trans 'Before using this feature, make sure that the application loader has been uploaded to the application server and successfully published as a RemoteApp application' %} + {% trans 'Download application loader' %} +
+{% endblock %} +{% block table_search %}{% endblock %} +{% block table_container %} + + + + + + + + + + + + + + + +
+ + {% trans 'Name' %}{% trans 'App type' %}{% trans 'Asset' %}{% trans 'System user' %}{% trans 'Comment' %}{% trans 'Action' %}
+{% endblock %} +{% block content_bottom_left %}{% endblock %} +{% block custom_foot_js %} + +{% endblock %} \ No newline at end of file diff --git a/apps/applications/templates/applications/user_remote_app_list.html b/apps/applications/templates/applications/user_remote_app_list.html new file mode 100644 index 000000000..4199e805f --- /dev/null +++ b/apps/applications/templates/applications/user_remote_app_list.html @@ -0,0 +1,79 @@ +{% extends 'base.html' %} +{% load i18n static %} + +{% block custom_head_css_js %} + +{% endblock %} + +{% block content %} +
+ + + + + + + + + + + + + + +
+ + {% trans 'Name' %}{% trans 'App type' %}{% trans 'Asset' %}{% trans 'System user' %}{% trans 'Comment' %}{% trans 'Action' %}
+
+{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/applications/tests.py b/apps/applications/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/apps/applications/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/applications/urls/__init__.py b/apps/applications/urls/__init__.py new file mode 100644 index 000000000..3aab4972f --- /dev/null +++ b/apps/applications/urls/__init__.py @@ -0,0 +1,7 @@ +# coding: utf-8 +# + + +__all__ = [ + +] diff --git a/apps/applications/urls/api_urls.py b/apps/applications/urls/api_urls.py new file mode 100644 index 000000000..97487b5a1 --- /dev/null +++ b/apps/applications/urls/api_urls.py @@ -0,0 +1,20 @@ +# coding:utf-8 +# + +from django.urls import path +from rest_framework_bulk.routes import BulkRouter + +from .. import api + +app_name = 'applications' + +router = BulkRouter() +router.register(r'remote-app', api.RemoteAppViewSet, 'remote-app') + +urlpatterns = [ + path('remote-apps//connection-info/', + api.RemoteAppConnectionInfoApi.as_view(), + name='remote-app-connection-info') +] + +urlpatterns += router.urls diff --git a/apps/applications/urls/views_urls.py b/apps/applications/urls/views_urls.py new file mode 100644 index 000000000..3ffcffc5c --- /dev/null +++ b/apps/applications/urls/views_urls.py @@ -0,0 +1,16 @@ +# coding:utf-8 +from django.urls import path +from .. import views + +app_name = 'applications' + +urlpatterns = [ + # RemoteApp + path('remote-app/', views.RemoteAppListView.as_view(), name='remote-app-list'), + path('remote-app/create/', views.RemoteAppCreateView.as_view(), name='remote-app-create'), + path('remote-app//update/', views.RemoteAppUpdateView.as_view(), name='remote-app-update'), + path('remote-app//', views.RemoteAppDetailView.as_view(), name='remote-app-detail'), + # User RemoteApp view + path('user-remote-app/', views.UserRemoteAppListView.as_view(), name='user-remote-app-list') + +] diff --git a/apps/applications/views/__init__.py b/apps/applications/views/__init__.py new file mode 100644 index 000000000..e6bc7adb4 --- /dev/null +++ b/apps/applications/views/__init__.py @@ -0,0 +1 @@ +from .remote_app import * diff --git a/apps/applications/views/remote_app.py b/apps/applications/views/remote_app.py new file mode 100644 index 000000000..e21db3ad8 --- /dev/null +++ b/apps/applications/views/remote_app.py @@ -0,0 +1,99 @@ +# coding: utf-8 +# + +from django.utils.translation import ugettext as _ +from django.views.generic import TemplateView +from django.views.generic.edit import CreateView, UpdateView +from django.views.generic.detail import DetailView +from django.contrib.messages.views import SuccessMessageMixin +from django.contrib.auth.mixins import LoginRequiredMixin +from django.urls import reverse_lazy + + +from common.permissions import AdminUserRequiredMixin +from common.const import create_success_msg, update_success_msg + +from ..models import RemoteApp +from .. import forms + + +__all__ = [ + 'RemoteAppListView', 'RemoteAppCreateView', 'RemoteAppUpdateView', + 'RemoteAppDetailView', 'UserRemoteAppListView', +] + + +class RemoteAppListView(AdminUserRequiredMixin, TemplateView): + template_name = 'applications/remote_app_list.html' + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('RemoteApp list'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class RemoteAppCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): + template_name = 'applications/remote_app_create_update.html' + model = RemoteApp + form_class = forms.RemoteAppCreateUpdateForm + success_url = reverse_lazy('applications:remote-app-list') + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('Create RemoteApp'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + def get_success_message(self, cleaned_data): + return create_success_msg % ({'name': cleaned_data['name']}) + + +class RemoteAppUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): + template_name = 'applications/remote_app_create_update.html' + model = RemoteApp + form_class = forms.RemoteAppCreateUpdateForm + success_url = reverse_lazy('applications:remote-app-list') + + def get_initial(self): + return {k: v for k, v in self.object.params.items()} + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('Update RemoteApp'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + def get_success_message(self, cleaned_data): + return update_success_msg % ({'name': cleaned_data['name']}) + + +class RemoteAppDetailView(AdminUserRequiredMixin, DetailView): + template_name = 'applications/remote_app_detail.html' + model = RemoteApp + context_object_name = 'remote_app' + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('RemoteApp detail'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class UserRemoteAppListView(LoginRequiredMixin, TemplateView): + template_name = 'applications/user_remote_app_list.html' + + def get_context_data(self, **kwargs): + context = { + 'action': _('My RemoteApp'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) diff --git a/apps/assets/const.py b/apps/assets/const.py index a110683d0..0cd74ba0d 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- # +from django.utils.translation import ugettext_lazy as _ + + UPDATE_ASSETS_HARDWARE_TASKS = [ { 'name': "setup", @@ -51,3 +54,4 @@ TASK_OPTIONS = { CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}' + diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index b87a18796..c82c66a69 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -8,3 +8,4 @@ from .asset import * from .cmd_filter import * from .utils import * from .authbook import * +from applications.models.remote_app import * diff --git a/apps/common/fields/model.py b/apps/common/fields/model.py index b844d3517..c2bb1e0be 100644 --- a/apps/common/fields/model.py +++ b/apps/common/fields/model.py @@ -11,7 +11,7 @@ __all__ = [ 'JsonMixin', 'JsonDictMixin', 'JsonListMixin', 'JsonTypeMixin', 'JsonCharField', 'JsonTextField', 'JsonListCharField', 'JsonListTextField', 'JsonDictCharField', 'JsonDictTextField', 'EncryptCharField', - 'EncryptTextField', 'EncryptMixin', + 'EncryptTextField', 'EncryptMixin', 'EncryptJsonDictTextField', ] signer = get_signer() @@ -129,4 +129,7 @@ class EncryptCharField(EncryptMixin, models.CharField): super().__init__(*args, **kwargs) +class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField): + pass + diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index d97acb361..a6ec9c92a 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -67,6 +67,7 @@ INSTALLED_APPS = [ 'terminal.apps.TerminalConfig', 'audits.apps.AuditsConfig', 'authentication.apps.AuthenticationConfig', # authentication + 'applications.apps.ApplicationsConfig', 'rest_framework', 'rest_framework_swagger', 'drf_yasg', diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 5ef225828..a2538b6c0 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -20,6 +20,7 @@ api_v1 = [ path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')), path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')), path('authentication/v1/', include('authentication.urls.api_urls', namespace='api-auth')), + path('applications/v1/', include('applications.urls.api_urls', namespace='api-applications')), ] api_v2 = [ @@ -37,6 +38,7 @@ app_view_patterns = [ path('audits/', include('audits.urls.view_urls', namespace='audits')), path('orgs/', include('orgs.urls.views_urls', namespace='orgs')), path('auth/', include('authentication.urls.view_urls'), name='auth'), + path('applications/', include('applications.urls.views_urls', namespace='applications')), ] diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 4d188c93e46ee615c0be0c00ea39a0646ea361be..0d17f388ba3f785308213fd499c61003499de620 100644 GIT binary patch delta 23608 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 delta 21334 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\n" "Language-Team: Jumpserver team\n" @@ -17,6 +17,573 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#: applications/const.py:17 +msgid "Browser" +msgstr "浏览器" + +#: applications/const.py:23 +msgid "Database tools" +msgstr "数据库工具" + +#: applications/const.py:29 +msgid "Virtualization tools" +msgstr "虚拟化工具" + +#: applications/const.py:34 +msgid "Custom" +msgstr "自定义" + +#: applications/forms/remote_app.py:20 +msgid "Target URL" +msgstr "目标URL" + +#: applications/forms/remote_app.py:23 applications/forms/remote_app.py:52 +#: applications/forms/remote_app.py:68 +msgid "Login username" +msgstr "登录账号" + +#: applications/forms/remote_app.py:27 applications/forms/remote_app.py:56 +#: applications/forms/remote_app.py:72 +msgid "Login password" +msgstr "登录密码" + +#: applications/forms/remote_app.py:33 +msgid "Database IP" +msgstr "数据库IP" + +#: applications/forms/remote_app.py:36 +msgid "Database name" +msgstr "数据库名" + +#: applications/forms/remote_app.py:39 +msgid "Database username" +msgstr "数据库账号" + +#: applications/forms/remote_app.py:43 +msgid "Database password" +msgstr "数据库密码" + +#: applications/forms/remote_app.py:49 applications/forms/remote_app.py:65 +msgid "Target address" +msgstr "目标地址" + +#: applications/forms/remote_app.py:62 +msgid "Operating parameter" +msgstr "运行参数" + +#: applications/forms/remote_app.py:105 applications/models/remote_app.py:23 +#: applications/templates/applications/remote_app_detail.html:57 +#: applications/templates/applications/remote_app_list.html:22 +#: applications/templates/applications/user_remote_app_list.html:18 +#: assets/forms/domain.py:15 assets/forms/label.py:13 +#: assets/models/asset.py:279 assets/models/authbook.py:27 +#: assets/templates/assets/admin_user_list.html:28 +#: assets/templates/assets/domain_detail.html:60 +#: assets/templates/assets/domain_list.html:26 +#: assets/templates/assets/label_list.html:16 +#: assets/templates/assets/system_user_list.html:33 audits/models.py:19 +#: audits/templates/audits/ftp_log_list.html:41 +#: audits/templates/audits/ftp_log_list.html:71 +#: perms/forms/asset_permission.py:46 perms/models/asset_permission.py:55 +#: perms/templates/perms/asset_permission_create_update.html:45 +#: perms/templates/perms/asset_permission_list.html:56 +#: perms/templates/perms/asset_permission_list.html:125 +#: terminal/backends/command/models.py:13 terminal/models.py:155 +#: terminal/templates/terminal/command_list.html:40 +#: terminal/templates/terminal/command_list.html:73 +#: terminal/templates/terminal/session_list.html:41 +#: terminal/templates/terminal/session_list.html:72 +#: xpack/plugins/change_auth_plan/forms.py:114 +#: xpack/plugins/change_auth_plan/models.py:409 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:14 +#: xpack/plugins/cloud/models.py:187 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 +#: xpack/plugins/orgs/templates/orgs/org_list.html:16 +msgid "Asset" +msgstr "资产" + +#: applications/forms/remote_app.py:108 applications/models/remote_app.py:27 +#: applications/templates/applications/remote_app_detail.html:61 +#: applications/templates/applications/remote_app_list.html:23 +#: applications/templates/applications/user_remote_app_list.html:19 +#: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:168 +#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49 +#: audits/templates/audits/ftp_log_list.html:72 +#: perms/forms/asset_permission.py:52 perms/models/asset_permission.py:57 +#: perms/models/asset_permission.py:112 +#: perms/templates/perms/asset_permission_detail.html:140 +#: perms/templates/perms/asset_permission_list.html:58 +#: perms/templates/perms/asset_permission_list.html:79 +#: perms/templates/perms/asset_permission_list.html:131 templates/_nav.html:25 +#: terminal/backends/command/models.py:14 terminal/models.py:156 +#: terminal/templates/terminal/command_list.html:48 +#: terminal/templates/terminal/command_list.html:74 +#: terminal/templates/terminal/session_list.html:49 +#: terminal/templates/terminal/session_list.html:73 +#: xpack/plugins/orgs/templates/orgs/org_list.html:19 +msgid "System user" +msgstr "系统用户" + +#: applications/models/remote_app.py:21 +#: applications/templates/applications/remote_app_detail.html:53 +#: applications/templates/applications/remote_app_list.html:20 +#: applications/templates/applications/user_remote_app_list.html:16 +#: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:146 +#: assets/models/base.py:26 assets/models/cluster.py:18 +#: assets/models/cmd_filter.py:20 assets/models/domain.py:20 +#: assets/models/group.py:20 assets/models/label.py:18 +#: assets/templates/assets/admin_user_detail.html:56 +#: assets/templates/assets/admin_user_list.html:26 +#: assets/templates/assets/cmd_filter_detail.html:61 +#: assets/templates/assets/cmd_filter_list.html:24 +#: assets/templates/assets/domain_detail.html:56 +#: assets/templates/assets/domain_gateway_list.html:67 +#: assets/templates/assets/domain_list.html:25 +#: assets/templates/assets/label_list.html:14 +#: assets/templates/assets/system_user_detail.html:58 +#: assets/templates/assets/system_user_list.html:29 ops/models/adhoc.py:37 +#: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:27 +#: orgs/models.py:12 perms/models/asset_permission.py:22 +#: perms/models/asset_permission.py:52 perms/models/remote_app_permission.py:33 +#: perms/templates/perms/asset_permission_detail.html:62 +#: perms/templates/perms/asset_permission_list.html:53 +#: perms/templates/perms/asset_permission_list.html:72 +#: perms/templates/perms/asset_permission_user.html:54 +#: perms/templates/perms/remote_app_permission_detail.html:62 +#: perms/templates/perms/remote_app_permission_list.html:14 +#: perms/templates/perms/remote_app_permission_remote_app.html:53 +#: perms/templates/perms/remote_app_permission_user.html:53 +#: settings/models.py:29 +#: settings/templates/settings/_ldap_list_users_modal.html:38 +#: settings/templates/settings/command_storage_create.html:41 +#: settings/templates/settings/replay_storage_create.html:44 +#: settings/templates/settings/terminal_setting.html:80 +#: settings/templates/settings/terminal_setting.html:102 terminal/models.py:22 +#: terminal/models.py:241 terminal/templates/terminal/terminal_detail.html:43 +#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 +#: users/models/user.py:61 users/templates/users/_select_user_modal.html:13 +#: users/templates/users/user_detail.html:63 +#: users/templates/users/user_group_detail.html:55 +#: users/templates/users/user_group_list.html:12 +#: users/templates/users/user_list.html:23 +#: users/templates/users/user_profile.html:51 +#: users/templates/users/user_pubkey_update.html:53 +#: xpack/plugins/change_auth_plan/forms.py:97 +#: xpack/plugins/change_auth_plan/models.py:58 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 +#: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:50 +#: xpack/plugins/cloud/templates/cloud/account_list.html:12 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:52 +#: xpack/plugins/orgs/templates/orgs/org_list.html:12 +msgid "Name" +msgstr "名称" + +#: applications/models/remote_app.py:32 +#: applications/templates/applications/remote_app_detail.html:65 +#: applications/templates/applications/remote_app_list.html:21 +#: applications/templates/applications/user_remote_app_list.html:17 +msgid "App type" +msgstr "应用类型" + +#: applications/models/remote_app.py:36 +#: applications/templates/applications/remote_app_detail.html:69 +msgid "App path" +msgstr "应用路径" + +#: applications/models/remote_app.py:40 +msgid "Parameters" +msgstr "参数" + +#: applications/models/remote_app.py:43 +#: applications/templates/applications/remote_app_detail.html:77 +#: assets/models/asset.py:109 assets/models/base.py:34 +#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25 +#: assets/models/cmd_filter.py:58 assets/models/group.py:21 +#: assets/templates/assets/admin_user_detail.html:68 +#: assets/templates/assets/asset_detail.html:128 +#: assets/templates/assets/cmd_filter_detail.html:77 +#: assets/templates/assets/domain_detail.html:72 +#: assets/templates/assets/system_user_detail.html:100 +#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 +#: perms/models/asset_permission.py:62 perms/models/asset_permission.py:115 +#: perms/models/remote_app_permission.py:40 +#: perms/templates/perms/asset_permission_detail.html:98 +#: perms/templates/perms/remote_app_permission_detail.html:90 +#: users/models/user.py:102 users/templates/users/user_detail.html:111 +#: xpack/plugins/change_auth_plan/models.py:103 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 +#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127 +msgid "Created by" +msgstr "创建者" + +#: applications/models/remote_app.py:46 +#: applications/templates/applications/remote_app_detail.html:73 +#: assets/models/asset.py:110 assets/models/cluster.py:26 +#: assets/models/domain.py:23 assets/models/group.py:22 +#: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:64 +#: assets/templates/assets/cmd_filter_detail.html:69 +#: assets/templates/assets/domain_detail.html:68 +#: assets/templates/assets/system_user_detail.html:96 +#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 +#: orgs/models.py:16 perms/models/asset_permission.py:63 +#: perms/models/asset_permission.py:116 +#: perms/models/remote_app_permission.py:41 +#: perms/templates/perms/asset_permission_detail.html:94 +#: perms/templates/perms/remote_app_permission_detail.html:86 +#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 +#: users/templates/users/user_group_detail.html:63 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105 +#: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:128 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:77 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:60 +msgid "Date created" +msgstr "创建日期" + +#: applications/models/remote_app.py:49 +#: applications/templates/applications/remote_app_detail.html:81 +#: applications/templates/applications/remote_app_list.html:24 +#: applications/templates/applications/user_remote_app_list.html:20 +#: assets/models/asset.py:111 assets/models/base.py:31 +#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22 +#: assets/models/cmd_filter.py:55 assets/models/domain.py:21 +#: assets/models/domain.py:53 assets/models/group.py:23 +#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72 +#: assets/templates/assets/admin_user_list.html:32 +#: assets/templates/assets/asset_detail.html:136 +#: assets/templates/assets/cmd_filter_detail.html:65 +#: assets/templates/assets/cmd_filter_list.html:27 +#: assets/templates/assets/cmd_filter_rule_list.html:62 +#: assets/templates/assets/domain_detail.html:76 +#: assets/templates/assets/domain_gateway_list.html:72 +#: assets/templates/assets/domain_list.html:28 +#: assets/templates/assets/system_user_detail.html:104 +#: assets/templates/assets/system_user_list.html:37 +#: assets/templates/assets/user_asset_list.html:171 ops/models/adhoc.py:43 +#: orgs/models.py:17 perms/models/asset_permission.py:64 +#: perms/models/asset_permission.py:117 +#: perms/models/remote_app_permission.py:42 +#: perms/templates/perms/asset_permission_detail.html:102 +#: perms/templates/perms/remote_app_permission_detail.html:94 +#: settings/models.py:34 terminal/models.py:32 +#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15 +#: users/models/user.py:94 users/templates/users/user_detail.html:127 +#: users/templates/users/user_group_detail.html:67 +#: users/templates/users/user_group_list.html:14 +#: users/templates/users/user_profile.html:134 +#: xpack/plugins/change_auth_plan/models.py:99 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 +#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:70 +#: xpack/plugins/cloud/templates/cloud/account_list.html:15 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:64 +#: xpack/plugins/orgs/templates/orgs/org_list.html:22 +msgid "Comment" +msgstr "备注" + +#: applications/models/remote_app.py:53 perms/forms/remote_app_permission.py:37 +#: perms/models/remote_app_permission.py:36 +#: perms/templates/perms/remote_app_permission_create_update.html:48 +#: perms/templates/perms/remote_app_permission_detail.html:27 +#: perms/templates/perms/remote_app_permission_list.html:17 +#: perms/templates/perms/remote_app_permission_remote_app.html:26 +#: perms/templates/perms/remote_app_permission_user.html:26 +#: templates/_nav.html:35 templates/_nav_user.html:14 +msgid "RemoteApp" +msgstr "远程应用" + +#: applications/templates/applications/remote_app_create_update.html:56 +#: assets/templates/assets/_system_user.html:75 +#: assets/templates/assets/admin_user_create_update.html:45 +#: assets/templates/assets/asset_bulk_update.html:23 +#: assets/templates/assets/asset_create.html:67 +#: assets/templates/assets/asset_update.html:71 +#: assets/templates/assets/cmd_filter_create_update.html:15 +#: assets/templates/assets/cmd_filter_rule_create_update.html:40 +#: assets/templates/assets/domain_create_update.html:16 +#: assets/templates/assets/gateway_create_update.html:58 +#: assets/templates/assets/label_create_update.html:18 +#: perms/templates/perms/asset_permission_create_update.html:83 +#: perms/templates/perms/remote_app_permission_create_update.html:83 +#: settings/templates/settings/basic_setting.html:61 +#: settings/templates/settings/command_storage_create.html:79 +#: settings/templates/settings/email_setting.html:62 +#: settings/templates/settings/ldap_setting.html:61 +#: settings/templates/settings/replay_storage_create.html:152 +#: settings/templates/settings/security_setting.html:70 +#: settings/templates/settings/terminal_setting.html:68 +#: terminal/templates/terminal/terminal_update.html:45 +#: users/templates/users/_user.html:50 +#: users/templates/users/user_bulk_update.html:23 +#: users/templates/users/user_detail.html:176 +#: users/templates/users/user_password_update.html:71 +#: users/templates/users/user_profile.html:204 +#: users/templates/users/user_profile_update.html:63 +#: users/templates/users/user_pubkey_update.html:70 +#: users/templates/users/user_pubkey_update.html:76 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:71 +#: xpack/plugins/cloud/templates/cloud/account_create_update.html:33 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:35 +#: xpack/plugins/interface/templates/interface/interface.html:72 +msgid "Reset" +msgstr "重置" + +#: applications/templates/applications/remote_app_create_update.html:58 +#: assets/templates/assets/_system_user.html:76 +#: assets/templates/assets/admin_user_create_update.html:46 +#: assets/templates/assets/asset_bulk_update.html:24 +#: assets/templates/assets/asset_create.html:68 +#: assets/templates/assets/asset_list.html:113 +#: assets/templates/assets/asset_update.html:72 +#: assets/templates/assets/cmd_filter_create_update.html:16 +#: assets/templates/assets/cmd_filter_rule_create_update.html:41 +#: assets/templates/assets/domain_create_update.html:17 +#: assets/templates/assets/gateway_create_update.html:59 +#: assets/templates/assets/label_create_update.html:19 +#: audits/templates/audits/login_log_list.html:89 +#: perms/templates/perms/asset_permission_create_update.html:84 +#: perms/templates/perms/remote_app_permission_create_update.html:84 +#: settings/templates/settings/basic_setting.html:62 +#: settings/templates/settings/command_storage_create.html:80 +#: settings/templates/settings/email_setting.html:63 +#: settings/templates/settings/ldap_setting.html:64 +#: settings/templates/settings/replay_storage_create.html:153 +#: settings/templates/settings/security_setting.html:71 +#: settings/templates/settings/terminal_setting.html:70 +#: terminal/templates/terminal/command_list.html:103 +#: terminal/templates/terminal/session_list.html:126 +#: terminal/templates/terminal/terminal_update.html:46 +#: users/templates/users/_user.html:51 +#: users/templates/users/forgot_password.html:42 +#: users/templates/users/user_bulk_update.html:24 +#: users/templates/users/user_list.html:45 +#: users/templates/users/user_password_update.html:72 +#: users/templates/users/user_profile_update.html:64 +#: users/templates/users/user_pubkey_update.html:77 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:72 +#: xpack/plugins/interface/templates/interface/interface.html:74 +msgid "Submit" +msgstr "提交" + +#: applications/templates/applications/remote_app_detail.html:18 +#: assets/templates/assets/admin_user_assets.html:18 +#: assets/templates/assets/admin_user_detail.html:18 +#: assets/templates/assets/cmd_filter_detail.html:19 +#: assets/templates/assets/cmd_filter_rule_list.html:19 +#: assets/templates/assets/domain_detail.html:18 +#: assets/templates/assets/domain_gateway_list.html:20 +#: assets/templates/assets/system_user_asset.html:18 +#: assets/templates/assets/system_user_detail.html:18 +#: ops/templates/ops/adhoc_history.html:130 +#: ops/templates/ops/task_adhoc.html:116 +#: ops/templates/ops/task_history.html:136 +#: perms/templates/perms/asset_permission_asset.html:18 +#: perms/templates/perms/asset_permission_detail.html:18 +#: perms/templates/perms/asset_permission_user.html:18 +#: perms/templates/perms/remote_app_permission_detail.html:18 +#: perms/templates/perms/remote_app_permission_remote_app.html:17 +#: perms/templates/perms/remote_app_permission_user.html:17 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:17 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:20 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:17 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:106 +#: xpack/plugins/change_auth_plan/views.py:83 +msgid "Detail" +msgstr "详情" + +#: applications/templates/applications/remote_app_detail.html:21 +#: applications/templates/applications/remote_app_list.html:56 +#: assets/templates/assets/admin_user_detail.html:24 +#: assets/templates/assets/admin_user_list.html:88 +#: assets/templates/assets/asset_detail.html:27 +#: assets/templates/assets/asset_list.html:178 +#: assets/templates/assets/cmd_filter_detail.html:29 +#: assets/templates/assets/cmd_filter_list.html:58 +#: assets/templates/assets/cmd_filter_rule_list.html:86 +#: assets/templates/assets/domain_detail.html:24 +#: assets/templates/assets/domain_detail.html:103 +#: assets/templates/assets/domain_gateway_list.html:97 +#: assets/templates/assets/domain_list.html:54 +#: assets/templates/assets/label_list.html:39 +#: assets/templates/assets/system_user_detail.html:26 +#: assets/templates/assets/system_user_list.html:93 audits/models.py:33 +#: perms/templates/perms/asset_permission_detail.html:30 +#: perms/templates/perms/asset_permission_list.html:181 +#: perms/templates/perms/remote_app_permission_detail.html:30 +#: perms/templates/perms/remote_app_permission_list.html:59 +#: terminal/templates/terminal/terminal_detail.html:16 +#: terminal/templates/terminal/terminal_list.html:72 +#: users/templates/users/user_detail.html:25 +#: users/templates/users/user_group_detail.html:28 +#: users/templates/users/user_group_list.html:45 +#: users/templates/users/user_list.html:83 +#: users/templates/users/user_list.html:86 +#: users/templates/users/user_profile.html:177 +#: users/templates/users/user_profile.html:187 +#: users/templates/users/user_profile.html:196 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:55 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:23 +#: xpack/plugins/cloud/templates/cloud/account_list.html:39 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:25 +#: xpack/plugins/orgs/templates/orgs/org_list.html:87 +msgid "Update" +msgstr "更新" + +#: applications/templates/applications/remote_app_detail.html:25 +#: applications/templates/applications/remote_app_list.html:57 +#: assets/templates/assets/admin_user_detail.html:28 +#: assets/templates/assets/admin_user_list.html:89 +#: assets/templates/assets/asset_detail.html:31 +#: assets/templates/assets/asset_list.html:179 +#: assets/templates/assets/cmd_filter_detail.html:33 +#: assets/templates/assets/cmd_filter_list.html:59 +#: assets/templates/assets/cmd_filter_rule_list.html:87 +#: assets/templates/assets/domain_detail.html:28 +#: assets/templates/assets/domain_detail.html:104 +#: assets/templates/assets/domain_gateway_list.html:98 +#: assets/templates/assets/domain_list.html:55 +#: assets/templates/assets/label_list.html:40 +#: assets/templates/assets/system_user_detail.html:30 +#: assets/templates/assets/system_user_list.html:94 audits/models.py:34 +#: ops/templates/ops/task_list.html:64 +#: perms/templates/perms/asset_permission_detail.html:34 +#: perms/templates/perms/asset_permission_list.html:182 +#: perms/templates/perms/remote_app_permission_detail.html:34 +#: perms/templates/perms/remote_app_permission_list.html:60 +#: settings/templates/settings/terminal_setting.html:90 +#: settings/templates/settings/terminal_setting.html:112 +#: terminal/templates/terminal/terminal_list.html:74 +#: users/templates/users/user_detail.html:30 +#: users/templates/users/user_group_detail.html:32 +#: users/templates/users/user_group_list.html:47 +#: users/templates/users/user_list.html:91 +#: users/templates/users/user_list.html:95 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:57 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:27 +#: xpack/plugins/cloud/templates/cloud/account_list.html:41 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:29 +#: xpack/plugins/orgs/templates/orgs/org_list.html:89 +msgid "Delete" +msgstr "删除" + +#: applications/templates/applications/remote_app_list.html:5 +msgid "" +"Before using this feature, make sure that the application loader has been " +"uploaded to the application server and successfully published as a RemoteApp " +"application" +msgstr "" +"使用此功能前,请确保已将应用加载器上传到应用服务器并成功发布为一个RemoteApp应" +"用" + +#: applications/templates/applications/remote_app_list.html:6 +msgid "Download application loader" +msgstr "下载应用加载器" + +#: applications/templates/applications/remote_app_list.html:12 +#: applications/views/remote_app.py:47 +msgid "Create RemoteApp" +msgstr "创建远程应用" + +#: applications/templates/applications/remote_app_list.html:25 +#: applications/templates/applications/user_remote_app_list.html:21 +#: assets/models/cmd_filter.py:54 +#: assets/templates/assets/admin_user_assets.html:52 +#: assets/templates/assets/admin_user_list.html:33 +#: assets/templates/assets/asset_asset_user_list.html:48 +#: assets/templates/assets/asset_list.html:96 +#: assets/templates/assets/cmd_filter_list.html:28 +#: assets/templates/assets/cmd_filter_rule_list.html:63 +#: assets/templates/assets/domain_gateway_list.html:73 +#: assets/templates/assets/domain_list.html:29 +#: assets/templates/assets/label_list.html:17 +#: assets/templates/assets/system_user_asset.html:54 +#: assets/templates/assets/system_user_list.html:38 +#: assets/templates/assets/user_asset_list.html:48 audits/models.py:38 +#: 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/task_history.html:65 ops/templates/ops/task_list.html:34 +#: perms/forms/asset_permission.py:55 perms/models/asset_permission.py:26 +#: perms/models/asset_permission.py:58 +#: perms/templates/perms/asset_permission_create_update.html:50 +#: perms/templates/perms/asset_permission_list.html:60 +#: perms/templates/perms/asset_permission_list.html:134 +#: perms/templates/perms/remote_app_permission_list.html:19 +#: settings/templates/settings/terminal_setting.html:82 +#: settings/templates/settings/terminal_setting.html:104 +#: terminal/templates/terminal/session_list.html:81 +#: terminal/templates/terminal/terminal_list.html:36 +#: users/templates/users/user_group_list.html:15 +#: users/templates/users/user_list.html:29 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:18 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:20 +#: xpack/plugins/cloud/templates/cloud/account_list.html:16 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18 +#: xpack/plugins/orgs/templates/orgs/org_list.html:23 +msgid "Action" +msgstr "动作" + +#: applications/templates/applications/user_remote_app_list.html:57 +#: assets/templates/assets/user_asset_list.html:100 perms/const.py:19 +msgid "Connect" +msgstr "连接" + +#: applications/views/remote_app.py:31 applications/views/remote_app.py:46 +#: applications/views/remote_app.py:67 applications/views/remote_app.py:84 +#: assets/models/user.py:134 +#: assets/templates/assets/_asset_group_bulk_update_modal.html:11 +#: assets/templates/assets/system_user_asset.html:22 +#: 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:63 assets/views/admin_user.py:78 +#: assets/views/admin_user.py:102 assets/views/asset.py:51 +#: assets/views/asset.py:67 assets/views/asset.py:104 assets/views/asset.py:145 +#: assets/views/asset.py:162 assets/views/asset.py:186 +#: assets/views/cmd_filter.py:30 assets/views/cmd_filter.py:46 +#: assets/views/cmd_filter.py:62 assets/views/cmd_filter.py:78 +#: assets/views/cmd_filter.py:97 assets/views/cmd_filter.py:130 +#: assets/views/cmd_filter.py:163 assets/views/domain.py:29 +#: assets/views/domain.py:45 assets/views/domain.py:61 +#: assets/views/domain.py:74 assets/views/domain.py:98 +#: assets/views/domain.py:126 assets/views/domain.py:145 +#: assets/views/label.py:26 assets/views/label.py:43 assets/views/label.py:69 +#: assets/views/system_user.py:28 assets/views/system_user.py:44 +#: assets/views/system_user.py:60 assets/views/system_user.py:74 +#: templates/_nav.html:19 xpack/plugins/change_auth_plan/models.py:65 +msgid "Assets" +msgstr "资产管理" + +#: applications/views/remote_app.py:32 +msgid "RemoteApp list" +msgstr "远程应用列表" + +#: applications/views/remote_app.py:68 +msgid "Update RemoteApp" +msgstr "更新远程应用" + +#: applications/views/remote_app.py:85 +msgid "RemoteApp detail" +msgstr "远程应用详情" + +#: applications/views/remote_app.py:96 +msgid "My RemoteApp" +msgstr "我的远程应用" + #: assets/api/asset.py:112 msgid "Please select assets that need to be updated" msgstr "请选择需要更新的资产" @@ -36,7 +603,8 @@ msgstr "测试节点下资产是否可连接: {}" #: assets/forms/asset.py:27 assets/models/asset.py:80 assets/models/user.py:133 #: assets/templates/assets/asset_detail.html:194 #: assets/templates/assets/asset_detail.html:202 -#: assets/templates/assets/system_user_asset.html:95 perms/models.py:51 +#: assets/templates/assets/system_user_asset.html:95 +#: perms/models/asset_permission.py:56 #: xpack/plugins/change_auth_plan/models.py:69 msgid "Nodes" msgstr "节点管理" @@ -72,8 +640,9 @@ msgstr "网域" #: assets/forms/asset.py:41 assets/forms/asset.py:63 assets/forms/asset.py:77 #: assets/forms/asset.py:112 assets/models/node.py:31 #: assets/templates/assets/asset_create.html:30 -#: assets/templates/assets/asset_update.html:35 perms/forms.py:45 -#: perms/forms.py:55 perms/models.py:105 +#: assets/templates/assets/asset_update.html:35 +#: perms/forms/asset_permission.py:49 perms/forms/asset_permission.py:59 +#: perms/models/asset_permission.py:110 #: perms/templates/perms/asset_permission_list.html:57 #: perms/templates/perms/asset_permission_list.html:78 #: perms/templates/perms/asset_permission_list.html:128 @@ -111,36 +680,6 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域, msgid "Select assets" msgstr "选择资产" -#: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:279 assets/models/authbook.py:27 -#: assets/templates/assets/admin_user_list.html:28 -#: assets/templates/assets/domain_detail.html:60 -#: assets/templates/assets/domain_list.html:26 -#: assets/templates/assets/label_list.html:16 -#: assets/templates/assets/system_user_list.html:33 audits/models.py:19 -#: audits/templates/audits/ftp_log_list.html:41 -#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:42 -#: perms/models.py:50 -#: perms/templates/perms/asset_permission_create_update.html:45 -#: perms/templates/perms/asset_permission_list.html:56 -#: perms/templates/perms/asset_permission_list.html:125 -#: terminal/backends/command/models.py:13 terminal/models.py:155 -#: terminal/templates/terminal/command_list.html:40 -#: terminal/templates/terminal/command_list.html:73 -#: terminal/templates/terminal/session_list.html:41 -#: terminal/templates/terminal/session_list.html:72 -#: xpack/plugins/change_auth_plan/forms.py:114 -#: xpack/plugins/change_auth_plan/models.py:409 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:14 -#: xpack/plugins/cloud/models.py:187 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 -#: xpack/plugins/orgs/templates/orgs/org_list.html:16 -msgid "Asset" -msgstr "资产" - #: assets/forms/domain.py:51 msgid "Password should not contain special characters" msgstr "不能包含特殊字符" @@ -149,54 +688,6 @@ msgstr "不能包含特殊字符" msgid "SSH gateway support proxy SSH,RDP,VNC" msgstr "SSH网关,支持代理SSH,RDP和VNC" -#: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:146 -#: assets/models/base.py:26 assets/models/cluster.py:18 -#: assets/models/cmd_filter.py:20 assets/models/domain.py:20 -#: assets/models/group.py:20 assets/models/label.py:18 -#: assets/templates/assets/admin_user_detail.html:56 -#: assets/templates/assets/admin_user_list.html:26 -#: assets/templates/assets/cmd_filter_detail.html:61 -#: assets/templates/assets/cmd_filter_list.html:24 -#: assets/templates/assets/domain_detail.html:56 -#: assets/templates/assets/domain_gateway_list.html:67 -#: assets/templates/assets/domain_list.html:25 -#: assets/templates/assets/label_list.html:14 -#: assets/templates/assets/system_user_detail.html:58 -#: assets/templates/assets/system_user_list.html:29 ops/models/adhoc.py:37 -#: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:27 -#: orgs/models.py:12 perms/models.py:17 perms/models.py:47 -#: perms/templates/perms/asset_permission_detail.html:62 -#: perms/templates/perms/asset_permission_list.html:53 -#: perms/templates/perms/asset_permission_list.html:72 -#: perms/templates/perms/asset_permission_user.html:54 settings/models.py:29 -#: settings/templates/settings/_ldap_list_users_modal.html:38 -#: settings/templates/settings/command_storage_create.html:41 -#: settings/templates/settings/replay_storage_create.html:44 -#: settings/templates/settings/terminal_setting.html:80 -#: settings/templates/settings/terminal_setting.html:102 terminal/models.py:22 -#: terminal/models.py:241 terminal/templates/terminal/terminal_detail.html:43 -#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 -#: users/models/user.py:61 users/templates/users/_select_user_modal.html:13 -#: users/templates/users/user_detail.html:63 -#: users/templates/users/user_group_detail.html:55 -#: users/templates/users/user_group_list.html:12 -#: users/templates/users/user_list.html:23 -#: users/templates/users/user_profile.html:51 -#: users/templates/users/user_pubkey_update.html:53 -#: xpack/plugins/change_auth_plan/forms.py:97 -#: xpack/plugins/change_auth_plan/models.py:58 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 -#: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:50 -#: xpack/plugins/cloud/templates/cloud/account_list.html:12 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:52 -#: xpack/plugins/orgs/templates/orgs/org_list.html:12 -msgid "Name" -msgstr "名称" - #: assets/forms/domain.py:74 assets/forms/user.py:85 assets/forms/user.py:147 #: assets/models/base.py:27 #: assets/templates/assets/_asset_user_auth_modal.html:15 @@ -212,6 +703,7 @@ msgstr "名称" #: authentication/templates/authentication/new_login.html:90 #: ops/models/adhoc.py:164 perms/templates/perms/asset_permission_list.html:74 #: perms/templates/perms/asset_permission_user.html:55 +#: perms/templates/perms/remote_app_permission_user.html:54 #: settings/templates/settings/_ldap_list_users_modal.html:37 users/forms.py:13 #: users/models/user.py:59 users/templates/users/_select_user_modal.html:14 #: users/templates/users/user_detail.html:67 @@ -434,79 +926,6 @@ msgstr "主机名原始" msgid "Labels" msgstr "标签管理" -#: assets/models/asset.py:109 assets/models/base.py:34 -#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25 -#: assets/models/cmd_filter.py:58 assets/models/group.py:21 -#: assets/templates/assets/admin_user_detail.html:68 -#: assets/templates/assets/asset_detail.html:128 -#: assets/templates/assets/cmd_filter_detail.html:77 -#: assets/templates/assets/domain_detail.html:72 -#: assets/templates/assets/system_user_detail.html:100 -#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:57 -#: perms/models.py:110 perms/templates/perms/asset_permission_detail.html:98 -#: users/models/user.py:102 users/templates/users/user_detail.html:111 -#: xpack/plugins/change_auth_plan/models.py:103 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 -#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127 -msgid "Created by" -msgstr "创建者" - -#: assets/models/asset.py:110 assets/models/cluster.py:26 -#: assets/models/domain.py:23 assets/models/group.py:22 -#: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:64 -#: assets/templates/assets/cmd_filter_detail.html:69 -#: assets/templates/assets/domain_detail.html:68 -#: assets/templates/assets/system_user_detail.html:96 -#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 -#: orgs/models.py:16 perms/models.py:58 perms/models.py:111 -#: perms/templates/perms/asset_permission_detail.html:94 -#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 -#: users/templates/users/user_group_detail.html:63 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105 -#: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:128 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:66 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:77 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:60 -msgid "Date created" -msgstr "创建日期" - -#: assets/models/asset.py:111 assets/models/base.py:31 -#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22 -#: assets/models/cmd_filter.py:55 assets/models/domain.py:21 -#: assets/models/domain.py:53 assets/models/group.py:23 -#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72 -#: assets/templates/assets/admin_user_list.html:32 -#: assets/templates/assets/asset_detail.html:136 -#: assets/templates/assets/cmd_filter_detail.html:65 -#: assets/templates/assets/cmd_filter_list.html:27 -#: assets/templates/assets/cmd_filter_rule_list.html:62 -#: assets/templates/assets/domain_detail.html:76 -#: assets/templates/assets/domain_gateway_list.html:72 -#: assets/templates/assets/domain_list.html:28 -#: assets/templates/assets/system_user_detail.html:104 -#: assets/templates/assets/system_user_list.html:37 -#: assets/templates/assets/user_asset_list.html:171 ops/models/adhoc.py:43 -#: orgs/models.py:17 perms/models.py:59 perms/models.py:112 -#: perms/templates/perms/asset_permission_detail.html:102 settings/models.py:34 -#: terminal/models.py:32 terminal/templates/terminal/terminal_detail.html:63 -#: users/models/group.py:15 users/models/user.py:94 -#: users/templates/users/user_detail.html:127 -#: users/templates/users/user_group_detail.html:67 -#: users/templates/users/user_group_list.html:14 -#: users/templates/users/user_profile.html:134 -#: xpack/plugins/change_auth_plan/models.py:99 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 -#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:70 -#: xpack/plugins/cloud/templates/cloud/account_list.html:15 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:64 -#: xpack/plugins/orgs/templates/orgs/org_list.html:22 -msgid "Comment" -msgstr "备注" - #: assets/models/asset.py:117 assets/models/base.py:38 #: assets/templates/assets/admin_user_list.html:30 #: assets/templates/assets/system_user_list.html:35 @@ -639,6 +1058,7 @@ msgstr "过滤器" #: assets/models/cmd_filter.py:50 #: assets/templates/assets/cmd_filter_rule_list.html:58 #: audits/templates/audits/login_log_list.html:52 +#: perms/templates/perms/remote_app_permission_remote_app.html:54 #: settings/templates/settings/command_storage_create.html:31 #: settings/templates/settings/replay_storage_create.html:31 #: settings/templates/settings/terminal_setting.html:81 @@ -665,42 +1085,6 @@ msgstr "内容" msgid "One line one command" msgstr "每行一个命令" -#: assets/models/cmd_filter.py:54 -#: assets/templates/assets/admin_user_assets.html:52 -#: assets/templates/assets/admin_user_list.html:33 -#: assets/templates/assets/asset_asset_user_list.html:48 -#: assets/templates/assets/asset_list.html:96 -#: assets/templates/assets/cmd_filter_list.html:28 -#: assets/templates/assets/cmd_filter_rule_list.html:63 -#: assets/templates/assets/domain_gateway_list.html:73 -#: assets/templates/assets/domain_list.html:29 -#: assets/templates/assets/label_list.html:17 -#: assets/templates/assets/system_user_asset.html:54 -#: assets/templates/assets/system_user_list.html:38 -#: assets/templates/assets/user_asset_list.html:48 audits/models.py:38 -#: 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/task_history.html:65 ops/templates/ops/task_list.html:34 -#: perms/forms.py:51 perms/models.py:21 perms/models.py:53 -#: perms/templates/perms/asset_permission_create_update.html:50 -#: perms/templates/perms/asset_permission_list.html:60 -#: perms/templates/perms/asset_permission_list.html:134 -#: settings/templates/settings/terminal_setting.html:82 -#: settings/templates/settings/terminal_setting.html:104 -#: terminal/templates/terminal/session_list.html:81 -#: terminal/templates/terminal/terminal_list.html:36 -#: users/templates/users/user_group_list.html:15 -#: users/templates/users/user_list.html:29 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:18 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:20 -#: xpack/plugins/cloud/templates/cloud/account_list.html:16 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18 -#: xpack/plugins/orgs/templates/orgs/org_list.html:23 -msgid "Action" -msgstr "动作" - #: assets/models/cmd_filter.py:64 msgid "Command filter rule" msgstr "命令过滤规则" @@ -728,13 +1112,16 @@ msgstr "默认资产组" #: audits/templates/audits/password_change_log_list.html:33 #: audits/templates/audits/password_change_log_list.html:50 #: ops/templates/ops/command_execution_list.html:35 -#: ops/templates/ops/command_execution_list.html:60 perms/forms.py:36 -#: perms/models.py:48 +#: ops/templates/ops/command_execution_list.html:60 +#: perms/forms/asset_permission.py:40 perms/forms/remote_app_permission.py:31 +#: perms/models/asset_permission.py:53 perms/models/remote_app_permission.py:34 #: perms/templates/perms/asset_permission_create_update.html:41 #: perms/templates/perms/asset_permission_list.html:54 -#: perms/templates/perms/asset_permission_list.html:119 templates/index.html:87 -#: terminal/backends/command/models.py:12 terminal/models.py:154 -#: terminal/templates/terminal/command_list.html:32 +#: perms/templates/perms/asset_permission_list.html:119 +#: perms/templates/perms/remote_app_permission_create_update.html:43 +#: perms/templates/perms/remote_app_permission_list.html:15 +#: templates/index.html:87 terminal/backends/command/models.py:12 +#: terminal/models.py:154 terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:71 users/forms.py:283 @@ -772,29 +1159,6 @@ msgstr "自动登录" msgid "Manually login" msgstr "手动登录" -#: assets/models/user.py:134 -#: assets/templates/assets/_asset_group_bulk_update_modal.html:11 -#: assets/templates/assets/system_user_asset.html:22 -#: 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:63 assets/views/admin_user.py:78 -#: assets/views/admin_user.py:102 assets/views/asset.py:51 -#: assets/views/asset.py:67 assets/views/asset.py:104 assets/views/asset.py:145 -#: assets/views/asset.py:162 assets/views/asset.py:186 -#: assets/views/cmd_filter.py:30 assets/views/cmd_filter.py:46 -#: assets/views/cmd_filter.py:62 assets/views/cmd_filter.py:78 -#: assets/views/cmd_filter.py:97 assets/views/cmd_filter.py:130 -#: assets/views/cmd_filter.py:163 assets/views/domain.py:29 -#: assets/views/domain.py:45 assets/views/domain.py:61 -#: assets/views/domain.py:74 assets/views/domain.py:98 -#: assets/views/domain.py:126 assets/views/domain.py:145 -#: assets/views/label.py:26 assets/views/label.py:43 assets/views/label.py:69 -#: assets/views/system_user.py:28 assets/views/system_user.py:44 -#: assets/views/system_user.py:60 assets/views/system_user.py:74 -#: templates/_nav.html:19 xpack/plugins/change_auth_plan/models.py:65 -msgid "Assets" -msgstr "资产管理" - #: assets/models/user.py:137 assets/templates/assets/_system_user.html:59 #: assets/templates/assets/system_user_detail.html:122 #: assets/templates/assets/system_user_update.html:10 @@ -814,23 +1178,6 @@ msgstr "Shell" msgid "Login mode" msgstr "登录模式" -#: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:168 -#: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49 -#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:48 -#: perms/models.py:52 perms/models.py:107 -#: perms/templates/perms/asset_permission_detail.html:140 -#: perms/templates/perms/asset_permission_list.html:58 -#: perms/templates/perms/asset_permission_list.html:79 -#: perms/templates/perms/asset_permission_list.html:131 templates/_nav.html:25 -#: terminal/backends/command/models.py:14 terminal/models.py:156 -#: terminal/templates/terminal/command_list.html:48 -#: terminal/templates/terminal/command_list.html:74 -#: terminal/templates/terminal/session_list.html:49 -#: terminal/templates/terminal/session_list.html:73 -#: xpack/plugins/orgs/templates/orgs/org_list.html:19 -msgid "System user" -msgstr "系统用户" - #: assets/models/utils.py:29 #, python-format msgid "%(value)s is not an even number" @@ -983,9 +1330,9 @@ msgid "Asset user auth" msgstr "资产用户信息" #: assets/templates/assets/_asset_user_view_auth_modal.html:14 -#: assets/templates/assets/_otp_verify_modal.html:8 audits/models.py:99 -#: audits/templates/audits/login_log_list.html:56 users/forms.py:142 -#: users/models/user.py:83 users/templates/users/first_login.html:45 +#: audits/models.py:99 audits/templates/audits/login_log_list.html:56 +#: users/forms.py:142 users/models/user.py:83 +#: users/templates/users/first_login.html:45 msgid "MFA" msgstr "MFA" @@ -1045,15 +1392,12 @@ msgstr "SSH端口" msgid "If use nat, set the ssh real port" msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口" -#: assets/templates/assets/_otp_verify_modal.html:4 -msgid "MFA Confirm" -msgstr "确认" - #: assets/templates/assets/_system_user.html:37 #: assets/templates/assets/asset_create.html:16 #: assets/templates/assets/asset_update.html:21 #: assets/templates/assets/gateway_create_update.html:37 #: perms/templates/perms/asset_permission_create_update.html:38 +#: perms/templates/perms/remote_app_permission_create_update.html:39 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:43 msgid "Basic" msgstr "基本" @@ -1075,108 +1419,18 @@ msgstr "自动生成密钥" #: assets/templates/assets/asset_update.html:64 #: assets/templates/assets/gateway_create_update.html:53 #: perms/templates/perms/asset_permission_create_update.html:53 +#: perms/templates/perms/remote_app_permission_create_update.html:52 #: terminal/templates/terminal/terminal_update.html:40 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:67 msgid "Other" msgstr "其它" -#: assets/templates/assets/_system_user.html:75 -#: assets/templates/assets/admin_user_create_update.html:45 -#: assets/templates/assets/asset_bulk_update.html:23 -#: assets/templates/assets/asset_create.html:67 -#: assets/templates/assets/asset_update.html:71 -#: assets/templates/assets/cmd_filter_create_update.html:15 -#: assets/templates/assets/cmd_filter_rule_create_update.html:40 -#: assets/templates/assets/domain_create_update.html:16 -#: assets/templates/assets/gateway_create_update.html:58 -#: assets/templates/assets/label_create_update.html:18 -#: perms/templates/perms/asset_permission_create_update.html:83 -#: settings/templates/settings/basic_setting.html:61 -#: settings/templates/settings/command_storage_create.html:79 -#: settings/templates/settings/email_setting.html:62 -#: settings/templates/settings/ldap_setting.html:61 -#: settings/templates/settings/replay_storage_create.html:152 -#: settings/templates/settings/security_setting.html:70 -#: settings/templates/settings/terminal_setting.html:68 -#: terminal/templates/terminal/terminal_update.html:45 -#: users/templates/users/_user.html:50 -#: users/templates/users/user_bulk_update.html:23 -#: users/templates/users/user_detail.html:176 -#: users/templates/users/user_password_update.html:71 -#: users/templates/users/user_profile.html:204 -#: users/templates/users/user_profile_update.html:63 -#: users/templates/users/user_pubkey_update.html:70 -#: users/templates/users/user_pubkey_update.html:76 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:71 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:33 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:35 -#: xpack/plugins/interface/templates/interface/interface.html:72 -msgid "Reset" -msgstr "重置" - -#: assets/templates/assets/_system_user.html:76 -#: assets/templates/assets/admin_user_create_update.html:46 -#: assets/templates/assets/asset_bulk_update.html:24 -#: assets/templates/assets/asset_create.html:68 -#: assets/templates/assets/asset_list.html:113 -#: assets/templates/assets/asset_update.html:72 -#: assets/templates/assets/cmd_filter_create_update.html:16 -#: assets/templates/assets/cmd_filter_rule_create_update.html:41 -#: assets/templates/assets/domain_create_update.html:17 -#: assets/templates/assets/gateway_create_update.html:59 -#: assets/templates/assets/label_create_update.html:19 -#: audits/templates/audits/login_log_list.html:89 -#: perms/templates/perms/asset_permission_create_update.html:84 -#: settings/templates/settings/basic_setting.html:62 -#: settings/templates/settings/command_storage_create.html:80 -#: settings/templates/settings/email_setting.html:63 -#: settings/templates/settings/ldap_setting.html:64 -#: settings/templates/settings/replay_storage_create.html:153 -#: settings/templates/settings/security_setting.html:71 -#: settings/templates/settings/terminal_setting.html:70 -#: terminal/templates/terminal/command_list.html:103 -#: terminal/templates/terminal/session_list.html:126 -#: terminal/templates/terminal/terminal_update.html:46 -#: users/templates/users/_user.html:51 -#: users/templates/users/forgot_password.html:42 -#: users/templates/users/user_bulk_update.html:24 -#: users/templates/users/user_list.html:45 -#: users/templates/users/user_password_update.html:72 -#: users/templates/users/user_profile_update.html:64 -#: users/templates/users/user_pubkey_update.html:77 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:72 -#: xpack/plugins/interface/templates/interface/interface.html:74 -msgid "Submit" -msgstr "提交" - #: assets/templates/assets/_user_asset_detail_modal.html:11 #: assets/templates/assets/asset_asset_user_list.html:13 #: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:187 msgid "Asset detail" msgstr "资产详情" -#: assets/templates/assets/admin_user_assets.html:18 -#: assets/templates/assets/admin_user_detail.html:18 -#: assets/templates/assets/cmd_filter_detail.html:19 -#: assets/templates/assets/cmd_filter_rule_list.html:19 -#: assets/templates/assets/domain_detail.html:18 -#: assets/templates/assets/domain_gateway_list.html:20 -#: assets/templates/assets/system_user_asset.html:18 -#: assets/templates/assets/system_user_detail.html:18 -#: ops/templates/ops/adhoc_history.html:130 -#: ops/templates/ops/task_adhoc.html:116 -#: ops/templates/ops/task_history.html:136 -#: perms/templates/perms/asset_permission_asset.html:18 -#: perms/templates/perms/asset_permission_detail.html:18 -#: perms/templates/perms/asset_permission_user.html:18 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:17 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:20 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:17 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:106 -#: xpack/plugins/change_auth_plan/views.py:83 -msgid "Detail" -msgstr "详情" - #: assets/templates/assets/admin_user_assets.html:21 #: assets/templates/assets/admin_user_detail.html:21 msgid "Assets list" @@ -1192,6 +1446,7 @@ msgstr "资产列表" #: assets/templates/assets/system_user_asset.html:66 #: assets/templates/assets/system_user_detail.html:116 #: perms/templates/perms/asset_permission_detail.html:114 +#: perms/templates/perms/remote_app_permission_detail.html:106 msgid "Quick update" msgstr "快速更新" @@ -1240,77 +1495,6 @@ msgstr "更新成功" msgid "Update failed!" msgstr "更新失败" -#: assets/templates/assets/admin_user_detail.html:24 -#: assets/templates/assets/admin_user_list.html:88 -#: assets/templates/assets/asset_detail.html:27 -#: assets/templates/assets/asset_list.html:178 -#: assets/templates/assets/cmd_filter_detail.html:29 -#: assets/templates/assets/cmd_filter_list.html:58 -#: assets/templates/assets/cmd_filter_rule_list.html:86 -#: assets/templates/assets/domain_detail.html:24 -#: assets/templates/assets/domain_detail.html:103 -#: assets/templates/assets/domain_gateway_list.html:97 -#: assets/templates/assets/domain_list.html:54 -#: assets/templates/assets/label_list.html:39 -#: assets/templates/assets/system_user_detail.html:26 -#: assets/templates/assets/system_user_list.html:93 audits/models.py:33 -#: perms/templates/perms/asset_permission_detail.html:30 -#: perms/templates/perms/asset_permission_list.html:181 -#: terminal/templates/terminal/terminal_detail.html:16 -#: terminal/templates/terminal/terminal_list.html:72 -#: users/templates/users/user_detail.html:25 -#: users/templates/users/user_group_detail.html:28 -#: users/templates/users/user_group_list.html:45 -#: users/templates/users/user_list.html:83 -#: users/templates/users/user_list.html:86 -#: users/templates/users/user_profile.html:177 -#: users/templates/users/user_profile.html:187 -#: users/templates/users/user_profile.html:196 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:55 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:23 -#: xpack/plugins/cloud/templates/cloud/account_list.html:39 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:25 -#: xpack/plugins/orgs/templates/orgs/org_list.html:87 -msgid "Update" -msgstr "更新" - -#: assets/templates/assets/admin_user_detail.html:28 -#: assets/templates/assets/admin_user_list.html:89 -#: assets/templates/assets/asset_detail.html:31 -#: assets/templates/assets/asset_list.html:179 -#: assets/templates/assets/cmd_filter_detail.html:33 -#: assets/templates/assets/cmd_filter_list.html:59 -#: assets/templates/assets/cmd_filter_rule_list.html:87 -#: assets/templates/assets/domain_detail.html:28 -#: assets/templates/assets/domain_detail.html:104 -#: assets/templates/assets/domain_gateway_list.html:98 -#: assets/templates/assets/domain_list.html:55 -#: assets/templates/assets/label_list.html:40 -#: assets/templates/assets/system_user_detail.html:30 -#: assets/templates/assets/system_user_list.html:94 audits/models.py:34 -#: ops/templates/ops/task_list.html:64 -#: perms/templates/perms/asset_permission_detail.html:34 -#: perms/templates/perms/asset_permission_list.html:182 -#: settings/templates/settings/terminal_setting.html:90 -#: settings/templates/settings/terminal_setting.html:112 -#: terminal/templates/terminal/terminal_list.html:74 -#: users/templates/users/user_detail.html:30 -#: users/templates/users/user_group_detail.html:32 -#: users/templates/users/user_group_list.html:47 -#: users/templates/users/user_list.html:91 -#: users/templates/users/user_list.html:95 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:57 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:27 -#: xpack/plugins/cloud/templates/cloud/account_list.html:41 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:29 -#: xpack/plugins/orgs/templates/orgs/org_list.html:89 -msgid "Delete" -msgstr "删除" - #: assets/templates/assets/admin_user_detail.html:83 msgid "Replace node assets admin user with this" msgstr "替换资产的管理员" @@ -1405,10 +1589,13 @@ msgid "Date joined" msgstr "创建日期" #: assets/templates/assets/asset_detail.html:154 -#: assets/templates/assets/user_asset_list.html:46 perms/models.py:54 -#: perms/models.py:108 +#: assets/templates/assets/user_asset_list.html:46 +#: perms/models/asset_permission.py:59 perms/models/asset_permission.py:113 +#: perms/models/remote_app_permission.py:37 #: perms/templates/perms/asset_permission_create_update.html:55 #: perms/templates/perms/asset_permission_detail.html:120 +#: perms/templates/perms/remote_app_permission_create_update.html:54 +#: perms/templates/perms/remote_app_permission_detail.html:112 #: terminal/templates/terminal/terminal_list.html:34 #: users/templates/users/_select_user_modal.html:18 #: users/templates/users/user_detail.html:144 @@ -1783,10 +1970,6 @@ msgstr "删除系统用户" msgid "System Users Deleting failed." msgstr "系统用户删除失败" -#: assets/templates/assets/user_asset_list.html:100 perms/const.py:19 -msgid "Connect" -msgstr "连接" - #: assets/views/admin_user.py:30 msgid "Admin user list" msgstr "管理用户列表" @@ -2009,9 +2192,11 @@ msgstr "登录日期" #: ops/templates/ops/adhoc_history.html:52 #: ops/templates/ops/adhoc_history_detail.html:61 #: ops/templates/ops/command_execution_list.html:66 -#: ops/templates/ops/task_history.html:58 perms/models.py:55 -#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:165 -#: terminal/templates/terminal/session_list.html:78 +#: ops/templates/ops/task_history.html:58 perms/models/asset_permission.py:60 +#: perms/models/remote_app_permission.py:38 +#: perms/templates/perms/asset_permission_detail.html:86 +#: perms/templates/perms/remote_app_permission_detail.html:78 +#: terminal/models.py:165 terminal/templates/terminal/session_list.html:78 #: xpack/plugins/change_auth_plan/models.py:246 #: xpack/plugins/change_auth_plan/models.py:416 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59 @@ -2021,6 +2206,7 @@ msgstr "开始日期" #: audits/templates/audits/login_log_list.html:28 #: perms/templates/perms/asset_permission_user.html:88 +#: perms/templates/perms/remote_app_permission_user.html:87 msgid "Select user" msgstr "选择用户" @@ -2071,23 +2257,23 @@ msgid "Datetime" msgstr "日期" #: audits/views.py:85 audits/views.py:129 audits/views.py:165 -#: audits/views.py:209 audits/views.py:241 templates/_nav.html:72 +#: audits/views.py:209 audits/views.py:241 templates/_nav.html:83 msgid "Audits" msgstr "日志审计" -#: audits/views.py:86 templates/_nav.html:76 +#: audits/views.py:86 templates/_nav.html:87 msgid "FTP log" msgstr "FTP日志" -#: audits/views.py:130 templates/_nav.html:77 +#: audits/views.py:130 templates/_nav.html:88 msgid "Operate log" msgstr "操作日志" -#: audits/views.py:166 templates/_nav.html:78 +#: audits/views.py:166 templates/_nav.html:89 msgid "Password change log" msgstr "改密日志" -#: audits/views.py:210 templates/_nav.html:75 +#: audits/views.py:210 templates/_nav.html:86 msgid "Login log" msgstr "登录日志" @@ -2712,7 +2898,7 @@ msgstr "更新任务内容: {}" msgid "Ops" msgstr "作业中心" -#: ops/views/adhoc.py:45 templates/_nav.html:66 +#: ops/views/adhoc.py:45 templates/_nav.html:77 msgid "Task list" msgstr "任务列表" @@ -2724,7 +2910,7 @@ msgstr "执行历史" msgid "Command execution list" msgstr "命令执行列表" -#: ops/views/command.py:69 templates/_nav_user.html:9 +#: ops/views/command.py:69 templates/_nav_user.html:21 msgid "Command execution" msgstr "命令执行" @@ -2744,46 +2930,59 @@ msgstr "上传文件" msgid "Download file" msgstr "下载文件" -#: perms/forms.py:39 perms/models.py:49 perms/models.py:106 +#: perms/forms/asset_permission.py:43 perms/forms/remote_app_permission.py:34 +#: perms/models/asset_permission.py:54 perms/models/asset_permission.py:111 +#: perms/models/remote_app_permission.py:35 #: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:75 -#: perms/templates/perms/asset_permission_list.html:122 templates/_nav.html:14 -#: users/forms.py:253 users/models/group.py:26 users/models/user.py:67 -#: users/templates/users/_select_user_modal.html:16 +#: perms/templates/perms/asset_permission_list.html:122 +#: perms/templates/perms/remote_app_permission_list.html:16 +#: templates/_nav.html:14 users/forms.py:253 users/models/group.py:26 +#: users/models/user.py:67 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:213 #: users/templates/users/user_list.html:26 #: xpack/plugins/orgs/templates/orgs/org_list.html:15 msgid "User group" msgstr "用户组" -#: perms/forms.py:58 +#: perms/forms/asset_permission.py:62 msgid "" "Tips: The RDP protocol does not support separate controls for uploading or " "downloading files" msgstr "提示:RDP 协议不支持单独控制上传或下载文件" -#: perms/forms.py:68 +#: perms/forms/asset_permission.py:72 perms/forms/remote_app_permission.py:47 msgid "User or group at least one required" msgstr "用户和用户组至少选一个" -#: perms/forms.py:77 +#: perms/forms/asset_permission.py:81 msgid "Asset or group at least one required" msgstr "资产和节点至少选一个" -#: perms/models.py:56 perms/models.py:109 +#: perms/models/asset_permission.py:61 perms/models/asset_permission.py:114 +#: perms/models/remote_app_permission.py:39 #: perms/templates/perms/asset_permission_detail.html:90 +#: perms/templates/perms/remote_app_permission_detail.html:82 #: users/models/user.py:99 users/templates/users/user_detail.html:107 #: users/templates/users/user_profile.html:116 msgid "Date expired" msgstr "失效日期" -#: perms/models.py:65 perms/models.py:118 templates/_nav.html:34 +#: perms/models/asset_permission.py:70 perms/models/asset_permission.py:123 +#: templates/_nav.html:42 msgid "Asset permission" msgstr "资产授权" +#: perms/models/remote_app_permission.py:48 templates/_nav.html:45 +msgid "RemoteApp permission" +msgstr "远程应用授权" + #: perms/templates/perms/asset_permission_asset.html:22 #: perms/templates/perms/asset_permission_detail.html:22 #: perms/templates/perms/asset_permission_user.html:22 +#: perms/templates/perms/remote_app_permission_detail.html:22 +#: perms/templates/perms/remote_app_permission_remote_app.html:21 +#: perms/templates/perms/remote_app_permission_user.html:21 msgid "Users and user groups" msgstr "用户或用户组" @@ -2804,6 +3003,9 @@ msgstr "添加资产" #: perms/templates/perms/asset_permission_detail.html:157 #: perms/templates/perms/asset_permission_user.html:97 #: perms/templates/perms/asset_permission_user.html:125 +#: perms/templates/perms/remote_app_permission_remote_app.html:96 +#: perms/templates/perms/remote_app_permission_user.html:96 +#: perms/templates/perms/remote_app_permission_user.html:124 #: settings/templates/settings/terminal_setting.html:95 #: settings/templates/settings/terminal_setting.html:117 #: users/templates/users/user_group_detail.html:95 @@ -2824,17 +3026,20 @@ msgid "Join" msgstr "加入" #: perms/templates/perms/asset_permission_create_update.html:61 +#: perms/templates/perms/remote_app_permission_create_update.html:60 msgid "Validity period" msgstr "有效期" #: perms/templates/perms/asset_permission_detail.html:66 +#: perms/templates/perms/remote_app_permission_detail.html:66 #: xpack/plugins/license/templates/license/license_detail.html:76 msgid "User count" msgstr "用户数量" #: perms/templates/perms/asset_permission_detail.html:70 +#: perms/templates/perms/remote_app_permission_detail.html:70 msgid "User group count" -msgstr "用户组列表" +msgstr "用户组数量" #: perms/templates/perms/asset_permission_detail.html:74 #: xpack/plugins/license/templates/license/license_detail.html:72 @@ -2854,11 +3059,13 @@ msgid "Select system users" msgstr "选择系统用户" #: perms/templates/perms/asset_permission_list.html:46 +#: perms/templates/perms/remote_app_permission_list.html:6 msgid "Create permission" msgstr "创建授权规则" #: perms/templates/perms/asset_permission_list.html:59 #: perms/templates/perms/asset_permission_list.html:73 +#: perms/templates/perms/remote_app_permission_list.html:18 #: users/templates/users/user_list.html:28 xpack/plugins/cloud/models.py:53 #: xpack/plugins/cloud/templates/cloud/account_detail.html:58 #: xpack/plugins/cloud/templates/cloud/account_list.html:14 @@ -2866,6 +3073,7 @@ msgid "Validity" msgstr "有效" #: perms/templates/perms/asset_permission_user.html:35 +#: perms/templates/perms/remote_app_permission_user.html:34 msgid "User list of " msgstr "用户列表" @@ -2878,35 +3086,95 @@ msgid "Add user group to asset permission" msgstr "添加用户组" #: perms/templates/perms/asset_permission_user.html:116 +#: perms/templates/perms/remote_app_permission_user.html:115 msgid "Select user groups" msgstr "选择用户组" -#: perms/views.py:24 perms/views.py:56 perms/views.py:71 perms/views.py:86 -#: perms/views.py:121 perms/views.py:153 templates/_nav.html:31 +#: perms/templates/perms/remote_app_permission_detail.html:74 +msgid "RemoteApp count" +msgstr "远程应用数量" + +#: perms/templates/perms/remote_app_permission_remote_app.html:34 +msgid "RemoteApp list of " +msgstr "远程应用列表" + +#: perms/templates/perms/remote_app_permission_remote_app.html:79 +msgid "Add RemoteApp to this permission" +msgstr "添加远程应用" + +#: perms/templates/perms/remote_app_permission_remote_app.html:87 +msgid "Select RemoteApp" +msgstr "选择远程应用" + +#: perms/templates/perms/remote_app_permission_user.html:79 +msgid "Add user to this permission" +msgstr "添加用户" + +#: perms/templates/perms/remote_app_permission_user.html:107 +msgid "Add user group to this permission" +msgstr "添加用户组" + +#: perms/views/asset_permission.py:33 perms/views/asset_permission.py:65 +#: perms/views/asset_permission.py:80 perms/views/asset_permission.py:95 +#: perms/views/asset_permission.py:130 perms/views/asset_permission.py:162 +#: perms/views/remote_app_permission.py:33 +#: perms/views/remote_app_permission.py:48 +#: perms/views/remote_app_permission.py:63 +#: perms/views/remote_app_permission.py:76 +#: perms/views/remote_app_permission.py:102 +#: perms/views/remote_app_permission.py:138 templates/_nav.html:39 #: xpack/plugins/orgs/templates/orgs/org_list.html:21 msgid "Perms" msgstr "权限管理" -#: perms/views.py:25 +#: perms/views/asset_permission.py:34 msgid "Asset permission list" msgstr "资产授权列表" -#: perms/views.py:57 +#: perms/views/asset_permission.py:66 msgid "Create asset permission" msgstr "创建权限规则" -#: perms/views.py:72 perms/views.py:87 +#: perms/views/asset_permission.py:81 msgid "Update asset permission" msgstr "更新资产授权" -#: perms/views.py:122 +#: perms/views/asset_permission.py:96 +msgid "Asset permission detail" +msgstr "资产授权详情" + +#: perms/views/asset_permission.py:131 msgid "Asset permission user list" msgstr "资产授权用户列表" -#: perms/views.py:154 +#: perms/views/asset_permission.py:163 msgid "Asset permission asset list" msgstr "资产授权资产列表" +#: perms/views/remote_app_permission.py:34 +msgid "RemoteApp permission list" +msgstr "远程应用授权列表" + +#: perms/views/remote_app_permission.py:49 +msgid "Create RemoteApp permission" +msgstr "创建远程应用授权规则" + +#: perms/views/remote_app_permission.py:64 +msgid "Update RemoteApp permission" +msgstr "更新远程应用授权规则" + +#: perms/views/remote_app_permission.py:77 +msgid "RemoteApp permission detail" +msgstr "远程应用授权详情" + +#: perms/views/remote_app_permission.py:103 +msgid "RemoteApp permission user list" +msgstr "远程应用授权用户列表" + +#: perms/views/remote_app_permission.py:139 +msgid "RemoteApp permission RemoteApp list" +msgstr "远程应用授权远程应用列表" + #: settings/api.py:26 msgid "Test mail sent to {}, please check" msgstr "邮件已经发送{}, 请检查" @@ -3376,7 +3644,7 @@ msgstr "在ou:{}中没有匹配条目" #: settings/views.py:18 settings/views.py:44 settings/views.py:70 #: settings/views.py:99 settings/views.py:126 settings/views.py:138 -#: settings/views.py:151 templates/_nav.html:107 +#: settings/views.py:151 templates/_nav.html:118 msgid "Settings" msgstr "系统设置" @@ -3405,7 +3673,7 @@ msgstr "文档" msgid "Commercial support" msgstr "商业支持" -#: templates/_header_bar.html:89 templates/_nav_user.html:14 users/forms.py:121 +#: templates/_header_bar.html:89 templates/_nav_user.html:26 users/forms.py:121 #: users/templates/users/_user.html:43 #: users/templates/users/first_login.html:39 #: users/templates/users/user_password_update.html:40 @@ -3508,57 +3776,65 @@ msgstr "用户列表" msgid "Command filters" msgstr "命令过滤" -#: templates/_nav.html:40 +#: templates/_nav.html:32 +msgid "Applications" +msgstr "应用管理" + +#: templates/_nav.html:51 msgid "Sessions" msgstr "会话管理" -#: templates/_nav.html:43 +#: templates/_nav.html:54 msgid "Session online" msgstr "在线会话" -#: templates/_nav.html:44 +#: templates/_nav.html:55 msgid "Session offline" msgstr "历史会话" -#: templates/_nav.html:45 +#: templates/_nav.html:56 msgid "Commands" msgstr "命令记录" -#: templates/_nav.html:48 templates/_nav_user.html:19 +#: templates/_nav.html:59 templates/_nav_user.html:31 msgid "Web terminal" msgstr "Web终端" -#: templates/_nav.html:53 templates/_nav_user.html:24 +#: templates/_nav.html:64 templates/_nav_user.html:36 msgid "File manager" msgstr "文件管理" -#: templates/_nav.html:57 terminal/views/command.py:50 +#: templates/_nav.html:68 terminal/views/command.py:50 #: terminal/views/session.py:75 terminal/views/session.py:93 #: terminal/views/session.py:115 terminal/views/terminal.py:31 #: terminal/views/terminal.py:46 terminal/views/terminal.py:58 msgid "Terminal" msgstr "终端管理" -#: templates/_nav.html:63 +#: templates/_nav.html:74 msgid "Job Center" msgstr "作业中心" -#: templates/_nav.html:67 templates/_nav.html:79 +#: templates/_nav.html:78 templates/_nav.html:90 msgid "Batch command" msgstr "批量命令" -#: templates/_nav.html:85 +#: templates/_nav.html:96 msgid "XPack" msgstr "" -#: templates/_nav.html:93 xpack/plugins/cloud/views.py:26 +#: templates/_nav.html:104 xpack/plugins/cloud/views.py:26 msgid "Account list" msgstr "账户列表" -#: templates/_nav.html:94 +#: templates/_nav.html:105 msgid "Sync instance" msgstr "同步实例" +#: templates/_nav_user.html:9 +msgid "My Applications" +msgstr "我的应用" + #: templates/_pagination.html:59 msgid "" "Displays the results of items _START_ to _END_; A total of _TOTAL_ entries" @@ -5452,6 +5728,9 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" +#~ msgid "MFA Confirm" +#~ msgstr "确认" + #~ msgid "Monitor" #~ msgstr "监控" @@ -5501,24 +5780,6 @@ msgstr "更新组织" #~ msgid "Invalid private key" #~ msgstr "ssh密钥不合法" -#, fuzzy -#~| msgid "CPU count" -#~ msgid "Cpu count" -#~ msgstr "CPU数量" - -#~ msgid "Login Jumpserver" -#~ msgstr "登录 Jumpserver" - -#, fuzzy -#~| msgid "Delete succeed" -#~ msgid "Delete success!" -#~ msgstr "删除成功" - -#, fuzzy -#~| msgid "Username does not exist" -#~ msgid "This license does not exist!" -#~ msgstr "用户名不存在" - #~ msgid "Valid" #~ msgstr "账户状态" @@ -5540,11 +5801,6 @@ msgstr "更新组织" #~ msgid "Date finished" #~ msgstr "结束日期" -#, fuzzy -#~| msgid "Audits" -#~ msgid "Audit" -#~ msgstr "日志审计" - #~ msgid "User id" #~ msgstr "用户" @@ -5554,11 +5810,6 @@ msgstr "更新组织" #~ msgid "Start" #~ msgstr "开始" -#, fuzzy -#~| msgid "Update setting successfully" -#~ msgid "Update setting successfully, please restart program" -#~ msgstr "更新设置成功" - #~ msgid "User login settings" #~ msgstr "用户登录设置" diff --git a/apps/perms/api/__init__.py b/apps/perms/api/__init__.py index e90e0262c..aebfb2d33 100644 --- a/apps/perms/api/__init__.py +++ b/apps/perms/api/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # -from .permission import * +from .asset_permission import * from .user_permission import * from .user_group_permission import * +from .remote_app_permission import * diff --git a/apps/perms/api/permission.py b/apps/perms/api/asset_permission.py similarity index 100% rename from apps/perms/api/permission.py rename to apps/perms/api/asset_permission.py diff --git a/apps/perms/api/remote_app_permission.py b/apps/perms/api/remote_app_permission.py new file mode 100644 index 000000000..d75e80b8f --- /dev/null +++ b/apps/perms/api/remote_app_permission.py @@ -0,0 +1,101 @@ +# coding: utf-8 +# + + +from rest_framework import viewsets, generics +from rest_framework.pagination import LimitOffsetPagination +from rest_framework.views import Response + +from common.permissions import IsOrgAdmin + +from ..models import RemoteAppPermission +from ..serializers import ( + RemoteAppPermissionSerializer, + RemoteAppPermissionUpdateUserSerializer, + RemoteAppPermissionUpdateRemoteAppSerializer, +) + + +__all__ = [ + 'RemoteAppPermissionViewSet', + 'RemoteAppPermissionAddUserApi', 'RemoteAppPermissionAddRemoteAppApi', + 'RemoteAppPermissionRemoveUserApi', 'RemoteAppPermissionRemoveRemoteAppApi', +] + + +class RemoteAppPermissionViewSet(viewsets.ModelViewSet): + filter_fields = ('name', ) + search_fields = filter_fields + queryset = RemoteAppPermission.objects.all() + serializer_class = RemoteAppPermissionSerializer + pagination_class = LimitOffsetPagination + permission_classes = (IsOrgAdmin,) + + +class RemoteAppPermissionAddUserApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = RemoteAppPermissionUpdateUserSerializer + queryset = RemoteAppPermission.objects.all() + + def update(self, request, *args, **kwargs): + perm = self.get_object() + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + users = serializer.validated_data.get('users') + if users: + perm.users.add(*tuple(users)) + return Response({"msg": "ok"}) + else: + return Response({"error": serializer.errors}) + + +class RemoteAppPermissionRemoveUserApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = RemoteAppPermissionUpdateUserSerializer + queryset = RemoteAppPermission.objects.all() + + def update(self, request, *args, **kwargs): + perm = self.get_object() + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + users = serializer.validated_data.get('users') + if users: + perm.users.remove(*tuple(users)) + return Response({"msg": "ok"}) + else: + return Response({"error": serializer.errors}) + + +class RemoteAppPermissionAddRemoteAppApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer + queryset = RemoteAppPermission.objects.all() + + def update(self, request, *args, **kwargs): + perm = self.get_object() + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + remote_apps = serializer.validated_data.get('remote_apps') + if remote_apps: + perm.remote_apps.add(*tuple(remote_apps)) + return Response({"msg": "ok"}) + else: + return Response({"error": serializer.errors}) + + +class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer + queryset = RemoteAppPermission.objects.all() + + def update(self, request, *args, **kwargs): + perm = self.get_object() + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + remote_apps = serializer.validated_data.get('remote_apps') + if remote_apps: + perm.remote_apps.remove(*tuple(remote_apps)) + return Response({"msg": "ok"}) + else: + return Response({"error": serializer.errors}) + diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index 82b71bb63..4e5c8acd9 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -10,10 +10,12 @@ from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.tree import TreeNodeSerializer from orgs.utils import set_to_root_org from ..utils import ( - AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node + AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node, + RemoteAppPermissionUtil, ) from ..hands import ( - AssetGrantedSerializer, UserGroup, Node, NodeSerializer + AssetGrantedSerializer, UserGroup, Node, NodeSerializer, + RemoteAppSerializer, ) from .. import serializers @@ -22,6 +24,7 @@ __all__ = [ 'UserGroupGrantedAssetsApi', 'UserGroupGrantedNodesApi', 'UserGroupGrantedNodesWithAssetsApi', 'UserGroupGrantedNodeAssetsApi', 'UserGroupGrantedNodesWithAssetsAsTreeApi', + 'UserGroupGrantedRemoteAppsApi', ] @@ -138,3 +141,20 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView): for asset, system_users in assets.items(): asset.system_users_granted = system_users return assets + + +# RemoteApp permission + +class UserGroupGrantedRemoteAppsApi(ListAPIView): + permission_classes = (IsOrgAdmin, ) + serializer_class = RemoteAppSerializer + + def get_queryset(self): + queryset = [] + user_group_id = self.kwargs.get('pk') + if not user_group_id: + return queryset + user_group = get_object_or_404(UserGroup, id=user_group_id) + util = RemoteAppPermissionUtil(user_group) + queryset = util.get_remote_apps() + return queryset diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index 8d1a42c26..7c934340e 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -17,14 +17,15 @@ from common.utils import get_logger from orgs.utils import set_to_root_org from ..utils import ( AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node, - check_system_user_action + check_system_user_action, RemoteAppPermissionUtil, + construct_remote_apps_tree_root, parse_remote_app_to_tree_node, ) from ..hands import ( - AssetGrantedSerializer, User, Asset, Node, - SystemUser, NodeSerializer + User, Asset, Node, SystemUser, RemoteApp, AssetGrantedSerializer, + NodeSerializer, RemoteAppSerializer, ) from .. import serializers -from ..mixins import AssetsFilterMixin +from ..mixins import AssetsFilterMixin, RemoteAppFilterMixin from ..models import Action logger = get_logger(__name__) @@ -34,6 +35,8 @@ __all__ = [ 'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi', 'ValidateUserAssetPermissionApi', 'UserGrantedNodeChildrenApi', 'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi', + 'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi', + 'UserGrantedRemoteAppsAsTreeApi', ] @@ -447,3 +450,78 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView): actions = [action.name for action in getattr(_su, 'actions', [])] return Response({'actions': actions}, status=200) + + +# RemoteApp permission + +class UserGrantedRemoteAppsApi(RemoteAppFilterMixin, ListAPIView): + permission_classes = (IsOrgAdminOrAppUser,) + serializer_class = RemoteAppSerializer + pagination_class = LimitOffsetPagination + + def get_object(self): + user_id = self.kwargs.get('pk', '') + if user_id: + user = get_object_or_404(User, id=user_id) + else: + user = self.request.user + return user + + def get_queryset(self): + util = RemoteAppPermissionUtil(self.get_object()) + queryset = util.get_remote_apps() + queryset = list(queryset) + return queryset + + def get_permissions(self): + if self.kwargs.get('pk') is None: + self.permission_classes = (IsValidUser,) + return super().get_permissions() + + +class UserGrantedRemoteAppsAsTreeApi(ListAPIView): + serializer_class = TreeNodeSerializer + permission_classes = (IsOrgAdminOrAppUser,) + + def get_object(self): + user_id = self.kwargs.get('pk', '') + if not user_id: + user = self.request.user + else: + user = get_object_or_404(User, id=user_id) + return user + + def get_queryset(self): + queryset = [] + tree_root = construct_remote_apps_tree_root() + queryset.append(tree_root) + + util = RemoteAppPermissionUtil(self.get_object()) + remote_apps = util.get_remote_apps() + for remote_app in remote_apps: + node = parse_remote_app_to_tree_node(tree_root, remote_app) + queryset.append(node) + + queryset = sorted(queryset) + return queryset + + def get_permissions(self): + if self.kwargs.get('pk') is None: + self.permission_classes = (IsValidUser,) + return super().get_permissions() + + +class ValidateUserRemoteAppPermissionApi(APIView): + + def get(self, request, *args, **kwargs): + user_id = request.query_params.get('user_id', '') + remote_app_id = request.query_params.get('remote_app_id', '') + user = get_object_or_404(User, id=user_id) + remote_app = get_object_or_404(RemoteApp, id=remote_app_id) + + util = RemoteAppPermissionUtil(user) + remote_apps = util.get_remote_apps() + if remote_app not in remote_apps: + return Response({'msg': False}, status=403) + + return Response({'msg': True}, status=200) diff --git a/apps/perms/forms/__init__.py b/apps/perms/forms/__init__.py new file mode 100644 index 000000000..129901afc --- /dev/null +++ b/apps/perms/forms/__init__.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# + +from .asset_permission import * +from .remote_app_permission import * diff --git a/apps/perms/forms.py b/apps/perms/forms/asset_permission.py similarity index 97% rename from apps/perms/forms.py rename to apps/perms/forms/asset_permission.py index 35d8a2528..81845fb77 100644 --- a/apps/perms/forms.py +++ b/apps/perms/forms/asset_permission.py @@ -6,9 +6,13 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins import OrgModelForm from orgs.utils import current_org -from .models import AssetPermission +from perms.models import AssetPermission from assets.models import Asset +__all__ = [ + 'AssetPermissionForm', +] + class AssetPermissionForm(OrgModelForm): def __init__(self, *args, **kwargs): diff --git a/apps/perms/forms/remote_app_permission.py b/apps/perms/forms/remote_app_permission.py new file mode 100644 index 000000000..2e0cc1b66 --- /dev/null +++ b/apps/perms/forms/remote_app_permission.py @@ -0,0 +1,49 @@ +# coding: utf-8 +# + +from django.utils.translation import ugettext as _ +from django import forms +from orgs.mixins import OrgModelForm +from orgs.utils import current_org + +from ..models import RemoteAppPermission + + +__all__ = [ + 'RemoteAppPermissionCreateUpdateForm', +] + + +class RemoteAppPermissionCreateUpdateForm(OrgModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + users_field = self.fields.get('users') + if hasattr(users_field, 'queryset'): + users_field.queryset = current_org.get_org_users() + + class Meta: + model = RemoteAppPermission + exclude = ( + 'id', 'date_created', 'created_by', 'org_id' + ) + widgets = { + 'users': forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('User')} + ), + 'user_groups': forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('User group')} + ), + 'remote_apps': forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('RemoteApp')} + ) + } + + def clean_user_groups(self): + users = self.cleaned_data.get('users') + user_groups = self.cleaned_data.get('user_groups') + + if not users and not user_groups: + raise forms.ValidationError( + _("User or group at least one required") + ) + return self.cleaned_data['user_groups'] diff --git a/apps/perms/hands.py b/apps/perms/hands.py index 56a60cba7..2a208fefa 100644 --- a/apps/perms/hands.py +++ b/apps/perms/hands.py @@ -3,8 +3,11 @@ from common.permissions import AdminUserRequiredMixin from users.models import User, UserGroup -from assets.models import Asset, SystemUser, Node -from assets.serializers import AssetGrantedSerializer, NodeSerializer +from assets.models import Asset, SystemUser, Node, RemoteApp +from assets.serializers import ( + AssetGrantedSerializer, NodeSerializer +) +from applications.serializers import RemoteAppSerializer diff --git a/apps/perms/migrations/0005_auto_20190520_1904.py b/apps/perms/migrations/0005_auto_20190520_1904.py new file mode 100644 index 000000000..48c579201 --- /dev/null +++ b/apps/perms/migrations/0005_auto_20190520_1904.py @@ -0,0 +1,45 @@ +# Generated by Django 2.1.7 on 2019-05-20 11:04 + +import common.utils.django +from django.conf import settings +from django.db import migrations, models +import django.utils.timezone +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('applications', '0001_initial'), + ('users', '0019_auto_20190304_1459'), + ('perms', '0004_assetpermission_actions'), + ] + + operations = [ + migrations.CreateModel( + name='RemoteAppPermission', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('is_active', models.BooleanField(default=True, verbose_name='Active')), + ('date_start', models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='Date start')), + ('date_expired', models.DateTimeField(db_index=True, default=common.utils.django.date_expired_default, verbose_name='Date expired')), + ('created_by', models.CharField(blank=True, max_length=128, verbose_name='Created by')), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('remote_apps', models.ManyToManyField(blank=True, related_name='remote_app_permissions', to='applications.RemoteApp', verbose_name='RemoteApp')), + ('user_groups', models.ManyToManyField(blank=True, related_name='remote_app_permissions', to='users.UserGroup', verbose_name='User group')), + ('users', models.ManyToManyField(blank=True, related_name='remote_app_permissions', to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'verbose_name': 'RemoteApp permission', + 'ordering': ('name',), + }, + ), + migrations.AlterUniqueTogether( + name='remoteapppermission', + unique_together={('org_id', 'name')}, + ), + ] diff --git a/apps/perms/mixins.py b/apps/perms/mixins.py index e41c0529b..3adaa6e5b 100644 --- a/apps/perms/mixins.py +++ b/apps/perms/mixins.py @@ -2,6 +2,11 @@ # +__all__ = [ + 'AssetsFilterMixin', 'RemoteAppFilterMixin', +] + + class AssetsFilterMixin(object): """ 对资产进行过滤(查询,排序) @@ -34,3 +39,38 @@ class AssetsFilterMixin(object): queryset = sort_assets(queryset, order_by=order_by, reverse=reverse) return queryset + + +class RemoteAppFilterMixin(object): + """ + 对RemoteApp进行过滤(查询,排序) + """ + + def filter_queryset(self, queryset): + queryset = self.search_remote_apps(queryset) + queryset = self.sort_remote_apps(queryset) + return queryset + + def search_remote_apps(self, queryset): + value = self.request.query_params.get('search') + if not value: + return queryset + queryset = [ + remote_app for remote_app in queryset if value in remote_app.name + ] + return queryset + + def sort_remote_apps(self, queryset): + order_by = self.request.query_params.get('order') + if not order_by: + order_by = 'name' + if order_by.startswith('-'): + order_by = order_by.lstrip('-') + reverse = True + else: + reverse = False + + queryset = sorted( + queryset, key=lambda x: getattr(x, order_by), reverse=reverse + ) + return queryset diff --git a/apps/perms/models/__init__.py b/apps/perms/models/__init__.py new file mode 100644 index 000000000..129901afc --- /dev/null +++ b/apps/perms/models/__init__.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# + +from .asset_permission import * +from .remote_app_permission import * diff --git a/apps/perms/models.py b/apps/perms/models/asset_permission.py similarity index 97% rename from apps/perms/models.py rename to apps/perms/models/asset_permission.py index c524c4f58..ca917d69d 100644 --- a/apps/perms/models.py +++ b/apps/perms/models/asset_permission.py @@ -7,7 +7,12 @@ from django.utils import timezone from common.utils import date_expired_default, set_or_append_attr_bulk from orgs.mixins import OrgModelMixin, OrgManager -from .const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL +from perms.const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL + + +__all__ = [ + 'Action', 'AssetPermission', 'NodePermission', +] class Action(models.Model): diff --git a/apps/perms/models/remote_app_permission.py b/apps/perms/models/remote_app_permission.py new file mode 100644 index 000000000..7b29c8d20 --- /dev/null +++ b/apps/perms/models/remote_app_permission.py @@ -0,0 +1,75 @@ +# coding: utf-8 +# + +import uuid +from django.db import models +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ + +from orgs.mixins import OrgModelMixin, OrgManager +from common.utils import date_expired_default, set_or_append_attr_bulk + +__all__ = [ + 'RemoteAppPermission', +] + + +class RemoteAppPermissionQuerySet(models.QuerySet): + def active(self): + return self.filter(is_active=True) + + def valid(self): + return self.active().filter(date_start__lt=timezone.now())\ + .filter(date_expired__gt=timezone.now()) + + +class RemoteAppPermissionManager(OrgManager): + def valid(self): + return self.get_queryset().valid() + + +class RemoteAppPermission(OrgModelMixin): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, verbose_name=_('Name')) + users = models.ManyToManyField('users.User', related_name='remote_app_permissions', blank=True, verbose_name=_("User")) + user_groups = models.ManyToManyField('users.UserGroup', related_name='remote_app_permissions', blank=True, verbose_name=_("User group")) + remote_apps = models.ManyToManyField('applications.RemoteApp', related_name='remote_app_permissions', blank=True, verbose_name=_("RemoteApp")) + is_active = models.BooleanField(default=True, verbose_name=_('Active')) + date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start")) + date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired')) + created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) + comment = models.TextField(verbose_name=_('Comment'), blank=True) + + objects = RemoteAppPermissionManager.from_queryset(RemoteAppPermissionQuerySet)() + + class Meta: + unique_together = [('org_id', 'name')] + verbose_name = _('RemoteApp permission') + ordering = ('name',) + + def __str__(self): + return self.name + + @property + def is_expired(self): + if self.date_expired > timezone.now() > self.date_start: + return False + return True + + @property + def is_valid(self): + if not self.is_expired and self.is_active: + return True + return False + + def get_all_users(self): + users = set(self.users.all()) + for group in self.user_groups.all(): + _users = group.users.all() + set_or_append_attr_bulk(_users, 'inherit', group.name) + users.update(set(_users)) + return users + + def get_all_remote_apps(self): + return set(self.remote_apps.all()) diff --git a/apps/perms/serializers/__init__.py b/apps/perms/serializers/__init__.py new file mode 100644 index 000000000..129901afc --- /dev/null +++ b/apps/perms/serializers/__init__.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# + +from .asset_permission import * +from .remote_app_permission import * diff --git a/apps/perms/serializers.py b/apps/perms/serializers/asset_permission.py similarity index 97% rename from apps/perms/serializers.py rename to apps/perms/serializers/asset_permission.py index 01c5a077a..a1eec6710 100644 --- a/apps/perms/serializers.py +++ b/apps/perms/serializers/asset_permission.py @@ -4,7 +4,7 @@ from rest_framework import serializers from common.fields import StringManyToManyField -from .models import AssetPermission, Action +from perms.models import AssetPermission, Action from assets.models import Node, Asset, SystemUser from assets.serializers import AssetGrantedSerializer @@ -13,7 +13,7 @@ __all__ = [ 'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer', 'AssetPermissionNodeSerializer', 'GrantedNodeSerializer', 'GrantedAssetSerializer', 'GrantedSystemUserSerializer', - 'ActionSerializer', + 'ActionSerializer', 'NodeGrantedSerializer', ] diff --git a/apps/perms/serializers/remote_app_permission.py b/apps/perms/serializers/remote_app_permission.py new file mode 100644 index 000000000..bb95d910c --- /dev/null +++ b/apps/perms/serializers/remote_app_permission.py @@ -0,0 +1,36 @@ +# coding: utf-8 +# + +from rest_framework import serializers + +from ..models import RemoteAppPermission + + +__all__ = [ + 'RemoteAppPermissionSerializer', + 'RemoteAppPermissionUpdateUserSerializer', + 'RemoteAppPermissionUpdateRemoteAppSerializer', +] + + +class RemoteAppPermissionSerializer(serializers.ModelSerializer): + class Meta: + model = RemoteAppPermission + fields = [ + 'id', 'name', 'users', 'user_groups', 'remote_apps', 'comment', + 'is_active', 'date_start', 'date_expired', 'is_valid', + 'created_by', 'date_created', 'org_id' + ] + read_only_fields = ['created_by', 'date_created'] + + +class RemoteAppPermissionUpdateUserSerializer(serializers.ModelSerializer): + class Meta: + model = RemoteAppPermission + fields = ['id', 'users'] + + +class RemoteAppPermissionUpdateRemoteAppSerializer(serializers.ModelSerializer): + class Meta: + model = RemoteAppPermission + fields = ['id', 'remote_apps'] diff --git a/apps/perms/templates/perms/remote_app_permission_create_update.html b/apps/perms/templates/perms/remote_app_permission_create_update.html new file mode 100644 index 000000000..3d6154991 --- /dev/null +++ b/apps/perms/templates/perms/remote_app_permission_create_update.html @@ -0,0 +1,120 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load static %} +{% load bootstrap3 %} +{% block custom_head_css_js %} + + + +{% endblock %} + +{% block content %} +
+
+
+
+
+
{{ action }}
+ +
+
+
+ {% if form.non_field_errors %} +
+ {{ form.non_field_errors }} +
+ {% endif %} + {% csrf_token %} + +

{% trans 'Basic' %}

+ {% bootstrap_field form.name layout="horizontal" %} +
+ +

{% trans 'User' %}

+ {% bootstrap_field form.users layout="horizontal" %} + {% bootstrap_field form.user_groups layout="horizontal" %} +
+ +

{% trans 'RemoteApp' %}

+ {% bootstrap_field form.remote_apps layout="horizontal" %} +
+ +

{% trans 'Other' %}

+
+ +
+ {{ form.is_active }} +
+
+
+ +
+
+ + {% if form.errors %} + + to + + {% else %} + + to + + {% endif %} +
+ {{ form.date_expired.errors }} + {{ form.date_start.errors }} +
+
+ + {% bootstrap_field form.comment layout="horizontal" %} + +
+
+ + +
+
+ +
+
+
+
+
+
+{% endblock %} +{% block custom_foot_js %} + + + + + + +{% endblock %} \ No newline at end of file diff --git a/apps/perms/templates/perms/remote_app_permission_detail.html b/apps/perms/templates/perms/remote_app_permission_detail.html new file mode 100644 index 000000000..98bc6633e --- /dev/null +++ b/apps/perms/templates/perms/remote_app_permission_detail.html @@ -0,0 +1,169 @@ +{% extends 'base.html' %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} + + +{% endblock %} + +{% block content %} +
+
+
+
+ +
+
+
+
+ {{ object.name }} +
+ + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans 'Name' %}:{{ object.name }}
{% trans 'User count' %}:{{ object.users.count }}
{% trans 'User group count' %}:{{ object.user_groups.count }}
{% trans 'RemoteApp count' %}:{{ object.remote_apps.count }}
{% trans 'Date start' %}:{{ object.date_start }}
{% trans 'Date expired' %}:{{ object.date_expired }}
{% trans 'Date created' %}:{{ object.date_created }}
{% trans 'Created by' %}:{{ object.created_by }}
{% trans 'Comment' %}:{{ object.comment }}
+
+
+
+ +
+
+
+ {% trans 'Quick update' %} +
+
+ + + + + + + +
{% trans 'Active' %} : +
+
+ + +
+
+
+
+
+ +
+
+
+
+
+
+{% endblock %} +{% block custom_foot_js %} + +{% endblock %} \ No newline at end of file diff --git a/apps/perms/templates/perms/remote_app_permission_list.html b/apps/perms/templates/perms/remote_app_permission_list.html new file mode 100644 index 000000000..8f1681781 --- /dev/null +++ b/apps/perms/templates/perms/remote_app_permission_list.html @@ -0,0 +1,93 @@ +{% extends '_base_list.html' %} +{% load i18n static %} +{% block table_search %}{% endblock %} +{% block table_container %} + + + + + + + + + + + + + + + +
+ + {% trans 'Name' %}{% trans 'User' %}{% trans 'User group' %}{% trans 'RemoteApp' %}{% trans 'Validity' %}{% trans 'Action' %}
+{% endblock %} +{% block content_bottom_left %}{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/perms/templates/perms/remote_app_permission_remote_app.html b/apps/perms/templates/perms/remote_app_permission_remote_app.html new file mode 100644 index 000000000..63d395941 --- /dev/null +++ b/apps/perms/templates/perms/remote_app_permission_remote_app.html @@ -0,0 +1,164 @@ +{% extends 'base.html' %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} + + +{% endblock %} +{% block content %} +
+
+
+
+ +
+
+
+
+ {% trans 'RemoteApp list of ' %} {{ remote_app_permission.name }} +
+ + + + + + + + + + +
+
+
+ + + + + + + + + + {% for remote_app in object_list %} + + + + + + {% endfor %} + +
{% trans 'Name' %}{% trans 'Type' %}
{{ remote_app.name }}{{ remote_app.get_type_display }} + +
+
+ {% include '_pagination.html' %} +
+
+
+
+
+
+
+ {% trans 'Add RemoteApp to this permission' %} +
+
+ + + + + + + + + + + +
+ +
+ +
+
+
+ +
+
+
+
+
+
+ +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} \ No newline at end of file diff --git a/apps/perms/templates/perms/remote_app_permission_user.html b/apps/perms/templates/perms/remote_app_permission_user.html new file mode 100644 index 000000000..7433327c5 --- /dev/null +++ b/apps/perms/templates/perms/remote_app_permission_user.html @@ -0,0 +1,256 @@ +{% extends 'base.html' %} +{% load static %} +{% load i18n %} + +{% block custom_head_css_js %} + + +{% endblock %} +{% block content %} +
+
+
+
+ +
+
+
+
+ {% trans 'User list of ' %} {{ remote_app_permission.name }} +
+ + + + + + + + + + +
+
+
+ + + + + + + + + + {% for user in object_list %} + + + + + + {% endfor %} + +
{% trans 'Name' %}{% trans 'Username' %}
{{ user.name }}{{ user.username }} + +
+
+ {% include '_pagination.html' %} +
+
+
+
+
+
+
+ {% trans 'Add user to this permission' %} +
+
+ + + + + + + + + + + +
+ +
+ +
+
+
+ +
+
+ {% trans 'Add user group to this permission' %} +
+
+ + + + + + + + + + + + {% for user_group in remote_app_permission.user_groups.all %} + + + + + {% endfor %} + +
+ +
+ +
{{ user_group }} + +
+
+
+
+
+
+
+
+
+ +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} \ No newline at end of file diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index 5894d3835..48f2ace61 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -9,8 +9,9 @@ app_name = 'perms' router = routers.DefaultRouter() router.register('actions', api.ActionViewSet, 'action') router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission') +router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remote-app-permission') -urlpatterns = [ +asset_permission_urlpatterns = [ # 查询某个用户授权的资产和资产组 path('user//assets/', api.UserGrantedAssetsApi.as_view(), name='user-assets'), @@ -35,7 +36,6 @@ urlpatterns = [ path('user/nodes-assets/tree/', api.UserGrantedNodesWithAssetsAsTreeApi.as_view(), name='my-nodes-assets-as-tree'), - # 查询某个用户组授权的资产和资产组 path('user-group//assets/', api.UserGroupGrantedAssetsApi.as_view(), name='user-group-assets'), @@ -72,5 +72,48 @@ urlpatterns = [ name='get-user-asset-permission-actions'), ] + +remote_app_permission_urlpatterns = [ + # 查询用户授权的RemoteApp + path('user//remote-apps/', + api.UserGrantedRemoteAppsApi.as_view(), name='user-remote-apps'), + path('user/remote-apps/', + api.UserGrantedRemoteAppsApi.as_view(), name='my-remote-apps'), + + # 获取用户授权的RemoteApp树 + path('user//remote-apps/tree/', + api.UserGrantedRemoteAppsAsTreeApi.as_view(), + name='user-remote-apps-as-tree'), + path('user/remote-apps/tree/', + api.UserGrantedRemoteAppsAsTreeApi.as_view(), + name='my-remote-apps-as-tree'), + + # 查询用户组授权的RemoteApp + path('user-group//remote-apps/', + api.UserGroupGrantedRemoteAppsApi.as_view(), + name='user-group-remote-apps'), + + # 校验用户对RemoteApp的权限 + path('remote-app-permission/user/validate/', + api.ValidateUserRemoteAppPermissionApi.as_view(), + name='validate-user-remote-app-permission'), + + # 用户和RemoteApp变更 + path('remote-app-permissions//user/add/', + api.RemoteAppPermissionAddUserApi.as_view(), + name='remote-app-permission-add-user'), + path('remote-app-permissions//user/remove/', + api.RemoteAppPermissionRemoveUserApi.as_view(), + name='remote-app-permission-remove-user'), + path('remote-app-permissions//remote-app/remove/', + api.RemoteAppPermissionRemoveRemoteAppApi.as_view(), + name='remote-app-permission-remove-remote-app'), + path('remote-app-permissions//remote-app/add/', + api.RemoteAppPermissionAddRemoteAppApi.as_view(), + name='remote-app-permission-add-remote-app'), +] + +urlpatterns = asset_permission_urlpatterns + remote_app_permission_urlpatterns + urlpatterns += router.urls diff --git a/apps/perms/urls/views_urls.py b/apps/perms/urls/views_urls.py index 8b8348d28..964025db3 100644 --- a/apps/perms/urls/views_urls.py +++ b/apps/perms/urls/views_urls.py @@ -7,6 +7,7 @@ from .. import views app_name = 'perms' urlpatterns = [ + # asset-permission path('asset-permission/', views.AssetPermissionListView.as_view(), name='asset-permission-list'), path('asset-permission/create/', views.AssetPermissionCreateView.as_view(), name='asset-permission-create'), path('asset-permission//update/', views.AssetPermissionUpdateView.as_view(), name='asset-permission-update'), @@ -14,4 +15,12 @@ urlpatterns = [ path('asset-permission//delete/', views.AssetPermissionDeleteView.as_view(), name='asset-permission-delete'), path('asset-permission//user/', views.AssetPermissionUserView.as_view(), name='asset-permission-user-list'), path('asset-permission//asset/', views.AssetPermissionAssetView.as_view(), name='asset-permission-asset-list'), + + # remote-app-permission + path('remote-app-permission/', views.RemoteAppPermissionListView.as_view(), name='remote-app-permission-list'), + path('remote-app-permission/create/', views.RemoteAppPermissionCreateView.as_view(), name='remote-app-permission-create'), + path('remote-app-permission//update/', views.RemoteAppPermissionUpdateView.as_view(), name='remote-app-permission-update'), + path('remote-app-permission//', views.RemoteAppPermissionDetailView.as_view(), name='remote-app-permission-detail'), + path('remote-app-permission//user/', views.RemoteAppPermissionUserView.as_view(), name='remote-app-permission-user-list'), + path('remote-app-permission//remote-app/', views.RemoteAppPermissionRemoteAppView.as_view(), name='remote-app-permission-remote-app-list'), ] diff --git a/apps/perms/utils/__init__.py b/apps/perms/utils/__init__.py new file mode 100644 index 000000000..129901afc --- /dev/null +++ b/apps/perms/utils/__init__.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# + +from .asset_permission import * +from .remote_app_permission import * diff --git a/apps/perms/utils.py b/apps/perms/utils/asset_permission.py similarity index 98% rename from apps/perms/utils.py rename to apps/perms/utils/asset_permission.py index bd5094def..cacad1718 100644 --- a/apps/perms/utils.py +++ b/apps/perms/utils/asset_permission.py @@ -12,12 +12,19 @@ from django.conf import settings from common.utils import get_logger from common.tree import TreeNode -from .models import AssetPermission, Action -from .hands import Node +from perms.models import AssetPermission, Action +from perms.hands import Node logger = get_logger(__file__) +__all__ = [ + 'AssetPermissionUtil', 'is_obj_attr_has', 'sort_assets', + 'parse_asset_to_tree_node', 'parse_node_to_tree_node', + 'check_system_user_action', +] + + class GenerateTree: def __init__(self): """ @@ -378,7 +385,7 @@ def sort_assets(assets, order_by='hostname', reverse=False): def parse_node_to_tree_node(node): - from . import serializers + from .. import serializers name = '{} ({})'.format(node.value, node.assets_amount) node_serializer = serializers.GrantedNodeSerializer(node) data = { @@ -444,11 +451,6 @@ def parse_asset_to_tree_node(node, asset, system_users): return tree_node -# -# actions -# - - def check_system_user_action(system_user, action): """ :param system_user: SystemUser object (包含动态属性: actions) diff --git a/apps/perms/utils/remote_app_permission.py b/apps/perms/utils/remote_app_permission.py new file mode 100644 index 000000000..0f67e32dc --- /dev/null +++ b/apps/perms/utils/remote_app_permission.py @@ -0,0 +1,81 @@ +# coding: utf-8 +# + +from django.db.models import Q + +from common.tree import TreeNode + +from ..models import RemoteAppPermission + + +__all__ = [ + 'RemoteAppPermissionUtil', + 'construct_remote_apps_tree_root', + 'parse_remote_app_to_tree_node', +] + + +def get_user_remote_app_permissions(user, include_group=True): + if include_group: + groups = user.groups.all() + arg = Q(users=user) | Q(user_groups__in=groups) + else: + arg = Q(users=user) + return RemoteAppPermission.objects.all().valid().filter(arg) + + +def get_user_group_remote_app_permissions(user_group): + return RemoteAppPermission.objects.all().valid().filter( + user_groups=user_group + ) + + +class RemoteAppPermissionUtil: + get_permissions_map = { + "User": get_user_remote_app_permissions, + "UserGroup": get_user_group_remote_app_permissions, + } + + def __init__(self, obj): + self.object = obj + + @property + def permissions(self): + obj_class = self.object.__class__.__name__ + func = self.get_permissions_map[obj_class] + _permissions = func(self.object) + return _permissions + + def get_remote_apps(self): + remote_apps = set() + for perm in self.permissions: + remote_apps.update(list(perm.remote_apps.all())) + return remote_apps + + +def construct_remote_apps_tree_root(): + tree_root = { + 'id': 'ID_REMOTE_APP_ROOT', + 'name': 'RemoteApp', + 'title': 'RemoteApp', + 'pId': '', + 'open': False, + 'isParent': True, + 'iconSkin': '', + 'meta': {'type': 'remote_app'} + } + return TreeNode(**tree_root) + + +def parse_remote_app_to_tree_node(parent, remote_app): + tree_node = { + 'id': remote_app.id, + 'name': remote_app.name, + 'title': remote_app.name, + 'pId': parent.id, + 'open': False, + 'isParent': False, + 'iconSkin': 'file', + 'meta': {'type': 'remote_app'} + } + return TreeNode(**tree_node) diff --git a/apps/perms/views/__init__.py b/apps/perms/views/__init__.py new file mode 100644 index 000000000..129901afc --- /dev/null +++ b/apps/perms/views/__init__.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# + +from .asset_permission import * +from .remote_app_permission import * diff --git a/apps/perms/views.py b/apps/perms/views/asset_permission.py similarity index 90% rename from apps/perms/views.py rename to apps/perms/views/asset_permission.py index 0e02b38a7..fc16b70a9 100644 --- a/apps/perms/views.py +++ b/apps/perms/views/asset_permission.py @@ -10,10 +10,19 @@ from django.conf import settings from common.permissions import AdminUserRequiredMixin from orgs.utils import current_org -from .hands import Node, Asset, SystemUser, User, UserGroup -from .models import AssetPermission, Action -from .forms import AssetPermissionForm -from .const import PERMS_ACTION_NAME_ALL +from perms.hands import Node, Asset, SystemUser, User, UserGroup +from perms.models import AssetPermission, Action +from perms.forms import AssetPermissionForm +from perms.const import PERMS_ACTION_NAME_ALL + + +__all__ = [ + 'AssetPermissionListView', 'AssetPermissionCreateView', + 'AssetPermissionUpdateView', 'AssetPermissionDetailView', + 'AssetPermissionDeleteView', 'AssetPermissionUserView', + 'AssetPermissionAssetView', + +] class AssetPermissionListView(AdminUserRequiredMixin, TemplateView): @@ -84,7 +93,7 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView): def get_context_data(self, **kwargs): context = { 'app': _('Perms'), - 'action': _('Update asset permission'), + 'action': _('Asset permission detail'), 'system_users_remain': SystemUser.objects.exclude( granted_by_permissions=self.object ), diff --git a/apps/perms/views/remote_app_permission.py b/apps/perms/views/remote_app_permission.py new file mode 100644 index 000000000..f4da75ffe --- /dev/null +++ b/apps/perms/views/remote_app_permission.py @@ -0,0 +1,144 @@ +# coding: utf-8 +# + +from django.utils.translation import ugettext as _ +from django.urls import reverse_lazy +from django.views.generic import ( + TemplateView, CreateView, UpdateView, DetailView, ListView +) +from django.views.generic.edit import SingleObjectMixin +from django.conf import settings + +from common.permissions import AdminUserRequiredMixin +from orgs.utils import current_org +from users.models import UserGroup +from assets.models import RemoteApp + +from ..models import RemoteAppPermission +from ..forms import RemoteAppPermissionCreateUpdateForm + + +__all__ = [ + 'RemoteAppPermissionListView', 'RemoteAppPermissionCreateView', + 'RemoteAppPermissionUpdateView', 'RemoteAppPermissionDetailView', + 'RemoteAppPermissionUserView', 'RemoteAppPermissionRemoteAppView' +] + + +class RemoteAppPermissionListView(AdminUserRequiredMixin, TemplateView): + template_name = 'perms/remote_app_permission_list.html' + + def get_context_data(self, **kwargs): + context = { + 'app': _('Perms'), + 'action': _('RemoteApp permission list'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class RemoteAppPermissionCreateView(AdminUserRequiredMixin, CreateView): + template_name = 'perms/remote_app_permission_create_update.html' + model = RemoteAppPermission + form_class = RemoteAppPermissionCreateUpdateForm + success_url = reverse_lazy('perms:remote-app-permission-list') + + def get_context_data(self, **kwargs): + context = { + 'app': _('Perms'), + 'action': _('Create RemoteApp permission'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class RemoteAppPermissionUpdateView(AdminUserRequiredMixin, UpdateView): + template_name = 'perms/remote_app_permission_create_update.html' + model = RemoteAppPermission + form_class = RemoteAppPermissionCreateUpdateForm + success_url = reverse_lazy('perms:remote-app-permission-list') + + def get_context_data(self, **kwargs): + context = { + 'app': _('Perms'), + 'action': _('Update RemoteApp permission') + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class RemoteAppPermissionDetailView(AdminUserRequiredMixin, DetailView): + template_name = 'perms/remote_app_permission_detail.html' + model = RemoteAppPermission + + def get_context_data(self, **kwargs): + context = { + 'app': _('Perms'), + 'action': _('RemoteApp permission detail'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class RemoteAppPermissionUserView(AdminUserRequiredMixin, + SingleObjectMixin, + ListView): + template_name = 'perms/remote_app_permission_user.html' + context_object_name = 'remote_app_permission' + paginate_by = settings.DISPLAY_PER_PAGE + object = None + + def get(self, request, *args, **kwargs): + self.object = self.get_object( + queryset=RemoteAppPermission.objects.all()) + return super().get(request, *args, **kwargs) + + def get_queryset(self): + queryset = list(self.object.get_all_users()) + return queryset + + def get_context_data(self, **kwargs): + context = { + 'app': _('Perms'), + 'action': _('RemoteApp permission user list'), + 'users_remain': current_org.get_org_users().exclude( + remote_app_permissions=self.object + ), + 'user_groups_remain': UserGroup.objects.exclude( + remote_app_permissions=self.object + ) + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class RemoteAppPermissionRemoteAppView(AdminUserRequiredMixin, + SingleObjectMixin, + ListView): + template_name = 'perms/remote_app_permission_remote_app.html' + context_object_name = 'remote_app_permission' + paginate_by = settings.DISPLAY_PER_PAGE + object = None + + def get(self, request, *args, **kwargs): + self.object = self.get_object( + queryset=RemoteAppPermission.objects.all() + ) + return super().get(request, *args, **kwargs) + + def get_queryset(self): + queryset = list(self.object.get_all_remote_apps()) + return queryset + + def get_context_data(self, **kwargs): + remote_app_granted = self.get_queryset() + remote_app_remain = RemoteApp.objects.exclude( + id__in=[a.id for a in remote_app_granted]) + context = { + 'app': _('Perms'), + 'action': _('RemoteApp permission RemoteApp list'), + 'remote_app_remain': remote_app_remain + } + kwargs.update(context) + return super().get_context_data(**kwargs) + diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 5c3124f8e..c24a7acb4 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -27,12 +27,23 @@
  • {% trans 'Command filters' %}
  • +
  • + + {% trans 'Applications' %} + + +
  • {% trans 'Perms' %}
  • diff --git a/apps/templates/_nav_user.html b/apps/templates/_nav_user.html index 151ed124e..5412dc37d 100644 --- a/apps/templates/_nav_user.html +++ b/apps/templates/_nav_user.html @@ -4,6 +4,18 @@ {% trans 'My assets' %}
  • +
  • + + {% trans 'My Applications' %} + + +
  • {% trans 'Command execution' %} From 1e505d3d0f9a83ba2712baab761d8eca30115c81 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Tue, 21 May 2019 16:07:40 +0800 Subject: [PATCH 07/36] =?UTF-8?q?[Update]=20OpenID=20Middleware=E5=8E=BB?= =?UTF-8?q?=E6=8E=89=E8=BE=93=E5=87=BA=E6=97=A5=E5=BF=97=20(#2711)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/openid/middleware.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/authentication/backends/openid/middleware.py b/apps/authentication/backends/openid/middleware.py index b1556ff04..43c7dbd22 100644 --- a/apps/authentication/backends/openid/middleware.py +++ b/apps/authentication/backends/openid/middleware.py @@ -23,15 +23,12 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin): def process_request(self, request): # Don't need openid auth if AUTH_OPENID is False if not settings.AUTH_OPENID: - logger.debug("Not settings.AUTH_OPENID") return # Don't need check single logout if user not authenticated if not request.user.is_authenticated: - logger.debug("User is not authenticated") return elif not request.session[BACKEND_SESSION_KEY].endswith( BACKEND_OPENID_AUTH_CODE): - logger.debug("BACKEND_SESSION_KEY is not BACKEND_OPENID_AUTH_CODE") return # Check openid user single logout or not with access_token From 4942900886ace106ebdf515f4dbd5eb84e67d330 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Tue, 21 May 2019 16:10:48 +0800 Subject: [PATCH 08/36] =?UTF-8?q?[Update]=20RemoteApp=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=B7=A6=E4=BE=A7=E8=8F=9C=E5=8D=95=E6=8F=8F=E8=BF=B0=20(#2709?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 74215 -> 74215 bytes apps/locale/zh/LC_MESSAGES/django.po | 6 +++--- apps/templates/_nav.html | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 0d17f388ba3f785308213fd499c61003499de620..b8c64ffce52e56ef3698928f7ac974fed7ffb69f 100644 GIT binary patch delta 22 ecmaEUnC1ClmJMh3u^TEF8d@0`ZoaZlI2izXM+v9^ delta 22 ecmaEUnC1ClmJMh3u^T8DT3Q*KZ@#imI2izXya~7f diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 9eda46fc6..b1f1774ef 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-05-20 19:37+0800\n" +"POT-Creation-Date: 2019-05-21 11:01+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -298,7 +298,7 @@ msgstr "备注" #: perms/templates/perms/remote_app_permission_list.html:17 #: perms/templates/perms/remote_app_permission_remote_app.html:26 #: perms/templates/perms/remote_app_permission_user.html:26 -#: templates/_nav.html:35 templates/_nav_user.html:14 +#: templates/_nav.html:35 templates/_nav.html:45 templates/_nav_user.html:14 msgid "RemoteApp" msgstr "远程应用" @@ -2973,7 +2973,7 @@ msgstr "失效日期" msgid "Asset permission" msgstr "资产授权" -#: perms/models/remote_app_permission.py:48 templates/_nav.html:45 +#: perms/models/remote_app_permission.py:48 msgid "RemoteApp permission" msgstr "远程应用授权" diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index c24a7acb4..684f90a1d 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -42,7 +42,7 @@ {% trans 'Asset permission' %}
  • - {% trans 'RemoteApp permission' %} + {% trans 'RemoteApp' %}
  • From 22f362aab36725e27a6bd434414395d4698e2085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AB=E5=8D=83=E6=B5=81?= <40739051+jym503558564@users.noreply.github.com> Date: Tue, 21 May 2019 16:24:01 +0800 Subject: [PATCH 09/36] Dev csv (#2640) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 封装JMSCSVRender和JMSCSVParser * [Update] 更改JMSCSVRender,根据请求参数控制导出csv的字段和下载csv模板的字段 * [Update] 导入空数据,提示错误消息 * [Update] 修改用户导入和导出功能代码 * [Update] 修改导入路由为动态反向解析 * [Update] 修改JMSCSVRender和JMSCSVParser以及用户导入导出代码 * [Update] 优化parsers逻辑 * [Update] 优化parsers csv代码结构 * [Update] 优化renders csv代码逻辑 * [Update] 删除parsers csv多余代码 * [Update] 删除parsers csv多余变量 * [Update] 优化renders csv代码结构 * [Update] 优化renders csv代码结构2 * [Update] 优化renders csv获取header逻辑 * [Update] 优化Cache Resources ID View逻辑 * [Update] 优化ViewSet IDCacheFilterMixin逻辑 * [Update] csv: parser render 添加异常捕获逻辑 * [Update] 删除多余代码 * [Update] 优化前端代码 * [Update] 修改小问题 * [Update] 修改前端导出用户的问题 * [Update] 前端 - 优化数据导出逻辑 APIExportData * [Update] 修复批量创建用户时发送created信号的bug * [Update] 优化导入时错误信息展示 * [Update] 优化parser、render时,对于多对多字段的处理 * [Update] 修改前端上传空文件问题 * [Update] 添加IDExportFilter,控制下载模版时的queryset * [Update] 修改判断导出模版时参数变量名 action => template * [Update] 修复导入用户数据时,用户组不生效的bug * [Update] 修改前端导入信息展示 * [Update] 抽象资源导入模版 * [Update] 优化资源导入模版 * [Update] 修改js设置url的params逻辑 * [Update] 修改users序列类控制read_only字段方式 * [Update] 资产列表采用新的导入/导出csv文件逻辑 * [Update] 修改导入资产时设置资产所在节点逻辑 * [Update] 添加用户组导入/导出功能 * [Update] 修改前端变量名 * [Update] 修改下载导入模版,不包含org字段 * [Update] 增加管理用户导入/导出功能 * [Update] 导入模版提供id字段(为了资源备份后导入直接使用); 修复资源导入时联合唯一字段不校验导致创建时报错的bug * [Update] 增加系统用户导入/导出功能 * [Update] 排序资源导入/导出字段 * [Update] 翻译导入/导出的字段和模版 * [Update] 更改csv导出和导出模版数据的控制在render实现 * [Update] 资产添加 更新导入 功能 * [Update] 用户/用户组/管理用户/系统用户/ 添加导入更新 * [Update] 翻译 * [Update] 优化资源序列化中的label * [Update] 去掉资源IDInFilterMixin过滤 * [Update] 翻译 --- apps/assets/api/admin_user.py | 4 +- apps/assets/api/asset.py | 22 +- apps/assets/api/system_user.py | 3 +- apps/assets/serializers/admin_user.py | 24 +- apps/assets/serializers/asset.py | 29 +- apps/assets/serializers/system_user.py | 34 +- .../assets/_admin_user_import_modal.html | 6 + .../assets/_admin_user_update_modal.html | 4 + .../templates/assets/_asset_import_modal.html | 33 +- .../templates/assets/_asset_update_modal.html | 4 + .../assets/_system_user_import_modal.html | 6 + .../assets/_system_user_update_modal.html | 4 + .../templates/assets/admin_user_list.html | 107 ++- apps/assets/templates/assets/asset_list.html | 237 ++++-- .../templates/assets/system_user_list.html | 99 +++ apps/assets/views/asset.py | 6 +- apps/common/api.py | 23 +- apps/common/const.py | 1 + apps/common/mixins.py | 30 +- apps/common/parsers/__init__.py | 1 + apps/common/parsers/csv.py | 101 +++ apps/common/renders/__init__.py | 1 + apps/common/renders/csv.py | 71 ++ apps/common/urls/__init__.py | 0 apps/common/urls/api_urls.py | 13 + apps/jumpserver/settings.py | 10 + apps/jumpserver/urls.py | 1 + apps/locale/zh/LC_MESSAGES/django.mo | Bin 74215 -> 72271 bytes apps/locale/zh/LC_MESSAGES/django.po | 792 ++++++++++++++---- apps/orgs/mixins.py | 16 +- apps/orgs/utils.py | 6 + apps/static/js/jumpserver.js | 85 +- apps/templates/_import_modal.html | 28 + apps/templates/_modal.html | 26 +- apps/templates/_update_modal.html | 28 + apps/users/api/group.py | 4 +- apps/users/api/user.py | 16 +- apps/users/serializers/v1.py | 36 +- apps/users/signals_handler.py | 1 - .../users/_user_groups_import_modal.html | 6 + .../users/_user_groups_update_modal.html | 4 + .../templates/users/_user_import_modal.html | 32 +- .../templates/users/_user_update_modal.html | 4 + .../templates/users/user_group_list.html | 100 ++- apps/users/templates/users/user_list.html | 219 +++-- apps/users/views/user.py | 13 +- 46 files changed, 1868 insertions(+), 422 deletions(-) create mode 100644 apps/assets/templates/assets/_admin_user_import_modal.html create mode 100644 apps/assets/templates/assets/_admin_user_update_modal.html create mode 100644 apps/assets/templates/assets/_asset_update_modal.html create mode 100644 apps/assets/templates/assets/_system_user_import_modal.html create mode 100644 apps/assets/templates/assets/_system_user_update_modal.html create mode 100644 apps/common/parsers/__init__.py create mode 100644 apps/common/parsers/csv.py create mode 100644 apps/common/renders/__init__.py create mode 100644 apps/common/renders/csv.py create mode 100644 apps/common/urls/__init__.py create mode 100644 apps/common/urls/api_urls.py create mode 100644 apps/templates/_import_modal.html create mode 100644 apps/templates/_update_modal.html create mode 100644 apps/users/templates/users/_user_groups_import_modal.html create mode 100644 apps/users/templates/users/_user_groups_update_modal.html create mode 100644 apps/users/templates/users/_user_update_modal.html diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py index f2229022f..e84d9731a 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -20,7 +20,7 @@ from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from rest_framework.pagination import LimitOffsetPagination -from common.mixins import IDInFilterMixin +from common.mixins import IDInCacheFilterMixin from common.utils import get_logger from ..hands import IsOrgAdmin from ..models import AdminUser, Asset @@ -36,7 +36,7 @@ __all__ = [ ] -class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet): +class AdminUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): """ Admin user api set, for add,delete,update,list,retrieve resource """ diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index a734806fb..9e21c81be 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -16,8 +16,9 @@ from django.urls import reverse_lazy from django.core.cache import cache from django.db.models import Q -from common.mixins import IDInFilterMixin -from common.utils import get_logger +from common.mixins import IDInCacheFilterMixin + +from common.utils import get_logger, get_object_or_none from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX from ..models import Asset, AdminUser, Node @@ -35,7 +36,7 @@ __all__ = [ ] -class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): +class AssetViewSet(IDInCacheFilterMixin, LabelFilter, BulkModelViewSet): """ API endpoint that allows Asset to be viewed or edited. """ @@ -47,6 +48,19 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): pagination_class = LimitOffsetPagination permission_classes = (IsOrgAdminOrAppUser,) + def set_assets_node(self, assets): + if not isinstance(assets, list): + assets = [assets] + node = Node.objects.get(value='Default') + node_id = self.request.query_params.get('node_id') + if node_id: + node = get_object_or_none(Node, pk=node_id) + node.assets.add(*assets) + + def perform_create(self, serializer): + assets = serializer.save() + self.set_assets_node(assets) + def filter_node(self, queryset): node_id = self.request.query_params.get("node_id") if not node_id: @@ -89,7 +103,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): return queryset -class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): +class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView): """ Asset bulk update api """ diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 9805872e7..f6398e974 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -21,6 +21,7 @@ from rest_framework.pagination import LimitOffsetPagination from common.utils import get_logger from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser +from common.mixins import IDInCacheFilterMixin from ..models import SystemUser, Asset from .. import serializers from ..tasks import push_system_user_to_assets_manual, \ @@ -38,7 +39,7 @@ __all__ = [ ] -class SystemUserViewSet(BulkModelViewSet): +class SystemUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): """ System user api set, for add,delete,update,list,retrieve resource """ diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py index e44679995..66f25db87 100644 --- a/apps/assets/serializers/admin_user.py +++ b/apps/assets/serializers/admin_user.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # from django.core.cache import cache +from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from common.serializers import AdaptedBulkListSerializer @@ -15,14 +16,29 @@ class AdminUserSerializer(serializers.ModelSerializer): """ 管理用户 """ - assets_amount = serializers.SerializerMethodField() - unreachable_amount = serializers.SerializerMethodField() - reachable_amount = serializers.SerializerMethodField() + password = serializers.CharField( + required=False, write_only=True, label=_('Password') + ) + unreachable_amount = serializers.SerializerMethodField(label=_('Unreachable')) + assets_amount = serializers.SerializerMethodField(label=_('Asset')) + reachable_amount = serializers.SerializerMethodField(label=_('Reachable')) class Meta: list_serializer_class = AdaptedBulkListSerializer model = AdminUser - fields = '__all__' + fields = [ + 'id', 'org_id', 'name', 'username', 'assets_amount', + 'reachable_amount', 'unreachable_amount', 'password', 'comment', + 'date_created', 'date_updated', 'become', 'become_method', + 'become_user', 'created_by', + ] + + extra_kwargs = { + 'date_created': {'label': _('Date created')}, + 'date_updated': {'label': _('Date updated')}, + 'become': {'read_only': True}, 'become_method': {'read_only': True}, + 'become_user': {'read_only': True}, 'created_by': {'read_only': True} + } def get_field_names(self, declared_fields, info): fields = super().get_field_names(declared_fields, info) diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index c0f435adc..3e1cf39bb 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -2,6 +2,9 @@ # from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ + +from orgs.mixins import OrgResourceSerializerMixin from common.mixins import BulkSerializerMixin from common.serializers import AdaptedBulkListSerializer from ..models import Asset @@ -13,15 +16,35 @@ __all__ = [ ] -class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): +class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResourceSerializerMixin): """ 资产的数据结构 """ class Meta: model = Asset list_serializer_class = AdaptedBulkListSerializer - fields = '__all__' - validators = [] + # validators = [] # 解决批量导入时unique_together字段校验失败 + fields = [ + 'id', 'org_id', 'org_name', 'ip', 'hostname', 'protocol', 'port', + 'platform', 'is_active', 'public_ip', 'domain', 'admin_user', + 'nodes', 'labels', 'number', 'vendor', 'model', 'sn', + 'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory', + 'disk_total', 'disk_info', 'os', 'os_version', 'os_arch', + 'hostname_raw', 'comment', 'created_by', 'date_created', + 'hardware_info', 'connectivity' + ] + read_only_fields = ( + 'number', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', + 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', + 'os', 'os_version', 'os_arch', 'hostname_raw', + 'created_by', 'date_created', + ) + extra_kwargs = { + 'hardware_info': {'label': _('Hardware info')}, + 'connectivity': {'label': _('Connectivity')}, + 'org_name': {'label': _('Org name')} + + } @classmethod def setup_eager_loading(cls, queryset): diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index c737f8cbe..88ae6eb38 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -1,5 +1,7 @@ from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ + from common.serializers import AdaptedBulkListSerializer from ..models import SystemUser, Asset @@ -10,16 +12,36 @@ class SystemUserSerializer(serializers.ModelSerializer): """ 系统用户 """ - unreachable_amount = serializers.SerializerMethodField() - reachable_amount = serializers.SerializerMethodField() - unreachable_assets = serializers.SerializerMethodField() - reachable_assets = serializers.SerializerMethodField() - assets_amount = serializers.SerializerMethodField() + password = serializers.CharField( + required=False, write_only=True, label=_('Password') + ) + unreachable_amount = serializers.SerializerMethodField( + label=_('Unreachable') + ) + unreachable_assets = serializers.SerializerMethodField( + label=_('Unreachable assets') + ) + reachable_assets = serializers.SerializerMethodField( + label=_('Reachable assets') + ) + reachable_amount = serializers.SerializerMethodField(label=_('Reachable')) + assets_amount = serializers.SerializerMethodField(label=_('Asset')) class Meta: model = SystemUser - exclude = ('_password', '_private_key', '_public_key') list_serializer_class = AdaptedBulkListSerializer + fields = [ + 'id', 'org_id', 'name', 'username', 'login_mode', + 'login_mode_display', 'priority', 'protocol', 'auto_push', + 'password', 'assets_amount', 'reachable_amount', 'reachable_assets', + 'unreachable_amount', 'unreachable_assets', 'cmd_filters', 'sudo', + 'shell', 'comment', 'nodes', 'assets' + ] + extra_kwargs = { + 'login_mode_display': {'label': _('Login mode display')}, + 'created_by': {'read_only': True}, 'nodes': {'read_only': True}, + 'assets': {'read_only': True} + } def get_field_names(self, declared_fields, info): fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info) diff --git a/apps/assets/templates/assets/_admin_user_import_modal.html b/apps/assets/templates/assets/_admin_user_import_modal.html new file mode 100644 index 000000000..a4afc1a14 --- /dev/null +++ b/apps/assets/templates/assets/_admin_user_import_modal.html @@ -0,0 +1,6 @@ +{% extends '_import_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Import admin user" %}{% endblock %} + +{% block import_modal_download_template_url %}{% url "api-assets:admin-user-list" %}{% endblock %} diff --git a/apps/assets/templates/assets/_admin_user_update_modal.html b/apps/assets/templates/assets/_admin_user_update_modal.html new file mode 100644 index 000000000..9af051dd2 --- /dev/null +++ b/apps/assets/templates/assets/_admin_user_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update admin user" %}{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/_asset_import_modal.html b/apps/assets/templates/assets/_asset_import_modal.html index ca7729e05..2460cb053 100644 --- a/apps/assets/templates/assets/_asset_import_modal.html +++ b/apps/assets/templates/assets/_asset_import_modal.html @@ -1,29 +1,6 @@ -{% extends '_modal.html' %} +{% extends '_import_modal.html' %} {% load i18n %} -{% block modal_id %}asset_import_modal{% endblock %} -{% block modal_title%}{% trans "Import asset" %}{% endblock %} -{% block modal_body %} -
    - {% csrf_token %} -
    - - {% trans 'Download' %} -
    -
    - - - - {% trans 'If set id, will use this id update asset existed' %} - -
    -
    -

    -

    -

    -

    -

    -

    -

    -

    -{% endblock %} -{% block modal_confirm_id %}btn_asset_import{% endblock %} + +{% block modal_title%}{% trans "Import assets" %}{% endblock %} + +{% block import_modal_download_template_url %}{% url "api-assets:asset-list" %}{% endblock %} diff --git a/apps/assets/templates/assets/_asset_update_modal.html b/apps/assets/templates/assets/_asset_update_modal.html new file mode 100644 index 000000000..68b2ff8db --- /dev/null +++ b/apps/assets/templates/assets/_asset_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update assets" %}{% endblock %} diff --git a/apps/assets/templates/assets/_system_user_import_modal.html b/apps/assets/templates/assets/_system_user_import_modal.html new file mode 100644 index 000000000..b8687d696 --- /dev/null +++ b/apps/assets/templates/assets/_system_user_import_modal.html @@ -0,0 +1,6 @@ +{% extends '_import_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Import system user" %}{% endblock %} + +{% block import_modal_download_template_url %}{% url "api-assets:system-user-list" %}{% endblock %} diff --git a/apps/assets/templates/assets/_system_user_update_modal.html b/apps/assets/templates/assets/_system_user_update_modal.html new file mode 100644 index 000000000..9e2920e6a --- /dev/null +++ b/apps/assets/templates/assets/_system_user_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update system user" %}{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html index 605e89060..c182ba4c4 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -1,8 +1,5 @@ {% extends '_base_list.html' %} {% load i18n static %} -{% block table_search %} -{% endblock %} - {% block help_message %}
    {# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#} @@ -12,6 +9,30 @@ {% trans 'You can set any one for Windows or other hardware.' %}
    {% endblock %} +{% block table_search %} + +{% endblock %} {% block table_container %}
    @@ -36,6 +57,8 @@ + {% include 'assets/_admin_user_import_modal.html' %} + {% include 'assets/_admin_user_update_modal.html' %} {% endblock %} {% block content_bottom_left %}{% endblock %} {% block custom_foot_js %} @@ -107,6 +130,82 @@ $(document).ready(function(){ $data_table.ajax.reload(); }, 3000); -}); +}) +.on('click', '.btn_export', function(){ + var data_table = $('#admin_user_list_table').DataTable(); + var rows = data_table.rows('.selected').data(); + var admin_users = []; + $.each(rows, function (index, obj) { + admin_users.push(obj.id) + }); + var data = { + 'resources': admin_users + }; + var search = $("input[type='search']").val(); + var props = { + method: "POST", + body: JSON.stringify(data), + success_url: "{% url 'api-assets:admin-user-list' %}", + format: "csv", + params: { + search: search + } + }; + APIExportData(props); +}).on('click', '#btn_import_confirm',function () { + var url = "{% url 'api-assets:admin-user-list' %}"; + var file = document.getElementById('id_file').files[0]; + if(!file){ + toastr.error("{% trans "Please select file" %}"); + return + } + var data_table = $('#admin_user_list_table').DataTable(); + APIImportData({ + url: url, + method: "POST", + body: file, + data_table: data_table + }); +}) +.on('click', '#download_update_template', function () { + var $data_table = $('#admin_user_list_table').DataTable(); + var rows = $data_table.rows('.selected').data(); + + var admin_users = []; + $.each(rows, function (index, obj) { + admin_users.push(obj.id) + }); + + var data = { + 'resources': admin_users + }; + var search = $("input[type='search']").val(); + var props = { + method: "POST", + body: JSON.stringify(data), + success_url: "{% url 'api-assets:admin-user-list' %}?format=csv&template=update", + format: 'csv', + params: { + search: search + } + }; + APIExportData(props); +}) +.on('click', '#btn_update_confirm', function () { + var file = document.getElementById('update_file').files[0]; + if(!file){ + toastr.error("{% trans "Please select file" %}"); + return + } + var url = "{% url 'api-assets:admin-user-list' %}"; + var data_table = $('#admin_user_list_table').DataTable(); + + APIImportData({ + url: url, + method: "PUT", + body: file, + data_table: data_table + }); +}) {% endblock %} diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index aa27de7a8..ea74e25d1 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -67,14 +67,26 @@
    -
    -
    - - {% trans "Import" %} - - - {% trans "Export" %} - +
    @@ -140,7 +152,7 @@ {#
  • {% trans 'Refresh' %}
  • #}
    - +{% include 'assets/_asset_update_modal.html' %} {% include 'assets/_asset_import_modal.html' %} {% include 'assets/_asset_list_modal.html' %} {% endblock %} @@ -464,42 +476,85 @@ $(document).ready(function(){ $.each(rows, function (index, obj) { assets.push(obj.id) }); - $.ajax({ - url: "{% url "assets:asset-export" %}", - method: 'POST', - data: JSON.stringify({assets_id: assets, node_id: current_node_id}), - dataType: "json", - success: function (data, textStatus) { - window.open(data.redirect) - }, - error: function () { - toastr.error('Export failed'); + + var data = { + 'resources': assets + }; + var search = $("input[type='search']").val(); + var props = { + method: "POST", + body: JSON.stringify(data), + success_url: "{% url 'api-assets:asset-list' %}", + format: 'csv', + params: { + search: search, + node_id: current_node_id || '' } - }) + }; + APIExportData(props); }) -.on('click', '#btn_asset_import', function () { - var $form = $('#fm_asset_import'); - var action = $form.attr("action"); +.on('click', '#btn_import_confirm', function () { + var file = document.getElementById('id_file').files[0]; + if(!file){ + toastr.error("{% trans "Please select file" %}"); + return + } + var url = "{% url 'api-assets:asset-list' %}"; if (current_node_id){ - action = setUrlParam(action, 'node_id', current_node_id); - $form.attr("action", action) + url = setUrlParam(url, 'node_id', current_node_id); } - $form.find('.help-block').remove(); - function success (data) { - if (data.valid === false) { - $('', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_assets')); - } else { - $('#id_created').html(data.created_info); - $('#id_created_detail').html(data.created.join(', ')); - $('#id_updated').html(data.updated_info); - $('#id_updated_detail').html(data.updated.join(', ')); - $('#id_failed').html(data.failed_info); - $('#id_failed_detail').html(data.failed.join(', ')); - var $data_table = $('#asset_list_table').DataTable(); - $data_table.ajax.reload(); + var data_table = $('#asset_list_table').DataTable(); + + APIImportData({ + url: url, + method: "POST", + body: file, + data_table: data_table + }); +}) +.on('click', '#download_update_template', function () { + var $data_table = $('#asset_list_table').DataTable(); + var rows = $data_table.rows('.selected').data(); + + var assets = []; + $.each(rows, function (index, obj) { + assets.push(obj.id) + }); + + var data = { + 'resources': assets + }; + var search = $("input[type='search']").val(); + var props = { + method: "POST", + body: JSON.stringify(data), + success_url: "{% url 'api-assets:asset-list' %}?format=csv&template=update", + format: 'csv', + params: { + search: search, + node_id: current_node_id || '' } + }; + APIExportData(props); +}) +.on('click', '#btn_update_confirm', function () { + var file = document.getElementById('update_file').files[0]; + if(!file){ + toastr.error("{% trans "Please select file" %}"); + return } - $form.ajaxSubmit({success: success}); + var url = "{% url 'api-assets:asset-list' %}"; + if (current_node_id){ + url = setUrlParam(url, 'node_id', current_node_id); + } + var data_table = $('#asset_list_table').DataTable(); + + APIImportData({ + url: url, + method: "PUT", + body: file, + data_table: data_table + }); }) .on('click', '.btn-create-asset', function () { var url = "{% url 'assets:asset-create' %}"; @@ -593,6 +648,12 @@ $(document).ready(function(){ return false; } var the_url = "{% url 'api-assets:asset-list' %}"; + var data = { + 'resources': id_list + }; + function refreshTag() { + $('#asset_list_table').DataTable().ajax.reload(); + } function doDeactive() { var data = []; @@ -601,7 +662,8 @@ $(document).ready(function(){ data.push(obj); }); function success() { - asset_table.ajax.reload() + setTimeout( function () { + window.location.reload();}, 500); } APIUpdateAttr({ url: the_url, @@ -617,7 +679,8 @@ $(document).ready(function(){ data.push(obj); }); function success() { - asset_table.ajax.reload() + setTimeout( function () { + window.location.reload();}, 300); } APIUpdateAttr({ url: the_url, @@ -636,68 +699,72 @@ $(document).ready(function(){ confirmButtonColor: "#DD6B55", confirmButtonText: "{% trans 'Confirm' %}", closeOnConfirm: false - }, function() { - var success = function() { + },function () { + function success(data) { + url = setUrlParam(the_url, 'spm', data.spm); + APIUpdateAttr({ + url:url, + method:'DELETE', + success:refreshTag, + flash_message:false, + }); var msg = "{% trans 'Asset Deleted.' %}"; swal("{% trans 'Asset Delete' %}", msg, "success"); - $('#asset_list_table').DataTable().ajax.reload(); - }; - var fail = function() { + } + function fail() { var msg = "{% trans 'Asset Deleting failed.' %}"; swal("{% trans 'Asset Delete' %}", msg, "error"); - }; - var url_delete = the_url + '?id__in=' + JSON.stringify(id_list); + } APIUpdateAttr({ - url: url_delete, - method: 'DELETE', - success: success, - error: fail - }); - $data_table.ajax.reload(); - jumpserver.checked = false; - }); + url: "{% url 'api-common:resources-cache' %}", + method:'POST', + body:JSON.stringify(data), + success:success, + error:fail + }) + }) } + function doUpdate() { - var data = { - 'assets_id':id_list - }; - function error(data) { - toastr.error(JSON.parse(data).error) + function fail(data) { + toastr.error(JSON.parse(data)) } function success(data) { - location.href = data.url; + var url = "{% url 'assets:asset-bulk-update' %}"; + location.href= setUrlParam(url, 'spm', data.spm); } APIUpdateAttr({ - 'url': "{% url 'api-assets:asset-bulk-update-select' %}", - 'method': 'POST', - 'body': JSON.stringify(data), - 'flash_message': false, - 'success': success, - 'error': error, + url: "{% url 'api-common:resources-cache' %}", + method:'POST', + body:JSON.stringify(data), + flash_message:false, + success:success, + error:fail }) - } + } function doRemove() { - var nodes = zTree.getSelectedNodes(); - if (!current_node_id) { - return - } + var nodes = zTree.getSelectedNodes(); + if (!current_node_id) { + return + } - var data = { - 'assets': id_list - }; + var data = { + 'assets': id_list + }; - var success = function () { - asset_table.ajax.reload() - }; + var success = function () { + asset_table.ajax.reload() + }; - APIUpdateAttr({ - 'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/', - 'method': 'PUT', - 'body': JSON.stringify(data), - 'success': success - }) + APIUpdateAttr({ + 'url': '/api/assets/v1/nodes/' + current_node_id + '/assets/remove/', + 'method': 'PUT', + 'body': JSON.stringify(data), + 'success': success + }) } + switch(action) { case 'deactive': doDeactive(); diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html index b31039a46..16a0fd11c 100644 --- a/apps/assets/templates/assets/system_user_list.html +++ b/apps/assets/templates/assets/system_user_list.html @@ -14,6 +14,28 @@ {% endblock %} {% block table_search %} + {% endblock %} {% block table_container %} @@ -41,6 +63,8 @@ + {% include 'assets/_system_user_import_modal.html' %} + {% include 'assets/_system_user_update_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index 96bc451f0..653fa12c3 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -27,7 +27,9 @@ from django.contrib.messages.views import SuccessMessageMixin from common.mixins import JSONResponseMixin from common.utils import get_object_or_none, get_logger from common.permissions import AdminUserRequiredMixin -from common.const import create_success_msg, update_success_msg +from common.const import ( + create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID +) from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX from orgs.utils import current_org from .. import forms @@ -122,7 +124,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView): def get(self, request, *args, **kwargs): spm = request.GET.get('spm', '') - assets_id = cache.get(CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm)) + assets_id = cache.get(KEY_CACHE_RESOURCES_ID.format(spm)) if kwargs.get('form'): self.form = kwargs['form'] elif assets_id: diff --git a/apps/common/api.py b/apps/common/api.py index 269d493d0..4f5f8da30 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -3,10 +3,18 @@ import os import uuid -from rest_framework.views import Response -from rest_framework import generics, serializers from django.core.cache import cache +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import generics, serializers + +from .const import KEY_CACHE_RESOURCES_ID + +__all__ = [ + 'LogTailApi', 'ResourcesIDCacheApi', +] + class OutputSerializer(serializers.Serializer): output = serializers.CharField() @@ -68,3 +76,14 @@ class LogTailApi(generics.RetrieveAPIView): data, end, new_mark = self.read_from_file() return Response({"data": data, 'end': end, 'mark': new_mark}) + + +class ResourcesIDCacheApi(APIView): + + def post(self, request, *args, **kwargs): + spm = str(uuid.uuid4()) + resources_id = request.data.get('resources') + if resources_id: + cache_key = KEY_CACHE_RESOURCES_ID.format(spm) + cache.set(cache_key, resources_id, 300) + return Response({'spm': spm}) diff --git a/apps/common/const.py b/apps/common/const.py index 018177d89..72d92da81 100644 --- a/apps/common/const.py +++ b/apps/common/const.py @@ -7,3 +7,4 @@ create_success_msg = _("%(name)s was created successfully") update_success_msg = _("%(name)s was updated successfully") FILE_END_GUARD = ">>> Content End <<<" celery_task_pre_key = "CELERY_" +KEY_CACHE_RESOURCES_ID = "RESOURCES_ID_{}" diff --git a/apps/common/mixins.py b/apps/common/mixins.py index a5e9a58d3..8e4af26dd 100644 --- a/apps/common/mixins.py +++ b/apps/common/mixins.py @@ -3,12 +3,15 @@ from django.db import models from django.http import JsonResponse from django.utils import timezone +from django.core.cache import cache from django.utils.translation import ugettext_lazy as _ from rest_framework.utils import html from rest_framework.settings import api_settings from rest_framework.exceptions import ValidationError from rest_framework.fields import SkipField +from .const import KEY_CACHE_RESOURCES_ID + class NoDeleteQuerySet(models.query.QuerySet): @@ -65,6 +68,27 @@ class IDInFilterMixin(object): return queryset +class IDInCacheFilterMixin(object): + + def filter_queryset(self, queryset): + queryset = super(IDInCacheFilterMixin, self).filter_queryset(queryset) + spm = self.request.query_params.get('spm') + cache_key = KEY_CACHE_RESOURCES_ID.format(spm) + resources_id = cache.get(cache_key) + if resources_id and isinstance(resources_id, list): + queryset = queryset.filter(id__in=resources_id) + return queryset + + +class IDExportFilterMixin(object): + def filter_queryset(self, queryset): + # 下载导入模版 + if self.request.query_params.get('template') == 'import': + return [] + else: + return super(IDExportFilterMixin, self).filter_queryset(queryset) + + class BulkSerializerMixin(object): """ Become rest_framework_bulk not support uuid as a primary key @@ -131,7 +155,11 @@ class BulkListSerializerMixin(object): for item in data: try: # prepare child serializer to only handle one instance - self.child.instance = self.instance.get(id=item['id']) if self.instance else None + if 'id' in item.keys(): + self.child.instance = self.instance.get(id=item['id']) if self.instance else None + if 'pk' in item.keys(): + self.child.instance = self.instance.get(id=item['pk']) if self.instance else None + self.child.initial_data = item # raw validated = self.child.run_validation(item) diff --git a/apps/common/parsers/__init__.py b/apps/common/parsers/__init__.py new file mode 100644 index 000000000..671c86586 --- /dev/null +++ b/apps/common/parsers/__init__.py @@ -0,0 +1 @@ +from .csv import * \ No newline at end of file diff --git a/apps/common/parsers/csv.py b/apps/common/parsers/csv.py new file mode 100644 index 000000000..b536a0f73 --- /dev/null +++ b/apps/common/parsers/csv.py @@ -0,0 +1,101 @@ +# ~*~ coding: utf-8 ~*~ +# + +import json +import unicodecsv + +from rest_framework.parsers import BaseParser +from rest_framework.exceptions import ParseError + +from ..utils import get_logger + +logger = get_logger(__file__) + + +class JMSCSVParser(BaseParser): + """ + Parses CSV file to serializer data + """ + + media_type = 'text/csv' + + @staticmethod + def _universal_newlines(stream): + """ + 保证在`通用换行模式`下打开文件 + """ + for line in stream.splitlines(): + yield line + + @staticmethod + def _gen_rows(csv_data, charset='utf-8', **kwargs): + csv_reader = unicodecsv.reader(csv_data, encoding=charset, **kwargs) + for row in csv_reader: + if not any(row): # 空行 + continue + yield row + + @staticmethod + def _get_fields_map(serializer): + fields_map = {} + fields = serializer.get_fields() + fields_map.update({v.label: k for k, v in fields.items()}) + fields_map.update({k: k for k, _ in fields.items()}) + return fields_map + + @staticmethod + def _process_row(row): + """ + 构建json数据前的行处理 + """ + _row = [] + for col in row: + # 列表转换 + if isinstance(col, str) and col.find("[") != -1 and col.find("]") != -1: + # 替换中文格式引号 + col = col.replace("“", '"').replace("”", '"').\ + replace("‘", '"').replace('’', '"').replace("'", '"') + col = json.loads(col) + _row.append(col) + return _row + + @staticmethod + def _process_row_data(row_data): + """ + 构建json数据后的行数据处理 + """ + _row_data = {} + for k, v in row_data.items(): + if isinstance(v, list) \ + or isinstance(v, str) and k.strip() and v.strip(): + _row_data[k] = v + return _row_data + + def parse(self, stream, media_type=None, parser_context=None): + parser_context = parser_context or {} + encoding = parser_context.get('encoding', 'utf-8') + try: + serializer = parser_context["view"].get_serializer() + except Exception as e: + logger.debug(e, exc_info=True) + raise ParseError('The resource does not support imports!') + + try: + stream_data = stream.read() + binary = self._universal_newlines(stream_data) + rows = self._gen_rows(binary, charset=encoding) + + header = next(rows) + fields_map = self._get_fields_map(serializer) + header = [fields_map.get(name, '') for name in header] + + data = [] + for row in rows: + row = self._process_row(row) + row_data = dict(zip(header, row)) + row_data = self._process_row_data(row_data) + data.append(row_data) + return data + except Exception as e: + logger.debug(e, exc_info=True) + raise ParseError('CSV parse error!') diff --git a/apps/common/renders/__init__.py b/apps/common/renders/__init__.py new file mode 100644 index 000000000..671c86586 --- /dev/null +++ b/apps/common/renders/__init__.py @@ -0,0 +1 @@ +from .csv import * \ No newline at end of file diff --git a/apps/common/renders/csv.py b/apps/common/renders/csv.py new file mode 100644 index 000000000..cec857ddb --- /dev/null +++ b/apps/common/renders/csv.py @@ -0,0 +1,71 @@ +# ~*~ coding: utf-8 ~*~ +# + +import unicodecsv + +from six import BytesIO +from rest_framework.renderers import BaseRenderer +from rest_framework.utils import encoders, json + +from ..utils import get_logger + +logger = get_logger(__file__) + + +class JMSCSVRender(BaseRenderer): + + media_type = 'text/csv' + format = 'csv' + + @staticmethod + def _get_header(fields, template): + if template == 'import': + header = [ + k for k, v in fields.items() + if not v.read_only and k != 'org_id' + ] + elif template == 'update': + header = [k for k, v in fields.items() if not v.read_only] + else: + # template in ['export'] + header = [k for k, v in fields.items() if not v.write_only] + return header + + @staticmethod + def _gen_table(data, header, labels=None): + labels = labels or {} + yield [labels.get(k, k) for k in header] + + for item in data: + row = [item.get(key) for key in header] + yield row + + def render(self, data, media_type=None, renderer_context=None): + renderer_context = renderer_context or {} + encoding = renderer_context.get('encoding', 'utf-8') + request = renderer_context['request'] + template = request.query_params.get('template', 'export') + view = renderer_context['view'] + data = json.loads(json.dumps(data, cls=encoders.JSONEncoder)) + if template == 'import': + data = [data[0]] if data else data + + try: + serializer = view.get_serializer() + except Exception as e: + logger.debug(e, exc_info=True) + value = 'The resource not support export!'.encode('utf-8') + else: + fields = serializer.get_fields() + header = self._get_header(fields, template) + labels = {k: v.label for k, v in fields.items() if v.label} + table = self._gen_table(data, header, labels) + + csv_buffer = BytesIO() + csv_writer = unicodecsv.writer(csv_buffer, encoding=encoding) + for row in table: + csv_writer.writerow(row) + + value = csv_buffer.getvalue() + + return value diff --git a/apps/common/urls/__init__.py b/apps/common/urls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/common/urls/api_urls.py b/apps/common/urls/api_urls.py new file mode 100644 index 000000000..01f164b00 --- /dev/null +++ b/apps/common/urls/api_urls.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# + +from django.urls import path + +from .. import api + +app_name = 'common' + +urlpatterns = [ + path('resources/cache/', + api.ResourcesIDCacheApi.as_view(), name='resources-cache'), +] diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index a6ec9c92a..bdc71ae3c 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -363,6 +363,16 @@ REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'common.permissions.IsOrgAdmin', ), + 'DEFAULT_RENDERER_CLASSES': ( + 'rest_framework.renderers.JSONRenderer', + 'rest_framework.renderers.BrowsableAPIRenderer', + 'common.renders.JMSCSVRender', + ), + 'DEFAULT_PARSER_CLASSES': ( + 'rest_framework.parsers.JSONParser', + 'rest_framework.parsers.FormParser', + 'common.parsers.JMSCSVParser' + ), 'DEFAULT_AUTHENTICATION_CLASSES': ( # 'rest_framework.authentication.BasicAuthentication', 'authentication.backends.api.AccessKeyAuthentication', diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index a2538b6c0..96c702acc 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -20,6 +20,7 @@ api_v1 = [ path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')), path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')), path('authentication/v1/', include('authentication.urls.api_urls', namespace='api-auth')), + path('common/v1/', include('common.urls.api_urls', namespace='api-common')), path('applications/v1/', include('applications.urls.api_urls', namespace='api-applications')), ] diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index b8c64ffce52e56ef3698928f7ac974fed7ffb69f..820dc398eb1f58f52cc32addee4b8e37cd6a0cf8 100644 GIT binary patch delta 22687 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 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index b1f1774ef..97df573d9 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -621,7 +621,7 @@ msgstr "管理用户" #: assets/forms/asset.py:33 assets/forms/asset.py:69 assets/forms/asset.py:109 #: assets/templates/assets/asset_create.html:36 #: assets/templates/assets/asset_create.html:38 -#: assets/templates/assets/asset_list.html:81 +#: assets/templates/assets/asset_list.html:93 #: assets/templates/assets/asset_update.html:41 #: assets/templates/assets/asset_update.html:43 #: assets/templates/assets/user_asset_list.html:33 @@ -680,6 +680,37 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域, msgid "Select assets" msgstr "选择资产" +#: assets/forms/domain.py:15 assets/forms/label.py:13 +#: assets/models/asset.py:279 assets/models/authbook.py:27 +#: assets/serializers/admin_user.py:31 assets/serializers/system_user.py:32 +#: assets/templates/assets/admin_user_list.html:49 +#: assets/templates/assets/domain_detail.html:60 +#: assets/templates/assets/domain_list.html:26 +#: assets/templates/assets/label_list.html:16 +#: assets/templates/assets/system_user_list.html:55 audits/models.py:19 +#: audits/templates/audits/ftp_log_list.html:41 +#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:42 +#: perms/models.py:50 +#: perms/templates/perms/asset_permission_create_update.html:45 +#: perms/templates/perms/asset_permission_list.html:56 +#: perms/templates/perms/asset_permission_list.html:125 +#: terminal/backends/command/models.py:13 terminal/models.py:155 +#: terminal/templates/terminal/command_list.html:40 +#: terminal/templates/terminal/command_list.html:73 +#: terminal/templates/terminal/session_list.html:41 +#: terminal/templates/terminal/session_list.html:72 +#: xpack/plugins/change_auth_plan/forms.py:114 +#: xpack/plugins/change_auth_plan/models.py:409 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:14 +#: xpack/plugins/cloud/models.py:187 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 +#: xpack/plugins/orgs/templates/orgs/org_list.html:16 +msgid "Asset" +msgstr "资产" + #: assets/forms/domain.py:51 msgid "Password should not contain special characters" msgstr "不能包含特殊字符" @@ -688,16 +719,65 @@ msgstr "不能包含特殊字符" msgid "SSH gateway support proxy SSH,RDP,VNC" msgstr "SSH网关,支持代理SSH,RDP和VNC" +#: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:146 +#: assets/models/base.py:26 assets/models/cluster.py:18 +#: assets/models/cmd_filter.py:20 assets/models/domain.py:20 +#: assets/models/group.py:20 assets/models/label.py:18 +#: assets/templates/assets/admin_user_detail.html:56 +#: assets/templates/assets/admin_user_list.html:47 +#: assets/templates/assets/cmd_filter_detail.html:61 +#: assets/templates/assets/cmd_filter_list.html:24 +#: assets/templates/assets/domain_detail.html:56 +#: assets/templates/assets/domain_gateway_list.html:67 +#: assets/templates/assets/domain_list.html:25 +#: assets/templates/assets/label_list.html:14 +#: assets/templates/assets/system_user_detail.html:58 +#: assets/templates/assets/system_user_list.html:51 ops/models/adhoc.py:37 +#: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:27 +#: orgs/models.py:12 perms/models.py:17 perms/models.py:47 +#: perms/templates/perms/asset_permission_detail.html:62 +#: perms/templates/perms/asset_permission_list.html:53 +#: perms/templates/perms/asset_permission_list.html:72 +#: perms/templates/perms/asset_permission_user.html:54 settings/models.py:29 +#: settings/templates/settings/_ldap_list_users_modal.html:38 +#: settings/templates/settings/command_storage_create.html:41 +#: settings/templates/settings/replay_storage_create.html:44 +#: settings/templates/settings/terminal_setting.html:80 +#: settings/templates/settings/terminal_setting.html:102 terminal/models.py:22 +#: terminal/models.py:241 terminal/templates/terminal/terminal_detail.html:43 +#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 +#: users/models/user.py:61 users/templates/users/_select_user_modal.html:13 +#: users/templates/users/user_detail.html:63 +#: users/templates/users/user_group_detail.html:55 +#: users/templates/users/user_group_list.html:35 +#: users/templates/users/user_list.html:35 +#: users/templates/users/user_profile.html:51 +#: users/templates/users/user_pubkey_update.html:53 +#: xpack/plugins/change_auth_plan/forms.py:97 +#: xpack/plugins/change_auth_plan/models.py:58 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 +#: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:50 +#: xpack/plugins/cloud/templates/cloud/account_list.html:12 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:52 +#: xpack/plugins/orgs/templates/orgs/org_list.html:12 +msgid "Name" +msgstr "名称" + #: assets/forms/domain.py:74 assets/forms/user.py:85 assets/forms/user.py:147 #: assets/models/base.py:27 #: assets/templates/assets/_asset_user_auth_modal.html:15 #: assets/templates/assets/_asset_user_view_auth_modal.html:31 #: assets/templates/assets/admin_user_detail.html:60 -#: assets/templates/assets/admin_user_list.html:27 + +#: assets/templates/assets/admin_user_list.html:48 #: assets/templates/assets/asset_asset_user_list.html:44 #: assets/templates/assets/domain_gateway_list.html:71 #: assets/templates/assets/system_user_detail.html:62 -#: assets/templates/assets/system_user_list.html:30 audits/models.py:94 +#: assets/templates/assets/system_user_list.html:52 audits/models.py:94 #: audits/templates/audits/login_log_list.html:51 authentication/forms.py:11 #: authentication/templates/authentication/login.html:64 #: authentication/templates/authentication/new_login.html:90 @@ -707,7 +787,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: settings/templates/settings/_ldap_list_users_modal.html:37 users/forms.py:13 #: users/models/user.py:59 users/templates/users/_select_user_modal.html:14 #: users/templates/users/user_detail.html:67 -#: users/templates/users/user_list.html:24 +#: users/templates/users/user_list.html:36 #: users/templates/users/user_profile.html:47 #: xpack/plugins/change_auth_plan/forms.py:99 #: xpack/plugins/change_auth_plan/models.py:60 @@ -724,7 +804,8 @@ msgid "Password or private key passphrase" msgstr "密码或密钥密码" #: assets/forms/user.py:26 assets/models/base.py:28 -#: assets/serializers/asset_user.py:19 +#: assets/serializers/admin_user.py:20 assets/serializers/asset_user.py:19 +#: assets/serializers/system_user.py:16 #: assets/templates/assets/_asset_user_auth_modal.html:21 #: assets/templates/assets/_asset_user_view_auth_modal.html:37 #: authentication/forms.py:13 @@ -792,7 +873,7 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" #: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/admin_user_assets.html:49 #: assets/templates/assets/asset_detail.html:64 -#: assets/templates/assets/asset_list.html:93 +#: assets/templates/assets/asset_list.html:105 #: assets/templates/assets/domain_gateway_list.html:68 #: assets/templates/assets/system_user_asset.html:51 #: assets/templates/assets/user_asset_list.html:45 @@ -810,7 +891,7 @@ msgstr "IP" #: assets/templates/assets/_asset_user_view_auth_modal.html:25 #: assets/templates/assets/admin_user_assets.html:48 #: assets/templates/assets/asset_detail.html:60 -#: assets/templates/assets/asset_list.html:92 +#: assets/templates/assets/asset_list.html:104 #: assets/templates/assets/system_user_asset.html:50 #: assets/templates/assets/user_asset_list.html:44 #: assets/templates/assets/user_asset_list.html:162 @@ -826,7 +907,7 @@ msgstr "主机名" #: assets/models/user.py:136 assets/templates/assets/asset_detail.html:76 #: assets/templates/assets/domain_gateway_list.html:70 #: assets/templates/assets/system_user_detail.html:70 -#: assets/templates/assets/system_user_list.html:31 +#: assets/templates/assets/system_user_list.html:53 #: assets/templates/assets/user_asset_list.html:165 #: terminal/templates/terminal/session_list.html:75 msgid "Protocol" @@ -926,19 +1007,96 @@ msgstr "主机名原始" msgid "Labels" msgstr "标签管理" +#: assets/models/asset.py:109 assets/models/base.py:34 +#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25 +#: assets/models/cmd_filter.py:58 assets/models/group.py:21 +#: assets/templates/assets/admin_user_detail.html:68 +#: assets/templates/assets/asset_detail.html:128 +#: assets/templates/assets/cmd_filter_detail.html:77 +#: assets/templates/assets/domain_detail.html:72 +#: assets/templates/assets/system_user_detail.html:100 +#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:57 +#: perms/models.py:110 perms/templates/perms/asset_permission_detail.html:98 +#: users/models/user.py:102 users/serializers/v1.py:69 +#: users/templates/users/user_detail.html:111 +#: xpack/plugins/change_auth_plan/models.py:103 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 +#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127 +msgid "Created by" +msgstr "创建者" + +#: assets/models/asset.py:110 assets/models/cluster.py:26 +#: assets/models/domain.py:23 assets/models/group.py:22 +#: assets/models/label.py:25 assets/serializers/admin_user.py:23 +#: assets/templates/assets/admin_user_detail.html:64 +#: assets/templates/assets/cmd_filter_detail.html:69 +#: assets/templates/assets/domain_detail.html:68 +#: assets/templates/assets/system_user_detail.html:96 +#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 +#: orgs/models.py:16 perms/models.py:58 perms/models.py:111 +#: perms/templates/perms/asset_permission_detail.html:94 +#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 +#: users/templates/users/user_group_detail.html:63 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105 +#: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:128 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:77 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:60 +msgid "Date created" +msgstr "创建日期" + +#: assets/models/asset.py:111 assets/models/base.py:31 +#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22 +#: assets/models/cmd_filter.py:55 assets/models/domain.py:21 +#: assets/models/domain.py:53 assets/models/group.py:23 +#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72 +#: assets/templates/assets/admin_user_list.html:53 +#: assets/templates/assets/asset_detail.html:136 +#: assets/templates/assets/cmd_filter_detail.html:65 +#: assets/templates/assets/cmd_filter_list.html:27 +#: assets/templates/assets/cmd_filter_rule_list.html:62 +#: assets/templates/assets/domain_detail.html:76 +#: assets/templates/assets/domain_gateway_list.html:72 +#: assets/templates/assets/domain_list.html:28 +#: assets/templates/assets/system_user_detail.html:104 +#: assets/templates/assets/system_user_list.html:59 +#: assets/templates/assets/user_asset_list.html:171 ops/models/adhoc.py:43 +#: orgs/models.py:17 perms/models.py:59 perms/models.py:112 +#: perms/templates/perms/asset_permission_detail.html:102 settings/models.py:34 +#: terminal/models.py:32 terminal/templates/terminal/terminal_detail.html:63 +#: users/models/group.py:15 users/models/user.py:94 +#: users/templates/users/user_detail.html:127 +#: users/templates/users/user_group_detail.html:67 +#: users/templates/users/user_group_list.html:37 +#: users/templates/users/user_profile.html:134 +#: xpack/plugins/change_auth_plan/models.py:99 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 +#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:70 +#: xpack/plugins/cloud/templates/cloud/account_list.html:15 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:64 +#: xpack/plugins/orgs/templates/orgs/org_list.html:22 +msgid "Comment" +msgstr "备注" + #: assets/models/asset.py:117 assets/models/base.py:38 -#: assets/templates/assets/admin_user_list.html:30 -#: assets/templates/assets/system_user_list.html:35 +#: assets/serializers/admin_user.py:29 assets/serializers/system_user.py:22 +#: assets/templates/assets/admin_user_list.html:51 +#: assets/templates/assets/system_user_list.html:57 msgid "Unreachable" msgstr "不可达" #: assets/models/asset.py:118 assets/models/base.py:39 +#: assets/serializers/admin_user.py:32 assets/serializers/system_user.py:31 #: assets/templates/assets/admin_user_assets.html:51 -#: assets/templates/assets/admin_user_list.html:29 +#: assets/templates/assets/admin_user_list.html:50 +#: assets/templates/assets/asset_list.html:107 #: assets/templates/assets/asset_asset_user_list.html:46 -#: assets/templates/assets/asset_list.html:95 #: assets/templates/assets/system_user_asset.html:53 -#: assets/templates/assets/system_user_list.html:34 +#: assets/templates/assets/system_user_list.html:56 #: users/templates/users/user_group_granted_asset.html:47 msgid "Reachable" msgstr "可连接" @@ -1085,6 +1243,42 @@ msgstr "内容" msgid "One line one command" msgstr "每行一个命令" +#: assets/models/cmd_filter.py:54 +#: assets/templates/assets/admin_user_assets.html:52 +#: assets/templates/assets/admin_user_list.html:54 +#: assets/templates/assets/asset_list.html:108 +#: assets/templates/assets/asset_asset_user_list.html:48 +#: assets/templates/assets/cmd_filter_list.html:28 +#: assets/templates/assets/cmd_filter_rule_list.html:63 +#: assets/templates/assets/domain_gateway_list.html:73 +#: assets/templates/assets/domain_list.html:29 +#: assets/templates/assets/label_list.html:17 +#: assets/templates/assets/system_user_asset.html:54 +#: assets/templates/assets/system_user_list.html:60 +#: assets/templates/assets/user_asset_list.html:48 audits/models.py:38 +#: 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/task_history.html:65 ops/templates/ops/task_list.html:34 +#: perms/forms.py:51 perms/models.py:21 perms/models.py:53 +#: perms/templates/perms/asset_permission_create_update.html:50 +#: perms/templates/perms/asset_permission_list.html:60 +#: perms/templates/perms/asset_permission_list.html:134 +#: settings/templates/settings/terminal_setting.html:82 +#: settings/templates/settings/terminal_setting.html:104 +#: terminal/templates/terminal/session_list.html:81 +#: terminal/templates/terminal/terminal_list.html:36 +#: users/templates/users/user_group_list.html:38 +#: users/templates/users/user_list.html:41 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:18 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:20 +#: xpack/plugins/cloud/templates/cloud/account_list.html:16 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18 +#: xpack/plugins/orgs/templates/orgs/org_list.html:23 +msgid "Action" +msgstr "动作" + #: assets/models/cmd_filter.py:64 msgid "Command filter rule" msgstr "命令过滤规则" @@ -1125,9 +1319,9 @@ msgstr "默认资产组" #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:71 users/forms.py:283 -#: users/models/user.py:36 users/models/user.py:467 +#: users/models/user.py:36 users/models/user.py:467 users/serializers/v1.py:61 #: users/templates/users/user_group_detail.html:78 -#: users/templates/users/user_group_list.html:13 users/views/user.py:395 +#: users/templates/users/user_group_list.html:36 users/views/user.py:395 #: xpack/plugins/orgs/forms.py:26 #: xpack/plugins/orgs/templates/orgs/org_detail.html:113 #: xpack/plugins/orgs/templates/orgs/org_list.html:14 @@ -1174,7 +1368,7 @@ msgid "Shell" msgstr "Shell" #: assets/models/user.py:140 assets/templates/assets/system_user_detail.html:66 -#: assets/templates/assets/system_user_list.html:32 +#: assets/templates/assets/system_user_list.html:54 msgid "Login mode" msgstr "登录模式" @@ -1183,6 +1377,25 @@ msgstr "登录模式" msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" +#: assets/serializers/admin_user.py:26 +#: assets/templates/assets/asset_asset_user_list.html:51 +#: assets/templates/assets/cmd_filter_detail.html:73 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109 +msgid "Date updated" +msgstr "更新日期" + +#: assets/serializers/asset.py:20 +msgid "Org name" +msgstr "组织名" + +#: assets/serializers/asset.py:22 +msgid "Hardware info" +msgstr "硬件信息" + +#: assets/serializers/asset.py:25 +msgid "Connectivity" +msgstr "连接" + #: assets/serializers/asset_user.py:23 users/forms.py:230 #: users/models/user.py:91 users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:46 @@ -1192,6 +1405,18 @@ msgstr "%(value)s is not an even number" msgid "Public key" msgstr "ssh公钥" +#: assets/serializers/system_user.py:19 +msgid "Login mode display" +msgstr "登录模式显示" + +#: assets/serializers/system_user.py:25 +msgid "Unreachable assets" +msgstr "不可达资产" + +#: assets/serializers/system_user.py:29 +msgid "Reachable assets" +msgstr "可连接资产" + #: assets/tasks.py:31 msgid "Asset has been disabled, skipped: {}" msgstr "资产或许不支持ansible, 跳过: {}" @@ -1261,6 +1486,15 @@ msgstr "推送系统用户到入资产: {} => {}" msgid "Test asset user connectivity: {}" msgstr "测试资产用户可连接性: {}" +#: assets/templates/assets/_admin_user_import_modal.html:4 +msgid "Import admin user" +msgstr "导入管理用户" + +#: assets/templates/assets/_admin_user_update_modal.html:4 +#: assets/views/admin_user.py:64 +msgid "Update admin user" +msgstr "更新管理用户" + #: assets/templates/assets/_asset_group_bulk_update_modal.html:5 msgid "Update asset group" msgstr "更新用户组" @@ -1290,32 +1524,18 @@ msgid "Enable-MFA" msgstr "启用MFA" #: assets/templates/assets/_asset_import_modal.html:4 -msgid "Import asset" +msgid "Import assets" msgstr "导入资产" -#: assets/templates/assets/_asset_import_modal.html:9 -#: users/templates/users/_user_import_modal.html:10 -msgid "Template" -msgstr "模板" - -#: assets/templates/assets/_asset_import_modal.html:10 -#: users/templates/users/_user_import_modal.html:11 -msgid "Download" -msgstr "下载" - -#: assets/templates/assets/_asset_import_modal.html:13 -msgid "Asset csv file" -msgstr "资产csv文件" - -#: assets/templates/assets/_asset_import_modal.html:16 -msgid "If set id, will use this id update asset existed" -msgstr "如果设置了id,则会使用该行信息更新该id的资产" - #: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:52 #: templates/_nav.html:22 xpack/plugins/change_auth_plan/views.py:110 msgid "Asset list" msgstr "资产列表" +#: assets/templates/assets/_asset_update_modal.html:4 +msgid "Update assets" +msgstr "更新资产" + #: assets/templates/assets/_asset_user_auth_modal.html:4 msgid "Update asset user auth" msgstr "更新资产用户认证信息" @@ -1425,6 +1645,84 @@ msgstr "自动生成密钥" msgid "Other" msgstr "其它" +#: assets/templates/assets/_system_user.html:75 +#: assets/templates/assets/admin_user_create_update.html:45 +#: assets/templates/assets/asset_bulk_update.html:23 +#: assets/templates/assets/asset_create.html:67 +#: assets/templates/assets/asset_update.html:71 +#: assets/templates/assets/cmd_filter_create_update.html:15 +#: assets/templates/assets/cmd_filter_rule_create_update.html:40 +#: assets/templates/assets/domain_create_update.html:16 +#: assets/templates/assets/gateway_create_update.html:58 +#: assets/templates/assets/label_create_update.html:18 +#: perms/templates/perms/asset_permission_create_update.html:83 +#: settings/templates/settings/basic_setting.html:61 +#: settings/templates/settings/command_storage_create.html:79 +#: settings/templates/settings/email_setting.html:62 +#: settings/templates/settings/ldap_setting.html:61 +#: settings/templates/settings/replay_storage_create.html:152 +#: settings/templates/settings/security_setting.html:70 +#: settings/templates/settings/terminal_setting.html:68 +#: terminal/templates/terminal/terminal_update.html:45 +#: users/templates/users/_user.html:50 +#: users/templates/users/user_bulk_update.html:23 +#: users/templates/users/user_detail.html:176 +#: users/templates/users/user_password_update.html:71 +#: users/templates/users/user_profile.html:204 +#: users/templates/users/user_profile_update.html:63 +#: users/templates/users/user_pubkey_update.html:70 +#: users/templates/users/user_pubkey_update.html:76 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:71 +#: xpack/plugins/cloud/templates/cloud/account_create_update.html:33 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:35 +#: xpack/plugins/interface/templates/interface/interface.html:72 +msgid "Reset" +msgstr "重置" + +#: assets/templates/assets/_system_user.html:76 +#: assets/templates/assets/admin_user_create_update.html:46 +#: assets/templates/assets/asset_bulk_update.html:24 +#: assets/templates/assets/asset_create.html:68 +#: assets/templates/assets/asset_list.html:125 +#: assets/templates/assets/asset_update.html:72 +#: assets/templates/assets/cmd_filter_create_update.html:16 +#: assets/templates/assets/cmd_filter_rule_create_update.html:41 +#: assets/templates/assets/domain_create_update.html:17 +#: assets/templates/assets/gateway_create_update.html:59 +#: assets/templates/assets/label_create_update.html:19 +#: audits/templates/audits/login_log_list.html:89 +#: perms/templates/perms/asset_permission_create_update.html:84 +#: settings/templates/settings/basic_setting.html:62 +#: settings/templates/settings/command_storage_create.html:80 +#: settings/templates/settings/email_setting.html:63 +#: settings/templates/settings/ldap_setting.html:64 +#: settings/templates/settings/replay_storage_create.html:153 +#: settings/templates/settings/security_setting.html:71 +#: settings/templates/settings/terminal_setting.html:70 +#: terminal/templates/terminal/command_list.html:103 +#: terminal/templates/terminal/session_list.html:126 +#: terminal/templates/terminal/terminal_update.html:46 +#: users/templates/users/_user.html:51 +#: users/templates/users/forgot_password.html:42 +#: users/templates/users/user_bulk_update.html:24 +#: users/templates/users/user_list.html:57 +#: users/templates/users/user_password_update.html:72 +#: users/templates/users/user_profile_update.html:64 +#: users/templates/users/user_pubkey_update.html:77 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:72 +#: xpack/plugins/interface/templates/interface/interface.html:74 +msgid "Submit" +msgstr "提交" + +#: assets/templates/assets/_system_user_import_modal.html:4 +msgid "Import system user" +msgstr "导入系统用户" + +#: assets/templates/assets/_system_user_update_modal.html:4 +#: assets/views/system_user.py:61 +msgid "Update system user" +msgstr "更新系统用户" + #: assets/templates/assets/_user_asset_detail_modal.html:11 #: assets/templates/assets/asset_asset_user_list.html:13 #: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:187 @@ -1495,6 +1793,82 @@ msgstr "更新成功" msgid "Update failed!" msgstr "更新失败" +#: assets/templates/assets/admin_user_detail.html:24 +#: assets/templates/assets/admin_user_list.html:29 +#: assets/templates/assets/admin_user_list.html:111 +#: assets/templates/assets/asset_detail.html:27 +#: assets/templates/assets/asset_list.html:86 +#: assets/templates/assets/asset_list.html:190 +#: assets/templates/assets/cmd_filter_detail.html:29 +#: assets/templates/assets/cmd_filter_list.html:58 +#: assets/templates/assets/cmd_filter_rule_list.html:86 +#: assets/templates/assets/domain_detail.html:24 +#: assets/templates/assets/domain_detail.html:103 +#: assets/templates/assets/domain_gateway_list.html:97 +#: assets/templates/assets/domain_list.html:54 +#: assets/templates/assets/label_list.html:39 +#: assets/templates/assets/system_user_detail.html:26 +#: assets/templates/assets/system_user_list.html:33 +#: assets/templates/assets/system_user_list.html:117 audits/models.py:33 +#: perms/templates/perms/asset_permission_detail.html:30 +#: perms/templates/perms/asset_permission_list.html:181 +#: terminal/templates/terminal/terminal_detail.html:16 +#: terminal/templates/terminal/terminal_list.html:72 +#: users/templates/users/user_detail.html:25 +#: users/templates/users/user_group_detail.html:28 +#: users/templates/users/user_group_list.html:20 +#: users/templates/users/user_group_list.html:69 +#: users/templates/users/user_list.html:20 +#: users/templates/users/user_list.html:96 +#: users/templates/users/user_list.html:99 +#: users/templates/users/user_profile.html:177 +#: users/templates/users/user_profile.html:187 +#: users/templates/users/user_profile.html:196 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:55 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:23 +#: xpack/plugins/cloud/templates/cloud/account_list.html:39 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:25 +#: xpack/plugins/orgs/templates/orgs/org_list.html:87 +msgid "Update" +msgstr "更新" + +#: assets/templates/assets/admin_user_detail.html:28 +#: assets/templates/assets/admin_user_list.html:112 +#: assets/templates/assets/asset_detail.html:31 +#: assets/templates/assets/asset_list.html:191 +#: assets/templates/assets/cmd_filter_detail.html:33 +#: assets/templates/assets/cmd_filter_list.html:59 +#: assets/templates/assets/cmd_filter_rule_list.html:87 +#: assets/templates/assets/domain_detail.html:28 +#: assets/templates/assets/domain_detail.html:104 +#: assets/templates/assets/domain_gateway_list.html:98 +#: assets/templates/assets/domain_list.html:55 +#: assets/templates/assets/label_list.html:40 +#: assets/templates/assets/system_user_detail.html:30 +#: assets/templates/assets/system_user_list.html:118 audits/models.py:34 +#: ops/templates/ops/task_list.html:64 +#: perms/templates/perms/asset_permission_detail.html:34 +#: perms/templates/perms/asset_permission_list.html:182 +#: settings/templates/settings/terminal_setting.html:90 +#: settings/templates/settings/terminal_setting.html:112 +#: terminal/templates/terminal/terminal_list.html:74 +#: users/templates/users/user_detail.html:30 +#: users/templates/users/user_group_detail.html:32 +#: users/templates/users/user_group_list.html:71 +#: users/templates/users/user_list.html:104 +#: users/templates/users/user_list.html:108 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:57 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:27 +#: xpack/plugins/cloud/templates/cloud/account_list.html:41 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:29 +#: xpack/plugins/orgs/templates/orgs/org_list.html:89 +msgid "Delete" +msgstr "删除" + #: assets/templates/assets/admin_user_detail.html:83 msgid "Replace node assets admin user with this" msgstr "替换资产的管理员" @@ -1506,35 +1880,94 @@ msgstr "替换资产的管理员" msgid "Select nodes" msgstr "选择节点" -#: assets/templates/assets/admin_user_list.html:10 +#: assets/templates/assets/admin_user_detail.html:100 +#: assets/templates/assets/asset_detail.html:211 +#: assets/templates/assets/asset_list.html:692 +#: assets/templates/assets/cmd_filter_detail.html:106 +#: assets/templates/assets/system_user_asset.html:112 +#: assets/templates/assets/system_user_detail.html:182 +#: assets/templates/assets/system_user_list.html:168 +#: settings/templates/settings/terminal_setting.html:165 +#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108 +#: users/templates/users/user_detail.html:388 +#: users/templates/users/user_detail.html:414 +#: users/templates/users/user_detail.html:437 +#: users/templates/users/user_detail.html:482 +#: users/templates/users/user_group_create_update.html:32 +#: users/templates/users/user_group_list.html:114 +#: users/templates/users/user_list.html:260 +#: users/templates/users/user_profile.html:238 +#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 +#: xpack/plugins/interface/templates/interface/interface.html:103 +#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 +msgid "Confirm" +msgstr "确认" + +#: assets/templates/assets/admin_user_list.html:7 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 +#: assets/templates/assets/admin_user_list.html:8 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 +#: assets/templates/assets/admin_user_list.html:9 msgid "You can set any one for Windows or other hardware." msgstr "Windows或其它硬件可以随意设置一个" -#: assets/templates/assets/admin_user_list.html:18 +#: assets/templates/assets/admin_user_list.html:19 +#: assets/templates/assets/asset_list.html:76 +#: assets/templates/assets/system_user_list.html:23 +#: audits/templates/audits/login_log_list.html:85 +#: users/templates/users/user_group_list.html:10 +#: users/templates/users/user_list.html:10 +msgid "Export" +msgstr "导出" + +#: assets/templates/assets/admin_user_list.html:24 +#: assets/templates/assets/asset_list.html:81 +#: assets/templates/assets/system_user_list.html:28 +#: settings/templates/settings/_ldap_list_users_modal.html:97 +#: users/templates/users/user_group_list.html:15 +#: users/templates/users/user_list.html:15 +#: xpack/plugins/license/templates/license/license_detail.html:110 +msgid "Import" +msgstr "导入" + +#: assets/templates/assets/admin_user_list.html:39 #: assets/views/admin_user.py:48 msgid "Create admin user" msgstr "创建管理用户" -#: assets/templates/assets/admin_user_list.html:31 -#: assets/templates/assets/system_user_list.html:36 +#: assets/templates/assets/admin_user_list.html:52 +#: assets/templates/assets/system_user_list.html:58 #: ops/templates/ops/adhoc_history.html:54 #: ops/templates/ops/task_history.html:60 msgid "Ratio" msgstr "比例" +#: assets/templates/assets/admin_user_list.html:159 +#: assets/templates/assets/admin_user_list.html:197 +#: assets/templates/assets/asset_list.html:499 +#: assets/templates/assets/asset_list.html:543 +#: assets/templates/assets/system_user_list.html:226 +#: assets/templates/assets/system_user_list.html:262 +#: users/templates/users/user_group_list.html:163 +#: users/templates/users/user_group_list.html:199 +#: users/templates/users/user_list.html:162 +#: users/templates/users/user_list.html:198 +#, fuzzy +#| msgid "Please Select User" +msgid "Please select file" +msgstr "选择用户" + + #: assets/templates/assets/asset_asset_user_list.html:16 #: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:68 msgid "Asset user list" @@ -1548,6 +1981,7 @@ msgstr "资产用户" msgid "Password version" msgstr "密码版本" + #: assets/templates/assets/asset_asset_user_list.html:47 #: assets/templates/assets/cmd_filter_detail.html:73 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109 @@ -1626,146 +2060,133 @@ msgstr "" msgid "Create asset" msgstr "创建资产" -#: assets/templates/assets/asset_list.html:73 -#: settings/templates/settings/_ldap_list_users_modal.html:97 -#: users/templates/users/user_list.html:7 -#: xpack/plugins/license/templates/license/license_detail.html:110 -msgid "Import" -msgstr "导入" - -#: assets/templates/assets/asset_list.html:76 -#: audits/templates/audits/login_log_list.html:85 -#: users/templates/users/user_list.html:10 -msgid "Export" -msgstr "导出" - -#: assets/templates/assets/asset_list.html:94 +#: assets/templates/assets/asset_list.html:106 msgid "Hardware" msgstr "硬件" -#: assets/templates/assets/asset_list.html:105 -#: users/templates/users/user_list.html:38 +#: assets/templates/assets/asset_list.html:117 +#: users/templates/users/user_list.html:50 msgid "Delete selected" msgstr "批量删除" -#: assets/templates/assets/asset_list.html:106 -#: users/templates/users/user_list.html:39 +#: assets/templates/assets/asset_list.html:118 +#: users/templates/users/user_list.html:51 msgid "Update selected" msgstr "批量更新" -#: assets/templates/assets/asset_list.html:107 +#: assets/templates/assets/asset_list.html:119 msgid "Remove from this node" msgstr "从节点移除" -#: assets/templates/assets/asset_list.html:108 -#: users/templates/users/user_list.html:40 +#: assets/templates/assets/asset_list.html:120 +#: users/templates/users/user_list.html:52 msgid "Deactive selected" msgstr "禁用所选" -#: assets/templates/assets/asset_list.html:109 -#: users/templates/users/user_list.html:41 +#: assets/templates/assets/asset_list.html:121 +#: users/templates/users/user_list.html:53 msgid "Active selected" msgstr "激活所选" -#: assets/templates/assets/asset_list.html:126 +#: assets/templates/assets/asset_list.html:138 msgid "Add node" msgstr "新建节点" -#: assets/templates/assets/asset_list.html:127 +#: assets/templates/assets/asset_list.html:139 msgid "Rename node" msgstr "重命名节点" -#: assets/templates/assets/asset_list.html:128 +#: assets/templates/assets/asset_list.html:140 msgid "Delete node" msgstr "删除节点" -#: assets/templates/assets/asset_list.html:130 +#: assets/templates/assets/asset_list.html:142 msgid "Add assets to node" msgstr "添加资产到节点" -#: assets/templates/assets/asset_list.html:131 +#: assets/templates/assets/asset_list.html:143 msgid "Move assets to node" msgstr "移动资产到节点" -#: assets/templates/assets/asset_list.html:133 +#: assets/templates/assets/asset_list.html:145 msgid "Refresh node hardware info" msgstr "更新节点资产硬件信息" -#: assets/templates/assets/asset_list.html:134 +#: assets/templates/assets/asset_list.html:146 msgid "Test node connective" msgstr "测试节点资产可连接性" -#: assets/templates/assets/asset_list.html:136 +#: assets/templates/assets/asset_list.html:148 msgid "Refresh all node assets amount" msgstr "刷新所有节点资产数量" -#: assets/templates/assets/asset_list.html:138 +#: assets/templates/assets/asset_list.html:150 msgid "Display only current node assets" msgstr "仅显示当前节点资产" -#: assets/templates/assets/asset_list.html:139 +#: assets/templates/assets/asset_list.html:151 msgid "Displays all child node assets" msgstr "显示所有子节点资产" -#: assets/templates/assets/asset_list.html:217 +#: assets/templates/assets/asset_list.html:229 msgid "Create node failed" msgstr "创建节点失败" -#: assets/templates/assets/asset_list.html:229 +#: assets/templates/assets/asset_list.html:241 msgid "Have child node, cancel" msgstr "存在子节点,不能删除" -#: assets/templates/assets/asset_list.html:231 +#: assets/templates/assets/asset_list.html:243 msgid "Have assets, cancel" msgstr "存在资产,不能删除" -#: assets/templates/assets/asset_list.html:302 +#: assets/templates/assets/asset_list.html:314 msgid "Rename success" msgstr "重命名成功" -#: assets/templates/assets/asset_list.html:303 +#: assets/templates/assets/asset_list.html:315 msgid "Rename failed, do not change the root node name" msgstr "重命名失败,不能更改root节点的名称" -#: assets/templates/assets/asset_list.html:631 -#: assets/templates/assets/system_user_list.html:138 +#: assets/templates/assets/asset_list.html:686 +#: assets/templates/assets/system_user_list.html:162 #: users/templates/users/user_detail.html:382 #: users/templates/users/user_detail.html:408 #: users/templates/users/user_detail.html:476 -#: users/templates/users/user_group_list.html:84 -#: users/templates/users/user_list.html:209 +#: users/templates/users/user_group_list.html:108 +#: users/templates/users/user_list.html:254 #: xpack/plugins/interface/templates/interface/interface.html:97 msgid "Are you sure?" msgstr "你确认吗?" -#: assets/templates/assets/asset_list.html:632 +#: assets/templates/assets/asset_list.html:687 msgid "This will delete the selected assets !!!" msgstr "删除选择资产" -#: assets/templates/assets/asset_list.html:635 -#: assets/templates/assets/system_user_list.html:142 +#: assets/templates/assets/asset_list.html:690 +#: assets/templates/assets/system_user_list.html:166 #: settings/templates/settings/terminal_setting.html:163 #: users/templates/users/user_detail.html:386 #: users/templates/users/user_detail.html:412 #: users/templates/users/user_detail.html:480 #: users/templates/users/user_group_create_update.html:31 -#: users/templates/users/user_group_list.html:88 -#: users/templates/users/user_list.html:213 +#: users/templates/users/user_group_list.html:112 +#: users/templates/users/user_list.html:258 #: xpack/plugins/interface/templates/interface/interface.html:101 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:32 msgid "Cancel" msgstr "取消" -#: assets/templates/assets/asset_list.html:641 +#: assets/templates/assets/asset_list.html:696 msgid "Asset Deleted." msgstr "已被删除" -#: assets/templates/assets/asset_list.html:642 -#: assets/templates/assets/asset_list.html:647 +#: assets/templates/assets/asset_list.html:697 +#: assets/templates/assets/asset_list.html:702 msgid "Asset Delete" msgstr "删除" -#: assets/templates/assets/asset_list.html:646 +#: assets/templates/assets/asset_list.html:701 msgid "Asset Deleting failed." msgstr "删除失败" @@ -1948,25 +2369,25 @@ msgstr "" "资产中,如果资产(交换机、windows)不支持ansible, 请手动填写账号密码。目前还不" "支持Windows的自动推送" -#: assets/templates/assets/system_user_list.html:21 +#: assets/templates/assets/system_user_list.html:43 #: assets/views/system_user.py:45 msgid "Create system user" msgstr "创建系统用户" -#: assets/templates/assets/system_user_list.html:139 +#: assets/templates/assets/system_user_list.html:163 msgid "This will delete the selected System Users !!!" msgstr "删除选择系统用户" -#: assets/templates/assets/system_user_list.html:148 +#: assets/templates/assets/system_user_list.html:172 msgid "System Users Deleted." msgstr "已被删除" -#: assets/templates/assets/system_user_list.html:149 -#: assets/templates/assets/system_user_list.html:154 +#: assets/templates/assets/system_user_list.html:173 +#: assets/templates/assets/system_user_list.html:178 msgid "System Users Delete" msgstr "删除系统用户" -#: assets/templates/assets/system_user_list.html:153 +#: assets/templates/assets/system_user_list.html:177 msgid "System Users Deleting failed." msgstr "系统用户删除失败" @@ -1974,10 +2395,6 @@ msgstr "系统用户删除失败" msgid "Admin user list" msgstr "管理用户列表" -#: assets/views/admin_user.py:64 -msgid "Update admin user" -msgstr "更新管理用户" - #: assets/views/admin_user.py:79 assets/views/admin_user.py:103 msgid "Admin user detail" msgstr "管理用户详情" @@ -2058,10 +2475,6 @@ msgstr "更新标签" msgid "System user list" msgstr "系统用户列表" -#: assets/views/system_user.py:61 -msgid "Update system user" -msgstr "更新系统用户" - #: assets/views/system_user.py:75 msgid "System user detail" msgstr "系统用户详情" @@ -2551,11 +2964,11 @@ msgstr "" msgid "Encrypt field using Secret Key" msgstr "" -#: common/mixins.py:32 +#: common/mixins.py:35 msgid "is discard" msgstr "" -#: common/mixins.py:33 +#: common/mixins.py:36 msgid "discard time" msgstr "" @@ -2914,7 +3327,7 @@ msgstr "命令执行列表" msgid "Command execution" msgstr "命令执行" -#: orgs/mixins.py:77 orgs/models.py:24 +#: orgs/mixins.py:81 orgs/models.py:24 msgid "Organization" msgstr "组织管理" @@ -2940,7 +3353,7 @@ msgstr "下载文件" #: templates/_nav.html:14 users/forms.py:253 users/models/group.py:26 #: users/models/user.py:67 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:213 -#: users/templates/users/user_list.html:26 +#: users/templates/users/user_list.html:38 #: xpack/plugins/orgs/templates/orgs/org_list.html:15 msgid "User group" msgstr "用户组" @@ -3065,9 +3478,9 @@ msgstr "创建授权规则" #: perms/templates/perms/asset_permission_list.html:59 #: perms/templates/perms/asset_permission_list.html:73 +#: users/templates/users/user_list.html:40 xpack/plugins/cloud/models.py:53 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:60 #: perms/templates/perms/remote_app_permission_list.html:18 -#: users/templates/users/user_list.html:28 xpack/plugins/cloud/models.py:53 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:58 #: xpack/plugins/cloud/templates/cloud/account_list.html:14 msgid "Validity" msgstr "有效" @@ -3700,6 +4113,18 @@ msgstr "注销登录" msgid "Dashboard" msgstr "仪表盘" +#: templates/_import_modal.html:12 +msgid "Download the imported template or use the exported CSV file format" +msgstr "下载导入的模板或使用导出的csv格式" + +#: templates/_import_modal.html:13 +msgid "Download the import template" +msgstr "下载导入模版" + +#: templates/_import_modal.html:17 templates/_update_modal.html:17 +msgid "Select the CSV file to import" +msgstr "请选择csv文件导入" + #: templates/_message.html:7 #, python-format msgid "" @@ -3840,6 +4265,14 @@ msgid "" "Displays the results of items _START_ to _END_; A total of _TOTAL_ entries" msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项" +#: templates/_update_modal.html:12 +msgid "Download the update template or use the exported CSV file format" +msgstr "下载更新的模板或使用导出的csv格式" + +#: templates/_update_modal.html:13 +msgid "Download the update template" +msgstr "下载更新模版" + #: templates/captcha/image.html:3 msgid "Play CAPTCHA as audio file" msgstr "语言播放验证码" @@ -4206,18 +4639,18 @@ msgid "" "You should use your ssh client tools connect terminal: {}

    {}" msgstr "你可以使用ssh客户端工具连接终端" -#: users/api/user.py:69 users/api/user.py:80 users/api/user.py:106 +#: users/api/user.py:77 users/api/user.py:88 users/api/user.py:114 msgid "You do not have permission." msgstr "你没有权限" -#: users/api/user.py:210 +#: users/api/user.py:218 msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" #: users/forms.py:32 users/models/user.py:71 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:87 -#: users/templates/users/user_list.html:25 +#: users/templates/users/user_list.html:37 #: users/templates/users/user_profile.html:55 msgid "Role" msgstr "角色" @@ -4242,7 +4675,7 @@ msgstr "添加到用户组" msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:89 users/forms.py:219 users/serializers/v1.py:38 +#: users/forms.py:89 users/forms.py:219 users/serializers/v1.py:53 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" @@ -4339,7 +4772,7 @@ msgid "Wechat" msgstr "微信" #: users/models/user.py:106 users/templates/users/user_detail.html:103 -#: users/templates/users/user_list.html:27 +#: users/templates/users/user_list.html:39 #: users/templates/users/user_profile.html:100 msgid "Source" msgstr "用户来源" @@ -4357,6 +4790,34 @@ msgstr "用户认证源来自 {}, 请去相应系统修改密码" msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" +#: users/serializers/v1.py:17 +msgid "Groups name" +msgstr "用户组名" + +#: users/serializers/v1.py:20 +msgid "Source name" +msgstr "用户来源名" + +#: users/serializers/v1.py:23 +msgid "Is first login" +msgstr "首次登录" + +#: users/serializers/v1.py:25 +msgid "Role name" +msgstr "角色名" + +#: users/serializers/v1.py:26 +msgid "Is valid" +msgstr "账户是否有效" + +#: users/serializers/v1.py:27 +msgid "Is expired" +msgstr " 是否过期" + +#: users/serializers/v1.py:28 +msgid "Avatar url" +msgstr "头像路径" + #: users/serializers_v2/user.py:36 msgid "name not unique" msgstr "名称重复" @@ -4393,21 +4854,22 @@ msgstr "资产数量" msgid "Security and Role" msgstr "角色安全" +#: users/templates/users/_user_groups_import_modal.html:4 +msgid "Import user groups" +msgstr "导入用户组" + +#: users/templates/users/_user_groups_update_modal.html:4 +msgid "Update user groups" +msgstr "更新用户组" + #: users/templates/users/_user_import_modal.html:4 -msgid "Import user" -msgstr "导入" +msgid "Import users" +msgstr "导入用户" -#: users/templates/users/_user_import_modal.html:6 -msgid "Download template or use export csv format" -msgstr "下载模板或使用导出的csv格式" - -#: users/templates/users/_user_import_modal.html:14 -msgid "Users csv file" -msgstr "用户csv文件" - -#: users/templates/users/_user_import_modal.html:16 -msgid "If set id, will use this id update user existed" -msgstr "如果设置了id,则会使用该行信息更新该id的用户" +#: users/templates/users/_user_update_modal.html:4 +#: users/templates/users/user_update.html:4 users/views/user.py:123 +msgid "Update user" +msgstr "更新用户" #: users/templates/users/_user_update_pk_modal.html:4 msgid "Update User SSH Public Key" @@ -4535,7 +4997,7 @@ msgid "Very strong" msgstr "很强" #: users/templates/users/user_create.html:4 -#: users/templates/users/user_list.html:16 users/views/user.py:83 +#: users/templates/users/user_list.html:28 users/views/user.py:83 msgid "Create user" msgstr "创建用户" @@ -4656,49 +5118,49 @@ msgstr "用户组详情" msgid "Add user" msgstr "添加用户" -#: users/templates/users/user_group_list.html:5 users/views/group.py:44 +#: users/templates/users/user_group_list.html:28 users/views/group.py:44 msgid "Create user group" msgstr "创建用户组" -#: users/templates/users/user_group_list.html:85 +#: users/templates/users/user_group_list.html:109 msgid "This will delete the selected groups !!!" msgstr "删除选择组" -#: users/templates/users/user_group_list.html:94 +#: users/templates/users/user_group_list.html:118 msgid "UserGroups Deleted." msgstr "用户组删除" -#: users/templates/users/user_group_list.html:95 -#: users/templates/users/user_group_list.html:100 +#: users/templates/users/user_group_list.html:119 +#: users/templates/users/user_group_list.html:124 msgid "UserGroups Delete" msgstr "用户组删除" -#: users/templates/users/user_group_list.html:99 +#: users/templates/users/user_group_list.html:123 msgid "UserGroup Deleting failed." msgstr "用户组删除失败" -#: users/templates/users/user_list.html:210 +#: users/templates/users/user_list.html:255 msgid "This will delete the selected users !!!" msgstr "删除选中用户 !!!" -#: users/templates/users/user_list.html:219 +#: users/templates/users/user_list.html:264 msgid "User Deleted." msgstr "已被删除" -#: users/templates/users/user_list.html:220 -#: users/templates/users/user_list.html:225 +#: users/templates/users/user_list.html:265 +#: users/templates/users/user_list.html:270 msgid "User Delete" msgstr "删除" -#: users/templates/users/user_list.html:224 +#: users/templates/users/user_list.html:269 msgid "User Deleting failed." msgstr "用户删除失败" -#: users/templates/users/user_list.html:260 +#: users/templates/users/user_list.html:305 msgid "User is expired" msgstr "用户已失效" -#: users/templates/users/user_list.html:263 +#: users/templates/users/user_list.html:308 msgid "User is inactive" msgstr "用户已禁用" @@ -4798,10 +5260,6 @@ msgid "" "corresponding private key." msgstr "新的公钥已设置成功,请下载对应的私钥" -#: users/templates/users/user_update.html:4 users/views/user.py:123 -msgid "Update user" -msgstr "更新用户" - #: users/utils.py:38 msgid "Create account successfully" msgstr "创建账户成功" @@ -5728,6 +6186,25 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" + +#~ msgid "Template" +#~ msgstr "模板" + +#~ msgid "Download" +#~ msgstr "下载" + +#~ msgid "Asset csv file" +#~ msgstr "资产csv文件" + +#~ msgid "If set id, will use this id update asset existed" +#~ msgstr "如果设置了id,则会使用该行信息更新该id的资产" + +#~ msgid "Users csv file" +#~ msgstr "用户csv文件" + +#~ msgid "If set id, will use this id update user existed" +#~ msgstr "如果设置了id,则会使用该行信息更新该id的用户" + #~ msgid "MFA Confirm" #~ msgstr "确认" @@ -5747,6 +6224,7 @@ msgstr "更新组织" #~ msgid "Restore default successfully!" #~ msgstr "恢复默认成功!" + #~ msgid "Beijing Duizhan Tech, Inc." #~ msgstr "北京堆栈科技有限公司" @@ -5780,6 +6258,24 @@ msgstr "更新组织" #~ msgid "Invalid private key" #~ msgstr "ssh密钥不合法" +#, fuzzy +#~| msgid "CPU count" +#~ msgid "Cpu count" +#~ msgstr "CPU数量" + +#~ msgid "Login Jumpserver" +#~ msgstr "登录 Jumpserver" + +#, fuzzy +#~| msgid "Delete succeed" +#~ msgid "Delete success!" +#~ msgstr "删除成功" + +#, fuzzy +#~| msgid "Username does not exist" +#~ msgid "This license does not exist!" +#~ msgstr "用户名不存在" + #~ msgid "Valid" #~ msgstr "账户状态" diff --git a/apps/orgs/mixins.py b/apps/orgs/mixins.py index 5c4879204..431443ebe 100644 --- a/apps/orgs/mixins.py +++ b/apps/orgs/mixins.py @@ -8,9 +8,12 @@ from django.shortcuts import redirect, get_object_or_404 from django.forms import ModelForm from django.http.response import HttpResponseForbidden from django.core.exceptions import ValidationError +from rest_framework import serializers from common.utils import get_logger -from .utils import current_org, set_current_org, set_to_root_org +from .utils import ( + current_org, set_current_org, set_to_root_org, get_current_org_id +) from .models import Organization logger = get_logger(__file__) @@ -18,7 +21,8 @@ tl = Local() __all__ = [ 'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm', - 'RootOrgViewMixin', 'OrgMembershipSerializerMixin', 'OrgMembershipModelViewSetMixin' + 'RootOrgViewMixin', 'OrgMembershipSerializerMixin', + 'OrgMembershipModelViewSetMixin', 'OrgResourceSerializerMixin', ] @@ -202,3 +206,11 @@ class OrgMembershipModelViewSetMixin: def get_queryset(self): queryset = self.membership_class.objects.filter(organization=self.org) return queryset + + +class OrgResourceSerializerMixin(serializers.Serializer): + """ + 通过API批量操作资源时, 自动给每个资源添加所需属性org_id的值为current_org_id + (同时为serializer.is_valid()对Model的unique_together校验做准备) + """ + org_id = serializers.HiddenField(default=get_current_org_id) diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index 7898e0343..808536984 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -38,4 +38,10 @@ def get_current_org(): return _find('current_org') +def get_current_org_id(): + org = get_current_org() + org_id = str(org.id) if org.is_real() else '' + return org_id + + current_org = LocalProxy(partial(_find, 'current_org')) diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index b3a2bd35e..b452c353a 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -954,9 +954,92 @@ function rootNodeAddDom(ztree, callback) { }) } +function APIExportData(props) { + props = props || {}; + $.ajax({ + url: '/api/common/v1/resources/cache/', + type: props.method || "POST", + data: props.body, + contentType: props.content_type || "application/json; charset=utf-8", + dataType: props.data_type || "json", + success: function (data) { + var export_url = props.success_url; + var params = props.params || {}; + params['format'] = props.format; + params['spm'] = data.spm; + for (var k in params){ + export_url = setUrlParam(export_url, k, params[k]) + } + window.open(export_url); + }, + error: function () { + toastr.error('Export failed'); + } + }) +} + +function APIImportData(props){ + props = props || {}; + $.ajax({ + url: props.url, + type: props.method || "POST", + processData: false, + data: props.body, + contentType: props.content_type || 'text/csv', + success: function (data) { + if(props.method === 'POST'){ + $('#created_failed').html(''); + $('#created_failed_detail').html(''); + $('#success_created').html("Import Success"); + $('#success_created_detail').html("Count" + ": " + data.length); + }else{ + $('#updated_failed').html(''); + $('#updated_failed_detail').html(''); + $('#success_updated').html("Update Success"); + $('#success_updated_detail').html("Count" + ": " + data.length); + } + + props.data_table.ajax.reload() + }, + error: function (error) { + var data = error.responseJSON; + if (data instanceof Array){ + var html = ''; + var li = ''; + var err = ''; + $.each(data, function (index, item){ + err = ''; + for (var prop in item) { + err += prop + ": " + item[prop][0] + " " + } + if (err) { + li = "
  • " + "Line " + (++index) + ". " + err + "
  • "; + html += li + } + }); + html = "
      " + html + "
    " + } + else { + html = error.responseText + } + if(props.method === 'POST'){ + $('#success_created').html(''); + $('#success_created_detail').html(''); + $('#created_failed').html("Import failed"); + $('#created_failed_detail').html(html); + }else{ + $('#success_updated').html(''); + $('#success_updated_detail').html(''); + $('#updated_failed').html("Update failed"); + $('#updated_failed_detail').html(html); + } + } + }) +} + function htmlEscape ( d ) { return typeof d === 'string' ? d.replace(//g, '>').replace(/"/g, '"') : d; -} \ No newline at end of file +} diff --git a/apps/templates/_import_modal.html b/apps/templates/_import_modal.html new file mode 100644 index 000000000..01a1cdf72 --- /dev/null +++ b/apps/templates/_import_modal.html @@ -0,0 +1,28 @@ +{% extends '_modal.html' %} +{% load i18n %} + +{% block modal_id %}import_modal{% endblock %} + +{% block modal_confirm_id %}btn_import_confirm{% endblock %} + +{% block modal_body %} +
    + {% csrf_token %} +
    + + {% trans 'Download the import template' %} +
    + +
    + + +
    +
    + +
    +

    +

    +

    +

    +
    +{% endblock %} diff --git a/apps/templates/_modal.html b/apps/templates/_modal.html index 237e5618b..7b5f55de4 100644 --- a/apps/templates/_modal.html +++ b/apps/templates/_modal.html @@ -8,7 +8,7 @@
    + diff --git a/apps/templates/_update_modal.html b/apps/templates/_update_modal.html new file mode 100644 index 000000000..db2b14110 --- /dev/null +++ b/apps/templates/_update_modal.html @@ -0,0 +1,28 @@ +{% extends '_modal.html' %} +{% load i18n %} + +{% block modal_id %}update_modal{% endblock %} + +{% block modal_confirm_id %}btn_update_confirm{% endblock %} + +{% block modal_body %} +
    + {% csrf_token %} +
    + + {% trans 'Download the update template' %} +
    + +
    + + +
    +
    + +
    +

    +

    +

    +

    +
    +{% endblock %} diff --git a/apps/users/api/group.py b/apps/users/api/group.py index 8cf9fcb0e..fc9a84928 100644 --- a/apps/users/api/group.py +++ b/apps/users/api/group.py @@ -9,13 +9,13 @@ from ..serializers import UserGroupSerializer, \ UserGroupUpdateMemberSerializer from ..models import UserGroup from common.permissions import IsOrgAdmin -from common.mixins import IDInFilterMixin +from common.mixins import IDInCacheFilterMixin __all__ = ['UserGroupViewSet', 'UserGroupUpdateUserApi'] -class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet): +class UserGroupViewSet(IDInCacheFilterMixin, BulkModelViewSet): filter_fields = ("name",) search_fields = filter_fields queryset = UserGroup.objects.all() diff --git a/apps/users/api/user.py b/apps/users/api/user.py index 63c3dab12..c7668cb86 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -15,7 +15,7 @@ from rest_framework.pagination import LimitOffsetPagination from common.permissions import ( IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser ) -from common.mixins import IDInFilterMixin +from common.mixins import IDInCacheFilterMixin from common.utils import get_logger from orgs.utils import current_org from ..serializers import UserSerializer, UserPKUpdateSerializer, \ @@ -32,7 +32,7 @@ __all__ = [ ] -class UserViewSet(IDInFilterMixin, BulkModelViewSet): +class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet): filter_fields = ('username', 'email', 'name', 'id') search_fields = filter_fields queryset = User.objects.exclude(role=User.ROLE_APP) @@ -40,9 +40,15 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet): permission_classes = (IsOrgAdmin,) pagination_class = LimitOffsetPagination + def send_created_signal(self, users): + if not isinstance(users, list): + users = [users] + for user in users: + post_user_create.send(self.__class__, user=user) + def perform_create(self, serializer): - user = serializer.save() - post_user_create.send(self.__class__, user=user) + users = serializer.save() + self.send_created_signal(users) def get_queryset(self): queryset = current_org.get_org_users() @@ -213,4 +219,4 @@ class UserResetOTPApi(generics.RetrieveAPIView): user.otp_secret_key = '' user.save() logout(request) - return Response({"msg": "success"}) + return Response({"msg": "success"}) \ No newline at end of file diff --git a/apps/users/serializers/v1.py b/apps/users/serializers/v1.py index b8c91417d..a0a13d4fa 100644 --- a/apps/users/serializers/v1.py +++ b/apps/users/serializers/v1.py @@ -19,12 +19,21 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): list_serializer_class = AdaptedBulkListSerializer fields = [ 'id', 'name', 'username', 'email', 'groups', 'groups_display', - 'role', 'role_display', 'avatar_url', 'wechat', 'phone', - 'otp_level', 'comment', 'source', 'source_display', - 'is_valid', 'is_expired', 'is_active', - 'created_by', 'is_first_login', - 'date_password_last_updated', 'date_expired', + 'role', 'role_display', 'wechat', 'phone', 'otp_level', + 'comment', 'source', 'source_display', 'is_valid', 'is_expired', + 'is_active', 'created_by', 'is_first_login', + 'date_password_last_updated', 'date_expired', 'avatar_url', ] + extra_kwargs = { + 'groups_display': {'label': _('Groups name')}, + 'source_display': {'label': _('Source name')}, + 'is_first_login': {'label': _('Is first login'), 'read_only': True}, + 'role_display': {'label': _('Role name')}, + 'is_valid': {'label': _('Is valid')}, + 'is_expired': {'label': _('Is expired')}, + 'avatar_url': {'label': _('Avatar url')}, + 'created_by': {'read_only': True}, 'source': {'read_only': True} + } class UserPKUpdateSerializer(serializers.ModelSerializer): @@ -48,17 +57,20 @@ class UserUpdateGroupSerializer(serializers.ModelSerializer): class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): - users = serializers.SerializerMethodField() + users = serializers.PrimaryKeyRelatedField( + required=False, many=True, queryset=User.objects.all(), label=_('User') + ) class Meta: model = UserGroup list_serializer_class = AdaptedBulkListSerializer - fields = '__all__' - read_only_fields = ['created_by'] - - @staticmethod - def get_users(obj): - return [user.name for user in obj.users.all()] + fields = [ + 'id', 'org_id', 'name', 'users', 'comment', 'date_created', + 'created_by', + ] + extra_kwargs = { + 'created_by': {'label': _('Created by'), 'read_only': True} + } class UserGroupUpdateMemberSerializer(serializers.ModelSerializer): diff --git a/apps/users/signals_handler.py b/apps/users/signals_handler.py index 1bc3ef430..4c6afc663 100644 --- a/apps/users/signals_handler.py +++ b/apps/users/signals_handler.py @@ -28,4 +28,3 @@ def on_user_create(sender, user=None, **kwargs): logger.info(" - Sending welcome mail ...".format(user.name)) if user.email: send_user_created_mail(user) - diff --git a/apps/users/templates/users/_user_groups_import_modal.html b/apps/users/templates/users/_user_groups_import_modal.html new file mode 100644 index 000000000..63d057215 --- /dev/null +++ b/apps/users/templates/users/_user_groups_import_modal.html @@ -0,0 +1,6 @@ +{% extends '_import_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Import user groups" %}{% endblock %} + +{% block import_modal_download_template_url %}{% url "api-users:user-group-list" %}{% endblock %} \ No newline at end of file diff --git a/apps/users/templates/users/_user_groups_update_modal.html b/apps/users/templates/users/_user_groups_update_modal.html new file mode 100644 index 000000000..a07c0f82c --- /dev/null +++ b/apps/users/templates/users/_user_groups_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update user group" %}{% endblock %} \ No newline at end of file diff --git a/apps/users/templates/users/_user_import_modal.html b/apps/users/templates/users/_user_import_modal.html index 678023cfc..e53d67fa7 100644 --- a/apps/users/templates/users/_user_import_modal.html +++ b/apps/users/templates/users/_user_import_modal.html @@ -1,28 +1,6 @@ -{% extends '_modal.html' %} +{% extends '_import_modal.html' %} {% load i18n %} -{% block modal_id %}user_import_modal{% endblock %} -{% block modal_title%}{% trans "Import user" %}{% endblock %} -{% block modal_body %} -

    {% trans "Download template or use export csv format" %}

    -
    - {% csrf_token %} -
    - - {% trans 'Download' %} -
    -
    - - - {% trans 'If set id, will use this id update user existed' %} -
    -
    -

    -

    -

    -

    -

    -

    -

    -

    -{% endblock %} -{% block modal_confirm_id %}btn_user_import{% endblock %} + +{% block modal_title%}{% trans "Import users" %}{% endblock %} + +{% block import_modal_download_template_url %}{% url "api-users:user-list" %}{% endblock %} diff --git a/apps/users/templates/users/_user_update_modal.html b/apps/users/templates/users/_user_update_modal.html new file mode 100644 index 000000000..9dfe60c96 --- /dev/null +++ b/apps/users/templates/users/_user_update_modal.html @@ -0,0 +1,4 @@ +{% extends '_update_modal.html' %} +{% load i18n %} + +{% block modal_title%}{% trans "Update user" %}{% endblock %} \ No newline at end of file diff --git a/apps/users/templates/users/user_group_list.html b/apps/users/templates/users/user_group_list.html index 6f6c6fc72..d8fc92e87 100644 --- a/apps/users/templates/users/user_group_list.html +++ b/apps/users/templates/users/user_group_list.html @@ -1,6 +1,29 @@ {% extends '_base_list.html' %} {% load i18n static %} -{% block table_search %}{% endblock %} +{% block table_search %} + +{% endblock %} {% block table_container %} @@ -16,7 +39,8 @@
    - +{% include "users/_user_groups_import_modal.html" %} +{% include "users/_user_groups_update_modal.html" %} {% endblock %} {% block content_bottom_left %}{% endblock %} @@ -111,6 +135,78 @@ $(document).ready(function() { default: break; } +}).on('click', '.btn_export', function(){ + var data_table = $('#group_list_table').DataTable(); + var rows = data_table.rows('.selected').data(); + var groups = []; + $.each(rows, function (index, obj) { + groups.push(obj.id) + }); + var data = { + 'resources': groups + }; + var search = $("input[type='search']").val(); + var props = { + method: "POST", + body: JSON.stringify(data), + success_url: "{% url 'api-users:user-group-list' %}", + format: "csv", + params: { + search: search + } + }; + APIExportData(props); +}).on('click', '#btn_import_confirm',function () { + var url = "{% url 'api-users:user-group-list' %}"; + var file = document.getElementById('id_file').files[0]; + if(!file){ + toastr.error("{% trans "Please select file" %}"); + return + } + var data_table = $('#group_list_table').DataTable(); + APIImportData({ + url: url, + method: "POST", + body: file, + data_table: data_table + }); }) +.on('click', '#download_update_template', function(){ + var data_table = $('#group_list_table').DataTable(); + var rows = data_table.rows('.selected').data(); + var groups = []; + $.each(rows, function (index, obj) { + groups.push(obj.id) + }); + var data = { + 'resources': groups + }; + var search = $("input[type='search']").val(); + var props = { + method: "POST", + body: JSON.stringify(data), + success_url: "{% url 'api-users:user-group-list' %}?format=csv&template=update", + format: "csv", + params: { + search: search + } + }; + APIExportData(props); +}).on('click', '#btn_update_confirm',function () { + var url = "{% url 'api-users:user-group-list' %}"; + var file = document.getElementById('update_file').files[0]; + if(!file){ + toastr.error("{% trans "Please select file" %}"); + return + } + var data_table = $('#group_list_table').DataTable(); + APIImportData({ + url: url, + method: "PUT", + body: file, + data_table: data_table + }); +}) + {% endblock %} diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index 15b5fb2f9..5d33a96aa 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -1,16 +1,28 @@ {% extends '_base_list.html' %} {% load i18n static %} {% block table_search %} - + {% endblock %} {% block table_container %} @@ -48,6 +60,7 @@
    {% include "users/_user_import_modal.html" %} +{% include "users/_user_update_modal.html" %} {% endblock %} {% block content_bottom_left %}{% endblock %} {% block custom_foot_js %} @@ -113,6 +126,7 @@ function initTable() { return table } + $(document).ready(function(){ var table = initTable(); var fields = $('#fm_user_bulk_update .form-group'); @@ -120,87 +134,127 @@ $(document).ready(function(){ console.log(value) }); $('.btn_export').click(function () { - var users = []; var rows = table.rows('.selected').data(); - if(rows.length===0){ - rows = table.rows().data(); - } + var users = []; $.each(rows, function (index, obj) { users.push(obj.id) }); - $.ajax({ - url: "{% url 'users:user-export' %}", - method: 'POST', - data: JSON.stringify({users_id: users}), - dataType: "json", - success: function (data, textStatus) { - window.open(data.redirect) - }, - error: function () { - toastr.error('Export failed'); + var data = { + 'resources': users + }; + var search = $("input[type='search']").val(); + var props = { + method: "POST", + body: JSON.stringify(data), + success_url: "{% url 'api-users:user-list' %}", + format: 'csv', + params: { + search: search } - }) + }; + APIExportData(props); }); - $('#btn_user_import').click(function() { - var $form = $('#fm_user_import'); - $form.find('.help-block').remove(); - function success (data) { - if (data.valid === false) { - $('', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_users')); - } else { - $('#id_created').html(data.created_info); - $('#id_created_detail').html(data.created.join(', ')); - $('#id_updated').html(data.updated_info); - $('#id_updated_detail').html(data.updated.join(', ')); - $('#id_failed').html(data.failed_info); - $('#id_failed_detail').html(data.failed.join(', ')); - var $data_table = $('#user_list_table').DataTable(); - $data_table.ajax.reload(); - } + $('#btn_import_confirm').click(function() { + var url = "{% url 'api-users:user-list' %}"; + var file = document.getElementById('id_file').files[0]; + if(!file){ + toastr.error("{% trans "Please select file" %}"); + return } - $form.ajaxSubmit({success: success}); - }) + var data_table = $('#user_list_table').DataTable(); + APIImportData({ + url: url, + method: "POST", + body: file, + data_table: data_table + }); + }); + $('#download_update_template').click(function () { + var rows = table.rows('.selected').data(); + var users = []; + $.each(rows, function (index, obj) { + users.push(obj.id) + }); + var data = { + 'resources': users + }; + var search = $("input[type='search']").val(); + var props = { + method: "POST", + body: JSON.stringify(data), + success_url: "{% url 'api-users:user-list' %}?format=csv&template=update", + format: 'csv', + params: { + search: search + } + }; + APIExportData(props); + }); + $('#btn_update_confirm').click(function() { + var url = "{% url 'api-users:user-list' %}"; + var file = document.getElementById('update_file').files[0]; + if(!file){ + toastr.error("{% trans "Please select file" %}"); + return + } + var data_table = $('#user_list_table').DataTable(); + APIImportData({ + url: url, + method: "PUT", + body: file, + data_table: data_table + }); + }); }).on('click', '#btn_bulk_update', function(){ var action = $('#slct_bulk_update').val(); var $data_table = $('#user_list_table').DataTable(); var id_list = []; - var plain_id_list = []; $data_table.rows({selected: true}).every(function(){ - id_list.push({pk: this.data().id}); - plain_id_list.push(this.data().id); + id_list.push(this.data().id); }); - if (id_list === []) { + if (id_list.length === 0) { return false; } var the_url = "{% url 'api-users:user-list' %}"; + var data = { + 'resources': id_list + }; + function refreshTag() { + $('#user_list_table').DataTable().ajax.reload() + } function doDeactive() { - var body = $.each(id_list, function(index, user_object) { - user_object['is_active'] = false; + var data = []; + $.each(id_list, function(index, object_id) { + var obj = {"pk": object_id, "is_active": false}; + data.push(obj); }); function success() { - location.reload(); + setTimeout( function () { + window.location.reload();}, 300); } APIUpdateAttr({ url: the_url, method: 'PATCH', - body: JSON.stringify(body), + body: JSON.stringify(data), success: success }); - location.reload(); } - function doActive() { - var body = $.each(id_list, function(index, user_object) { - user_object['is_active'] = true; + function doActive() { + var data = []; + $.each(id_list, function(index, object_id) { + var obj = {"pk": object_id, "is_active": true}; + data.push(obj); }); function success() { - location.reload(); + setTimeout( function () { + window.location.reload();}, 300); } APIUpdateAttr({ url: the_url, method: 'PATCH', - body: JSON.stringify(body), + body: JSON.stringify(data), success: success }); } @@ -214,26 +268,49 @@ $(document).ready(function(){ confirmButtonColor: "#DD6B55", confirmButtonText: "{% trans 'Confirm' %}", closeOnConfirm: false - }, function() { - var success = function() { + },function () { + function success(data) { + url = setUrlParam(the_url, 'spm', data.spm); + APIUpdateAttr({ + url:url, + method:'DELETE', + success:refreshTag, + flash_message:false, + }); var msg = "{% trans 'User Deleted.' %}"; swal("{% trans 'User Delete' %}", msg, "success"); - $('#user_list_table').DataTable().ajax.reload(); - }; - var fail = function() { + } + function fail() { var msg = "{% trans 'User Deleting failed.' %}"; swal("{% trans 'User Delete' %}", msg, "error"); - }; - var url_delete = the_url + '?id__in=' + JSON.stringify(plain_id_list); - APIUpdateAttr({url: url_delete, method: 'DELETE', success: success, error: fail}); - jumpserver.checked = false; - }); + } + APIUpdateAttr({ + url: "{% url 'api-common:resources-cache' %}", + method:'POST', + body:JSON.stringify(data), + success:success, + error:fail + }) + }) } + function doUpdate() { - var users_id = plain_id_list.join(','); - var url = "{% url 'users:user-bulk-update' %}?users_id=" + users_id; - location.href = url - } + function fail(data) { + toastr.error(JSON.parse(data)) + } + function success(data) { + var url = "{% url 'users:user-bulk-update' %}"; + location.href= setUrlParam(url, 'spm', data.spm); + } + APIUpdateAttr({ + url: "{% url 'api-common:resources-cache' %}", + method:'POST', + body:JSON.stringify(data), + flash_message:false, + success:success, + error:fail + }) + } switch(action) { case 'deactive': doDeactive(); diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 270a9a5c9..a7515d030 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -31,7 +31,9 @@ from django.views.generic.detail import DetailView from django.views.decorators.csrf import csrf_exempt 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, KEY_CACHE_RESOURCES_ID +) from common.mixins import JSONResponseMixin from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen from common.permissions import AdminUserRequiredMixin @@ -156,15 +158,12 @@ class UserBulkUpdateView(AdminUserRequiredMixin, TemplateView): id_list = None def get(self, request, *args, **kwargs): - users_id = self.request.GET.get('users_id', '') - self.id_list = [i for i in users_id.split(',')] - + spm = request.GET.get('spm', '') + users_id = cache.get(KEY_CACHE_RESOURCES_ID.format(spm)) if kwargs.get('form'): self.form = kwargs['form'] elif users_id: - self.form = self.form_class( - initial={'users': self.id_list} - ) + self.form = self.form_class(initial={'users': users_id}) else: self.form = self.form_class() return super().get(request, *args, **kwargs) From d906df5b005095dd591cc768f0b3059e68470746 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Tue, 21 May 2019 16:27:01 +0800 Subject: [PATCH 10/36] =?UTF-8?q?[Update]=20=E6=8A=BD=E8=B1=A1BasePermissi?= =?UTF-8?q?on=20(#2710)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] AssetPermission/RemoteAppPermission抽象BasePermission * [Update] Perms模块添加迁移文件 * [Update] Perms删除多余迁移文件 * [Update] Perms重新生成RemoteAppPermission迁移文件 --- ...520_1904.py => 0005_auto_20190521_1619.py} | 22 ++++-- apps/perms/models/asset_permission.py | 61 +-------------- apps/perms/models/base.py | 74 +++++++++++++++++++ apps/perms/models/remote_app_permission.py | 60 ++------------- apps/perms/views/asset_permission.py | 4 +- apps/perms/views/remote_app_permission.py | 4 +- 6 files changed, 103 insertions(+), 122 deletions(-) rename apps/perms/migrations/{0005_auto_20190520_1904.py => 0005_auto_20190521_1619.py} (73%) create mode 100644 apps/perms/models/base.py diff --git a/apps/perms/migrations/0005_auto_20190520_1904.py b/apps/perms/migrations/0005_auto_20190521_1619.py similarity index 73% rename from apps/perms/migrations/0005_auto_20190520_1904.py rename to apps/perms/migrations/0005_auto_20190521_1619.py index 48c579201..f000b8685 100644 --- a/apps/perms/migrations/0005_auto_20190520_1904.py +++ b/apps/perms/migrations/0005_auto_20190521_1619.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.7 on 2019-05-20 11:04 +# Generated by Django 2.1.7 on 2019-05-21 08:19 import common.utils.django from django.conf import settings @@ -10,9 +10,9 @@ import uuid class Migration(migrations.Migration): dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('applications', '0001_initial'), ('users', '0019_auto_20190304_1459'), + ('applications', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('perms', '0004_assetpermission_actions'), ] @@ -29,15 +29,25 @@ class Migration(migrations.Migration): ('created_by', models.CharField(blank=True, max_length=128, verbose_name='Created by')), ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), ('comment', models.TextField(blank=True, verbose_name='Comment')), - ('remote_apps', models.ManyToManyField(blank=True, related_name='remote_app_permissions', to='applications.RemoteApp', verbose_name='RemoteApp')), - ('user_groups', models.ManyToManyField(blank=True, related_name='remote_app_permissions', to='users.UserGroup', verbose_name='User group')), - ('users', models.ManyToManyField(blank=True, related_name='remote_app_permissions', to=settings.AUTH_USER_MODEL, verbose_name='User')), + ('remote_apps', models.ManyToManyField(blank=True, related_name='granted_by_permissions', to='applications.RemoteApp', verbose_name='RemoteApp')), + ('user_groups', models.ManyToManyField(blank=True, to='users.UserGroup', verbose_name='User group')), + ('users', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='User')), ], options={ 'verbose_name': 'RemoteApp permission', 'ordering': ('name',), }, ), + migrations.AlterField( + model_name='assetpermission', + name='user_groups', + field=models.ManyToManyField(blank=True, to='users.UserGroup', verbose_name='User group'), + ), + migrations.AlterField( + model_name='assetpermission', + name='users', + field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='User'), + ), migrations.AlterUniqueTogether( name='remoteapppermission', unique_together={('org_id', 'name')}, diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index ca917d69d..9091ce3f0 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -2,12 +2,12 @@ import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.utils import timezone from common.utils import date_expired_default, set_or_append_attr_bulk -from orgs.mixins import OrgModelMixin, OrgManager +from orgs.mixins import OrgModelMixin -from perms.const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL +from ..const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL +from .base import BasePermission __all__ = [ @@ -33,69 +33,16 @@ class Action(models.Model): return cls.objects.get(name=PERMS_ACTION_NAME_ALL) -class AssetPermissionQuerySet(models.QuerySet): - def active(self): - return self.filter(is_active=True) - - def valid(self): - return self.active().filter(date_start__lt=timezone.now())\ - .filter(date_expired__gt=timezone.now()) - - -class AssetPermissionManager(OrgManager): - def valid(self): - return self.get_queryset().valid() - - -class AssetPermission(OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField(max_length=128, verbose_name=_('Name')) - users = models.ManyToManyField('users.User', related_name='asset_permissions', blank=True, verbose_name=_("User")) - user_groups = models.ManyToManyField('users.UserGroup', related_name='asset_permissions', blank=True, verbose_name=_("User group")) +class AssetPermission(OrgModelMixin, BasePermission): assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset")) nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes")) system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user")) actions = models.ManyToManyField('Action', related_name='permissions', blank=True, verbose_name=_('Action')) - is_active = models.BooleanField(default=True, verbose_name=_('Active')) - date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start")) - date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired')) - created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) - date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) - comment = models.TextField(verbose_name=_('Comment'), blank=True) - - objects = AssetPermissionManager.from_queryset(AssetPermissionQuerySet)() class Meta: unique_together = [('org_id', 'name')] verbose_name = _("Asset permission") - def __str__(self): - return self.name - - @property - def id_str(self): - return str(self.id) - - @property - def is_expired(self): - if self.date_expired > timezone.now() > self.date_start: - return False - return True - - @property - def is_valid(self): - if not self.is_expired and self.is_active: - return True - return False - - def get_all_users(self): - users = set(self.users.all()) - for group in self.user_groups.all(): - _users = group.users.all() - set_or_append_attr_bulk(_users, 'inherit', group.name) - users.update(set(_users)) - return users - def get_all_assets(self): assets = set(self.assets.all()) for node in self.nodes.all(): diff --git a/apps/perms/models/base.py b/apps/perms/models/base.py new file mode 100644 index 000000000..4665bcd2a --- /dev/null +++ b/apps/perms/models/base.py @@ -0,0 +1,74 @@ +# coding: utf-8 +# + +import uuid +from django.utils.translation import ugettext_lazy as _ +from django.db import models +from django.utils import timezone + +from common.utils import date_expired_default, set_or_append_attr_bulk +from orgs.mixins import OrgManager + + +__all__ = [ + 'BasePermission', +] + + +class BasePermissionQuerySet(models.QuerySet): + def active(self): + return self.filter(is_active=True) + + def valid(self): + return self.active().filter(date_start__lt=timezone.now()) \ + .filter(date_expired__gt=timezone.now()) + + +class BasePermissionManager(OrgManager): + def valid(self): + return self.get_queryset().valid() + + +class BasePermission(models.Model): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, verbose_name=_('Name')) + users = models.ManyToManyField('users.User', blank=True, verbose_name=_("User")) + user_groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User group")) + is_active = models.BooleanField(default=True, verbose_name=_('Active')) + date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start")) + date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired')) + created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) + comment = models.TextField(verbose_name=_('Comment'), blank=True) + + objects = BasePermissionManager.from_queryset(BasePermissionQuerySet)() + + class Meta: + abstract = True + + def __str__(self): + return self.name + + @property + def id_str(self): + return str(self.id) + + @property + def is_expired(self): + if self.date_expired > timezone.now() > self.date_start: + return False + return True + + @property + def is_valid(self): + if not self.is_expired and self.is_active: + return True + return False + + def get_all_users(self): + users = set(self.users.all()) + for group in self.user_groups.all(): + _users = group.users.all() + set_or_append_attr_bulk(_users, 'inherit', group.name) + users.update(set(_users)) + return users diff --git a/apps/perms/models/remote_app_permission.py b/apps/perms/models/remote_app_permission.py index 7b29c8d20..b705a2ab3 100644 --- a/apps/perms/models/remote_app_permission.py +++ b/apps/perms/models/remote_app_permission.py @@ -1,75 +1,25 @@ # coding: utf-8 # -import uuid from django.db import models -from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from orgs.mixins import OrgModelMixin, OrgManager -from common.utils import date_expired_default, set_or_append_attr_bulk +from orgs.mixins import OrgModelMixin + +from .base import BasePermission __all__ = [ 'RemoteAppPermission', ] -class RemoteAppPermissionQuerySet(models.QuerySet): - def active(self): - return self.filter(is_active=True) - - def valid(self): - return self.active().filter(date_start__lt=timezone.now())\ - .filter(date_expired__gt=timezone.now()) - - -class RemoteAppPermissionManager(OrgManager): - def valid(self): - return self.get_queryset().valid() - - -class RemoteAppPermission(OrgModelMixin): - id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField(max_length=128, verbose_name=_('Name')) - users = models.ManyToManyField('users.User', related_name='remote_app_permissions', blank=True, verbose_name=_("User")) - user_groups = models.ManyToManyField('users.UserGroup', related_name='remote_app_permissions', blank=True, verbose_name=_("User group")) - remote_apps = models.ManyToManyField('applications.RemoteApp', related_name='remote_app_permissions', blank=True, verbose_name=_("RemoteApp")) - is_active = models.BooleanField(default=True, verbose_name=_('Active')) - date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start")) - date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired')) - created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by')) - date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) - comment = models.TextField(verbose_name=_('Comment'), blank=True) - - objects = RemoteAppPermissionManager.from_queryset(RemoteAppPermissionQuerySet)() +class RemoteAppPermission(OrgModelMixin, BasePermission): + remote_apps = models.ManyToManyField('applications.RemoteApp', related_name='granted_by_permissions', blank=True, verbose_name=_("RemoteApp")) class Meta: unique_together = [('org_id', 'name')] verbose_name = _('RemoteApp permission') ordering = ('name',) - def __str__(self): - return self.name - - @property - def is_expired(self): - if self.date_expired > timezone.now() > self.date_start: - return False - return True - - @property - def is_valid(self): - if not self.is_expired and self.is_active: - return True - return False - - def get_all_users(self): - users = set(self.users.all()) - for group in self.user_groups.all(): - _users = group.users.all() - set_or_append_attr_bulk(_users, 'inherit', group.name) - users.update(set(_users)) - return users - def get_all_remote_apps(self): return set(self.remote_apps.all()) diff --git a/apps/perms/views/asset_permission.py b/apps/perms/views/asset_permission.py index fc16b70a9..e85acaf39 100644 --- a/apps/perms/views/asset_permission.py +++ b/apps/perms/views/asset_permission.py @@ -130,10 +130,10 @@ class AssetPermissionUserView(AdminUserRequiredMixin, 'app': _('Perms'), 'action': _('Asset permission user list'), 'users_remain': current_org.get_org_users().exclude( - asset_permissions=self.object + assetpermission=self.object ), 'user_groups_remain': UserGroup.objects.exclude( - asset_permissions=self.object + assetpermission=self.object ) } kwargs.update(context) diff --git a/apps/perms/views/remote_app_permission.py b/apps/perms/views/remote_app_permission.py index f4da75ffe..2e3db2f17 100644 --- a/apps/perms/views/remote_app_permission.py +++ b/apps/perms/views/remote_app_permission.py @@ -102,10 +102,10 @@ class RemoteAppPermissionUserView(AdminUserRequiredMixin, 'app': _('Perms'), 'action': _('RemoteApp permission user list'), 'users_remain': current_org.get_org_users().exclude( - remote_app_permissions=self.object + remoteapppermissions=self.object ), 'user_groups_remain': UserGroup.objects.exclude( - remote_app_permissions=self.object + remoteapppermissions=self.object ) } kwargs.update(context) From 84610f2a2c753e50e9c6e2bd7a7697db54c10ba0 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Tue, 21 May 2019 17:01:47 +0800 Subject: [PATCH 11/36] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E6=94=B9PermsModel?= =?UTF-8?q?=E6=8A=BD=E8=B1=A1=E5=90=8E=E7=9A=84objects-bug=20(#2713)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Bugfix] 修改PermsModel抽象后的objects-bug * [Bugfix] 删除无用代码 --- apps/perms/models/asset_permission.py | 2 +- apps/perms/models/base.py | 3 ++- apps/perms/models/remote_app_permission.py | 4 +--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 9091ce3f0..ed0aaa535 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -33,7 +33,7 @@ class Action(models.Model): return cls.objects.get(name=PERMS_ACTION_NAME_ALL) -class AssetPermission(OrgModelMixin, BasePermission): +class AssetPermission(BasePermission): assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset")) nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes")) system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user")) diff --git a/apps/perms/models/base.py b/apps/perms/models/base.py index 4665bcd2a..345ebd4e8 100644 --- a/apps/perms/models/base.py +++ b/apps/perms/models/base.py @@ -5,6 +5,7 @@ import uuid from django.utils.translation import ugettext_lazy as _ from django.db import models from django.utils import timezone +from orgs.mixins import OrgModelMixin from common.utils import date_expired_default, set_or_append_attr_bulk from orgs.mixins import OrgManager @@ -29,7 +30,7 @@ class BasePermissionManager(OrgManager): return self.get_queryset().valid() -class BasePermission(models.Model): +class BasePermission(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=128, verbose_name=_('Name')) users = models.ManyToManyField('users.User', blank=True, verbose_name=_("User")) diff --git a/apps/perms/models/remote_app_permission.py b/apps/perms/models/remote_app_permission.py index b705a2ab3..706467396 100644 --- a/apps/perms/models/remote_app_permission.py +++ b/apps/perms/models/remote_app_permission.py @@ -4,8 +4,6 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from orgs.mixins import OrgModelMixin - from .base import BasePermission __all__ = [ @@ -13,7 +11,7 @@ __all__ = [ ] -class RemoteAppPermission(OrgModelMixin, BasePermission): +class RemoteAppPermission(BasePermission): remote_apps = models.ManyToManyField('applications.RemoteApp', related_name='granted_by_permissions', blank=True, verbose_name=_("RemoteApp")) class Meta: From e8ceb58292342241993d20cf4f4728e0bec2fda6 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 21 May 2019 17:07:47 +0800 Subject: [PATCH 12/36] =?UTF-8?q?[Update]=20=E5=85=81=E8=AE=B8=E8=B5=84?= =?UTF-8?q?=E4=BA=A7ip=E5=A1=AB=E5=86=99=E4=B8=BAhost=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0027_auto_20190521_1703.py | 18 ++++++++++++++++++ apps/assets/models/asset.py | 2 +- requirements/requirements.txt | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 apps/assets/migrations/0027_auto_20190521_1703.py diff --git a/apps/assets/migrations/0027_auto_20190521_1703.py b/apps/assets/migrations/0027_auto_20190521_1703.py new file mode 100644 index 000000000..da38b1791 --- /dev/null +++ b/apps/assets/migrations/0027_auto_20190521_1703.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.7 on 2019-05-21 09:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0026_auto_20190325_2035'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='ip', + field=models.CharField(db_index=True, max_length=128, verbose_name='IP'), + ), + ] diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 078221e85..47a34243d 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -71,7 +71,7 @@ class Asset(OrgModelMixin): ) id = models.UUIDField(default=uuid.uuid4, primary_key=True) - ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True) + ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) hostname = models.CharField(max_length=128, verbose_name=_('Hostname')) protocol = models.CharField(max_length=128, default=PROTOCOL_SSH, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol')) port = models.IntegerField(default=22, verbose_name=_('Port')) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index bd48a40e7..5b3a786d0 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -81,3 +81,4 @@ tencentcloud-sdk-python==3.0.40 django-radius==1.3.3 ipip-ipdb==1.2.1 django-redis-sessions==0.6.1 +unicodecsv==0.14.1 From 7859499c97fa522aec60dc6a041be8123cb95e7a Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 21 May 2019 17:46:39 +0800 Subject: [PATCH 13/36] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=96=B0dockerfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0e55498cb..9f4c5c22b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,8 @@ RUN useradd jumpserver COPY ./requirements /tmp/requirements -RUN yum -y install epel-release && rpm -ivh https://repo.mysql.com/mysql57-community-release-el6.rpm +RUN yum -y install epel-release && \ + echo -e "[mysql]\nname=mysql\nbaseurl=https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql57-community-el6/\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo RUN cd /tmp/requirements && yum -y install $(cat rpm_requirements.txt) RUN cd /tmp/requirements && pip install --upgrade pip setuptools && \ pip install -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt || pip install -r requirements.txt From 3eaf4cd1421f1e4d194bbc0589af3d1cd0c47fe5 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Wed, 22 May 2019 10:01:41 +0800 Subject: [PATCH 14/36] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=20(#2714)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 72271 -> 75055 bytes apps/locale/zh/LC_MESSAGES/django.po | 681 ++++++--------------------- 2 files changed, 155 insertions(+), 526 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 820dc398eb1f58f52cc32addee4b8e37cd6a0cf8..58ec179b83bd35b8c88e98ab8aefe011277ccf4c 100644 GIT binary patch delta 24164 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 delta 21658 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 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 97df573d9..823a3b07c 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-05-21 11:01+0800\n" +"POT-Creation-Date: 2019-05-21 21:09+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -77,14 +77,15 @@ msgstr "运行参数" #: applications/templates/applications/user_remote_app_list.html:18 #: assets/forms/domain.py:15 assets/forms/label.py:13 #: assets/models/asset.py:279 assets/models/authbook.py:27 -#: assets/templates/assets/admin_user_list.html:28 +#: assets/serializers/admin_user.py:23 assets/serializers/system_user.py:28 +#: assets/templates/assets/admin_user_list.html:49 #: assets/templates/assets/domain_detail.html:60 #: assets/templates/assets/domain_list.html:26 #: assets/templates/assets/label_list.html:16 -#: assets/templates/assets/system_user_list.html:33 audits/models.py:19 +#: assets/templates/assets/system_user_list.html:55 audits/models.py:19 #: audits/templates/audits/ftp_log_list.html:41 #: audits/templates/audits/ftp_log_list.html:71 -#: perms/forms/asset_permission.py:46 perms/models/asset_permission.py:55 +#: perms/forms/asset_permission.py:46 perms/models/asset_permission.py:37 #: perms/templates/perms/asset_permission_create_update.html:45 #: perms/templates/perms/asset_permission_list.html:56 #: perms/templates/perms/asset_permission_list.html:125 @@ -112,8 +113,8 @@ msgstr "资产" #: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:168 #: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49 #: audits/templates/audits/ftp_log_list.html:72 -#: perms/forms/asset_permission.py:52 perms/models/asset_permission.py:57 -#: perms/models/asset_permission.py:112 +#: perms/forms/asset_permission.py:52 perms/models/asset_permission.py:39 +#: perms/models/asset_permission.py:59 #: perms/templates/perms/asset_permission_detail.html:140 #: perms/templates/perms/asset_permission_list.html:58 #: perms/templates/perms/asset_permission_list.html:79 @@ -136,7 +137,7 @@ msgstr "系统用户" #: assets/models/cmd_filter.py:20 assets/models/domain.py:20 #: assets/models/group.py:20 assets/models/label.py:18 #: assets/templates/assets/admin_user_detail.html:56 -#: assets/templates/assets/admin_user_list.html:26 +#: assets/templates/assets/admin_user_list.html:47 #: assets/templates/assets/cmd_filter_detail.html:61 #: assets/templates/assets/cmd_filter_list.html:24 #: assets/templates/assets/domain_detail.html:56 @@ -144,10 +145,10 @@ msgstr "系统用户" #: assets/templates/assets/domain_list.html:25 #: assets/templates/assets/label_list.html:14 #: assets/templates/assets/system_user_detail.html:58 -#: assets/templates/assets/system_user_list.html:29 ops/models/adhoc.py:37 +#: assets/templates/assets/system_user_list.html:51 ops/models/adhoc.py:37 #: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:27 #: orgs/models.py:12 perms/models/asset_permission.py:22 -#: perms/models/asset_permission.py:52 perms/models/remote_app_permission.py:33 +#: perms/models/base.py:35 #: perms/templates/perms/asset_permission_detail.html:62 #: perms/templates/perms/asset_permission_list.html:53 #: perms/templates/perms/asset_permission_list.html:72 @@ -167,8 +168,8 @@ msgstr "系统用户" #: users/models/user.py:61 users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_detail.html:63 #: users/templates/users/user_group_detail.html:55 -#: users/templates/users/user_group_list.html:12 -#: users/templates/users/user_list.html:23 +#: users/templates/users/user_group_list.html:35 +#: users/templates/users/user_list.html:35 #: users/templates/users/user_profile.html:51 #: users/templates/users/user_pubkey_update.html:53 #: xpack/plugins/change_auth_plan/forms.py:97 @@ -212,11 +213,11 @@ msgstr "参数" #: assets/templates/assets/domain_detail.html:72 #: assets/templates/assets/system_user_detail.html:100 #: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 -#: perms/models/asset_permission.py:62 perms/models/asset_permission.py:115 -#: perms/models/remote_app_permission.py:40 +#: perms/models/asset_permission.py:62 perms/models/base.py:41 #: perms/templates/perms/asset_permission_detail.html:98 #: perms/templates/perms/remote_app_permission_detail.html:90 -#: users/models/user.py:102 users/templates/users/user_detail.html:111 +#: users/models/user.py:102 users/serializers/v1.py:72 +#: users/templates/users/user_detail.html:111 #: xpack/plugins/change_auth_plan/models.py:103 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 #: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127 @@ -227,14 +228,14 @@ msgstr "创建者" #: applications/templates/applications/remote_app_detail.html:73 #: assets/models/asset.py:110 assets/models/cluster.py:26 #: assets/models/domain.py:23 assets/models/group.py:22 -#: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:64 +#: assets/models/label.py:25 assets/serializers/admin_user.py:37 +#: assets/templates/assets/admin_user_detail.html:64 #: assets/templates/assets/cmd_filter_detail.html:69 #: assets/templates/assets/domain_detail.html:68 #: assets/templates/assets/system_user_detail.html:96 #: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 #: orgs/models.py:16 perms/models/asset_permission.py:63 -#: perms/models/asset_permission.py:116 -#: perms/models/remote_app_permission.py:41 +#: perms/models/base.py:42 #: perms/templates/perms/asset_permission_detail.html:94 #: perms/templates/perms/remote_app_permission_detail.html:86 #: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 @@ -256,7 +257,7 @@ msgstr "创建日期" #: assets/models/cmd_filter.py:55 assets/models/domain.py:21 #: assets/models/domain.py:53 assets/models/group.py:23 #: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72 -#: assets/templates/assets/admin_user_list.html:32 +#: assets/templates/assets/admin_user_list.html:53 #: assets/templates/assets/asset_detail.html:136 #: assets/templates/assets/cmd_filter_detail.html:65 #: assets/templates/assets/cmd_filter_list.html:27 @@ -265,18 +266,17 @@ msgstr "创建日期" #: assets/templates/assets/domain_gateway_list.html:72 #: assets/templates/assets/domain_list.html:28 #: assets/templates/assets/system_user_detail.html:104 -#: assets/templates/assets/system_user_list.html:37 +#: assets/templates/assets/system_user_list.html:59 #: assets/templates/assets/user_asset_list.html:171 ops/models/adhoc.py:43 #: orgs/models.py:17 perms/models/asset_permission.py:64 -#: perms/models/asset_permission.py:117 -#: perms/models/remote_app_permission.py:42 +#: perms/models/base.py:43 #: perms/templates/perms/asset_permission_detail.html:102 #: perms/templates/perms/remote_app_permission_detail.html:94 #: settings/models.py:34 terminal/models.py:32 #: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15 #: users/models/user.py:94 users/templates/users/user_detail.html:127 #: users/templates/users/user_group_detail.html:67 -#: users/templates/users/user_group_list.html:14 +#: users/templates/users/user_group_list.html:37 #: users/templates/users/user_profile.html:134 #: xpack/plugins/change_auth_plan/models.py:99 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117 @@ -292,7 +292,7 @@ msgid "Comment" msgstr "备注" #: applications/models/remote_app.py:53 perms/forms/remote_app_permission.py:37 -#: perms/models/remote_app_permission.py:36 +#: perms/models/remote_app_permission.py:15 #: perms/templates/perms/remote_app_permission_create_update.html:48 #: perms/templates/perms/remote_app_permission_detail.html:27 #: perms/templates/perms/remote_app_permission_list.html:17 @@ -343,7 +343,7 @@ msgstr "重置" #: assets/templates/assets/admin_user_create_update.html:46 #: assets/templates/assets/asset_bulk_update.html:24 #: assets/templates/assets/asset_create.html:68 -#: assets/templates/assets/asset_list.html:113 +#: assets/templates/assets/asset_list.html:125 #: assets/templates/assets/asset_update.html:72 #: assets/templates/assets/cmd_filter_create_update.html:16 #: assets/templates/assets/cmd_filter_rule_create_update.html:41 @@ -366,7 +366,7 @@ msgstr "重置" #: users/templates/users/_user.html:51 #: users/templates/users/forgot_password.html:42 #: users/templates/users/user_bulk_update.html:24 -#: users/templates/users/user_list.html:45 +#: users/templates/users/user_list.html:57 #: users/templates/users/user_password_update.html:72 #: users/templates/users/user_profile_update.html:64 #: users/templates/users/user_pubkey_update.html:77 @@ -404,9 +404,11 @@ msgstr "详情" #: applications/templates/applications/remote_app_detail.html:21 #: applications/templates/applications/remote_app_list.html:56 #: assets/templates/assets/admin_user_detail.html:24 -#: assets/templates/assets/admin_user_list.html:88 +#: assets/templates/assets/admin_user_list.html:29 +#: assets/templates/assets/admin_user_list.html:111 #: assets/templates/assets/asset_detail.html:27 -#: assets/templates/assets/asset_list.html:178 +#: assets/templates/assets/asset_list.html:86 +#: assets/templates/assets/asset_list.html:190 #: assets/templates/assets/cmd_filter_detail.html:29 #: assets/templates/assets/cmd_filter_list.html:58 #: assets/templates/assets/cmd_filter_rule_list.html:86 @@ -416,7 +418,8 @@ msgstr "详情" #: assets/templates/assets/domain_list.html:54 #: assets/templates/assets/label_list.html:39 #: assets/templates/assets/system_user_detail.html:26 -#: assets/templates/assets/system_user_list.html:93 audits/models.py:33 +#: assets/templates/assets/system_user_list.html:33 +#: assets/templates/assets/system_user_list.html:117 audits/models.py:33 #: perms/templates/perms/asset_permission_detail.html:30 #: perms/templates/perms/asset_permission_list.html:181 #: perms/templates/perms/remote_app_permission_detail.html:30 @@ -425,9 +428,11 @@ msgstr "详情" #: terminal/templates/terminal/terminal_list.html:72 #: users/templates/users/user_detail.html:25 #: users/templates/users/user_group_detail.html:28 -#: users/templates/users/user_group_list.html:45 -#: users/templates/users/user_list.html:83 -#: users/templates/users/user_list.html:86 +#: users/templates/users/user_group_list.html:20 +#: users/templates/users/user_group_list.html:69 +#: users/templates/users/user_list.html:20 +#: users/templates/users/user_list.html:96 +#: users/templates/users/user_list.html:99 #: users/templates/users/user_profile.html:177 #: users/templates/users/user_profile.html:187 #: users/templates/users/user_profile.html:196 @@ -443,9 +448,9 @@ msgstr "更新" #: applications/templates/applications/remote_app_detail.html:25 #: applications/templates/applications/remote_app_list.html:57 #: assets/templates/assets/admin_user_detail.html:28 -#: assets/templates/assets/admin_user_list.html:89 +#: assets/templates/assets/admin_user_list.html:112 #: assets/templates/assets/asset_detail.html:31 -#: assets/templates/assets/asset_list.html:179 +#: assets/templates/assets/asset_list.html:191 #: assets/templates/assets/cmd_filter_detail.html:33 #: assets/templates/assets/cmd_filter_list.html:59 #: assets/templates/assets/cmd_filter_rule_list.html:87 @@ -455,7 +460,7 @@ msgstr "更新" #: assets/templates/assets/domain_list.html:55 #: assets/templates/assets/label_list.html:40 #: assets/templates/assets/system_user_detail.html:30 -#: assets/templates/assets/system_user_list.html:94 audits/models.py:34 +#: assets/templates/assets/system_user_list.html:118 audits/models.py:34 #: ops/templates/ops/task_list.html:64 #: perms/templates/perms/asset_permission_detail.html:34 #: perms/templates/perms/asset_permission_list.html:182 @@ -466,9 +471,9 @@ msgstr "更新" #: terminal/templates/terminal/terminal_list.html:74 #: users/templates/users/user_detail.html:30 #: users/templates/users/user_group_detail.html:32 -#: users/templates/users/user_group_list.html:47 -#: users/templates/users/user_list.html:91 -#: users/templates/users/user_list.html:95 +#: users/templates/users/user_group_list.html:71 +#: users/templates/users/user_list.html:104 +#: users/templates/users/user_list.html:108 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:57 #: xpack/plugins/cloud/templates/cloud/account_detail.html:27 @@ -502,23 +507,23 @@ msgstr "创建远程应用" #: applications/templates/applications/user_remote_app_list.html:21 #: assets/models/cmd_filter.py:54 #: assets/templates/assets/admin_user_assets.html:52 -#: assets/templates/assets/admin_user_list.html:33 +#: assets/templates/assets/admin_user_list.html:54 #: assets/templates/assets/asset_asset_user_list.html:48 -#: assets/templates/assets/asset_list.html:96 +#: assets/templates/assets/asset_list.html:108 #: assets/templates/assets/cmd_filter_list.html:28 #: assets/templates/assets/cmd_filter_rule_list.html:63 #: assets/templates/assets/domain_gateway_list.html:73 #: assets/templates/assets/domain_list.html:29 #: assets/templates/assets/label_list.html:17 #: assets/templates/assets/system_user_asset.html:54 -#: assets/templates/assets/system_user_list.html:38 +#: assets/templates/assets/system_user_list.html:60 #: assets/templates/assets/user_asset_list.html:48 audits/models.py:38 #: 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/task_history.html:65 ops/templates/ops/task_list.html:34 #: perms/forms/asset_permission.py:55 perms/models/asset_permission.py:26 -#: perms/models/asset_permission.py:58 +#: perms/models/asset_permission.py:40 #: perms/templates/perms/asset_permission_create_update.html:50 #: perms/templates/perms/asset_permission_list.html:60 #: perms/templates/perms/asset_permission_list.html:134 @@ -527,8 +532,8 @@ msgstr "创建远程应用" #: settings/templates/settings/terminal_setting.html:104 #: terminal/templates/terminal/session_list.html:81 #: terminal/templates/terminal/terminal_list.html:36 -#: users/templates/users/user_group_list.html:15 -#: users/templates/users/user_list.html:29 +#: users/templates/users/user_group_list.html:38 +#: users/templates/users/user_list.html:41 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:18 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:20 @@ -551,9 +556,9 @@ msgstr "连接" #: 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:63 assets/views/admin_user.py:78 -#: assets/views/admin_user.py:102 assets/views/asset.py:51 -#: assets/views/asset.py:67 assets/views/asset.py:104 assets/views/asset.py:145 -#: assets/views/asset.py:162 assets/views/asset.py:186 +#: assets/views/admin_user.py:102 assets/views/asset.py:53 +#: assets/views/asset.py:69 assets/views/asset.py:106 assets/views/asset.py:147 +#: assets/views/asset.py:164 assets/views/asset.py:188 #: assets/views/cmd_filter.py:30 assets/views/cmd_filter.py:46 #: assets/views/cmd_filter.py:62 assets/views/cmd_filter.py:78 #: assets/views/cmd_filter.py:97 assets/views/cmd_filter.py:130 @@ -584,7 +589,7 @@ msgstr "远程应用详情" msgid "My RemoteApp" msgstr "我的远程应用" -#: assets/api/asset.py:112 +#: assets/api/asset.py:126 msgid "Please select assets that need to be updated" msgstr "请选择需要更新的资产" @@ -604,7 +609,7 @@ msgstr "测试节点下资产是否可连接: {}" #: assets/templates/assets/asset_detail.html:194 #: assets/templates/assets/asset_detail.html:202 #: assets/templates/assets/system_user_asset.html:95 -#: perms/models/asset_permission.py:56 +#: perms/models/asset_permission.py:38 #: xpack/plugins/change_auth_plan/models.py:69 msgid "Nodes" msgstr "节点管理" @@ -642,7 +647,7 @@ msgstr "网域" #: assets/templates/assets/asset_create.html:30 #: assets/templates/assets/asset_update.html:35 #: perms/forms/asset_permission.py:49 perms/forms/asset_permission.py:59 -#: perms/models/asset_permission.py:110 +#: perms/models/asset_permission.py:57 #: perms/templates/perms/asset_permission_list.html:57 #: perms/templates/perms/asset_permission_list.html:78 #: perms/templates/perms/asset_permission_list.html:128 @@ -680,37 +685,6 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域, msgid "Select assets" msgstr "选择资产" -#: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:279 assets/models/authbook.py:27 -#: assets/serializers/admin_user.py:31 assets/serializers/system_user.py:32 -#: assets/templates/assets/admin_user_list.html:49 -#: assets/templates/assets/domain_detail.html:60 -#: assets/templates/assets/domain_list.html:26 -#: assets/templates/assets/label_list.html:16 -#: assets/templates/assets/system_user_list.html:55 audits/models.py:19 -#: audits/templates/audits/ftp_log_list.html:41 -#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:42 -#: perms/models.py:50 -#: perms/templates/perms/asset_permission_create_update.html:45 -#: perms/templates/perms/asset_permission_list.html:56 -#: perms/templates/perms/asset_permission_list.html:125 -#: terminal/backends/command/models.py:13 terminal/models.py:155 -#: terminal/templates/terminal/command_list.html:40 -#: terminal/templates/terminal/command_list.html:73 -#: terminal/templates/terminal/session_list.html:41 -#: terminal/templates/terminal/session_list.html:72 -#: xpack/plugins/change_auth_plan/forms.py:114 -#: xpack/plugins/change_auth_plan/models.py:409 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:14 -#: xpack/plugins/cloud/models.py:187 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 -#: xpack/plugins/orgs/templates/orgs/org_list.html:16 -msgid "Asset" -msgstr "资产" - #: assets/forms/domain.py:51 msgid "Password should not contain special characters" msgstr "不能包含特殊字符" @@ -719,60 +693,11 @@ msgstr "不能包含特殊字符" msgid "SSH gateway support proxy SSH,RDP,VNC" msgstr "SSH网关,支持代理SSH,RDP和VNC" -#: assets/forms/domain.py:73 assets/forms/user.py:84 assets/forms/user.py:146 -#: assets/models/base.py:26 assets/models/cluster.py:18 -#: assets/models/cmd_filter.py:20 assets/models/domain.py:20 -#: assets/models/group.py:20 assets/models/label.py:18 -#: assets/templates/assets/admin_user_detail.html:56 -#: assets/templates/assets/admin_user_list.html:47 -#: assets/templates/assets/cmd_filter_detail.html:61 -#: assets/templates/assets/cmd_filter_list.html:24 -#: assets/templates/assets/domain_detail.html:56 -#: assets/templates/assets/domain_gateway_list.html:67 -#: assets/templates/assets/domain_list.html:25 -#: assets/templates/assets/label_list.html:14 -#: assets/templates/assets/system_user_detail.html:58 -#: assets/templates/assets/system_user_list.html:51 ops/models/adhoc.py:37 -#: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:27 -#: orgs/models.py:12 perms/models.py:17 perms/models.py:47 -#: perms/templates/perms/asset_permission_detail.html:62 -#: perms/templates/perms/asset_permission_list.html:53 -#: perms/templates/perms/asset_permission_list.html:72 -#: perms/templates/perms/asset_permission_user.html:54 settings/models.py:29 -#: settings/templates/settings/_ldap_list_users_modal.html:38 -#: settings/templates/settings/command_storage_create.html:41 -#: settings/templates/settings/replay_storage_create.html:44 -#: settings/templates/settings/terminal_setting.html:80 -#: settings/templates/settings/terminal_setting.html:102 terminal/models.py:22 -#: terminal/models.py:241 terminal/templates/terminal/terminal_detail.html:43 -#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 -#: users/models/user.py:61 users/templates/users/_select_user_modal.html:13 -#: users/templates/users/user_detail.html:63 -#: users/templates/users/user_group_detail.html:55 -#: users/templates/users/user_group_list.html:35 -#: users/templates/users/user_list.html:35 -#: users/templates/users/user_profile.html:51 -#: users/templates/users/user_pubkey_update.html:53 -#: xpack/plugins/change_auth_plan/forms.py:97 -#: xpack/plugins/change_auth_plan/models.py:58 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 -#: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:50 -#: xpack/plugins/cloud/templates/cloud/account_list.html:12 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:52 -#: xpack/plugins/orgs/templates/orgs/org_list.html:12 -msgid "Name" -msgstr "名称" - #: assets/forms/domain.py:74 assets/forms/user.py:85 assets/forms/user.py:147 #: assets/models/base.py:27 #: assets/templates/assets/_asset_user_auth_modal.html:15 #: assets/templates/assets/_asset_user_view_auth_modal.html:31 #: assets/templates/assets/admin_user_detail.html:60 - #: assets/templates/assets/admin_user_list.html:48 #: assets/templates/assets/asset_asset_user_list.html:44 #: assets/templates/assets/domain_gateway_list.html:71 @@ -1007,94 +932,19 @@ msgstr "主机名原始" msgid "Labels" msgstr "标签管理" -#: assets/models/asset.py:109 assets/models/base.py:34 -#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25 -#: assets/models/cmd_filter.py:58 assets/models/group.py:21 -#: assets/templates/assets/admin_user_detail.html:68 -#: assets/templates/assets/asset_detail.html:128 -#: assets/templates/assets/cmd_filter_detail.html:77 -#: assets/templates/assets/domain_detail.html:72 -#: assets/templates/assets/system_user_detail.html:100 -#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:57 -#: perms/models.py:110 perms/templates/perms/asset_permission_detail.html:98 -#: users/models/user.py:102 users/serializers/v1.py:69 -#: users/templates/users/user_detail.html:111 -#: xpack/plugins/change_auth_plan/models.py:103 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 -#: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127 -msgid "Created by" -msgstr "创建者" - -#: assets/models/asset.py:110 assets/models/cluster.py:26 -#: assets/models/domain.py:23 assets/models/group.py:22 -#: assets/models/label.py:25 assets/serializers/admin_user.py:23 -#: assets/templates/assets/admin_user_detail.html:64 -#: assets/templates/assets/cmd_filter_detail.html:69 -#: assets/templates/assets/domain_detail.html:68 -#: assets/templates/assets/system_user_detail.html:96 -#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 -#: orgs/models.py:16 perms/models.py:58 perms/models.py:111 -#: perms/templates/perms/asset_permission_detail.html:94 -#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 -#: users/templates/users/user_group_detail.html:63 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105 -#: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:128 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:66 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:77 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:60 -msgid "Date created" -msgstr "创建日期" - -#: assets/models/asset.py:111 assets/models/base.py:31 -#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22 -#: assets/models/cmd_filter.py:55 assets/models/domain.py:21 -#: assets/models/domain.py:53 assets/models/group.py:23 -#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72 -#: assets/templates/assets/admin_user_list.html:53 -#: assets/templates/assets/asset_detail.html:136 -#: assets/templates/assets/cmd_filter_detail.html:65 -#: assets/templates/assets/cmd_filter_list.html:27 -#: assets/templates/assets/cmd_filter_rule_list.html:62 -#: assets/templates/assets/domain_detail.html:76 -#: assets/templates/assets/domain_gateway_list.html:72 -#: assets/templates/assets/domain_list.html:28 -#: assets/templates/assets/system_user_detail.html:104 -#: assets/templates/assets/system_user_list.html:59 -#: assets/templates/assets/user_asset_list.html:171 ops/models/adhoc.py:43 -#: orgs/models.py:17 perms/models.py:59 perms/models.py:112 -#: perms/templates/perms/asset_permission_detail.html:102 settings/models.py:34 -#: terminal/models.py:32 terminal/templates/terminal/terminal_detail.html:63 -#: users/models/group.py:15 users/models/user.py:94 -#: users/templates/users/user_detail.html:127 -#: users/templates/users/user_group_detail.html:67 -#: users/templates/users/user_group_list.html:37 -#: users/templates/users/user_profile.html:134 -#: xpack/plugins/change_auth_plan/models.py:99 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 -#: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:70 -#: xpack/plugins/cloud/templates/cloud/account_list.html:15 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:64 -#: xpack/plugins/orgs/templates/orgs/org_list.html:22 -msgid "Comment" -msgstr "备注" - #: assets/models/asset.py:117 assets/models/base.py:38 -#: assets/serializers/admin_user.py:29 assets/serializers/system_user.py:22 +#: assets/serializers/admin_user.py:22 assets/serializers/system_user.py:19 #: assets/templates/assets/admin_user_list.html:51 #: assets/templates/assets/system_user_list.html:57 msgid "Unreachable" msgstr "不可达" #: assets/models/asset.py:118 assets/models/base.py:39 -#: assets/serializers/admin_user.py:32 assets/serializers/system_user.py:31 +#: assets/serializers/admin_user.py:24 assets/serializers/system_user.py:27 #: assets/templates/assets/admin_user_assets.html:51 #: assets/templates/assets/admin_user_list.html:50 -#: assets/templates/assets/asset_list.html:107 #: assets/templates/assets/asset_asset_user_list.html:46 +#: assets/templates/assets/asset_list.html:107 #: assets/templates/assets/system_user_asset.html:53 #: assets/templates/assets/system_user_list.html:56 #: users/templates/users/user_group_granted_asset.html:47 @@ -1243,42 +1093,6 @@ msgstr "内容" msgid "One line one command" msgstr "每行一个命令" -#: assets/models/cmd_filter.py:54 -#: assets/templates/assets/admin_user_assets.html:52 -#: assets/templates/assets/admin_user_list.html:54 -#: assets/templates/assets/asset_list.html:108 -#: assets/templates/assets/asset_asset_user_list.html:48 -#: assets/templates/assets/cmd_filter_list.html:28 -#: assets/templates/assets/cmd_filter_rule_list.html:63 -#: assets/templates/assets/domain_gateway_list.html:73 -#: assets/templates/assets/domain_list.html:29 -#: assets/templates/assets/label_list.html:17 -#: assets/templates/assets/system_user_asset.html:54 -#: assets/templates/assets/system_user_list.html:60 -#: assets/templates/assets/user_asset_list.html:48 audits/models.py:38 -#: 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/task_history.html:65 ops/templates/ops/task_list.html:34 -#: perms/forms.py:51 perms/models.py:21 perms/models.py:53 -#: perms/templates/perms/asset_permission_create_update.html:50 -#: perms/templates/perms/asset_permission_list.html:60 -#: perms/templates/perms/asset_permission_list.html:134 -#: settings/templates/settings/terminal_setting.html:82 -#: settings/templates/settings/terminal_setting.html:104 -#: terminal/templates/terminal/session_list.html:81 -#: terminal/templates/terminal/terminal_list.html:36 -#: users/templates/users/user_group_list.html:38 -#: users/templates/users/user_list.html:41 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:18 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:20 -#: xpack/plugins/cloud/templates/cloud/account_list.html:16 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18 -#: xpack/plugins/orgs/templates/orgs/org_list.html:23 -msgid "Action" -msgstr "动作" - #: assets/models/cmd_filter.py:64 msgid "Command filter rule" msgstr "命令过滤规则" @@ -1308,7 +1122,7 @@ msgstr "默认资产组" #: ops/templates/ops/command_execution_list.html:35 #: ops/templates/ops/command_execution_list.html:60 #: perms/forms/asset_permission.py:40 perms/forms/remote_app_permission.py:31 -#: perms/models/asset_permission.py:53 perms/models/remote_app_permission.py:34 +#: perms/models/base.py:36 #: perms/templates/perms/asset_permission_create_update.html:41 #: perms/templates/perms/asset_permission_list.html:54 #: perms/templates/perms/asset_permission_list.html:119 @@ -1321,7 +1135,7 @@ msgstr "默认资产组" #: terminal/templates/terminal/session_list.html:71 users/forms.py:283 #: users/models/user.py:36 users/models/user.py:467 users/serializers/v1.py:61 #: users/templates/users/user_group_detail.html:78 -#: users/templates/users/user_group_list.html:36 users/views/user.py:395 +#: users/templates/users/user_group_list.html:36 users/views/user.py:394 #: xpack/plugins/orgs/forms.py:26 #: xpack/plugins/orgs/templates/orgs/org_detail.html:113 #: xpack/plugins/orgs/templates/orgs/org_list.html:14 @@ -1377,25 +1191,25 @@ msgstr "登录模式" msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" -#: assets/serializers/admin_user.py:26 -#: assets/templates/assets/asset_asset_user_list.html:51 +#: assets/serializers/admin_user.py:38 +#: assets/templates/assets/asset_asset_user_list.html:47 #: assets/templates/assets/cmd_filter_detail.html:73 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109 msgid "Date updated" msgstr "更新日期" -#: assets/serializers/asset.py:20 -msgid "Org name" -msgstr "组织名" - -#: assets/serializers/asset.py:22 +#: assets/serializers/asset.py:43 msgid "Hardware info" msgstr "硬件信息" -#: assets/serializers/asset.py:25 +#: assets/serializers/asset.py:44 msgid "Connectivity" msgstr "连接" +#: assets/serializers/asset.py:45 +msgid "Org name" +msgstr "组织名" + #: assets/serializers/asset_user.py:23 users/forms.py:230 #: users/models/user.py:91 users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:46 @@ -1405,18 +1219,18 @@ msgstr "连接" msgid "Public key" msgstr "ssh公钥" -#: assets/serializers/system_user.py:19 -msgid "Login mode display" -msgstr "登录模式显示" - -#: assets/serializers/system_user.py:25 +#: assets/serializers/system_user.py:22 msgid "Unreachable assets" msgstr "不可达资产" -#: assets/serializers/system_user.py:29 +#: assets/serializers/system_user.py:25 msgid "Reachable assets" msgstr "可连接资产" +#: assets/serializers/system_user.py:41 +msgid "Login mode display" +msgstr "登录模式显示" + #: assets/tasks.py:31 msgid "Asset has been disabled, skipped: {}" msgstr "资产或许不支持ansible, 跳过: {}" @@ -1527,7 +1341,7 @@ msgstr "启用MFA" msgid "Import assets" msgstr "导入资产" -#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:52 +#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:54 #: templates/_nav.html:22 xpack/plugins/change_auth_plan/views.py:110 msgid "Asset list" msgstr "资产列表" @@ -1563,11 +1377,11 @@ msgstr "需要二次认证来查看账号信息" #: assets/templates/assets/_asset_user_view_auth_modal.html:20 #: assets/templates/assets/admin_user_detail.html:100 #: assets/templates/assets/asset_detail.html:211 -#: assets/templates/assets/asset_list.html:637 +#: assets/templates/assets/asset_list.html:700 #: assets/templates/assets/cmd_filter_detail.html:106 #: assets/templates/assets/system_user_asset.html:112 #: assets/templates/assets/system_user_detail.html:182 -#: assets/templates/assets/system_user_list.html:144 +#: assets/templates/assets/system_user_list.html:168 #: settings/templates/settings/terminal_setting.html:165 #: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108 #: users/templates/users/user_detail.html:388 @@ -1575,8 +1389,8 @@ msgstr "需要二次认证来查看账号信息" #: users/templates/users/user_detail.html:437 #: users/templates/users/user_detail.html:482 #: users/templates/users/user_group_create_update.html:32 -#: users/templates/users/user_group_list.html:90 -#: users/templates/users/user_list.html:215 +#: users/templates/users/user_group_list.html:114 +#: users/templates/users/user_list.html:269 #: users/templates/users/user_profile.html:238 #: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 @@ -1645,75 +1459,6 @@ msgstr "自动生成密钥" msgid "Other" msgstr "其它" -#: assets/templates/assets/_system_user.html:75 -#: assets/templates/assets/admin_user_create_update.html:45 -#: assets/templates/assets/asset_bulk_update.html:23 -#: assets/templates/assets/asset_create.html:67 -#: assets/templates/assets/asset_update.html:71 -#: assets/templates/assets/cmd_filter_create_update.html:15 -#: assets/templates/assets/cmd_filter_rule_create_update.html:40 -#: assets/templates/assets/domain_create_update.html:16 -#: assets/templates/assets/gateway_create_update.html:58 -#: assets/templates/assets/label_create_update.html:18 -#: perms/templates/perms/asset_permission_create_update.html:83 -#: settings/templates/settings/basic_setting.html:61 -#: settings/templates/settings/command_storage_create.html:79 -#: settings/templates/settings/email_setting.html:62 -#: settings/templates/settings/ldap_setting.html:61 -#: settings/templates/settings/replay_storage_create.html:152 -#: settings/templates/settings/security_setting.html:70 -#: settings/templates/settings/terminal_setting.html:68 -#: terminal/templates/terminal/terminal_update.html:45 -#: users/templates/users/_user.html:50 -#: users/templates/users/user_bulk_update.html:23 -#: users/templates/users/user_detail.html:176 -#: users/templates/users/user_password_update.html:71 -#: users/templates/users/user_profile.html:204 -#: users/templates/users/user_profile_update.html:63 -#: users/templates/users/user_pubkey_update.html:70 -#: users/templates/users/user_pubkey_update.html:76 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:71 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:33 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:35 -#: xpack/plugins/interface/templates/interface/interface.html:72 -msgid "Reset" -msgstr "重置" - -#: assets/templates/assets/_system_user.html:76 -#: assets/templates/assets/admin_user_create_update.html:46 -#: assets/templates/assets/asset_bulk_update.html:24 -#: assets/templates/assets/asset_create.html:68 -#: assets/templates/assets/asset_list.html:125 -#: assets/templates/assets/asset_update.html:72 -#: assets/templates/assets/cmd_filter_create_update.html:16 -#: assets/templates/assets/cmd_filter_rule_create_update.html:41 -#: assets/templates/assets/domain_create_update.html:17 -#: assets/templates/assets/gateway_create_update.html:59 -#: assets/templates/assets/label_create_update.html:19 -#: audits/templates/audits/login_log_list.html:89 -#: perms/templates/perms/asset_permission_create_update.html:84 -#: settings/templates/settings/basic_setting.html:62 -#: settings/templates/settings/command_storage_create.html:80 -#: settings/templates/settings/email_setting.html:63 -#: settings/templates/settings/ldap_setting.html:64 -#: settings/templates/settings/replay_storage_create.html:153 -#: settings/templates/settings/security_setting.html:71 -#: settings/templates/settings/terminal_setting.html:70 -#: terminal/templates/terminal/command_list.html:103 -#: terminal/templates/terminal/session_list.html:126 -#: terminal/templates/terminal/terminal_update.html:46 -#: users/templates/users/_user.html:51 -#: users/templates/users/forgot_password.html:42 -#: users/templates/users/user_bulk_update.html:24 -#: users/templates/users/user_list.html:57 -#: users/templates/users/user_password_update.html:72 -#: users/templates/users/user_profile_update.html:64 -#: users/templates/users/user_pubkey_update.html:77 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:72 -#: xpack/plugins/interface/templates/interface/interface.html:74 -msgid "Submit" -msgstr "提交" - #: assets/templates/assets/_system_user_import_modal.html:4 msgid "Import system user" msgstr "导入系统用户" @@ -1725,7 +1470,7 @@ msgstr "更新系统用户" #: assets/templates/assets/_user_asset_detail_modal.html:11 #: assets/templates/assets/asset_asset_user_list.html:13 -#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:187 +#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:189 msgid "Asset detail" msgstr "资产详情" @@ -1793,82 +1538,6 @@ msgstr "更新成功" msgid "Update failed!" msgstr "更新失败" -#: assets/templates/assets/admin_user_detail.html:24 -#: assets/templates/assets/admin_user_list.html:29 -#: assets/templates/assets/admin_user_list.html:111 -#: assets/templates/assets/asset_detail.html:27 -#: assets/templates/assets/asset_list.html:86 -#: assets/templates/assets/asset_list.html:190 -#: assets/templates/assets/cmd_filter_detail.html:29 -#: assets/templates/assets/cmd_filter_list.html:58 -#: assets/templates/assets/cmd_filter_rule_list.html:86 -#: assets/templates/assets/domain_detail.html:24 -#: assets/templates/assets/domain_detail.html:103 -#: assets/templates/assets/domain_gateway_list.html:97 -#: assets/templates/assets/domain_list.html:54 -#: assets/templates/assets/label_list.html:39 -#: assets/templates/assets/system_user_detail.html:26 -#: assets/templates/assets/system_user_list.html:33 -#: assets/templates/assets/system_user_list.html:117 audits/models.py:33 -#: perms/templates/perms/asset_permission_detail.html:30 -#: perms/templates/perms/asset_permission_list.html:181 -#: terminal/templates/terminal/terminal_detail.html:16 -#: terminal/templates/terminal/terminal_list.html:72 -#: users/templates/users/user_detail.html:25 -#: users/templates/users/user_group_detail.html:28 -#: users/templates/users/user_group_list.html:20 -#: users/templates/users/user_group_list.html:69 -#: users/templates/users/user_list.html:20 -#: users/templates/users/user_list.html:96 -#: users/templates/users/user_list.html:99 -#: users/templates/users/user_profile.html:177 -#: users/templates/users/user_profile.html:187 -#: users/templates/users/user_profile.html:196 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:55 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:23 -#: xpack/plugins/cloud/templates/cloud/account_list.html:39 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:25 -#: xpack/plugins/orgs/templates/orgs/org_list.html:87 -msgid "Update" -msgstr "更新" - -#: assets/templates/assets/admin_user_detail.html:28 -#: assets/templates/assets/admin_user_list.html:112 -#: assets/templates/assets/asset_detail.html:31 -#: assets/templates/assets/asset_list.html:191 -#: assets/templates/assets/cmd_filter_detail.html:33 -#: assets/templates/assets/cmd_filter_list.html:59 -#: assets/templates/assets/cmd_filter_rule_list.html:87 -#: assets/templates/assets/domain_detail.html:28 -#: assets/templates/assets/domain_detail.html:104 -#: assets/templates/assets/domain_gateway_list.html:98 -#: assets/templates/assets/domain_list.html:55 -#: assets/templates/assets/label_list.html:40 -#: assets/templates/assets/system_user_detail.html:30 -#: assets/templates/assets/system_user_list.html:118 audits/models.py:34 -#: ops/templates/ops/task_list.html:64 -#: perms/templates/perms/asset_permission_detail.html:34 -#: perms/templates/perms/asset_permission_list.html:182 -#: settings/templates/settings/terminal_setting.html:90 -#: settings/templates/settings/terminal_setting.html:112 -#: terminal/templates/terminal/terminal_list.html:74 -#: users/templates/users/user_detail.html:30 -#: users/templates/users/user_group_detail.html:32 -#: users/templates/users/user_group_list.html:71 -#: users/templates/users/user_list.html:104 -#: users/templates/users/user_list.html:108 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:57 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:27 -#: xpack/plugins/cloud/templates/cloud/account_list.html:41 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:29 -#: xpack/plugins/orgs/templates/orgs/org_list.html:89 -msgid "Delete" -msgstr "删除" - #: assets/templates/assets/admin_user_detail.html:83 msgid "Replace node assets admin user with this" msgstr "替换资产的管理员" @@ -1880,30 +1549,6 @@ msgstr "替换资产的管理员" msgid "Select nodes" msgstr "选择节点" -#: assets/templates/assets/admin_user_detail.html:100 -#: assets/templates/assets/asset_detail.html:211 -#: assets/templates/assets/asset_list.html:692 -#: assets/templates/assets/cmd_filter_detail.html:106 -#: assets/templates/assets/system_user_asset.html:112 -#: assets/templates/assets/system_user_detail.html:182 -#: assets/templates/assets/system_user_list.html:168 -#: settings/templates/settings/terminal_setting.html:165 -#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108 -#: users/templates/users/user_detail.html:388 -#: users/templates/users/user_detail.html:414 -#: users/templates/users/user_detail.html:437 -#: users/templates/users/user_detail.html:482 -#: users/templates/users/user_group_create_update.html:32 -#: users/templates/users/user_group_list.html:114 -#: users/templates/users/user_list.html:260 -#: users/templates/users/user_profile.html:238 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 -#: xpack/plugins/interface/templates/interface/interface.html:103 -#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 -msgid "Confirm" -msgstr "确认" - #: assets/templates/assets/admin_user_list.html:7 msgid "" "Admin users are asset (charged server) on the root, or have NOPASSWD: ALL " @@ -1962,14 +1607,11 @@ msgstr "比例" #: users/templates/users/user_group_list.html:199 #: users/templates/users/user_list.html:162 #: users/templates/users/user_list.html:198 -#, fuzzy -#| msgid "Please Select User" msgid "Please select file" -msgstr "选择用户" - +msgstr "选择文件" #: assets/templates/assets/asset_asset_user_list.html:16 -#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:68 +#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:70 msgid "Asset user list" msgstr "资产用户列表" @@ -1981,13 +1623,6 @@ msgstr "资产用户" msgid "Password version" msgstr "密码版本" - -#: assets/templates/assets/asset_asset_user_list.html:47 -#: assets/templates/assets/cmd_filter_detail.html:73 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109 -msgid "Date updated" -msgstr "更新日期" - #: assets/templates/assets/asset_asset_user_list.html:60 #: assets/templates/assets/asset_detail.html:148 #: terminal/templates/terminal/session_detail.html:81 @@ -2024,8 +1659,7 @@ msgstr "创建日期" #: assets/templates/assets/asset_detail.html:154 #: assets/templates/assets/user_asset_list.html:46 -#: perms/models/asset_permission.py:59 perms/models/asset_permission.py:113 -#: perms/models/remote_app_permission.py:37 +#: perms/models/asset_permission.py:60 perms/models/base.py:38 #: perms/templates/perms/asset_permission_create_update.html:55 #: perms/templates/perms/asset_permission_detail.html:120 #: perms/templates/perms/remote_app_permission_create_update.html:54 @@ -2056,7 +1690,7 @@ msgstr "" "左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的," "右侧是属于该节点下的资产" -#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:105 +#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:107 msgid "Create asset" msgstr "创建资产" @@ -2148,22 +1782,22 @@ msgstr "重命名成功" msgid "Rename failed, do not change the root node name" msgstr "重命名失败,不能更改root节点的名称" -#: assets/templates/assets/asset_list.html:686 +#: assets/templates/assets/asset_list.html:694 #: assets/templates/assets/system_user_list.html:162 #: users/templates/users/user_detail.html:382 #: users/templates/users/user_detail.html:408 #: users/templates/users/user_detail.html:476 #: users/templates/users/user_group_list.html:108 -#: users/templates/users/user_list.html:254 +#: users/templates/users/user_list.html:263 #: xpack/plugins/interface/templates/interface/interface.html:97 msgid "Are you sure?" msgstr "你确认吗?" -#: assets/templates/assets/asset_list.html:687 +#: assets/templates/assets/asset_list.html:695 msgid "This will delete the selected assets !!!" msgstr "删除选择资产" -#: assets/templates/assets/asset_list.html:690 +#: assets/templates/assets/asset_list.html:698 #: assets/templates/assets/system_user_list.html:166 #: settings/templates/settings/terminal_setting.html:163 #: users/templates/users/user_detail.html:386 @@ -2171,22 +1805,22 @@ msgstr "删除选择资产" #: users/templates/users/user_detail.html:480 #: users/templates/users/user_group_create_update.html:31 #: users/templates/users/user_group_list.html:112 -#: users/templates/users/user_list.html:258 +#: users/templates/users/user_list.html:267 #: xpack/plugins/interface/templates/interface/interface.html:101 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:32 msgid "Cancel" msgstr "取消" -#: assets/templates/assets/asset_list.html:696 +#: assets/templates/assets/asset_list.html:711 msgid "Asset Deleted." msgstr "已被删除" -#: assets/templates/assets/asset_list.html:697 -#: assets/templates/assets/asset_list.html:702 +#: assets/templates/assets/asset_list.html:712 +#: assets/templates/assets/asset_list.html:716 msgid "Asset Delete" msgstr "删除" -#: assets/templates/assets/asset_list.html:701 +#: assets/templates/assets/asset_list.html:715 msgid "Asset Deleting failed." msgstr "删除失败" @@ -2399,23 +2033,23 @@ msgstr "管理用户列表" msgid "Admin user detail" msgstr "管理用户详情" -#: assets/views/asset.py:79 templates/_nav_user.html:4 +#: assets/views/asset.py:81 templates/_nav_user.html:4 msgid "My assets" msgstr "我的资产" -#: assets/views/asset.py:119 +#: assets/views/asset.py:121 msgid "Bulk update asset success" msgstr "批量更新资产成功" -#: assets/views/asset.py:146 +#: assets/views/asset.py:148 msgid "Bulk update asset" msgstr "批量更新资产" -#: assets/views/asset.py:163 +#: assets/views/asset.py:165 msgid "Update asset" msgstr "更新资产" -#: assets/views/asset.py:304 +#: assets/views/asset.py:306 msgid "already exists" msgstr "已经存在" @@ -2605,8 +2239,7 @@ msgstr "登录日期" #: ops/templates/ops/adhoc_history.html:52 #: ops/templates/ops/adhoc_history_detail.html:61 #: ops/templates/ops/command_execution_list.html:66 -#: ops/templates/ops/task_history.html:58 perms/models/asset_permission.py:60 -#: perms/models/remote_app_permission.py:38 +#: ops/templates/ops/task_history.html:58 perms/models/base.py:39 #: perms/templates/perms/asset_permission_detail.html:86 #: perms/templates/perms/remote_app_permission_detail.html:78 #: terminal/models.py:165 terminal/templates/terminal/session_list.html:78 @@ -2905,8 +2538,8 @@ msgstr "欢迎回来,请输入用户名和密码登录" msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: authentication/views/login.py:172 users/views/user.py:541 -#: users/views/user.py:566 +#: authentication/views/login.py:172 users/views/user.py:540 +#: users/views/user.py:565 msgid "MFA code invalid, or ntp sync server time" msgstr "MFA验证码不正确,或者服务器端时间不对" @@ -3344,8 +2977,7 @@ msgid "Download file" msgstr "下载文件" #: perms/forms/asset_permission.py:43 perms/forms/remote_app_permission.py:34 -#: perms/models/asset_permission.py:54 perms/models/asset_permission.py:111 -#: perms/models/remote_app_permission.py:35 +#: perms/models/asset_permission.py:58 perms/models/base.py:37 #: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:75 #: perms/templates/perms/asset_permission_list.html:122 @@ -3372,8 +3004,12 @@ msgstr "用户和用户组至少选一个" msgid "Asset or group at least one required" msgstr "资产和节点至少选一个" -#: perms/models/asset_permission.py:61 perms/models/asset_permission.py:114 -#: perms/models/remote_app_permission.py:39 +#: perms/models/asset_permission.py:44 perms/models/asset_permission.py:70 +#: templates/_nav.html:42 +msgid "Asset permission" +msgstr "资产授权" + +#: perms/models/asset_permission.py:61 perms/models/base.py:40 #: perms/templates/perms/asset_permission_detail.html:90 #: perms/templates/perms/remote_app_permission_detail.html:82 #: users/models/user.py:99 users/templates/users/user_detail.html:107 @@ -3381,12 +3017,7 @@ msgstr "资产和节点至少选一个" msgid "Date expired" msgstr "失效日期" -#: perms/models/asset_permission.py:70 perms/models/asset_permission.py:123 -#: templates/_nav.html:42 -msgid "Asset permission" -msgstr "资产授权" - -#: perms/models/remote_app_permission.py:48 +#: perms/models/remote_app_permission.py:19 msgid "RemoteApp permission" msgstr "远程应用授权" @@ -3478,9 +3109,9 @@ msgstr "创建授权规则" #: perms/templates/perms/asset_permission_list.html:59 #: perms/templates/perms/asset_permission_list.html:73 -#: users/templates/users/user_list.html:40 xpack/plugins/cloud/models.py:53 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:60 #: perms/templates/perms/remote_app_permission_list.html:18 +#: users/templates/users/user_list.html:40 xpack/plugins/cloud/models.py:53 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:58 #: xpack/plugins/cloud/templates/cloud/account_list.html:14 msgid "Validity" msgstr "有效" @@ -4093,7 +3724,7 @@ msgstr "商业支持" #: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:57 -#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:377 +#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:376 msgid "Profile" msgstr "个人信息" @@ -4187,13 +3818,13 @@ msgstr "" #: templates/_nav.html:10 users/views/group.py:27 users/views/group.py:43 #: users/views/group.py:59 users/views/group.py:75 users/views/group.py:91 -#: users/views/login.py:154 users/views/user.py:68 users/views/user.py:83 -#: users/views/user.py:122 users/views/user.py:203 users/views/user.py:364 -#: users/views/user.py:414 users/views/user.py:454 +#: users/views/login.py:154 users/views/user.py:70 users/views/user.py:85 +#: users/views/user.py:124 users/views/user.py:202 users/views/user.py:363 +#: users/views/user.py:413 users/views/user.py:453 msgid "Users" msgstr "用户管理" -#: templates/_nav.html:13 users/views/user.py:69 +#: templates/_nav.html:13 users/views/user.py:71 msgid "User list" msgstr "用户列表" @@ -4639,11 +4270,11 @@ msgid "" "You should use your ssh client tools connect terminal: {}

    {}" msgstr "你可以使用ssh客户端工具连接终端" -#: users/api/user.py:77 users/api/user.py:88 users/api/user.py:114 +#: users/api/user.py:75 users/api/user.py:86 users/api/user.py:112 msgid "You do not have permission." msgstr "你没有权限" -#: users/api/user.py:218 +#: users/api/user.py:216 msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" @@ -4675,7 +4306,7 @@ msgstr "添加到用户组" msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:89 users/forms.py:219 users/serializers/v1.py:53 +#: users/forms.py:89 users/forms.py:219 users/serializers/v1.py:47 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" @@ -4782,7 +4413,7 @@ msgid "Date password last updated" msgstr "最后更新密码日期" #: users/models/user.py:136 users/templates/users/user_update.html:22 -#: users/views/login.py:47 users/views/login.py:108 users/views/user.py:427 +#: users/views/login.py:47 users/views/login.py:108 users/views/user.py:426 msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" @@ -4790,31 +4421,31 @@ msgstr "用户认证源来自 {}, 请去相应系统修改密码" msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/serializers/v1.py:17 +#: users/serializers/v1.py:28 msgid "Groups name" msgstr "用户组名" -#: users/serializers/v1.py:20 +#: users/serializers/v1.py:29 msgid "Source name" msgstr "用户来源名" -#: users/serializers/v1.py:23 +#: users/serializers/v1.py:30 msgid "Is first login" msgstr "首次登录" -#: users/serializers/v1.py:25 +#: users/serializers/v1.py:31 msgid "Role name" msgstr "角色名" -#: users/serializers/v1.py:26 +#: users/serializers/v1.py:32 msgid "Is valid" msgstr "账户是否有效" -#: users/serializers/v1.py:27 +#: users/serializers/v1.py:33 msgid "Is expired" msgstr " 是否过期" -#: users/serializers/v1.py:28 +#: users/serializers/v1.py:34 msgid "Avatar url" msgstr "头像路径" @@ -4859,7 +4490,8 @@ msgid "Import user groups" msgstr "导入用户组" #: users/templates/users/_user_groups_update_modal.html:4 -msgid "Update user groups" +#: users/views/group.py:60 +msgid "Update user group" msgstr "更新用户组" #: users/templates/users/_user_import_modal.html:4 @@ -4867,7 +4499,7 @@ msgid "Import users" msgstr "导入用户" #: users/templates/users/_user_update_modal.html:4 -#: users/templates/users/user_update.html:4 users/views/user.py:123 +#: users/templates/users/user_update.html:4 users/views/user.py:125 msgid "Update user" msgstr "更新用户" @@ -4997,7 +4629,7 @@ msgid "Very strong" msgstr "很强" #: users/templates/users/user_create.html:4 -#: users/templates/users/user_list.html:28 users/views/user.py:83 +#: users/templates/users/user_list.html:28 users/views/user.py:85 msgid "Create user" msgstr "创建用户" @@ -5006,7 +4638,7 @@ msgid "Reset link will be generated and sent to the user. " msgstr "生成重置密码连接,通过邮件发送给用户" #: users/templates/users/user_detail.html:19 -#: users/templates/users/user_granted_asset.html:18 users/views/user.py:204 +#: users/templates/users/user_granted_asset.html:18 users/views/user.py:203 msgid "User detail" msgstr "用户详情" @@ -5139,28 +4771,28 @@ msgstr "用户组删除" msgid "UserGroup Deleting failed." msgstr "用户组删除失败" -#: users/templates/users/user_list.html:255 +#: users/templates/users/user_list.html:264 msgid "This will delete the selected users !!!" msgstr "删除选中用户 !!!" -#: users/templates/users/user_list.html:264 +#: users/templates/users/user_list.html:280 msgid "User Deleted." msgstr "已被删除" -#: users/templates/users/user_list.html:265 -#: users/templates/users/user_list.html:270 +#: users/templates/users/user_list.html:281 +#: users/templates/users/user_list.html:285 msgid "User Delete" msgstr "删除" -#: users/templates/users/user_list.html:269 +#: users/templates/users/user_list.html:284 msgid "User Deleting failed." msgstr "用户删除失败" -#: users/templates/users/user_list.html:305 +#: users/templates/users/user_list.html:337 msgid "User is expired" msgstr "用户已失效" -#: users/templates/users/user_list.html:308 +#: users/templates/users/user_list.html:340 msgid "User is inactive" msgstr "用户已禁用" @@ -5209,8 +4841,8 @@ msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接 msgid "Administrator Settings force MFA login" msgstr "管理员设置强制使用MFA登录" -#: users/templates/users/user_profile.html:120 users/views/user.py:240 -#: users/views/user.py:294 +#: users/templates/users/user_profile.html:120 users/views/user.py:239 +#: users/views/user.py:293 msgid "User groups" msgstr "用户组" @@ -5451,10 +5083,6 @@ msgstr "密码或密钥不合法" msgid "User group list" msgstr "用户组列表" -#: users/views/group.py:60 -msgid "Update user group" -msgstr "更新用户组" - #: users/views/group.py:92 msgid "User group granted asset" msgstr "用户组授权资产" @@ -5488,7 +5116,7 @@ msgstr "Token错误或失效" msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:115 users/views/user.py:137 users/views/user.py:437 +#: users/views/login.py:115 users/views/user.py:139 users/views/user.py:436 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" @@ -5496,51 +5124,51 @@ msgstr "* 您的密码不符合要求" msgid "First login" msgstr "首次登录" -#: users/views/user.py:154 +#: users/views/user.py:156 msgid "Bulk update user success" msgstr "批量更新用户成功" -#: users/views/user.py:184 +#: users/views/user.py:183 msgid "Bulk update user" msgstr "批量更新用户" -#: users/views/user.py:269 +#: users/views/user.py:268 msgid "Invalid file." msgstr "文件不合法" -#: users/views/user.py:365 +#: users/views/user.py:364 msgid "User granted assets" msgstr "用户授权资产" -#: users/views/user.py:396 +#: users/views/user.py:395 msgid "Profile setting" msgstr "个人信息设置" -#: users/views/user.py:415 +#: users/views/user.py:414 msgid "Password update" msgstr "密码更新" -#: users/views/user.py:455 +#: users/views/user.py:454 msgid "Public key update" msgstr "密钥更新" -#: users/views/user.py:496 +#: users/views/user.py:495 msgid "Password invalid" msgstr "用户名或密码无效" -#: users/views/user.py:596 +#: users/views/user.py:595 msgid "MFA enable success" msgstr "MFA 绑定成功" -#: users/views/user.py:597 +#: users/views/user.py:596 msgid "MFA enable success, return login page" msgstr "MFA 绑定成功,返回到登录页面" -#: users/views/user.py:599 +#: users/views/user.py:598 msgid "MFA disable success" msgstr "MFA 解绑成功" -#: users/views/user.py:600 +#: users/views/user.py:599 msgid "MFA disable success, return login page" msgstr "MFA 解绑成功,返回登录页面" @@ -6186,6 +5814,8 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" +#~ msgid "Update user groups" +#~ msgstr "更新用户组" #~ msgid "Template" #~ msgstr "模板" @@ -6224,7 +5854,6 @@ msgstr "更新组织" #~ msgid "Restore default successfully!" #~ msgstr "恢复默认成功!" - #~ msgid "Beijing Duizhan Tech, Inc." #~ msgstr "北京堆栈科技有限公司" From f863ed0f4f8fb2114c2579d57f8944c90d57c29b Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 22 May 2019 10:25:53 +0800 Subject: [PATCH 15/36] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E8=8E=B7?= =?UTF-8?q?=E5=8F=96common=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/conf.py | 1 + apps/jumpserver/settings.py | 7 +------ apps/terminal/api/session.py | 4 ++-- apps/terminal/templatetags/terminal_tags.py | 2 +- apps/terminal/views/session.py | 2 +- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 35254d1ca..753ffb3d2 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -343,6 +343,7 @@ defaults = { 'TERMINAL_SESSION_KEEP_DURATION': 9999, 'TERMINAL_HOST_KEY': '', 'TERMINAL_TELNET_REGEX': '', + 'TERMINAL_COMMAND_STORAGE': {}, 'SECURITY_MFA_AUTH': False, 'SECURITY_LOGIN_LIMIT_COUNT': 7, 'SECURITY_LOGIN_LIMIT_TIME': 30, diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index bdc71ae3c..57a3c0148 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -519,12 +519,7 @@ DEFAULT_TERMINAL_COMMAND_STORAGE = { }, } -TERMINAL_COMMAND_STORAGE = { - # 'ali-es': { - # 'TYPE': 'elasticsearch', - # 'HOSTS': ['http://elastic:changeme@localhost:9200'], - # }, -} +TERMINAL_COMMAND_STORAGE = CONFIG.TERMINAL_COMMAND_STORAGE DEFAULT_TERMINAL_REPLAY_STORAGE = { "default": { diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index 39d18b159..f52d7b2b7 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -67,7 +67,6 @@ class CommandViewSet(viewsets.ViewSet): """ command_store = get_command_storage() - multi_command_storage = get_multi_command_storage() serializer_class = SessionCommandSerializer permission_classes = (IsOrgAdminOrAppUser,) @@ -88,7 +87,8 @@ class CommandViewSet(viewsets.ViewSet): return Response({"msg": msg}, status=401) def list(self, request, *args, **kwargs): - queryset = self.multi_command_storage.filter() + multi_command_storage = get_multi_command_storage() + queryset = multi_command_storage.filter() serializer = self.serializer_class(queryset, many=True) return Response(serializer.data) diff --git a/apps/terminal/templatetags/terminal_tags.py b/apps/terminal/templatetags/terminal_tags.py index e8ba23186..c0844eb31 100644 --- a/apps/terminal/templatetags/terminal_tags.py +++ b/apps/terminal/templatetags/terminal_tags.py @@ -5,10 +5,10 @@ from django import template from ..backends import get_multi_command_storage register = template.Library() -command_store = get_multi_command_storage() @register.filter def get_session_command_amount(session_id): + command_store = get_multi_command_storage() return command_store.count(session=session_id) diff --git a/apps/terminal/views/session.py b/apps/terminal/views/session.py index bc49e1b65..706d6ee8a 100644 --- a/apps/terminal/views/session.py +++ b/apps/terminal/views/session.py @@ -19,7 +19,6 @@ __all__ = [ 'SessionDetailView', ] -command_store = get_multi_command_storage() class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): @@ -108,6 +107,7 @@ class SessionDetailView(SingleObjectMixin, AdminUserRequiredMixin, ListView): return super().get(request, *args, **kwargs) def get_queryset(self): + command_store = get_multi_command_storage() return command_store.filter(session=self.object.id) def get_context_data(self, **kwargs): From b18ca8c94f5155f101dd47fcb1576fe9f5d905bf Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Wed, 22 May 2019 12:35:14 +0800 Subject: [PATCH 16/36] =?UTF-8?q?[Update]=20=E6=A0=A1=E9=AA=8C=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=AF=B9RemoteApp=E6=9D=83=E9=99=90API=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6=20(#2715)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/user_permission.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index 7c934340e..377bd735c 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -512,6 +512,7 @@ class UserGrantedRemoteAppsAsTreeApi(ListAPIView): class ValidateUserRemoteAppPermissionApi(APIView): + permission_classes = (IsOrgAdminOrAppUser,) def get(self, request, *args, **kwargs): user_id = request.query_params.get('user_id', '') From 4cd3dd367099a40d8f18ae8e17da96ff6644034d Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Wed, 22 May 2019 18:56:34 +0800 Subject: [PATCH 17/36] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=96=B0RemoteApp=20?= =?UTF-8?q?(#2720)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] RemoteAppForm添加RemoteApp各类型参数保存逻辑 * [Update] RemoteApp添加默认应用路径 --- apps/applications/forms/remote_app.py | 21 ++++++++ .../remote_app_create_update.html | 48 +++++++++++++++---- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/apps/applications/forms/remote_app.py b/apps/applications/forms/remote_app.py index 88f1912e0..b5317fa1c 100644 --- a/apps/applications/forms/remote_app.py +++ b/apps/applications/forms/remote_app.py @@ -8,6 +8,7 @@ from orgs.mixins import OrgModelForm from assets.models import Asset, SystemUser from ..models import RemoteApp +from .. import const __all__ = [ @@ -109,3 +110,23 @@ class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm): }) } + def _clean_params(self): + app_type = self.data.get('type') + fields = const.REMOTE_APP_TYPE_MAP_FIELDS.get(app_type, []) + params = {} + for field in fields: + name = field['name'] + value = self.cleaned_data[name] + params.update({name: value}) + return params + + def _save_params(self, instance): + params = self._clean_params() + instance.params = params + instance.save() + return instance + + def save(self, commit=True): + instance = super().save(commit=commit) + instance = self._save_params(instance) + return instance diff --git a/apps/applications/templates/applications/remote_app_create_update.html b/apps/applications/templates/applications/remote_app_create_update.html index e64b8994c..4a616fc49 100644 --- a/apps/applications/templates/applications/remote_app_create_update.html +++ b/apps/applications/templates/applications/remote_app_create_update.html @@ -64,28 +64,60 @@ {% block custom_foot_js %} {% endblock %} \ No newline at end of file From 2df1dd2bb1aa37527351212fdab861d1d5ffa643 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Thu, 23 May 2019 18:52:19 +0800 Subject: [PATCH 18/36] =?UTF-8?q?[Bugfix]=20LDAP=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=88=97=E8=A1=A8=E4=B8=AD=E5=A6=82=E6=9E=9C?= =?UTF-8?q?API=E8=8E=B7=E5=8F=96=E5=88=B0=E7=9A=84=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=90=8D=E4=B8=AD=E6=9C=89=E7=A9=BA=E6=A0=BC=EF=BC=8C=E5=9C=A8?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E4=B8=BAid=E7=9A=84=E6=97=B6=E5=80=99?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E4=BC=9A=E5=8F=96=E7=AC=AC=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E7=A9=BA=E6=A0=BC=E5=89=8D=E7=9A=84=E5=AD=97=E7=AC=A6=E4=B8=B2?= =?UTF-8?q?=E4=BD=9C=E4=B8=BA=E7=94=A8=E6=88=B7=E5=90=8D=EF=BC=8C=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E4=B8=8D=E4=BC=9A=E5=AF=BC=E5=85=A5=E7=94=A8=E6=88=B7?= =?UTF-8?q?=20(#2726)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/templates/settings/_ldap_list_users_modal.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/settings/templates/settings/_ldap_list_users_modal.html b/apps/settings/templates/settings/_ldap_list_users_modal.html index 2009e9b58..9609d80ce 100644 --- a/apps/settings/templates/settings/_ldap_list_users_modal.html +++ b/apps/settings/templates/settings/_ldap_list_users_modal.html @@ -58,6 +58,9 @@ function initLdapUsersTable() { ele: $('#ldap_list_users_table'), ajax_url: '{% url "api-settings:ldap-user-list" %}', columnDefs: [ + {targets: 0, createdCell: function (td, cellData, rowData) { + $(td).html("".replace("ID_USERNAME", cellData)) + }}, {targets: 4, createdCell: function (td, cellData, rowData) { if(cellData){ $(td).html('') From 45cb39e9719691b7f4b15c0144ea3072bbc82211 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Thu, 23 May 2019 18:52:39 +0800 Subject: [PATCH 19/36] =?UTF-8?q?[Update]=20RemoteApp=E8=AE=BE=E7=BD=AEtyp?= =?UTF-8?q?e=20vSphere=20Client=E7=9A=84=E9=BB=98=E8=AE=A4=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=20(#2725)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/const.py | 2 +- .../templates/applications/remote_app_create_update.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/applications/const.py b/apps/applications/const.py index b64b1a14b..a5b6da895 100644 --- a/apps/applications/const.py +++ b/apps/applications/const.py @@ -28,7 +28,7 @@ REMOTE_APP_TYPE_CHOICES = ( ( _('Virtualization tools'), ( - (REMOTE_APP_TYPE_VMWARE_CLIENT, 'VMware Client'), + (REMOTE_APP_TYPE_VMWARE_CLIENT, 'vSphere Client'), ) ), (REMOTE_APP_TYPE_CUSTOM, _('Custom')), diff --git a/apps/applications/templates/applications/remote_app_create_update.html b/apps/applications/templates/applications/remote_app_create_update.html index 4a616fc49..ecd7254a1 100644 --- a/apps/applications/templates/applications/remote_app_create_update.html +++ b/apps/applications/templates/applications/remote_app_create_update.html @@ -80,7 +80,7 @@ var app_type_map_default_fields_value = { 'app_path': 'C:\\Program Files\\MySQL\\MySQL Workbench 8.0 CE\\MySQLWorkbench.exe' }, 'vmware_client': { - 'app_path': '' + 'app_path': 'C:\\Program Files (x86)\\VMware\\Infrastructure\\Virtual Infrastructure Client\\Launcher\\VpxClient.exe' }, 'custom': {'app_path': ''} }; From b79107380226f14324918794ac8832f001e6e766 Mon Sep 17 00:00:00 2001 From: xiaomao Date: Thu, 23 May 2019 18:55:28 +0800 Subject: [PATCH 20/36] =?UTF-8?q?[Update]=20=E8=A7=A3=E5=86=B3ldap?= =?UTF-8?q?=E6=98=A0=E5=B0=84is=5Factive=E7=AD=89=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E4=B8=BAbool=E5=80=BC=E7=9A=84=E9=97=AE=E9=A2=98=20(#2716)=20(?= =?UTF-8?q?#2721)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/ldap.py | 14 ++++++++++++-- apps/settings/utils.py | 9 +++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/authentication/backends/ldap.py b/apps/authentication/backends/ldap.py index b35903420..2b202847b 100644 --- a/apps/authentication/backends/ldap.py +++ b/apps/authentication/backends/ldap.py @@ -86,7 +86,18 @@ class LDAPUser(_LDAPUser): return user_dn def _populate_user_from_attributes(self): - super()._populate_user_from_attributes() + for field, attr in self.settings.USER_ATTR_MAP.items(): + try: + value = self.attrs[attr][0] + except LookupError: + logger.warning("{} does not have a value for the attribute {}".format(self.dn, attr)) + else: + if not hasattr(self._user, field): + continue + if isinstance(getattr(self._user, field), bool): + value = bool(int(value == 'true')) + setattr(self._user, field, value) + if not hasattr(self._user, 'email') or '@' not in self._user.email: if '@' not in self._user.username: email = '{}@{}'.format(self._user.username, settings.EMAIL_SUFFIX) @@ -95,4 +106,3 @@ class LDAPUser(_LDAPUser): setattr(self._user, 'email', email) - diff --git a/apps/settings/utils.py b/apps/settings/utils.py index 6893974f7..56a7812e8 100644 --- a/apps/settings/utils.py +++ b/apps/settings/utils.py @@ -60,6 +60,8 @@ class LDAPUtil: for field, value in user_item.items(): if not hasattr(user, field): continue + if isinstance(getattr(user, field), bool): + value = bool(int(value == 'true')) setattr(user, field, value) user.save() @@ -82,6 +84,13 @@ class LDAPUtil: @staticmethod def create_user(user_item): + user = User() + for field, value in user_item.items(): + if not hasattr(user, field): + continue + if isinstance(getattr(user, field), bool): + value = bool(int(value == 'true')) + user_item[field] = value user_item['source'] = User.SOURCE_LDAP try: User.objects.create(**user_item) From 217bb8172255de4acf9805e4cf202806730c223a Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Fri, 24 May 2019 11:11:50 +0800 Subject: [PATCH 21/36] =?UTF-8?q?[Update]=20=E4=BC=98=E5=8C=96LDAPUtil?= =?UTF-8?q?=E9=80=BB=E8=BE=91=20(#2728)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/ldap.py | 15 +- apps/settings/api.py | 8 +- .../templates/settings/ldap_setting.html | 10 +- apps/settings/utils.py | 172 ++++++++---------- apps/users/utils.py | 10 + 5 files changed, 105 insertions(+), 110 deletions(-) diff --git a/apps/authentication/backends/ldap.py b/apps/authentication/backends/ldap.py index 2b202847b..3e58e08fa 100644 --- a/apps/authentication/backends/ldap.py +++ b/apps/authentication/backends/ldap.py @@ -7,6 +7,8 @@ from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django_auth_ldap.backend import _LDAPUser, LDAPBackend from django_auth_ldap.config import _LDAPConfig, LDAPSearch, LDAPSearchUnion +from users.utils import construct_user_email + logger = _LDAPConfig.get_logger() @@ -95,14 +97,9 @@ class LDAPUser(_LDAPUser): if not hasattr(self._user, field): continue if isinstance(getattr(self._user, field), bool): - value = bool(int(value == 'true')) + value = value.lower() in ['true', '1'] setattr(self._user, field, value) - if not hasattr(self._user, 'email') or '@' not in self._user.email: - if '@' not in self._user.username: - email = '{}@{}'.format(self._user.username, settings.EMAIL_SUFFIX) - else: - email = self._user.username - setattr(self._user, 'email', email) - - + email = getattr(self._user, 'email', '') + email = construct_user_email(email, self._user.username) + setattr(self._user, 'email', email) diff --git a/apps/settings/api.py b/apps/settings/api.py index a65df113d..f2b3eb558 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -79,7 +79,7 @@ class LDAPTestingAPI(APIView): util = self.get_ldap_util(serializer) try: - users = util.get_search_user_items() + users = util.search_user_items() except Exception as e: return Response({"error": str(e)}, status=401) @@ -95,7 +95,7 @@ class LDAPUserListApi(APIView): def get(self, request): util = LDAPUtil() try: - users = util.get_search_user_items() + users = util.search_user_items() except Exception as e: users = [] logger.error(e, exc_info=True) @@ -108,11 +108,11 @@ class LDAPUserSyncAPI(APIView): permission_classes = (IsOrgAdmin,) def post(self, request): - user_names = request.data.get('user_names', '') + username_list = request.data.get('username_list', []) util = LDAPUtil() try: - result = util.sync_users(username_set=user_names) + result = util.sync_users(username_list) except Exception as e: logger.error(e, exc_info=True) return Response({'error': str(e)}, status=401) diff --git a/apps/settings/templates/settings/ldap_setting.html b/apps/settings/templates/settings/ldap_setting.html index 63d56cea0..7eb8167ef 100644 --- a/apps/settings/templates/settings/ldap_setting.html +++ b/apps/settings/templates/settings/ldap_setting.html @@ -107,13 +107,13 @@ $(document).ready(function () { }); }) .on("click","#btn_ldap_modal_confirm",function () { - var user_names=[]; + var username_list=[]; $("tbody input[type='checkbox']:checked").each(function () { - user_names.push($(this).attr('id')); + username_list.push($(this).attr('id')); }); - if (user_names.length === 0){ - var msg = "{% trans 'User is not currently selected, please check the user you want to import'%}" + if (username_list.length === 0){ + var msg = "{% trans 'User is not currently selected, please check the user you want to import'%}"; toastr.error(msg); return } @@ -129,7 +129,7 @@ $(document).ready(function () { } APIUpdateAttr({ url: the_url, - body: JSON.stringify({'user_names':user_names}), + body: JSON.stringify({'username_list':username_list}), method: "POST", flash_message: false, success: success, diff --git a/apps/settings/utils.py b/apps/settings/utils.py index 56a7812e8..563f5fc3a 100644 --- a/apps/settings/utils.py +++ b/apps/settings/utils.py @@ -5,7 +5,9 @@ from ldap3 import Server, Connection from django.utils.translation import ugettext_lazy as _ from users.models import User +from users.utils import construct_user_email from common.utils import get_logger + from .models import settings @@ -17,11 +19,11 @@ class LDAPOUGroupException(Exception): class LDAPUtil: + _conn = None def __init__(self, use_settings_config=True, server_uri=None, bind_dn=None, password=None, use_ssl=None, search_ougroup=None, search_filter=None, attr_map=None, auth_ldap=None): - # config if use_settings_config: self._load_config_from_settings() @@ -45,6 +47,15 @@ class LDAPUtil: self.attr_map = settings.AUTH_LDAP_USER_ATTR_MAP self.auth_ldap = settings.AUTH_LDAP + @property + def connection(self): + if self._conn is None: + server = Server(self.server_uri, use_ssl=self.use_ssl) + conn = Connection(server, self.bind_dn, self.password) + conn.bind() + self._conn = conn + return self._conn + @staticmethod def get_user_by_username(username): try: @@ -55,79 +66,6 @@ class LDAPUtil: else: return user - @staticmethod - def _update_user(user, user_item): - for field, value in user_item.items(): - if not hasattr(user, field): - continue - if isinstance(getattr(user, field), bool): - value = bool(int(value == 'true')) - setattr(user, field, value) - user.save() - - def update_user(self, user_item): - user = self.get_user_by_username(user_item['username']) - if not user: - msg = _('User does not exist') - return False, msg - if user.source != User.SOURCE_LDAP: - msg = _('The user source is not LDAP') - return False, msg - - try: - self._update_user(user, user_item) - except Exception as e: - logger.error(e, exc_info=True) - return False, str(e) - else: - return True, None - - @staticmethod - def create_user(user_item): - user = User() - for field, value in user_item.items(): - if not hasattr(user, field): - continue - if isinstance(getattr(user, field), bool): - value = bool(int(value == 'true')) - user_item[field] = value - user_item['source'] = User.SOURCE_LDAP - try: - User.objects.create(**user_item) - except Exception as e: - logger.error(e, exc_info=True) - return False, str(e) - else: - return True, None - - @staticmethod - def get_or_construct_email(user_item): - if not user_item.get('email', None): - if '@' in user_item['username']: - email = user_item['username'] - else: - email = '{}@{}'.format( - user_item['username'], settings.EMAIL_SUFFIX) - else: - email = user_item['email'] - return email - - def create_or_update_users(self, user_items, force_update=True): - succeed = failed = 0 - for user_item in user_items: - user_item['email'] = self.get_or_construct_email(user_item) - exist = user_item.pop('existing', None) - if exist: - ok, error = self.update_user(user_item) - else: - ok, error = self.create_user(user_item) - if not ok: - failed += 1 - else: - succeed += 1 - result = {'total': len(user_items), 'succeed': succeed, 'failed': failed} - return result - def _ldap_entry_to_user_item(self, entry): user_item = {} for attr, mapping in self.attr_map.items(): @@ -136,36 +74,86 @@ class LDAPUtil: user_item[attr] = getattr(entry, mapping).value or '' return user_item - def get_connection(self): - server = Server(self.server_uri, use_ssl=self.use_ssl) - conn = Connection(server, self.bind_dn, self.password) - conn.bind() - return conn - - def get_search_user_items(self): - conn = self.get_connection() + def search_user_items(self): user_items = [] - search_ougroup = str(self.search_ougroup).split("|") - for search_ou in search_ougroup: - ok = conn.search( + for search_ou in str(self.search_ougroup).split("|"): + ok = self.connection.search( search_ou, self.search_filter % ({"user": "*"}), attributes=list(self.attr_map.values()) ) if not ok: error = _("Search no entry matched in ou {}".format(search_ou)) raise LDAPOUGroupException(error) - - for entry in conn.entries: + for entry in self.connection.entries: user_item = self._ldap_entry_to_user_item(entry) user = self.get_user_by_username(user_item['username']) user_item['existing'] = bool(user) user_items.append(user_item) - return user_items - def sync_users(self, username_set): - user_items = self.get_search_user_items() - if username_set: - user_items = [u for u in user_items if u['username'] in username_set] + def search_filter_user_items(self, username_list): + user_items = self.search_user_items() + if username_list: + user_items = [u for u in user_items if u['username'] in username_list] + return user_items + + @staticmethod + def save_user(user, user_item): + for field, value in user_item.items(): + if not hasattr(user, field): + continue + if isinstance(getattr(user, field), bool): + value = value.lower() in ['true', 1] + setattr(user, field, value) + user.save() + + def update_user(self, user_item): + user = self.get_user_by_username(user_item['username']) + if user.source != User.SOURCE_LDAP: + msg = _('The user source is not LDAP') + return False, msg + try: + self.save_user(user, user_item) + except Exception as e: + logger.error(e, exc_info=True) + return False, str(e) + else: + return True, None + + def create_user(self, user_item): + user = User(source=User.SOURCE_LDAP) + try: + self.save_user(user, user_item) + except Exception as e: + logger.error(e, exc_info=True) + return False, str(e) + else: + return True, None + + @staticmethod + def construct_user_email(user_item): + username = user_item['username'] + email = user_item.get('email', '') + email = construct_user_email(username, email) + return email + + def create_or_update_users(self, user_items, force_update=True): + succeed = failed = 0 + for user_item in user_items: + exist = user_item.pop('existing', False) + user_item['email'] = self.construct_user_email(user_item) + if not exist: + ok, error = self.create_user(user_item) + else: + ok, error = self.update_user(user_item) + if not ok: + failed += 1 + else: + succeed += 1 + result = {'total': len(user_items), 'succeed': succeed, 'failed': failed} + return result + + def sync_users(self, username_list): + user_items = self.search_filter_user_items(username_list) result = self.create_or_update_users(user_items) return result diff --git a/apps/users/utils.py b/apps/users/utils.py index e8f361ffa..8e461bf54 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -313,3 +313,13 @@ def is_need_unblock(key_block): if not cache.get(key_block): return False return True + + +def construct_user_email(username, email): + if '@' not in email: + if '@' in username: + email = username + else: + email = '{}@{}'.format(username, settings.EMAIL_SUFFIX) + return email + From b7eac837f75bf75c947b763a110c585b007a72a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AB=E5=8D=83=E6=B5=81?= <40739051+jym503558564@users.noreply.github.com> Date: Fri, 24 May 2019 18:12:58 +0800 Subject: [PATCH 22/36] =?UTF-8?q?[Update]=20=E5=88=9B=E5=BB=BA=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=87=AA=E5=AE=9A=E4=B9=89=E9=82=AE=E4=BB=B6=E5=86=85?= =?UTF-8?q?=E5=AE=B9=20(#2703)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 增加创建用户自定义邮件内容功能 * [Update] 优化自定义创建用户邮件功能 * [Update] 修改自定义邮件内容的小图标icon * [Update] 发送邮件引入样式,避免不同浏览器不显示邮件格式问题 * [Update] 优化创建用户自定义邮件内容 * [Update] 修改翻译 * [Update] 优化邮件内容的变量名 * [Update]优化自定义邮件内容 * [Update] 修改小问题 --- apps/jumpserver/settings.py | 6 + apps/locale/zh/LC_MESSAGES/django.mo | Bin 75055 -> 76566 bytes apps/locale/zh/LC_MESSAGES/django.po | 422 +++++++++++------- apps/settings/forms.py | 23 + .../templates/settings/basic_setting.html | 3 + .../settings/email_content_setting.html | 72 +++ .../templates/settings/email_setting.html | 3 + .../templates/settings/ldap_setting.html | 3 + .../templates/settings/security_setting.html | 3 + .../templates/settings/terminal_setting.html | 3 + apps/settings/urls/view_urls.py | 1 + apps/settings/views.py | 28 +- apps/users/utils.py | 67 ++- 13 files changed, 450 insertions(+), 184 deletions(-) create mode 100644 apps/settings/templates/settings/email_content_setting.html diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index bdc71ae3c..97859ec6d 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -357,6 +357,12 @@ EMAIL_USE_SSL = False EMAIL_USE_TLS = False EMAIL_SUBJECT_PREFIX = '[JMS] ' +#Email custom content +EMAIL_CUSTOM_USER_CREATED_SUBJECT = '' +EMAIL_CUSTOM_USER_CREATED_HONORIFIC = '' +EMAIL_CUSTOM_USER_CREATED_BODY = '' +EMAIL_CUSTOM_USER_CREATED_SIGNATURE = '' + REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 58ec179b83bd35b8c88e98ab8aefe011277ccf4c..cc3a0249dbb06b08f685868c4acd68f3b30d198b 100644 GIT binary patch delta 24137 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 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 823a3b07c..875e91c25 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-05-21 21:09+0800\n" +"POT-Creation-Date: 2019-05-24 12:02+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -33,45 +33,45 @@ msgstr "虚拟化工具" msgid "Custom" msgstr "自定义" -#: applications/forms/remote_app.py:20 +#: applications/forms/remote_app.py:21 msgid "Target URL" msgstr "目标URL" -#: applications/forms/remote_app.py:23 applications/forms/remote_app.py:52 -#: applications/forms/remote_app.py:68 +#: applications/forms/remote_app.py:24 applications/forms/remote_app.py:53 +#: applications/forms/remote_app.py:69 msgid "Login username" msgstr "登录账号" -#: applications/forms/remote_app.py:27 applications/forms/remote_app.py:56 -#: applications/forms/remote_app.py:72 +#: applications/forms/remote_app.py:28 applications/forms/remote_app.py:57 +#: applications/forms/remote_app.py:73 msgid "Login password" msgstr "登录密码" -#: applications/forms/remote_app.py:33 +#: applications/forms/remote_app.py:34 msgid "Database IP" msgstr "数据库IP" -#: applications/forms/remote_app.py:36 +#: applications/forms/remote_app.py:37 msgid "Database name" msgstr "数据库名" -#: applications/forms/remote_app.py:39 +#: applications/forms/remote_app.py:40 msgid "Database username" msgstr "数据库账号" -#: applications/forms/remote_app.py:43 +#: applications/forms/remote_app.py:44 msgid "Database password" msgstr "数据库密码" -#: applications/forms/remote_app.py:49 applications/forms/remote_app.py:65 +#: applications/forms/remote_app.py:50 applications/forms/remote_app.py:66 msgid "Target address" msgstr "目标地址" -#: applications/forms/remote_app.py:62 +#: applications/forms/remote_app.py:63 msgid "Operating parameter" msgstr "运行参数" -#: applications/forms/remote_app.py:105 applications/models/remote_app.py:23 +#: applications/forms/remote_app.py:106 applications/models/remote_app.py:23 #: applications/templates/applications/remote_app_detail.html:57 #: applications/templates/applications/remote_app_list.html:22 #: applications/templates/applications/user_remote_app_list.html:18 @@ -101,12 +101,12 @@ msgstr "运行参数" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:14 #: xpack/plugins/cloud/models.py:187 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 #: xpack/plugins/orgs/templates/orgs/org_list.html:16 msgid "Asset" msgstr "资产" -#: applications/forms/remote_app.py:108 applications/models/remote_app.py:27 +#: applications/forms/remote_app.py:109 applications/models/remote_app.py:27 #: applications/templates/applications/remote_app_detail.html:61 #: applications/templates/applications/remote_app_list.html:23 #: applications/templates/applications/user_remote_app_list.html:19 @@ -161,8 +161,8 @@ msgstr "系统用户" #: settings/templates/settings/_ldap_list_users_modal.html:38 #: settings/templates/settings/command_storage_create.html:41 #: settings/templates/settings/replay_storage_create.html:44 -#: settings/templates/settings/terminal_setting.html:80 -#: settings/templates/settings/terminal_setting.html:102 terminal/models.py:22 +#: settings/templates/settings/terminal_setting.html:83 +#: settings/templates/settings/terminal_setting.html:105 terminal/models.py:22 #: terminal/models.py:241 terminal/templates/terminal/terminal_detail.html:43 #: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 #: users/models/user.py:61 users/templates/users/_select_user_modal.html:13 @@ -177,9 +177,9 @@ msgstr "系统用户" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 #: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:50 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:52 #: xpack/plugins/cloud/templates/cloud/account_list.html:12 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:55 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12 #: xpack/plugins/orgs/templates/orgs/org_detail.html:52 #: xpack/plugins/orgs/templates/orgs/org_list.html:12 @@ -224,6 +224,8 @@ msgstr "参数" msgid "Created by" msgstr "创建者" +# msgid "Created by" +# msgstr "创建者" #: applications/models/remote_app.py:46 #: applications/templates/applications/remote_app_detail.html:73 #: assets/models/asset.py:110 assets/models/cluster.py:26 @@ -242,12 +244,14 @@ msgstr "创建者" #: users/templates/users/user_group_detail.html:63 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105 #: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:128 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:66 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:77 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:68 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:79 #: xpack/plugins/orgs/templates/orgs/org_detail.html:60 msgid "Date created" msgstr "创建日期" +# msgid "Date created" +# msgstr "创建日期" #: applications/models/remote_app.py:49 #: applications/templates/applications/remote_app_detail.html:81 #: applications/templates/applications/remote_app_list.html:24 @@ -282,9 +286,9 @@ msgstr "创建日期" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 #: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:70 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:72 #: xpack/plugins/cloud/templates/cloud/account_list.html:15 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:71 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16 #: xpack/plugins/orgs/templates/orgs/org_detail.html:64 #: xpack/plugins/orgs/templates/orgs/org_list.html:22 @@ -315,13 +319,14 @@ msgstr "远程应用" #: assets/templates/assets/label_create_update.html:18 #: perms/templates/perms/asset_permission_create_update.html:83 #: perms/templates/perms/remote_app_permission_create_update.html:83 -#: settings/templates/settings/basic_setting.html:61 +#: settings/templates/settings/basic_setting.html:64 #: settings/templates/settings/command_storage_create.html:79 -#: settings/templates/settings/email_setting.html:62 -#: settings/templates/settings/ldap_setting.html:61 +#: settings/templates/settings/email_content_setting.html:54 +#: settings/templates/settings/email_setting.html:65 +#: settings/templates/settings/ldap_setting.html:64 #: settings/templates/settings/replay_storage_create.html:152 -#: settings/templates/settings/security_setting.html:70 -#: settings/templates/settings/terminal_setting.html:68 +#: settings/templates/settings/security_setting.html:73 +#: settings/templates/settings/terminal_setting.html:71 #: terminal/templates/terminal/terminal_update.html:45 #: users/templates/users/_user.html:50 #: users/templates/users/user_bulk_update.html:23 @@ -353,13 +358,14 @@ msgstr "重置" #: audits/templates/audits/login_log_list.html:89 #: perms/templates/perms/asset_permission_create_update.html:84 #: perms/templates/perms/remote_app_permission_create_update.html:84 -#: settings/templates/settings/basic_setting.html:62 +#: settings/templates/settings/basic_setting.html:65 #: settings/templates/settings/command_storage_create.html:80 -#: settings/templates/settings/email_setting.html:63 -#: settings/templates/settings/ldap_setting.html:64 +#: settings/templates/settings/email_content_setting.html:55 +#: settings/templates/settings/email_setting.html:66 +#: settings/templates/settings/ldap_setting.html:67 #: settings/templates/settings/replay_storage_create.html:153 -#: settings/templates/settings/security_setting.html:71 -#: settings/templates/settings/terminal_setting.html:70 +#: settings/templates/settings/security_setting.html:74 +#: settings/templates/settings/terminal_setting.html:73 #: terminal/templates/terminal/command_list.html:103 #: terminal/templates/terminal/session_list.html:126 #: terminal/templates/terminal/terminal_update.html:46 @@ -437,11 +443,11 @@ msgstr "详情" #: users/templates/users/user_profile.html:187 #: users/templates/users/user_profile.html:196 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:55 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:23 -#: xpack/plugins/cloud/templates/cloud/account_list.html:39 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:54 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:25 +#: xpack/plugins/cloud/templates/cloud/account_list.html:38 #: xpack/plugins/orgs/templates/orgs/org_detail.html:25 -#: xpack/plugins/orgs/templates/orgs/org_list.html:87 +#: xpack/plugins/orgs/templates/orgs/org_list.html:85 msgid "Update" msgstr "更新" @@ -466,8 +472,8 @@ msgstr "更新" #: perms/templates/perms/asset_permission_list.html:182 #: perms/templates/perms/remote_app_permission_detail.html:34 #: perms/templates/perms/remote_app_permission_list.html:60 -#: settings/templates/settings/terminal_setting.html:90 -#: settings/templates/settings/terminal_setting.html:112 +#: settings/templates/settings/terminal_setting.html:93 +#: settings/templates/settings/terminal_setting.html:115 #: terminal/templates/terminal/terminal_list.html:74 #: users/templates/users/user_detail.html:30 #: users/templates/users/user_group_detail.html:32 @@ -475,13 +481,13 @@ msgstr "更新" #: users/templates/users/user_list.html:104 #: users/templates/users/user_list.html:108 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:57 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:27 -#: xpack/plugins/cloud/templates/cloud/account_list.html:41 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:56 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:29 +#: xpack/plugins/cloud/templates/cloud/account_list.html:40 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:32 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:54 #: xpack/plugins/orgs/templates/orgs/org_detail.html:29 -#: xpack/plugins/orgs/templates/orgs/org_list.html:89 +#: xpack/plugins/orgs/templates/orgs/org_list.html:87 msgid "Delete" msgstr "删除" @@ -528,8 +534,8 @@ msgstr "创建远程应用" #: perms/templates/perms/asset_permission_list.html:60 #: perms/templates/perms/asset_permission_list.html:134 #: perms/templates/perms/remote_app_permission_list.html:19 -#: settings/templates/settings/terminal_setting.html:82 -#: settings/templates/settings/terminal_setting.html:104 +#: settings/templates/settings/terminal_setting.html:85 +#: settings/templates/settings/terminal_setting.html:107 #: terminal/templates/terminal/session_list.html:81 #: terminal/templates/terminal/terminal_list.html:36 #: users/templates/users/user_group_list.html:38 @@ -618,7 +624,7 @@ msgstr "节点管理" #: assets/models/cluster.py:19 assets/models/user.py:91 #: assets/templates/assets/asset_detail.html:80 templates/_nav.html:24 #: xpack/plugins/cloud/models.py:124 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:65 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:67 #: xpack/plugins/orgs/templates/orgs/org_list.html:18 msgid "Admin user" msgstr "管理用户" @@ -655,8 +661,8 @@ msgstr "网域" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:55 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:15 #: xpack/plugins/cloud/models.py:123 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:61 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:63 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66 msgid "Node" msgstr "节点" @@ -1069,8 +1075,8 @@ msgstr "过滤器" #: perms/templates/perms/remote_app_permission_remote_app.html:54 #: settings/templates/settings/command_storage_create.html:31 #: settings/templates/settings/replay_storage_create.html:31 -#: settings/templates/settings/terminal_setting.html:81 -#: settings/templates/settings/terminal_setting.html:103 +#: settings/templates/settings/terminal_setting.html:84 +#: settings/templates/settings/terminal_setting.html:106 msgid "Type" msgstr "类型" @@ -1382,7 +1388,7 @@ msgstr "需要二次认证来查看账号信息" #: assets/templates/assets/system_user_asset.html:112 #: assets/templates/assets/system_user_detail.html:182 #: assets/templates/assets/system_user_list.html:168 -#: settings/templates/settings/terminal_setting.html:165 +#: settings/templates/settings/terminal_setting.html:168 #: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:108 #: users/templates/users/user_detail.html:388 #: users/templates/users/user_detail.html:414 @@ -1409,7 +1415,7 @@ msgstr "获取认证信息错误" #: assets/templates/assets/_asset_user_view_auth_modal.html:139 #: assets/templates/assets/_user_asset_detail_modal.html:23 -#: settings/templates/settings/_ldap_list_users_modal.html:96 +#: settings/templates/settings/_ldap_list_users_modal.html:99 #: templates/_modal.html:22 msgid "Close" msgstr "关闭" @@ -1578,7 +1584,7 @@ msgstr "导出" #: assets/templates/assets/admin_user_list.html:24 #: assets/templates/assets/asset_list.html:81 #: assets/templates/assets/system_user_list.html:28 -#: settings/templates/settings/_ldap_list_users_modal.html:97 +#: settings/templates/settings/_ldap_list_users_modal.html:100 #: users/templates/users/user_group_list.html:15 #: users/templates/users/user_list.html:15 #: xpack/plugins/license/templates/license/license_detail.html:110 @@ -1799,7 +1805,7 @@ msgstr "删除选择资产" #: assets/templates/assets/asset_list.html:698 #: assets/templates/assets/system_user_list.html:166 -#: settings/templates/settings/terminal_setting.html:163 +#: settings/templates/settings/terminal_setting.html:166 #: users/templates/users/user_detail.html:386 #: users/templates/users/user_detail.html:412 #: users/templates/users/user_detail.html:480 @@ -1903,8 +1909,8 @@ msgstr "创建网关" #: assets/templates/assets/domain_gateway_list.html:99 #: assets/templates/assets/domain_gateway_list.html:101 -#: settings/templates/settings/email_setting.html:61 -#: settings/templates/settings/ldap_setting.html:62 +#: settings/templates/settings/email_setting.html:64 +#: settings/templates/settings/ldap_setting.html:65 msgid "Test connection" msgstr "测试连接" @@ -2227,7 +2233,7 @@ msgstr "原因" #: audits/models.py:101 audits/templates/audits/login_log_list.html:58 #: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:188 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67 msgid "Status" msgstr "状态" @@ -2267,7 +2273,7 @@ msgstr "选择用户" #: terminal/templates/terminal/command_list.html:60 #: terminal/templates/terminal/session_list.html:61 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:52 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:48 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:50 msgid "Search" msgstr "搜索" @@ -2277,7 +2283,7 @@ msgstr "搜索" #: ops/templates/ops/task_detail.html:56 #: terminal/templates/terminal/session_list.html:70 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:64 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:60 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62 msgid "ID" msgstr "ID" @@ -2679,7 +2685,7 @@ msgid "Become" msgstr "Become" #: ops/models/adhoc.py:166 users/templates/users/user_group_detail.html:59 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:62 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:64 #: xpack/plugins/orgs/templates/orgs/org_detail.html:56 msgid "Create by" msgstr "创建者" @@ -2925,8 +2931,8 @@ msgstr "版本" #: ops/templates/ops/task_list.html:63 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:137 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:53 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:53 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:52 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:52 msgid "Run" msgstr "执行" @@ -3050,8 +3056,8 @@ msgstr "添加资产" #: perms/templates/perms/remote_app_permission_remote_app.html:96 #: perms/templates/perms/remote_app_permission_user.html:96 #: perms/templates/perms/remote_app_permission_user.html:124 -#: settings/templates/settings/terminal_setting.html:95 -#: settings/templates/settings/terminal_setting.html:117 +#: settings/templates/settings/terminal_setting.html:98 +#: settings/templates/settings/terminal_setting.html:120 #: users/templates/users/user_group_detail.html:95 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:93 #: xpack/plugins/orgs/templates/orgs/org_detail.html:93 @@ -3111,7 +3117,7 @@ msgstr "创建授权规则" #: perms/templates/perms/asset_permission_list.html:73 #: perms/templates/perms/remote_app_permission_list.html:18 #: users/templates/users/user_list.html:40 xpack/plugins/cloud/models.py:53 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:58 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:60 #: xpack/plugins/cloud/templates/cloud/account_list.html:14 msgid "Validity" msgstr "有效" @@ -3246,7 +3252,7 @@ msgid "Create succeed" msgstr "创建成功" #: settings/api.py:166 settings/api.py:204 -#: settings/templates/settings/terminal_setting.html:151 +#: settings/templates/settings/terminal_setting.html:154 msgid "Delete succeed" msgstr "删除成功" @@ -3484,6 +3490,40 @@ msgid "" "characters" msgstr "开启后,用户密码修改、重置必须包含特殊字符" +#: settings/forms.py:248 +msgid "Create user email subject" +msgstr "创建用户邮件的主题" + +#: settings/forms.py:249 +msgid "" +"Tips: When creating a user, send the subject of the email (eg:Create account " +"successfully)" +msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" + +#: settings/forms.py:253 +msgid "Create user honorific" +msgstr "创建用户邮件的敬语" + +#: settings/forms.py:254 +msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" +msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)" + +#: settings/forms.py:259 +msgid "Create user email content" +msgstr "创建用户邮件的内容" + +#: settings/forms.py:260 +msgid "Tips:When creating a user, send the content of the email" +msgstr "提示: 创建用户时,发送设置密码邮件的内容" + +#: settings/forms.py:263 +msgid "Signature" +msgstr "署名" + +#: settings/forms.py:264 +msgid "Tips: Email signature (eg:jumpserver)" +msgstr "提示: 邮件的署名 (例如: jumpserver)" + #: settings/models.py:128 users/templates/users/reset_password.html:68 #: users/templates/users/user_profile.html:20 msgid "Setting" @@ -3508,15 +3548,17 @@ msgid "Existing" msgstr "已存在" #: settings/templates/settings/basic_setting.html:15 +#: settings/templates/settings/email_content_setting.html:15 #: settings/templates/settings/email_setting.html:15 #: settings/templates/settings/ldap_setting.html:15 #: settings/templates/settings/security_setting.html:15 #: settings/templates/settings/terminal_setting.html:16 -#: settings/templates/settings/terminal_setting.html:46 settings/views.py:19 +#: settings/templates/settings/terminal_setting.html:49 settings/views.py:19 msgid "Basic setting" msgstr "基本设置" #: settings/templates/settings/basic_setting.html:18 +#: settings/templates/settings/email_content_setting.html:18 #: settings/templates/settings/email_setting.html:18 #: settings/templates/settings/ldap_setting.html:18 #: settings/templates/settings/security_setting.html:18 @@ -3525,27 +3567,39 @@ msgid "Email setting" msgstr "邮件设置" #: settings/templates/settings/basic_setting.html:21 +#: settings/templates/settings/email_content_setting.html:21 #: settings/templates/settings/email_setting.html:21 #: settings/templates/settings/ldap_setting.html:21 #: settings/templates/settings/security_setting.html:21 -#: settings/templates/settings/terminal_setting.html:24 settings/views.py:71 -msgid "LDAP setting" -msgstr "LDAP设置" +#: settings/templates/settings/terminal_setting.html:23 settings/views.py:178 +msgid "Email content setting" +msgstr "邮件内容设置" #: settings/templates/settings/basic_setting.html:24 +#: settings/templates/settings/email_content_setting.html:24 #: settings/templates/settings/email_setting.html:24 #: settings/templates/settings/ldap_setting.html:24 #: settings/templates/settings/security_setting.html:24 -#: settings/templates/settings/terminal_setting.html:28 settings/views.py:100 -msgid "Terminal setting" -msgstr "终端设置" +#: settings/templates/settings/terminal_setting.html:27 settings/views.py:71 +msgid "LDAP setting" +msgstr "LDAP设置" #: settings/templates/settings/basic_setting.html:27 +#: settings/templates/settings/email_content_setting.html:27 #: settings/templates/settings/email_setting.html:27 #: settings/templates/settings/ldap_setting.html:27 #: settings/templates/settings/security_setting.html:27 -#: settings/templates/settings/security_setting.html:42 -#: settings/templates/settings/terminal_setting.html:31 settings/views.py:152 +#: settings/templates/settings/terminal_setting.html:31 settings/views.py:100 +msgid "Terminal setting" +msgstr "终端设置" + +#: settings/templates/settings/basic_setting.html:30 +#: settings/templates/settings/email_content_setting.html:30 +#: settings/templates/settings/email_setting.html:30 +#: settings/templates/settings/ldap_setting.html:30 +#: settings/templates/settings/security_setting.html:30 +#: settings/templates/settings/security_setting.html:45 +#: settings/templates/settings/terminal_setting.html:34 settings/views.py:152 msgid "Security setting" msgstr "安全设置" @@ -3561,11 +3615,15 @@ msgstr "索引" msgid "Doc type" msgstr "文档类型" -#: settings/templates/settings/ldap_setting.html:65 +#: settings/templates/settings/email_content_setting.html:45 +msgid "Create User setting" +msgstr "创建用户设置" + +#: settings/templates/settings/ldap_setting.html:68 msgid "Bulk import" msgstr "一键导入" -#: settings/templates/settings/ldap_setting.html:116 +#: settings/templates/settings/ldap_setting.html:119 msgid "" "User is not currently selected, please check the user you want to import" msgstr "当前无勾选用户,请勾选你想要导入的用户" @@ -3627,8 +3685,8 @@ msgstr "端点后缀" #: settings/templates/settings/replay_storage_create.html:136 #: xpack/plugins/cloud/models.py:186 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:81 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64 msgid "Region" msgstr "地域" @@ -3652,48 +3710,48 @@ msgstr "提交中" msgid "Endpoint need contain protocol, ex: http" msgstr "端点需要包含协议,如 http" -#: settings/templates/settings/security_setting.html:46 +#: settings/templates/settings/security_setting.html:49 msgid "Password check rule" msgstr "密码校验规则" -#: settings/templates/settings/terminal_setting.html:76 terminal/forms.py:27 +#: settings/templates/settings/terminal_setting.html:79 terminal/forms.py:27 #: terminal/models.py:26 msgid "Command storage" msgstr "命令存储" -#: settings/templates/settings/terminal_setting.html:98 terminal/forms.py:32 +#: settings/templates/settings/terminal_setting.html:101 terminal/forms.py:32 #: terminal/models.py:27 msgid "Replay storage" msgstr "录像存储" -#: settings/templates/settings/terminal_setting.html:154 +#: settings/templates/settings/terminal_setting.html:157 msgid "Delete failed" msgstr "删除失败" -#: settings/templates/settings/terminal_setting.html:159 +#: settings/templates/settings/terminal_setting.html:162 msgid "Are you sure about deleting it?" msgstr "您确定删除吗?" -#: settings/utils.py:69 +#: settings/utils.py:71 msgid "User does not exist" msgstr "用户不存在" -#: settings/utils.py:72 +#: settings/utils.py:74 msgid "The user source is not LDAP" msgstr "用户来源不是LDAP" -#: settings/utils.py:146 +#: settings/utils.py:155 msgid "Search no entry matched in ou {}" msgstr "在ou:{}中没有匹配条目" #: settings/views.py:18 settings/views.py:44 settings/views.py:70 #: settings/views.py:99 settings/views.py:126 settings/views.py:138 -#: settings/views.py:151 templates/_nav.html:118 +#: settings/views.py:151 settings/views.py:177 templates/_nav.html:118 msgid "Settings" msgstr "系统设置" #: settings/views.py:29 settings/views.py:55 settings/views.py:81 -#: settings/views.py:112 settings/views.py:162 +#: settings/views.py:112 settings/views.py:162 settings/views.py:188 msgid "Update setting successfully" msgstr "更新设置成功" @@ -4464,7 +4522,7 @@ msgstr "安全令牌验证" #: users/templates/users/_base_otp.html:44 users/templates/users/_user.html:13 #: users/templates/users/user_profile_update.html:51 #: xpack/plugins/cloud/models.py:120 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:57 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:59 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:13 msgid "Account" msgstr "账户" @@ -4572,7 +4630,7 @@ 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/user_detail.html:373 users/utils.py:77 +#: users/templates/users/user_detail.html:373 users/utils.py:98 msgid "Reset password" msgstr "重置密码" @@ -4893,55 +4951,60 @@ msgid "" msgstr "新的公钥已设置成功,请下载对应的私钥" #: users/utils.py:38 -msgid "Create account successfully" -msgstr "创建账户成功" - -#: users/utils.py:40 #, python-format msgid "" "\n" -" Hello %(name)s:\n" -"
    \n" -" Your account has been created successfully\n" -"
    \n" -" Username: %(username)s\n" -"
    \n" -" click " -"here to set your password\n" -"
    \n" -" This link is valid for 1 hour. After it expires, \n" +"

    \n" +" \n" +" Username: %(username)s.\n" +" \n" +" \n" +" click here to set your password\n" +" \n" +" \n" +" This link is valid for 1 hour. After it expires, request new one\n" -"\n" -"
    \n" -" ---\n" -"\n" -"
    \n" -" Login direct\n" -"\n" -"
    \n" -" " +"
    \n" +" \n" +" Login direct\n" +" \n" +"

    \n" +" " msgstr "" "\n" -" 你好 %(name)s:\n" -"
    \n" -" 恭喜您,您的账号已经创建成功
    \n" -" 用户名: %(username)s\n" -"
    \n" -" 请点击这" -"里设置密码
    \n" -" 这个链接有效期1小时, 超过时间您可以 重新申请\n" -"\n" -"
    \n" -" ---\n" -"\n" -"
    \n" -" Login direct\n" -"\n" -"
    \n" +" \n" +"

    \n" +" \n" +" 用户名: %(username)s.\n" +" \n" +" \n" +" " +"请点击这里设置密码\n" +" \n" +" \n" +" 这个链接有效期1小时, 超过时间您可以 重新申请\n" +" \n" +" \n" +" ---登录页面\n" +" \n" +"

    \n" " " -#: users/utils.py:79 +#: users/utils.py:73 +msgid "Create account successfully" +msgstr "创建账户成功" + +#: users/utils.py:77 +#, python-format +msgid "Hello %(name)s" +msgstr "您好 %(name)s" + +#: users/utils.py:100 #, python-format msgid "" "\n" @@ -4985,11 +5048,11 @@ msgstr "" "
    \n" " " -#: users/utils.py:110 +#: users/utils.py:131 msgid "Security notice" msgstr "安全通知" -#: users/utils.py:112 +#: users/utils.py:133 #, python-format msgid "" "\n" @@ -5038,11 +5101,11 @@ msgstr "" "
    \n" " " -#: users/utils.py:148 +#: users/utils.py:169 msgid "SSH Key Reset" msgstr "重置ssh密钥" -#: users/utils.py:150 +#: users/utils.py:171 #, python-format msgid "" "\n" @@ -5067,15 +5130,15 @@ msgstr "" "
    \n" " " -#: users/utils.py:183 +#: users/utils.py:204 msgid "User not exist" msgstr "用户不存在" -#: users/utils.py:185 +#: users/utils.py:206 msgid "Disabled or expired" msgstr "禁用或失效" -#: users/utils.py:198 +#: users/utils.py:219 msgid "Password or SSH public key invalid" msgstr "密码或密钥不合法" @@ -5341,7 +5404,7 @@ msgid "Run plan manually" msgstr "手动执行计划" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:179 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:102 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:101 msgid "Execute failed" msgstr "执行失败" @@ -5427,7 +5490,7 @@ msgid "Unavailable" msgstr "无效" #: xpack/plugins/cloud/models.py:50 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:54 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:56 #: xpack/plugins/cloud/templates/cloud/account_list.html:13 msgid "Provider" msgstr "云服务商" @@ -5453,7 +5516,7 @@ msgid "Instances" msgstr "实例" #: xpack/plugins/cloud/models.py:126 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:73 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:75 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:17 msgid "Date last sync" msgstr "最后同步日期" @@ -5472,7 +5535,7 @@ msgstr "" #: xpack/plugins/cloud/models.py:173 xpack/plugins/cloud/models.py:189 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:71 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:68 msgid "Date sync" msgstr "同步日期" @@ -5489,8 +5552,8 @@ msgid "Sync instance task history" msgstr "同步实例任务历史" #: xpack/plugins/cloud/models.py:185 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:89 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:61 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:91 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 msgid "Instance" msgstr "实例" @@ -5510,7 +5573,7 @@ msgstr "AWS (国际)" msgid "Qcloud" msgstr "腾讯云" -#: xpack/plugins/cloud/templates/cloud/account_detail.html:20 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:22 #: xpack/plugins/cloud/views.py:72 msgid "Account detail" msgstr "账户详情" @@ -5528,23 +5591,23 @@ msgstr "加载中..." msgid "Load failed" msgstr "加载失败" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:20 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:22 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:25 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:21 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:23 #: xpack/plugins/cloud/views.py:122 msgid "Sync task detail" msgstr "同步任务详情" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:23 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:25 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:28 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:24 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:26 #: xpack/plugins/cloud/views.py:137 msgid "Sync task history" msgstr "同步历史列表" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:26 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:28 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:31 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:27 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:29 #: xpack/plugins/cloud/views.py:188 msgid "Sync instance list" msgstr "同步实例列表" @@ -5577,7 +5640,7 @@ msgstr "执行次数" msgid "Instance count" msgstr "实例个数" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:93 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:92 msgid "Sync success" msgstr "同步成功" @@ -5661,7 +5724,6 @@ msgid "This will restore default Settings of the interface !!!" msgstr "您确定要恢复默认初始化吗?" #: xpack/plugins/interface/templates/interface/interface.html:107 -#: xpack/plugins/interface/views.py:53 msgid "Restore default successfully." msgstr "恢复默认成功!" @@ -5674,9 +5736,13 @@ msgid "Interface" msgstr "界面" #: xpack/plugins/interface/views.py:49 -msgid "It is already in the default setting state!" +msgid "It is already in the default setting state!" msgstr "当前已经是初始化状态!" +#: xpack/plugins/interface/views.py:53 +msgid "Restore default successfully!" +msgstr "恢复默认成功!" + #: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94 #: xpack/plugins/license/templates/license/license_detail.html:50 #: xpack/plugins/license/templates/license/license_detail.html:55 @@ -5817,6 +5883,45 @@ msgstr "更新组织" #~ msgid "Update user groups" #~ msgstr "更新用户组" +# msgid "Update user" +# msgstr "更新用户" +#~ msgid "" +#~ "\n" +#~ " \n" +#~ "

    \n" +#~ " \n" +#~ " click here to set your password\n" +#~ " \n" +#~ " \n" +#~ " This link is valid for 1 hour. After it expires, request new one\n" +#~ " \n" +#~ " \n" +#~ " Login direct\n" +#~ " \n" +#~ "

    \n" +#~ " " +#~ msgstr "" +#~ "\n" +#~ " \n" +#~ "

    \n" +#~ " \n" +#~ " 请点击这里设置密码\n" +#~ " \n" +#~ " \n" +#~ " 这个链接有效期1小时, 超过时间您可以, 重新申请\n" +#~ " \n" +#~ " \n" +#~ " Login direct\n" +#~ " \n" +#~ "

    \n" +#~ " " + #~ msgid "Template" #~ msgstr "模板" @@ -5851,9 +5956,6 @@ msgstr "更新组织" #~ msgid "Select host" #~ msgstr "选择资产" -#~ msgid "Restore default successfully!" -#~ msgstr "恢复默认成功!" - #~ msgid "Beijing Duizhan Tech, Inc." #~ msgstr "北京堆栈科技有限公司" diff --git a/apps/settings/forms.py b/apps/settings/forms.py index d87845ea9..0ca565434 100644 --- a/apps/settings/forms.py +++ b/apps/settings/forms.py @@ -242,3 +242,26 @@ class SecuritySettingForm(BaseForm): 'and resets must contain special characters') ) + +class EmailContentSettingForm(BaseForm): + EMAIL_CUSTOM_USER_CREATED_SUBJECT = forms.CharField( + max_length=1024, required=False, label=_("Create user email subject"), + help_text=_("Tips: When creating a user, send the subject of the email" + " (eg:Create account successfully)") + ) + EMAIL_CUSTOM_USER_CREATED_HONORIFIC = forms.CharField( + max_length=1024, required=False, label=_("Create user honorific"), + help_text=_("Tips: When creating a user, send the honorific of the " + "email (eg:Hello)") + ) + EMAIL_CUSTOM_USER_CREATED_BODY = forms.CharField( + max_length=4096, required=False, widget=forms.Textarea(), + label=_('Create user email content'), + help_text=_('Tips:When creating a user, send the content of the email') + ) + EMAIL_CUSTOM_USER_CREATED_SIGNATURE = forms.CharField( + max_length=512, required=False, label=_("Signature"), + help_text=_("Tips: Email signature (eg:jumpserver)") + ) + + diff --git a/apps/settings/templates/settings/basic_setting.html b/apps/settings/templates/settings/basic_setting.html index 17c8057bc..4c26e8bb3 100644 --- a/apps/settings/templates/settings/basic_setting.html +++ b/apps/settings/templates/settings/basic_setting.html @@ -17,6 +17,9 @@
  • {% trans 'Email setting' %}
  • +
  • + {% trans 'Email content setting' %} +
  • {% trans 'LDAP setting' %}
  • diff --git a/apps/settings/templates/settings/email_content_setting.html b/apps/settings/templates/settings/email_content_setting.html new file mode 100644 index 000000000..16cac426e --- /dev/null +++ b/apps/settings/templates/settings/email_content_setting.html @@ -0,0 +1,72 @@ +{% extends 'base.html' %} +{% load static %} +{% load bootstrap3 %} +{% load i18n %} +{% load common_tags %} + +{% block content %} +
    +
    +
    +
    + +
    +
    +
    +
    + {% if form.non_field_errors %} +
    + {{ form.non_field_errors }} +
    + {% endif %} + {% csrf_token %} + +

    {% trans "Create User setting" %}

    + {% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SUBJECT layout="horizontal" %} + {% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_HONORIFIC layout="horizontal" %} + {% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_BODY layout="horizontal" %} + {% bootstrap_field form.EMAIL_CUSTOM_USER_CREATED_SIGNATURE layout="horizontal" %} +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/settings/templates/settings/email_setting.html b/apps/settings/templates/settings/email_setting.html index 46846d7dc..46c4f5dac 100644 --- a/apps/settings/templates/settings/email_setting.html +++ b/apps/settings/templates/settings/email_setting.html @@ -17,6 +17,9 @@
  • {% trans 'Email setting' %}
  • +
  • + {% trans 'Email content setting' %} +
  • {% trans 'LDAP setting' %}
  • diff --git a/apps/settings/templates/settings/ldap_setting.html b/apps/settings/templates/settings/ldap_setting.html index 7eb8167ef..d19a84292 100644 --- a/apps/settings/templates/settings/ldap_setting.html +++ b/apps/settings/templates/settings/ldap_setting.html @@ -17,6 +17,9 @@
  • {% trans 'Email setting' %}
  • +
  • + {% trans 'Email content setting' %} +
  • {% trans 'LDAP setting' %}
  • diff --git a/apps/settings/templates/settings/security_setting.html b/apps/settings/templates/settings/security_setting.html index adc00191f..f13410cdf 100644 --- a/apps/settings/templates/settings/security_setting.html +++ b/apps/settings/templates/settings/security_setting.html @@ -16,6 +16,9 @@
  • {% trans 'Email setting' %} +
  • +
  • + {% trans 'Email content setting' %}
  • {% trans 'LDAP setting' %} diff --git a/apps/settings/templates/settings/terminal_setting.html b/apps/settings/templates/settings/terminal_setting.html index e6eb72982..3a9a4973d 100644 --- a/apps/settings/templates/settings/terminal_setting.html +++ b/apps/settings/templates/settings/terminal_setting.html @@ -18,6 +18,9 @@
  • {% trans 'Email setting' %} +
  • +
  • + {% trans 'Email content setting' %}
  • - Your account has been created successfully -
    - Username: %(username)s -
    -
    click here to set your password -
    - This link is valid for 1 hour. After it expires, request new one - -
    - --- - -
    - Login direct - -
    - """) % { - 'name': user.name, +def construct_user_created_email_body(user): + default_body = _(""" + +

    + + Username: %(username)s. + + + click here to set your password + + + This link is valid for 1 hour. After it expires, request new one + + + Login direct + +

    + """) % { 'username': user.username, 'rest_password_url': reverse('users:reset-password', external=True), 'rest_password_token': user.generate_reset_token(), @@ -64,6 +59,32 @@ def send_user_created_mail(user): 'email': user.email, 'login_url': reverse('authentication:login', external=True), } + + if settings.EMAIL_CUSTOM_USER_CREATED_BODY: + custom_body = '

    ' + settings.EMAIL_CUSTOM_USER_CREATED_BODY + '

    ' + else: + custom_body = '' + body = custom_body + default_body + return body + + +def send_user_created_mail(user): + recipient_list = [user.email] + subject = _('Create account successfully') + if settings.EMAIL_CUSTOM_USER_CREATED_SUBJECT: + subject = settings.EMAIL_CUSTOM_USER_CREATED_SUBJECT + + honorific = '

    ' + _('Hello %(name)s') % {'name': user.name} + ':

    ' + if settings.EMAIL_CUSTOM_USER_CREATED_HONORIFIC: + honorific = '

    ' + settings.EMAIL_CUSTOM_USER_CREATED_HONORIFIC + ':

    ' + + body = construct_user_created_email_body(user) + + signature = '

    jumpserver

    ' + if settings.EMAIL_CUSTOM_USER_CREATED_SIGNATURE: + signature = '

    ' + settings.EMAIL_CUSTOM_USER_CREATED_SIGNATURE + '

    ' + + message = honorific + body + signature if settings.DEBUG: try: print(message) From 9b5b48dd1a2fea5ad87a2ed9760ffa753e06ac17 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 24 May 2019 18:34:21 +0800 Subject: [PATCH 23/36] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9settings=20c?= =?UTF-8?q?onf=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/conf.py | 20 +++++++++++++++----- apps/jumpserver/settings.py | 2 ++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 753ffb3d2..28ac12456 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -273,6 +273,19 @@ class Config(dict): if default_value is None: return v tp = type(default_value) + # 对bool特殊处理 + if tp is bool and isinstance(v, str): + if v in ("true", "True", "1"): + return True + else: + return False + if tp in [list, dict] and isinstance(v, str): + try: + v = json.loads(v) + return v + except json.JSONDecodeError: + return v + try: v = tp(v) except Exception: @@ -289,14 +302,10 @@ class Config(dict): except KeyError: value = None if value is not None: - return self.convert_type(item, value) + return value # 其次从环境变量来 value = os.environ.get(item, None) if value is not None: - if value.lower() == 'false': - value = False - elif value.lower() == 'true': - value = True return self.convert_type(item, value) return self.defaults.get(item) @@ -362,6 +371,7 @@ defaults = { 'HTTP_LISTEN_PORT': 8080, 'LOGIN_LOG_KEEP_DAYS': 90, 'ASSETS_PERM_CACHE_TIME': 3600, + } diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 57a3c0148..86988e599 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -371,6 +371,8 @@ REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': ( 'rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser', + 'rest_framework.parsers.MultiPartParser', + 'rest_framework.parsers.FileUploadParser', 'common.parsers.JMSCSVParser' ), 'DEFAULT_AUTHENTICATION_CLASSES': ( From 21ac3eaf8bd78b5b01ce926aa44fc29a9a6d528b Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Fri, 24 May 2019 19:41:07 +0800 Subject: [PATCH 24/36] =?UTF-8?q?[Update]=20=E7=94=A8=E6=88=B7=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E6=B7=BB=E5=8A=A0=E5=AF=86=E7=A0=81=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E7=AD=96=E7=95=A5=20(#2731)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 优化创建用户的密码策略功能 * [Update] 优化用户初始密码设置以及清除初始密码 * [Update] 优化创建用户的密码策略功能 * [Update]统一变量名前缀 * [Update] 用户密码策略去掉自定义策略 * [Update] 修改小问题 * [Update] 优化创建用户密码策略 * [Update] 翻译 * [Update] 优化mfa按钮排布和间距 * [Update] 优化mfa按钮样式由前端控制 * [Update] 优化前端密码策略按钮的显示与隐藏 * [Update] 用户创建设置密码添加密码校验弹窗 --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 76566 -> 76482 bytes apps/locale/zh/LC_MESSAGES/django.po | 300 ++++++++++-------- apps/settings/api.py | 5 +- .../templates/settings/security_setting.html | 4 +- apps/users/forms.py | 10 + apps/users/templates/users/_user.html | 3 + apps/users/templates/users/user_create.html | 82 ++++- apps/users/views/user.py | 11 +- 8 files changed, 273 insertions(+), 142 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index cc3a0249dbb06b08f685868c4acd68f3b30d198b..fb8aab6444d1016140b90b68c22ae5d4fb64a578 100644 GIT binary patch delta 18401 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 delta 18459 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 PKI7siYnvqo24(v{D?kQ< diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 875e91c25..83393725d 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-05-24 12:02+0800\n" +"POT-Creation-Date: 2019-05-24 19:17+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -101,7 +101,7 @@ msgstr "运行参数" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:14 #: xpack/plugins/cloud/models.py:187 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 #: xpack/plugins/orgs/templates/orgs/org_list.html:16 msgid "Asset" msgstr "资产" @@ -177,9 +177,9 @@ msgstr "系统用户" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 #: xpack/plugins/cloud/models.py:49 xpack/plugins/cloud/models.py:119 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:52 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:50 #: xpack/plugins/cloud/templates/cloud/account_list.html:12 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:55 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12 #: xpack/plugins/orgs/templates/orgs/org_detail.html:52 #: xpack/plugins/orgs/templates/orgs/org_list.html:12 @@ -244,8 +244,8 @@ msgstr "创建者" #: users/templates/users/user_group_detail.html:63 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105 #: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:128 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:68 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:79 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:77 #: xpack/plugins/orgs/templates/orgs/org_detail.html:60 msgid "Date created" msgstr "创建日期" @@ -286,9 +286,9 @@ msgstr "创建日期" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 #: xpack/plugins/cloud/models.py:54 xpack/plugins/cloud/models.py:125 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:72 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:70 #: xpack/plugins/cloud/templates/cloud/account_list.html:15 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:71 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16 #: xpack/plugins/orgs/templates/orgs/org_detail.html:64 #: xpack/plugins/orgs/templates/orgs/org_list.html:22 @@ -443,11 +443,11 @@ msgstr "详情" #: users/templates/users/user_profile.html:187 #: users/templates/users/user_profile.html:196 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:54 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:25 -#: xpack/plugins/cloud/templates/cloud/account_list.html:38 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:55 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:23 +#: xpack/plugins/cloud/templates/cloud/account_list.html:39 #: 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:87 msgid "Update" msgstr "更新" @@ -481,13 +481,13 @@ msgstr "更新" #: users/templates/users/user_list.html:104 #: users/templates/users/user_list.html:108 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:56 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:29 -#: xpack/plugins/cloud/templates/cloud/account_list.html:40 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:32 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:54 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:57 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:27 +#: xpack/plugins/cloud/templates/cloud/account_list.html:41 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55 #: xpack/plugins/orgs/templates/orgs/org_detail.html:29 -#: xpack/plugins/orgs/templates/orgs/org_list.html:87 +#: xpack/plugins/orgs/templates/orgs/org_list.html:89 msgid "Delete" msgstr "删除" @@ -624,7 +624,7 @@ msgstr "节点管理" #: assets/models/cluster.py:19 assets/models/user.py:91 #: assets/templates/assets/asset_detail.html:80 templates/_nav.html:24 #: xpack/plugins/cloud/models.py:124 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:67 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:65 #: xpack/plugins/orgs/templates/orgs/org_list.html:18 msgid "Admin user" msgstr "管理用户" @@ -661,8 +661,8 @@ msgstr "网域" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:55 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:15 #: xpack/plugins/cloud/models.py:123 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:63 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:61 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64 msgid "Node" msgstr "节点" @@ -742,9 +742,8 @@ msgstr "密码或密钥密码" #: authentication/forms.py:13 #: authentication/templates/authentication/login.html:67 #: authentication/templates/authentication/new_login.html:93 -#: settings/forms.py:103 users/forms.py:15 users/forms.py:27 +#: settings/forms.py:103 users/forms.py:15 users/forms.py:33 #: users/templates/users/reset_password.html:53 -#: users/templates/users/user_create.html:10 #: users/templates/users/user_password_authentication.html:18 #: users/templates/users/user_password_update.html:43 #: users/templates/users/user_profile_update.html:40 @@ -1138,10 +1137,10 @@ msgstr "默认资产组" #: terminal/models.py:154 terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 -#: terminal/templates/terminal/session_list.html:71 users/forms.py:283 +#: terminal/templates/terminal/session_list.html:71 users/forms.py:293 #: users/models/user.py:36 users/models/user.py:467 users/serializers/v1.py:61 #: users/templates/users/user_group_detail.html:78 -#: users/templates/users/user_group_list.html:36 users/views/user.py:394 +#: users/templates/users/user_group_list.html:36 users/views/user.py:399 #: xpack/plugins/orgs/forms.py:26 #: xpack/plugins/orgs/templates/orgs/org_detail.html:113 #: xpack/plugins/orgs/templates/orgs/org_list.html:14 @@ -1216,7 +1215,7 @@ msgstr "连接" msgid "Org name" msgstr "组织名" -#: assets/serializers/asset_user.py:23 users/forms.py:230 +#: assets/serializers/asset_user.py:23 users/forms.py:240 #: users/models/user.py:91 users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:46 #: users/templates/users/user_profile.html:68 @@ -1371,7 +1370,7 @@ msgstr "资产用户信息" #: assets/templates/assets/_asset_user_view_auth_modal.html:14 #: audits/models.py:99 audits/templates/audits/login_log_list.html:56 -#: users/forms.py:142 users/models/user.py:83 +#: users/forms.py:152 users/models/user.py:83 #: users/templates/users/first_login.html:45 msgid "MFA" msgstr "MFA" @@ -2233,7 +2232,7 @@ msgstr "原因" #: audits/models.py:101 audits/templates/audits/login_log_list.html:58 #: xpack/plugins/cloud/models.py:171 xpack/plugins/cloud/models.py:188 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 msgid "Status" msgstr "状态" @@ -2273,7 +2272,7 @@ msgstr "选择用户" #: terminal/templates/terminal/command_list.html:60 #: terminal/templates/terminal/session_list.html:61 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:52 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:50 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:48 msgid "Search" msgstr "搜索" @@ -2283,7 +2282,7 @@ msgstr "搜索" #: ops/templates/ops/task_detail.html:56 #: terminal/templates/terminal/session_list.html:70 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:64 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:60 msgid "ID" msgstr "ID" @@ -2544,8 +2543,8 @@ msgstr "欢迎回来,请输入用户名和密码登录" msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: authentication/views/login.py:172 users/views/user.py:540 -#: users/views/user.py:565 +#: authentication/views/login.py:172 users/views/user.py:545 +#: users/views/user.py:570 msgid "MFA code invalid, or ntp sync server time" msgstr "MFA验证码不正确,或者服务器端时间不对" @@ -2685,7 +2684,7 @@ msgid "Become" msgstr "Become" #: ops/models/adhoc.py:166 users/templates/users/user_group_detail.html:59 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:64 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:62 #: xpack/plugins/orgs/templates/orgs/org_detail.html:56 msgid "Create by" msgstr "创建者" @@ -2931,8 +2930,8 @@ msgstr "版本" #: ops/templates/ops/task_list.html:63 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:137 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:52 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:52 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:53 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:53 msgid "Run" msgstr "执行" @@ -2988,7 +2987,7 @@ msgstr "下载文件" #: perms/templates/perms/asset_permission_list.html:75 #: perms/templates/perms/asset_permission_list.html:122 #: perms/templates/perms/remote_app_permission_list.html:16 -#: templates/_nav.html:14 users/forms.py:253 users/models/group.py:26 +#: templates/_nav.html:14 users/forms.py:263 users/models/group.py:26 #: users/models/user.py:67 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:213 #: users/templates/users/user_list.html:38 @@ -3117,7 +3116,7 @@ msgstr "创建授权规则" #: perms/templates/perms/asset_permission_list.html:73 #: perms/templates/perms/remote_app_permission_list.html:18 #: users/templates/users/user_list.html:40 xpack/plugins/cloud/models.py:53 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:60 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:58 #: xpack/plugins/cloud/templates/cloud/account_list.html:14 msgid "Validity" msgstr "有效" @@ -3685,8 +3684,8 @@ msgstr "端点后缀" #: settings/templates/settings/replay_storage_create.html:136 #: xpack/plugins/cloud/models.py:186 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:81 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62 msgid "Region" msgstr "地域" @@ -3732,18 +3731,14 @@ msgstr "删除失败" msgid "Are you sure about deleting it?" msgstr "您确定删除吗?" -#: settings/utils.py:71 -msgid "User does not exist" -msgstr "用户不存在" - -#: settings/utils.py:74 -msgid "The user source is not LDAP" -msgstr "用户来源不是LDAP" - -#: settings/utils.py:155 +#: settings/utils.py:85 msgid "Search no entry matched in ou {}" msgstr "在ou:{}中没有匹配条目" +#: settings/utils.py:113 +msgid "The user source is not LDAP" +msgstr "用户来源不是LDAP" + #: settings/views.py:18 settings/views.py:44 settings/views.py:70 #: settings/views.py:99 settings/views.py:126 settings/views.py:138 #: settings/views.py:151 settings/views.py:177 templates/_nav.html:118 @@ -3775,14 +3770,14 @@ msgstr "文档" msgid "Commercial support" msgstr "商业支持" -#: templates/_header_bar.html:89 templates/_nav_user.html:26 users/forms.py:121 +#: templates/_header_bar.html:89 templates/_nav_user.html:26 users/forms.py:131 #: users/templates/users/_user.html:43 #: users/templates/users/first_login.html:39 #: users/templates/users/user_password_update.html:40 #: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:57 -#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:376 +#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:381 msgid "Profile" msgstr "个人信息" @@ -3876,9 +3871,9 @@ msgstr "" #: templates/_nav.html:10 users/views/group.py:27 users/views/group.py:43 #: users/views/group.py:59 users/views/group.py:75 users/views/group.py:91 -#: users/views/login.py:154 users/views/user.py:70 users/views/user.py:85 -#: users/views/user.py:124 users/views/user.py:202 users/views/user.py:363 -#: users/views/user.py:413 users/views/user.py:453 +#: users/views/login.py:154 users/views/user.py:70 users/views/user.py:86 +#: users/views/user.py:129 users/views/user.py:207 users/views/user.py:368 +#: users/views/user.py:418 users/views/user.py:458 msgid "Users" msgstr "用户管理" @@ -3919,7 +3914,7 @@ msgid "File manager" msgstr "文件管理" #: templates/_nav.html:68 terminal/views/command.py:50 -#: terminal/views/session.py:75 terminal/views/session.py:93 +#: terminal/views/session.py:74 terminal/views/session.py:92 #: terminal/views/session.py:115 terminal/views/terminal.py:31 #: terminal/views/terminal.py:46 terminal/views/terminal.py:58 msgid "Terminal" @@ -4299,11 +4294,11 @@ msgstr "接受终端注册" msgid "Info" msgstr "信息" -#: terminal/views/session.py:76 +#: terminal/views/session.py:75 msgid "Session online list" msgstr "在线会话" -#: terminal/views/session.py:94 +#: terminal/views/session.py:93 msgid "Session offline list" msgstr "离线会话" @@ -4336,7 +4331,15 @@ msgstr "你没有权限" msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" -#: users/forms.py:32 users/models/user.py:71 +#: users/forms.py:25 +msgid "Reset link will be generated and sent to the user" +msgstr "生成重置密码链接,通过邮件发送给用户" + +#: users/forms.py:26 +msgid "Set password" +msgstr "设置密码" + +#: users/forms.py:38 users/models/user.py:71 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:87 #: users/templates/users/user_list.html:37 @@ -4344,31 +4347,39 @@ msgstr "不能再该页面重置MFA, 请去个人信息页面重置" msgid "Role" msgstr "角色" -#: users/forms.py:35 users/forms.py:200 +#: users/forms.py:41 users/forms.py:210 msgid "ssh public key" msgstr "ssh公钥" -#: users/forms.py:36 users/forms.py:201 +#: users/forms.py:42 users/forms.py:211 msgid "ssh-rsa AAAA..." msgstr "" -#: users/forms.py:37 +#: users/forms.py:43 msgid "Paste user id_rsa.pub here." msgstr "复制用户公钥到这里" -#: users/forms.py:51 users/templates/users/user_detail.html:221 +#: users/forms.py:47 xpack/plugins/change_auth_plan/models.py:83 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:16 +msgid "Password strategy" +msgstr "密码策略" + +#: users/forms.py:61 users/templates/users/user_detail.html:221 msgid "Join user groups" msgstr "添加到用户组" -#: users/forms.py:85 users/forms.py:215 +#: users/forms.py:95 users/forms.py:225 msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:89 users/forms.py:219 users/serializers/v1.py:47 +#: users/forms.py:99 users/forms.py:229 users/serializers/v1.py:47 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" -#: users/forms.py:127 +#: users/forms.py:137 msgid "" "Tip: when enabled, you will enter the MFA binding process the next time you " "log in. you can also directly bind in \"personal information -> quick " @@ -4377,11 +4388,11 @@ msgstr "" "提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修" "改->更改MFA设置)中直接绑定!" -#: users/forms.py:137 +#: users/forms.py:147 msgid "* Enable MFA authentication to make the account more secure." msgstr "* 启用MFA认证,使账号更加安全." -#: users/forms.py:147 +#: users/forms.py:157 msgid "" "In order to protect you and your company, please keep your account, password " "and key sensitive information properly. (for example: setting complex " @@ -4390,41 +4401,41 @@ msgstr "" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "设置复杂密码,启用MFA认证)" -#: users/forms.py:154 users/templates/users/first_login.html:48 +#: users/forms.py:164 users/templates/users/first_login.html:48 #: users/templates/users/first_login.html:107 #: users/templates/users/first_login.html:130 msgid "Finish" msgstr "完成" -#: users/forms.py:160 +#: users/forms.py:170 msgid "Old password" msgstr "原来密码" -#: users/forms.py:165 +#: users/forms.py:175 msgid "New password" msgstr "新密码" -#: users/forms.py:170 +#: users/forms.py:180 msgid "Confirm password" msgstr "确认密码" -#: users/forms.py:180 +#: users/forms.py:190 msgid "Old password error" msgstr "原来密码错误" -#: users/forms.py:188 +#: users/forms.py:198 msgid "Password does not match" msgstr "密码不一致" -#: users/forms.py:198 +#: users/forms.py:208 msgid "Automatically configure and download the SSH key" msgstr "自动配置并下载SSH密钥" -#: users/forms.py:202 +#: users/forms.py:212 msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms.py:236 users/forms.py:241 users/forms.py:287 +#: users/forms.py:246 users/forms.py:251 users/forms.py:297 #: xpack/plugins/orgs/forms.py:30 msgid "Select users" msgstr "选择用户" @@ -4471,7 +4482,7 @@ msgid "Date password last updated" msgstr "最后更新密码日期" #: users/models/user.py:136 users/templates/users/user_update.html:22 -#: users/views/login.py:47 users/views/login.py:108 users/views/user.py:426 +#: users/views/login.py:47 users/views/login.py:108 users/views/user.py:431 msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" @@ -4522,7 +4533,7 @@ msgstr "安全令牌验证" #: users/templates/users/_base_otp.html:44 users/templates/users/_user.html:13 #: users/templates/users/user_profile_update.html:51 #: xpack/plugins/cloud/models.py:120 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:59 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:57 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:13 msgid "Account" msgstr "账户" @@ -4557,7 +4568,7 @@ msgid "Import users" msgstr "导入用户" #: users/templates/users/_user_update_modal.html:4 -#: users/templates/users/user_update.html:4 users/views/user.py:125 +#: users/templates/users/user_update.html:4 users/views/user.py:130 msgid "Update user" msgstr "更新用户" @@ -4635,12 +4646,14 @@ msgid "Reset password" msgstr "重置密码" #: users/templates/users/reset_password.html:59 +#: users/templates/users/user_create.html:15 #: users/templates/users/user_password_update.html:61 #: users/templates/users/user_update.html:13 msgid "Your password must satisfy" msgstr "您的密码必须满足:" #: users/templates/users/reset_password.html:60 +#: users/templates/users/user_create.html:16 #: users/templates/users/user_password_update.html:62 #: users/templates/users/user_update.html:14 msgid "Password strength" @@ -4651,52 +4664,54 @@ msgid "Password again" msgstr "再次输入密码" #: users/templates/users/reset_password.html:105 +#: users/templates/users/user_create.html:35 #: users/templates/users/user_password_update.html:99 #: users/templates/users/user_update.html:46 msgid "Very weak" msgstr "很弱" #: users/templates/users/reset_password.html:106 +#: users/templates/users/user_create.html:36 #: users/templates/users/user_password_update.html:100 #: users/templates/users/user_update.html:47 msgid "Weak" msgstr "弱" #: users/templates/users/reset_password.html:107 +#: users/templates/users/user_create.html:37 #: users/templates/users/user_password_update.html:101 #: users/templates/users/user_update.html:48 msgid "Normal" msgstr "正常" #: users/templates/users/reset_password.html:108 +#: users/templates/users/user_create.html:38 #: users/templates/users/user_password_update.html:102 #: users/templates/users/user_update.html:49 msgid "Medium" msgstr "一般" #: users/templates/users/reset_password.html:109 +#: users/templates/users/user_create.html:39 #: users/templates/users/user_password_update.html:103 #: users/templates/users/user_update.html:50 msgid "Strong" msgstr "强" #: users/templates/users/reset_password.html:110 +#: users/templates/users/user_create.html:40 #: users/templates/users/user_password_update.html:104 #: users/templates/users/user_update.html:51 msgid "Very strong" msgstr "很强" #: users/templates/users/user_create.html:4 -#: users/templates/users/user_list.html:28 users/views/user.py:85 +#: users/templates/users/user_list.html:28 users/views/user.py:87 msgid "Create user" msgstr "创建用户" -#: users/templates/users/user_create.html:12 -msgid "Reset link will be generated and sent to the user. " -msgstr "生成重置密码连接,通过邮件发送给用户" - #: users/templates/users/user_detail.html:19 -#: users/templates/users/user_granted_asset.html:18 users/views/user.py:203 +#: users/templates/users/user_granted_asset.html:18 users/views/user.py:208 msgid "User detail" msgstr "用户详情" @@ -4899,8 +4914,8 @@ msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接 msgid "Administrator Settings force MFA login" msgstr "管理员设置强制使用MFA登录" -#: users/templates/users/user_profile.html:120 users/views/user.py:239 -#: users/views/user.py:293 +#: users/templates/users/user_profile.html:120 users/views/user.py:244 +#: users/views/user.py:298 msgid "User groups" msgstr "用户组" @@ -5179,7 +5194,7 @@ msgstr "Token错误或失效" msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:115 users/views/user.py:139 users/views/user.py:436 +#: users/views/login.py:115 users/views/user.py:144 users/views/user.py:441 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" @@ -5187,51 +5202,51 @@ msgstr "* 您的密码不符合要求" msgid "First login" msgstr "首次登录" -#: users/views/user.py:156 +#: users/views/user.py:161 msgid "Bulk update user success" msgstr "批量更新用户成功" -#: users/views/user.py:183 +#: users/views/user.py:188 msgid "Bulk update user" msgstr "批量更新用户" -#: users/views/user.py:268 +#: users/views/user.py:273 msgid "Invalid file." msgstr "文件不合法" -#: users/views/user.py:364 +#: users/views/user.py:369 msgid "User granted assets" msgstr "用户授权资产" -#: users/views/user.py:395 +#: users/views/user.py:400 msgid "Profile setting" msgstr "个人信息设置" -#: users/views/user.py:414 +#: users/views/user.py:419 msgid "Password update" msgstr "密码更新" -#: users/views/user.py:454 +#: users/views/user.py:459 msgid "Public key update" msgstr "密钥更新" -#: users/views/user.py:495 +#: users/views/user.py:500 msgid "Password invalid" msgstr "用户名或密码无效" -#: users/views/user.py:595 +#: users/views/user.py:600 msgid "MFA enable success" msgstr "MFA 绑定成功" -#: users/views/user.py:596 +#: users/views/user.py:601 msgid "MFA enable success, return login page" msgstr "MFA 绑定成功,返回到登录页面" -#: users/views/user.py:598 +#: users/views/user.py:603 msgid "MFA disable success" msgstr "MFA 解绑成功" -#: users/views/user.py:599 +#: users/views/user.py:604 msgid "MFA disable success, return login page" msgstr "MFA 解绑成功,返回登录页面" @@ -5324,14 +5339,6 @@ msgstr "周期执行" msgid "Regularly perform" msgstr "定期执行" -#: xpack/plugins/change_auth_plan/models.py:83 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:16 -msgid "Password strategy" -msgstr "密码策略" - #: xpack/plugins/change_auth_plan/models.py:87 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74 msgid "Password rules" @@ -5404,7 +5411,7 @@ msgid "Run plan manually" msgstr "手动执行计划" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:179 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:101 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:102 msgid "Execute failed" msgstr "执行失败" @@ -5490,7 +5497,7 @@ msgid "Unavailable" msgstr "无效" #: xpack/plugins/cloud/models.py:50 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:56 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:54 #: xpack/plugins/cloud/templates/cloud/account_list.html:13 msgid "Provider" msgstr "云服务商" @@ -5516,7 +5523,7 @@ msgid "Instances" msgstr "实例" #: xpack/plugins/cloud/models.py:126 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:75 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:73 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:17 msgid "Date last sync" msgstr "最后同步日期" @@ -5535,7 +5542,7 @@ msgstr "" #: xpack/plugins/cloud/models.py:173 xpack/plugins/cloud/models.py:189 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:71 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:68 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66 msgid "Date sync" msgstr "同步日期" @@ -5552,8 +5559,8 @@ msgid "Sync instance task history" msgstr "同步实例任务历史" #: xpack/plugins/cloud/models.py:185 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:91 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:89 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:61 msgid "Instance" msgstr "实例" @@ -5573,7 +5580,7 @@ msgstr "AWS (国际)" msgid "Qcloud" msgstr "腾讯云" -#: xpack/plugins/cloud/templates/cloud/account_detail.html:22 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:20 #: xpack/plugins/cloud/views.py:72 msgid "Account detail" msgstr "账户详情" @@ -5591,23 +5598,23 @@ msgstr "加载中..." msgid "Load failed" msgstr "加载失败" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:22 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:20 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:25 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:23 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:21 #: xpack/plugins/cloud/views.py:122 msgid "Sync task detail" msgstr "同步任务详情" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:25 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:23 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:28 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:26 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:24 #: xpack/plugins/cloud/views.py:137 msgid "Sync task history" msgstr "同步历史列表" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:28 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:26 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:31 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:29 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:27 #: xpack/plugins/cloud/views.py:188 msgid "Sync instance list" msgstr "同步实例列表" @@ -5640,7 +5647,7 @@ msgstr "执行次数" msgid "Instance count" msgstr "实例个数" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:92 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:93 msgid "Sync success" msgstr "同步成功" @@ -5724,6 +5731,7 @@ msgid "This will restore default Settings of the interface !!!" msgstr "您确定要恢复默认初始化吗?" #: xpack/plugins/interface/templates/interface/interface.html:107 +#: xpack/plugins/interface/views.py:53 msgid "Restore default successfully." msgstr "恢复默认成功!" @@ -5736,13 +5744,9 @@ msgid "Interface" msgstr "界面" #: xpack/plugins/interface/views.py:49 -msgid "It is already in the default setting state!" +msgid "It is already in the default setting state!" msgstr "当前已经是初始化状态!" -#: xpack/plugins/interface/views.py:53 -msgid "Restore default successfully!" -msgstr "恢复默认成功!" - #: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94 #: xpack/plugins/license/templates/license/license_detail.html:50 #: xpack/plugins/license/templates/license/license_detail.html:55 @@ -5880,6 +5884,50 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" +#~ msgid "User does not exist" +#~ msgstr "用户不存在" + +#~ msgid "Restore default successfully!" +#~ msgstr "恢复默认成功!" + +#~ msgid "The initial password has been cleared." +#~ msgstr "当前初始密码已经清除." + +#~ msgid "Clear initial password successfully." +#~ msgstr "清除初始密码成功." + +#~ msgid "User Initial Password" +#~ msgstr "用户初始密码" + +#~ msgid "Clear initial password" +#~ msgstr "清除初始密码" + +#~ msgid "This will be clear the initial password !!!" +#~ msgstr "这将会清除用户初始密码!!!" + +#~ msgid "Clear Initial Password" +#~ msgstr "清除初始密码" + +#~ msgid "Clear initial password failed." +#~ msgstr "清除初始密码失败." + +#~ msgid "INITIAL_PASSWORD" +#~ msgstr "初始化密码" + +#~ msgid "CUSTOM_PASSWORD" +#~ msgstr "自定义密码" + +#~ msgid "EMAIL_SET_PASSWORD" +#~ msgstr "邮件设置密码" + +#~ msgid "" +#~ "This will use the default initial password by the system. Please go to " +#~ "the system Settings application to set the initial password" +#~ msgstr "这将会使用系统设置的初始密码. 请先到系统设置应用里去设置初始密码." + +#~ msgid "The password cannot be the same as the initial password" +#~ msgstr "密码设置不能和系统设置的初始密码一致" + #~ msgid "Update user groups" #~ msgstr "更新用户组" diff --git a/apps/settings/api.py b/apps/settings/api.py index f2b3eb558..d7c789603 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -220,7 +220,4 @@ class DjangoSettingsAPI(APIView): data[k] = v except (json.JSONDecodeError, TypeError): data[k] = str(v) - return Response(data) - - - + return Response(data) \ No newline at end of file diff --git a/apps/settings/templates/settings/security_setting.html b/apps/settings/templates/settings/security_setting.html index f13410cdf..48206676d 100644 --- a/apps/settings/templates/settings/security_setting.html +++ b/apps/settings/templates/settings/security_setting.html @@ -33,8 +33,8 @@
    -
    -
    +
    + {% if form.non_field_errors %}
    {{ form.non_field_errors }} diff --git a/apps/users/forms.py b/apps/users/forms.py index 2ab6f11ac..cbca9c4fd 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -22,6 +22,12 @@ class UserCheckOtpCodeForm(forms.Form): class UserCreateUpdateForm(OrgModelForm): + EMAIL_SET_PASSWORD = _('Reset link will be generated and sent to the user') + CUSTOM_PASSWORD = _('Set password') + PASSWORD_STRATEGY_CHOICES = ( + (0, EMAIL_SET_PASSWORD), + (1, CUSTOM_PASSWORD) + ) role_choices = ((i, n) for i, n in User.ROLE_CHOICES if i != User.ROLE_APP) password = forms.CharField( label=_('Password'), widget=forms.PasswordInput, @@ -36,6 +42,10 @@ class UserCreateUpdateForm(OrgModelForm): widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}), help_text=_('Paste user id_rsa.pub here.') ) + password_strategy = forms.ChoiceField( + choices=PASSWORD_STRATEGY_CHOICES, required=True, initial=0, + widget=forms.RadioSelect(), label=_('Password strategy') + ) class Meta: model = User diff --git a/apps/users/templates/users/_user.html b/apps/users/templates/users/_user.html index 1db09db39..0e8764f6a 100644 --- a/apps/users/templates/users/_user.html +++ b/apps/users/templates/users/_user.html @@ -74,6 +74,9 @@ $(document).ready(function () { $('.select2').select2(); $('#id_date_expired').daterangepicker(dateOptions); + var mfa_radio = $('#id_otp_level'); + mfa_radio.addClass("form-inline"); + mfa_radio.children().css("margin-right","15px") }) {% endblock %} diff --git a/apps/users/templates/users/user_create.html b/apps/users/templates/users/user_create.html index 5760571e1..13bb26cbc 100644 --- a/apps/users/templates/users/user_create.html +++ b/apps/users/templates/users/user_create.html @@ -2,15 +2,83 @@ {% load i18n %} {% load bootstrap3 %} {% block user_template_title %}{% trans "Create user" %}{% endblock %} -{#{% block username %}#} -{# {% bootstrap_field form.username layout="horizontal" %}#} -{#{% endblock %}#} {% block password %} -
    - -
    - {% trans 'Reset link will be generated and sent to the user. ' %} + {% bootstrap_field form.password_strategy layout="horizontal" %} +
    + {% bootstrap_field form.password layout="horizontal" %} +
    + {# 密码popover #} +
    +
    + {% endblock %} diff --git a/apps/users/views/user.py b/apps/users/views/user.py index a7515d030..69ed441ba 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -81,9 +81,14 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): success_message = create_success_msg def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({'app': _('Users'), 'action': _('Create user')}) - return context + check_rules = get_password_check_rules() + context = { + 'app': _('Users'), + 'action': _('Create user'), + 'password_check_rules': check_rules, + } + kwargs.update(context) + return super().get_context_data(**kwargs) def form_valid(self, form): user = form.save(commit=False) From f576f2eda2d8a60de4bdb1d1434a84f9500c9b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AB=E5=8D=83=E6=B5=81?= <40739051+jym503558564@users.noreply.github.com> Date: Mon, 27 May 2019 15:21:33 +0800 Subject: [PATCH 25/36] =?UTF-8?q?[Bugfix]=E4=BF=AE=E5=A4=8D=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=99=A8=E9=A1=BA=E5=BA=8Fbug,csv=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E5=99=A8=E9=9C=80=E5=9C=A8file=E8=A7=A3=E6=9E=90=E5=99=A8?= =?UTF-8?q?=E5=89=8D=20(#2734)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 1ad32c332..2e09ea191 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -378,8 +378,8 @@ REST_FRAMEWORK = { 'rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser', + 'common.parsers.JMSCSVParser', 'rest_framework.parsers.FileUploadParser', - 'common.parsers.JMSCSVParser' ), 'DEFAULT_AUTHENTICATION_CLASSES': ( # 'rest_framework.authentication.BasicAuthentication', From 0847539e027d56aaf4a386277893f008fc26dd0a Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 27 May 2019 15:50:44 +0800 Subject: [PATCH 26/36] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E7=9A=84csv=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/renders/csv.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/common/renders/csv.py b/apps/common/renders/csv.py index cec857ddb..3352c73a0 100644 --- a/apps/common/renders/csv.py +++ b/apps/common/renders/csv.py @@ -2,6 +2,7 @@ # import unicodecsv +from datetime import datetime from six import BytesIO from rest_framework.renderers import BaseRenderer @@ -40,6 +41,16 @@ class JMSCSVRender(BaseRenderer): row = [item.get(key) for key in header] yield row + def set_response_disposition(self, serializer, context): + response = context.get('response') + if response and hasattr(serializer, 'Meta') and \ + hasattr(serializer.Meta, "model"): + model_name = serializer.Meta.model.__name__.lower() + now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + filename = "{}_{}.csv".format(model_name, now) + disposition = 'attachment; filename="{}"'.format(filename) + response['Content-Disposition'] = disposition + def render(self, data, media_type=None, renderer_context=None): renderer_context = renderer_context or {} encoding = renderer_context.get('encoding', 'utf-8') @@ -52,6 +63,7 @@ class JMSCSVRender(BaseRenderer): try: serializer = view.get_serializer() + self.set_response_disposition(serializer, renderer_context) except Exception as e: logger.debug(e, exc_info=True) value = 'The resource not support export!'.encode('utf-8') From e46a6f1d12bc7e398b55f7e333d034c70dcb4c12 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Mon, 27 May 2019 15:52:25 +0800 Subject: [PATCH 27/36] =?UTF-8?q?[Update]=20=E6=B7=BB=E5=8A=A0=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E4=B8=8B=E8=BD=BD=E5=99=A8=E5=9C=B0=E5=9D=80=EF=BC=9B?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=B3=BB=E7=BB=9F=E8=AE=BE=E7=BD=AE=E4=B8=AD?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E7=9A=84=E6=9C=80=E5=A4=A7=E3=80=81=E6=9C=80?= =?UTF-8?q?=E5=B0=8F=E5=80=BC=E9=99=90=E5=88=B6=20(#2735)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 添加应用下载器地址 * [Update] 系统设置中的Form, 添加最大、最小值限制 * [Update] 更新翻译 --- .../applications/remote_app_list.html | 2 +- apps/locale/zh/LC_MESSAGES/django.po | 50 +++++++++---------- apps/settings/forms.py | 17 ++++--- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/apps/applications/templates/applications/remote_app_list.html b/apps/applications/templates/applications/remote_app_list.html index c2f64235e..54f1806e9 100644 --- a/apps/applications/templates/applications/remote_app_list.html +++ b/apps/applications/templates/applications/remote_app_list.html @@ -3,7 +3,7 @@ {% block help_message %}
    {% trans 'Before using this feature, make sure that the application loader has been uploaded to the application server and successfully published as a RemoteApp application' %} - {% trans 'Download application loader' %} + {% trans 'Download application loader' %}
    {% endblock %} {% block table_search %}{% endblock %} diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 83393725d..c1acf7792 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-05-24 19:17+0800\n" +"POT-Creation-Date: 2019-05-27 15:38+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -3407,35 +3407,35 @@ msgid "" "for all users, including administrators)" msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)" -#: settings/forms.py:185 +#: settings/forms.py:186 msgid "Limit the number of login failures" msgstr "限制登录失败次数" -#: settings/forms.py:189 +#: settings/forms.py:190 msgid "No logon interval" msgstr "禁止登录时间间隔" -#: settings/forms.py:191 +#: settings/forms.py:192 msgid "" "Tip: (unit/minute) if the user has failed to log in for a limited number of " "times, no login is allowed during this time interval." msgstr "" "提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" -#: settings/forms.py:197 +#: settings/forms.py:199 msgid "Connection max idle time" msgstr "SSH最大空闲时间" -#: settings/forms.py:199 +#: settings/forms.py:201 msgid "" "If idle time more than it, disconnect connection(only ssh now) Unit: minute" msgstr "提示:(单位:分)如果超过该配置没有操作,连接会被断开(仅ssh)" -#: settings/forms.py:205 +#: settings/forms.py:207 msgid "Password expiration time" msgstr "密码过期时间" -#: settings/forms.py:208 +#: settings/forms.py:209 msgid "" "Tip: (unit: day) If the user does not update the password during the time, " "the user password will expire failure;The password expiration reminder mail " @@ -3445,81 +3445,81 @@ msgstr "" "提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" "提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" -#: settings/forms.py:217 +#: settings/forms.py:218 msgid "Password minimum length" msgstr "密码最小长度 " -#: settings/forms.py:221 +#: settings/forms.py:222 msgid "Must contain capital letters" msgstr "必须包含大写字母" -#: settings/forms.py:223 +#: settings/forms.py:224 msgid "" "After opening, the user password changes and resets must contain uppercase " "letters" msgstr "开启后,用户密码修改、重置必须包含大写字母" -#: settings/forms.py:228 +#: settings/forms.py:229 msgid "Must contain lowercase letters" msgstr "必须包含小写字母" -#: settings/forms.py:229 +#: settings/forms.py:230 msgid "" "After opening, the user password changes and resets must contain lowercase " "letters" msgstr "开启后,用户密码修改、重置必须包含小写字母" -#: settings/forms.py:234 +#: settings/forms.py:235 msgid "Must contain numeric characters" msgstr "必须包含数字字符" -#: settings/forms.py:235 +#: settings/forms.py:236 msgid "" "After opening, the user password changes and resets must contain numeric " "characters" msgstr "开启后,用户密码修改、重置必须包含数字字符" -#: settings/forms.py:240 +#: settings/forms.py:241 msgid "Must contain special characters" msgstr "必须包含特殊字符" -#: settings/forms.py:241 +#: settings/forms.py:242 msgid "" "After opening, the user password changes and resets must contain special " "characters" msgstr "开启后,用户密码修改、重置必须包含特殊字符" -#: settings/forms.py:248 +#: settings/forms.py:249 msgid "Create user email subject" msgstr "创建用户邮件的主题" -#: settings/forms.py:249 +#: settings/forms.py:250 msgid "" "Tips: When creating a user, send the subject of the email (eg:Create account " "successfully)" msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" -#: settings/forms.py:253 +#: settings/forms.py:254 msgid "Create user honorific" msgstr "创建用户邮件的敬语" -#: settings/forms.py:254 +#: settings/forms.py:255 msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)" -#: settings/forms.py:259 +#: settings/forms.py:260 msgid "Create user email content" msgstr "创建用户邮件的内容" -#: settings/forms.py:260 +#: settings/forms.py:261 msgid "Tips:When creating a user, send the content of the email" msgstr "提示: 创建用户时,发送设置密码邮件的内容" -#: settings/forms.py:263 +#: settings/forms.py:264 msgid "Signature" msgstr "署名" -#: settings/forms.py:264 +#: settings/forms.py:265 msgid "Tips: Email signature (eg:jumpserver)" msgstr "提示: 邮件的署名 (例如: jumpserver)" diff --git a/apps/settings/forms.py b/apps/settings/forms.py index 0ca565434..b27cade64 100644 --- a/apps/settings/forms.py +++ b/apps/settings/forms.py @@ -147,7 +147,7 @@ class TerminalSettingForm(BaseForm): required=False, label=_("Public key auth") ) TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField( - min_value=5, label=_("Heartbeat interval"), + min_value=5, max_value=99999, label=_("Heartbeat interval"), help_text=_("Units: seconds") ) TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField( @@ -157,7 +157,7 @@ class TerminalSettingForm(BaseForm): choices=PAGE_SIZE_CHOICES, label=_("List page size"), ) TERMINAL_SESSION_KEEP_DURATION = forms.IntegerField( - min_value=1, label=_("Session keep duration"), + min_value=1, max_value=99999, label=_("Session keep duration"), help_text=_("Units: days, Session, record, command will be delete " "if more than duration, only in database") ) @@ -182,11 +182,12 @@ class SecuritySettingForm(BaseForm): ) # limit login count SECURITY_LOGIN_LIMIT_COUNT = forms.IntegerField( - min_value=3, label=_("Limit the number of login failures") + min_value=3, max_value=99999, + label=_("Limit the number of login failures") ) # limit login time SECURITY_LOGIN_LIMIT_TIME = forms.IntegerField( - min_value=5, label=_("No logon interval"), + min_value=5, max_value=99999, label=_("No logon interval"), help_text=_( "Tip: (unit/minute) if the user has failed to log in for a limited " "number of times, no login is allowed during this time interval." @@ -194,7 +195,8 @@ class SecuritySettingForm(BaseForm): ) # ssh max idle time SECURITY_MAX_IDLE_TIME = forms.IntegerField( - required=False, label=_("Connection max idle time"), + min_value=1, max_value=99999, required=False, + label=_("Connection max idle time"), help_text=_( 'If idle time more than it, disconnect connection(only ssh now) ' 'Unit: minute' @@ -202,8 +204,7 @@ class SecuritySettingForm(BaseForm): ) # password expiration time SECURITY_PASSWORD_EXPIRATION_TIME = forms.IntegerField( - label=_("Password expiration time"), - min_value=1, max_value=99999, + min_value=1, max_value=99999, label=_("Password expiration time"), help_text=_( "Tip: (unit: day) " "If the user does not update the password during the time, " @@ -214,7 +215,7 @@ class SecuritySettingForm(BaseForm): ) # min length SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField( - min_value=6, label=_("Password minimum length"), + min_value=6, max_value=30, label=_("Password minimum length"), ) # upper case SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField( From 7a6027f35ae8d022af05dda4e5c5d4a9925802ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AB=E5=8D=83=E6=B5=81?= <40739051+jym503558564@users.noreply.github.com> Date: Mon, 27 May 2019 16:12:19 +0800 Subject: [PATCH 28/36] =?UTF-8?q?[Update]=20csv=E7=BF=BB=E8=AF=91=20(#2736?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/djangojs.mo | Bin 2465 -> 2698 bytes apps/locale/zh/LC_MESSAGES/djangojs.po | 76 ++++++++++++++++--------- apps/static/js/jumpserver.js | 10 ++-- 3 files changed, 55 insertions(+), 31 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/djangojs.mo b/apps/locale/zh/LC_MESSAGES/djangojs.mo index 189ddbc8eb356af1918545cfeb20150f6b06aa3b..aab4a06e6150424d2779b534a47cd2a73051e6c1 100644 GIT binary patch delta 833 zcmXxi%}Z2K7{~GFj-zIcn%ZcY+AJza5vySs#ul|Pn20c{KVZs3L5r*_Ey6%IZ3?1)z*TpOUUq@d3-tZnIdkCfInTXw&-0w~Or>inUfk`BJ~qYx zvyU0`%ueB7)OZ>(8^j78!w;CoAJ~M)8_b$<2s<%}m+&Iq!bjMMzp)i9YSspANL;jj z76*7RgeoA7D&{&WVZm9(eZ)St;1(wE9k$~ayo!6M!m?yNh~qepIaK0Ts6uuyPJa8y zLX`%1fm2`M>!ZXaj&%s1V-mMf8~<|t!5DFbsxsJ)W0*z#u7rBO>|AwjpbB`4hskg6 zwZPBLf6i`>sgsSMHad^Jco}t)DO79jqY_q78$Ux8{2KM$H|L%+y5DS`=N(vdZ4Ngk z4hJ_F_EJF)Q^}|)?6X>>+fiX^ms(XHO0Gekupk|(J~~)*F}Y;puMoC|ztDj76U3Ri zYK?B@5vD3PKNFeq2FK=S@7$eB+|1pc&QDr)CXBA#o0!NK3iYt|(qCyPdGY$I8?%$S zxxD+Xe6sOVYi9U#YWQqAaV9mIO8U{J@7{?QTfxGcVD07C&2R`RcOyWGNDnJ=4Y~~&_jQe~vj($=XS>L4SXrdj@eH>St=Y>?JM?Wiop8x&#v-SG6<#%_ diff --git a/apps/locale/zh/LC_MESSAGES/djangojs.po b/apps/locale/zh/LC_MESSAGES/djangojs.po index 720927f06..8e0ec5047 100644 --- a/apps/locale/zh/LC_MESSAGES/djangojs.po +++ b/apps/locale/zh/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-11-21 19:14+0800\n" +"POT-Creation-Date: 2019-05-27 15:53+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,58 +17,58 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: static/js/jumpserver.js:168 +#: static/js/jumpserver.js:249 msgid "Update is successful!" msgstr "更新成功" -#: static/js/jumpserver.js:170 +#: static/js/jumpserver.js:251 msgid "An unknown error occurred while updating.." msgstr "更新时发生未知错误" -#: static/js/jumpserver.js:236 static/js/jumpserver.js:273 -#: static/js/jumpserver.js:276 +#: static/js/jumpserver.js:315 static/js/jumpserver.js:352 +#: static/js/jumpserver.js:355 msgid "Error" msgstr "错误" -#: static/js/jumpserver.js:236 +#: static/js/jumpserver.js:315 msgid "Being used by the asset, please unbind the asset first." msgstr "正在被资产使用中,请先解除资产绑定" -#: static/js/jumpserver.js:242 static/js/jumpserver.js:283 +#: static/js/jumpserver.js:321 static/js/jumpserver.js:362 msgid "Delete the success" msgstr "删除成功" -#: static/js/jumpserver.js:248 +#: static/js/jumpserver.js:327 msgid "Are you sure about deleting it?" msgstr "你确定删除吗 ?" -#: static/js/jumpserver.js:252 static/js/jumpserver.js:293 +#: static/js/jumpserver.js:331 static/js/jumpserver.js:372 msgid "Cancel" msgstr "取消" -#: static/js/jumpserver.js:254 static/js/jumpserver.js:295 +#: static/js/jumpserver.js:333 static/js/jumpserver.js:374 msgid "Confirm" msgstr "确认" -#: static/js/jumpserver.js:273 +#: static/js/jumpserver.js:352 msgid "" "The organization contains undeleted information. Please try again after " "deleting" msgstr "组织中包含未删除信息,请删除后重试" -#: static/js/jumpserver.js:276 +#: static/js/jumpserver.js:355 msgid "" "Do not perform this operation under this organization. Try again after " "switching to another organization" msgstr "请勿在此组织下执行此操作,切换到其他组织后重试" -#: static/js/jumpserver.js:289 +#: static/js/jumpserver.js:368 msgid "" "Please ensure that the following information in the organization has been " "deleted" msgstr "请确保组织内的以下信息已删除" -#: static/js/jumpserver.js:290 +#: static/js/jumpserver.js:369 msgid "" "User list、User group、Asset list、Domain list、Admin user、System user、" "Labels、Asset permission" @@ -76,52 +76,76 @@ msgstr "" "用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权" "规则" -#: static/js/jumpserver.js:329 +#: static/js/jumpserver.js:408 msgid "Loading ..." msgstr "加载中 ..." -#: static/js/jumpserver.js:330 +#: static/js/jumpserver.js:409 msgid "Search" msgstr "搜索" -#: static/js/jumpserver.js:333 +#: static/js/jumpserver.js:412 #, javascript-format msgid "Selected item %d" msgstr "选中 %d 项" -#: static/js/jumpserver.js:337 +#: static/js/jumpserver.js:416 msgid "Per page _MENU_" msgstr "每页 _MENU_" -#: static/js/jumpserver.js:338 +#: static/js/jumpserver.js:417 msgid "" "Displays the results of items _START_ to _END_; A total of _TOTAL_ entries" msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项" -#: static/js/jumpserver.js:341 +#: static/js/jumpserver.js:420 msgid "No match" msgstr "没有匹配项" -#: static/js/jumpserver.js:342 +#: static/js/jumpserver.js:421 msgid "No record" msgstr "没有记录" -#: static/js/jumpserver.js:701 +#: static/js/jumpserver.js:563 +msgid "Unknown error occur" +msgstr "" + +#: static/js/jumpserver.js:800 msgid "Password minimum length {N} bits" msgstr "密码最小长度 {N} 位" -#: static/js/jumpserver.js:702 +#: static/js/jumpserver.js:801 msgid "Must contain capital letters" msgstr "必须包含大写字母" -#: static/js/jumpserver.js:703 +#: static/js/jumpserver.js:802 msgid "Must contain lowercase letters" msgstr "必须包含小写字母" -#: static/js/jumpserver.js:704 +#: static/js/jumpserver.js:803 msgid "Must contain numeric characters" msgstr "必须包含数字字符" -#: static/js/jumpserver.js:705 +#: static/js/jumpserver.js:804 msgid "Must contain special characters" msgstr "必须包含特殊字符" + +#: static/js/jumpserver.js:976 +msgid "Export failed" +msgstr "导出失败" + +#: static/js/jumpserver.js:993 +msgid "Import Success" +msgstr "导入成功" + +#: static/js/jumpserver.js:998 +msgid "Update Success" +msgstr "更新成功" + +#: static/js/jumpserver.js:1028 +msgid "Import failed" +msgstr "导入失败" + +#: static/js/jumpserver.js:1033 +msgid "Update failed" +msgstr "更新失败" diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index b452c353a..0197312fd 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -973,7 +973,7 @@ function APIExportData(props) { window.open(export_url); }, error: function () { - toastr.error('Export failed'); + toastr.error(gettext('Export failed')); } }) } @@ -990,12 +990,12 @@ function APIImportData(props){ if(props.method === 'POST'){ $('#created_failed').html(''); $('#created_failed_detail').html(''); - $('#success_created').html("Import Success"); + $('#success_created').html(gettext("Import Success")); $('#success_created_detail').html("Count" + ": " + data.length); }else{ $('#updated_failed').html(''); $('#updated_failed_detail').html(''); - $('#success_updated').html("Update Success"); + $('#success_updated').html(gettext("Update Success")); $('#success_updated_detail').html("Count" + ": " + data.length); } @@ -1025,12 +1025,12 @@ function APIImportData(props){ if(props.method === 'POST'){ $('#success_created').html(''); $('#success_created_detail').html(''); - $('#created_failed').html("Import failed"); + $('#created_failed').html(gettext("Import failed")); $('#created_failed_detail').html(html); }else{ $('#success_updated').html(''); $('#success_updated_detail').html(''); - $('#updated_failed').html("Update failed"); + $('#updated_failed').html(gettext("Update failed")); $('#updated_failed_detail').html(html); } } From 33e342f03fdf9ac2445711c0c60402d240b144ef Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 27 May 2019 16:38:43 +0800 Subject: [PATCH 29/36] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E8=8F=9C?= =?UTF-8?q?=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/templates/_nav.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 684f90a1d..1acb752c9 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -27,6 +27,7 @@
  • {% trans 'Command filters' %}
  • +{% if LICENSE_VALID %}
  • {% trans 'Applications' %} @@ -35,15 +36,18 @@
  • {% trans 'RemoteApp' %}
  • +{% endif %}
  • {% trans 'Perms' %}
  • From 9f67daeb1ea0f7a6fab24a6990959ea6a14a24d0 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Mon, 27 May 2019 20:04:41 +0800 Subject: [PATCH 30/36] =?UTF-8?q?[Update]=20=E4=BC=98=E5=8C=96AssetPermiss?= =?UTF-8?q?ionUtil=E5=8A=A8=E6=80=81=E8=AE=BE=E7=BD=AE=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E7=94=A8=E6=88=B7actions=E5=B1=9E=E6=80=A7=E9=80=BB=E8=BE=91?= =?UTF-8?q?=20(#2739)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 优化AssetPermissionUtil动态设置系统用户actions属性逻辑 * [Bugfix] 修复can't pickle dict_keys objects的bug --- apps/perms/utils/asset_permission.py | 75 ++++++++++++++++------------ 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index cacad1718..fb0676a5e 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -160,51 +160,53 @@ class AssetPermissionUtil: self._permissions = self.permissions.filter(**filters) self._filter_id = md5(filters_json.encode()).hexdigest() + @staticmethod + def _structured_system_user(system_users, actions): + """ + 结构化系统用户 + :param system_users: + :param actions: + :return: {system_user1: {'actions': set(), }, } + """ + _attr = {'actions': set(actions)} + _system_users = {system_user: _attr for system_user in system_users} + return _system_users + def get_nodes_direct(self): """ 返回用户/组授权规则直接关联的节点 - :return: {node1: set(system_user1,)} + :return: {asset1: {system_user1: {'actions': set()},}} """ - nodes = defaultdict(set) + nodes = defaultdict(dict) permissions = self.permissions.prefetch_related('nodes', 'system_users') for perm in permissions: + actions = perm.actions.all() for node in perm.nodes.all(): - nodes[node].update(perm.system_users.all()) + system_users = perm.system_users.all() + system_users = self._structured_system_user(system_users, actions) + nodes[node].update(system_users) return nodes def get_assets_direct(self): """ + 返回用户授权规则直接关联的资产 - :return: {asset1: set(system_user1,)} + :return: {asset1: {system_user1: {'actions': set()},}} """ - assets = defaultdict(set) + assets = defaultdict(dict) permissions = self.permissions.prefetch_related('assets', 'system_users') for perm in permissions: + actions = perm.actions.all() for asset in perm.assets.all().valid().prefetch_related('nodes'): - assets[asset].update( - perm.system_users.filter(protocol=asset.protocol) - ) + system_users = perm.system_users.filter(protocol=asset.protocol) + system_users = self._structured_system_user(system_users, actions) + assets[asset].update(system_users) return assets - def _setattr_actions_to_system_user(self): - """ - 动态给system_use设置属性actions - """ - for asset, system_users in self._assets.items(): - # 获取资产和资产的祖先节点的所有授权规则 - perms = get_asset_permissions(asset, include_node=True) - # 过滤当前self.permission的授权规则 - perms = perms.filter(id__in=[perm.id for perm in self.permissions]) - - for system_user in system_users: - actions = set() - _perms = perms.filter(system_users=system_user).\ - prefetch_related('actions') - for _perm in _perms: - actions.update(_perm.actions.all()) - setattr(system_user, 'actions', actions) - def get_assets_without_cache(self): + """ + :return: {asset1: set(system_user1,)} + """ if self._assets: return self._assets assets = self.get_assets_direct() @@ -212,11 +214,22 @@ class AssetPermissionUtil: for node, system_users in nodes.items(): _assets = node.get_all_assets().valid().prefetch_related('nodes') for asset in _assets: - assets[asset].update( - [s for s in system_users if s.protocol == asset.protocol] - ) - self._assets = assets - self._setattr_actions_to_system_user() + for system_user, attr_dict in system_users.items(): + if system_user.protocol != asset.protocol: + continue + if system_user in assets[asset]: + actions = assets[asset][system_user]['actions'] + attr_dict['actions'].update(actions) + system_users.update({system_user: attr_dict}) + assets[asset].update(system_users) + + __assets = defaultdict(set) + for asset, system_users in assets.items(): + for system_user, attr_dict in system_users.items(): + setattr(system_user, 'actions', attr_dict['actions']) + __assets[asset] = set(system_users.keys()) + + self._assets = __assets return self._assets def get_cache_key(self, resource): From 1097b11115f0dc670bb1a94aa4ce97c810e79681 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 28 May 2019 10:28:03 +0800 Subject: [PATCH 31/36] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9ldap?= =?UTF-8?q?=E6=94=AF=E6=8C=81ssl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings.py | 8 +++++++- apps/settings/forms.py | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 2e09ea191..d9be8c346 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -173,7 +173,7 @@ DATABASES = { 'OPTIONS': DB_OPTIONS } } -DB_CA_PATH = os.path.join(PROJECT_DIR, 'data', 'ca.pem') +DB_CA_PATH = os.path.join(PROJECT_DIR, 'data', 'certs', 'db_ca.pem') if CONFIG.DB_ENGINE.lower() == 'mysql': DB_OPTIONS['init_command'] = "SET sql_mode='STRICT_TRANS_TABLES'" if os.path.isfile(DB_CA_PATH): @@ -426,6 +426,12 @@ AUTH_LDAP_SEARCH_OU = 'ou=tech,dc=jumpserver,dc=org' AUTH_LDAP_SEARCH_FILTER = '(cn=%(user)s)' AUTH_LDAP_START_TLS = False AUTH_LDAP_USER_ATTR_MAP = {"username": "cn", "name": "sn", "email": "mail"} +AUTH_LDAP_GLOBAL_OPTIONS = { + ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER, +} +LDAP_CERT_FILE = os.path.join(PROJECT_DIR, "data", "certs", "ldap_ca.pem") +if os.path.isfile(LDAP_CERT_FILE): + AUTH_LDAP_GLOBAL_OPTIONS[ldap.OPT_X_TLS_CACERTFILE] = LDAP_CERT_FILE # 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 = LDAPSearch( diff --git a/apps/settings/forms.py b/apps/settings/forms.py index b27cade64..3f6a690a8 100644 --- a/apps/settings/forms.py +++ b/apps/settings/forms.py @@ -121,9 +121,9 @@ class LDAPSettingForm(BaseForm): ) # AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU # AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER - AUTH_LDAP_START_TLS = forms.BooleanField( - label=_("Use SSL"), required=False - ) + # AUTH_LDAP_START_TLS = forms.BooleanField( + # label=_("Use SSL"), required=False + # ) AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), required=False) From 059a8de44a0a692eaec372ad4a70814a7258505a Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Tue, 28 May 2019 12:11:55 +0800 Subject: [PATCH 32/36] =?UTF-8?q?[Bufix]=20=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=9B=B4=E6=96=B0=E6=97=B6password=5Fstrategy?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E4=B8=8D=E8=83=BD=E4=B8=BA=E7=A9=BA=E7=9A=84?= =?UTF-8?q?bug=20(#2741)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/forms.py | 31 +++++++++++++++++++------------ apps/users/views/user.py | 4 ++-- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/users/forms.py b/apps/users/forms.py index cbca9c4fd..43ef0b594 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -21,13 +21,7 @@ class UserCheckOtpCodeForm(forms.Form): otp_code = forms.CharField(label=_('MFA code'), max_length=6) -class UserCreateUpdateForm(OrgModelForm): - EMAIL_SET_PASSWORD = _('Reset link will be generated and sent to the user') - CUSTOM_PASSWORD = _('Set password') - PASSWORD_STRATEGY_CHOICES = ( - (0, EMAIL_SET_PASSWORD), - (1, CUSTOM_PASSWORD) - ) +class UserCreateUpdateFormMixin(OrgModelForm): role_choices = ((i, n) for i, n in User.ROLE_CHOICES if i != User.ROLE_APP) password = forms.CharField( label=_('Password'), widget=forms.PasswordInput, @@ -42,10 +36,6 @@ class UserCreateUpdateForm(OrgModelForm): widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}), help_text=_('Paste user id_rsa.pub here.') ) - password_strategy = forms.ChoiceField( - choices=PASSWORD_STRATEGY_CHOICES, required=True, initial=0, - widget=forms.RadioSelect(), label=_('Password strategy') - ) class Meta: model = User @@ -65,7 +55,7 @@ class UserCreateUpdateForm(OrgModelForm): def __init__(self, *args, **kwargs): self.request = kwargs.pop("request", None) - super(UserCreateUpdateForm, self).__init__(*args, **kwargs) + super(UserCreateUpdateFormMixin, self).__init__(*args, **kwargs) roles = [] # Super admin user @@ -115,6 +105,23 @@ class UserCreateUpdateForm(OrgModelForm): return user +class UserCreateForm(UserCreateUpdateFormMixin): + EMAIL_SET_PASSWORD = _('Reset link will be generated and sent to the user') + CUSTOM_PASSWORD = _('Set password') + PASSWORD_STRATEGY_CHOICES = ( + (0, EMAIL_SET_PASSWORD), + (1, CUSTOM_PASSWORD) + ) + password_strategy = forms.ChoiceField( + choices=PASSWORD_STRATEGY_CHOICES, required=True, initial=0, + widget=forms.RadioSelect(), label=_('Password strategy') + ) + + +class UserUpdateForm(UserCreateUpdateFormMixin): + pass + + class UserProfileForm(forms.ModelForm): username = forms.CharField(disabled=True) name = forms.CharField(disabled=True) diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 69ed441ba..963be613f 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -75,7 +75,7 @@ class UserListView(AdminUserRequiredMixin, TemplateView): class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): model = User - form_class = forms.UserCreateUpdateForm + form_class = forms.UserCreateForm template_name = 'users/user_create.html' success_url = reverse_lazy('users:user-list') success_message = create_success_msg @@ -108,7 +108,7 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): model = User - form_class = forms.UserCreateUpdateForm + form_class = forms.UserUpdateForm template_name = 'users/user_update.html' context_object_name = 'user_object' success_url = reverse_lazy('users:user-list') From dc4bf669b0d86477a43d9b9e4bc21047ea42a995 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Tue, 28 May 2019 17:05:31 +0800 Subject: [PATCH 33/36] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9RemoteApp?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E6=96=87=E4=BB=B6=20(#2742)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/migrations/0001_initial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/applications/migrations/0001_initial.py b/apps/applications/migrations/0001_initial.py index 35d6dc103..221fd5e22 100644 --- a/apps/applications/migrations/0001_initial.py +++ b/apps/applications/migrations/0001_initial.py @@ -21,7 +21,7 @@ class Migration(migrations.Migration): ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), ('name', models.CharField(max_length=128, verbose_name='Name')), - ('type', models.CharField(choices=[('Browser', (('chrome', 'Chrome'),)), ('Database tools', (('mysql_workbench', 'MySQL Workbench'),)), ('Virtualization tools', (('vmware_client', 'VMware Client'),)), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type')), + ('type', models.CharField(choices=[('Browser', (('chrome', 'Chrome'),)), ('Database tools', (('mysql_workbench', 'MySQL Workbench'),)), ('Virtualization tools', (('vmware_client', 'vSphere Client'),)), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type')), ('path', models.CharField(max_length=128, verbose_name='App path')), ('params', common.fields.model.EncryptJsonDictTextField(blank=True, default={}, max_length=4096, null=True, verbose_name='Parameters')), ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), From 00e986a64ed429a436981a01907cd78abfd221c7 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Wed, 29 May 2019 10:11:58 +0800 Subject: [PATCH 34/36] =?UTF-8?q?[Update]=20=E4=BC=98=E5=8C=96LDAP?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=AF=BC=E5=85=A5=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E5=8F=AF=E5=AF=BC=E5=85=A5=E8=B7=A8=E9=A1=B5=E9=80=89=E5=8F=96?= =?UTF-8?q?=E7=9A=84=E6=89=80=E6=9C=89=E7=94=A8=E6=88=B7=20(#2745)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/api.py | 46 +++++++++++++++++-- .../settings/_ldap_list_users_modal.html | 6 +-- .../templates/settings/ldap_setting.html | 5 +- apps/settings/utils.py | 1 - 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/apps/settings/api.py b/apps/settings/api.py index d7c789603..29986637e 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -5,7 +5,9 @@ import os import json import jms_storage +from rest_framework import generics from rest_framework.views import Response, APIView +from rest_framework.pagination import LimitOffsetPagination from django.conf import settings from django.core.mail import send_mail from django.utils.translation import ugettext_lazy as _ @@ -89,19 +91,55 @@ class LDAPTestingAPI(APIView): return Response({"error": "Have user but attr mapping error"}, status=401) -class LDAPUserListApi(APIView): +class LDAPUserListApi(generics.ListAPIView): + pagination_class = LimitOffsetPagination permission_classes = (IsOrgAdmin,) - def get(self, request): + def get_queryset(self): util = LDAPUtil() try: users = util.search_user_items() except Exception as e: users = [] logger.error(e, exc_info=True) + # 前端data_table会根据row.id对table.selected值进行操作 + for user in users: + user['id'] = user['username'] + return users + + def filter_queryset(self, queryset): + search = self.request.query_params.get('search') + if not search: + return queryset + search = search.lower() + queryset = [ + q for q in queryset + if + search in q['username'].lower() + or search in q['name'].lower() + or search in q['email'].lower() + ] + return queryset + + def sort_queryset(self, queryset): + order_by = self.request.query_params.get('order') + if not order_by: + order_by = 'existing' + if order_by.startswith('-'): + order_by = order_by.lstrip('-') + reverse = True else: - users = sorted(users, key=lambda u: (u['existing'], u['username'])) - return Response(users) + reverse = False + queryset = sorted(queryset, key=lambda x: x[order_by], reverse=reverse) + return queryset + + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + queryset = self.sort_queryset(queryset) + page = self.paginate_queryset(queryset) + if page is not None: + return self.get_paginated_response(page) + return Response(queryset) class LDAPUserSyncAPI(APIView): diff --git a/apps/settings/templates/settings/_ldap_list_users_modal.html b/apps/settings/templates/settings/_ldap_list_users_modal.html index 9609d80ce..c0d062714 100644 --- a/apps/settings/templates/settings/_ldap_list_users_modal.html +++ b/apps/settings/templates/settings/_ldap_list_users_modal.html @@ -52,7 +52,7 @@ var ldap_users_table = 0; function initLdapUsersTable() { if(ldap_users_table){ - return + return ldap_users_table } var options = { ele: $('#ldap_list_users_table'), @@ -73,10 +73,10 @@ function initLdapUsersTable() { {data: "username" },{data: "username" }, {data: "name" }, {data:"email"}, {data:'existing'} ], - pageLength: 10 + pageLength: 15 }; - ldap_users_table = jumpserver.initDataTable(options); + ldap_users_table = jumpserver.initServerSideDataTable(options); return ldap_users_table } diff --git a/apps/settings/templates/settings/ldap_setting.html b/apps/settings/templates/settings/ldap_setting.html index d19a84292..58e4ae71b 100644 --- a/apps/settings/templates/settings/ldap_setting.html +++ b/apps/settings/templates/settings/ldap_setting.html @@ -110,10 +110,7 @@ $(document).ready(function () { }); }) .on("click","#btn_ldap_modal_confirm",function () { - var username_list=[]; - $("tbody input[type='checkbox']:checked").each(function () { - username_list.push($(this).attr('id')); - }); + var username_list = ldap_users_table.selected; if (username_list.length === 0){ var msg = "{% trans 'User is not currently selected, please check the user you want to import'%}"; diff --git a/apps/settings/utils.py b/apps/settings/utils.py index 563f5fc3a..adcd2c839 100644 --- a/apps/settings/utils.py +++ b/apps/settings/utils.py @@ -61,7 +61,6 @@ class LDAPUtil: try: user = User.objects.get(username=username) except Exception as e: - logger.info(e) return None else: return user From 9c55450a9ebb2fd229259d32df88aed1e50eec4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AB=E5=8D=83=E6=B5=81?= <40739051+jym503558564@users.noreply.github.com> Date: Wed, 29 May 2019 10:13:19 +0800 Subject: [PATCH 35/36] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E8=A1=A8=E5=8F=AF=E4=BB=A5=E9=80=89=E6=8B=A9=E5=A4=9A?= =?UTF-8?q?=E9=A1=B5=E7=9A=84=E8=B5=84=E6=BA=90=E6=95=B0=E6=8D=AE=20(#2744?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Bugfix] 修复资源表可以选择多页的资源数据 * [Bugfix]修改小问题 --- .../templates/assets/admin_user_list.html | 26 +++++++------------ apps/assets/templates/assets/asset_list.html | 24 +++-------------- .../templates/assets/system_user_list.html | 18 ++++--------- .../templates/users/user_group_list.html | 23 +++++++--------- apps/users/templates/users/user_list.html | 26 +++++-------------- 5 files changed, 34 insertions(+), 83 deletions(-) diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html index c182ba4c4..4f4356539 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -63,7 +63,8 @@ {% block content_bottom_left %}{% endblock %} {% block custom_foot_js %}