From f8dae2a3c902c209514ad647a3e125401c5f7f26 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 1 Apr 2020 17:27:32 +0800 Subject: [PATCH 001/146] =?UTF-8?q?[Update]=20=E6=97=B6=E5=8C=BA=E5=85=81?= =?UTF-8?q?=E8=AE=B8=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/conf.py | 4 +++- apps/jumpserver/settings/base.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 9beb2954a..3c7eea19a 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -202,7 +202,9 @@ class Config(dict): 'FORCE_SCRIPT_NAME': '', 'LOGIN_CONFIRM_ENABLE': False, 'WINDOWS_SKIP_ALL_MANUAL_PASSWORD': False, - 'ORG_CHANGE_TO_URL': '' + 'ORG_CHANGE_TO_URL': '', + 'LANGUAGE_CODE': 'zh', + 'TIME_ZONE': 'Asia/Shanghai' } def convert_type(self, k, v): diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 22ebd1775..2671f2cba 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -173,9 +173,9 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/1.10/topics/i18n/ # LANGUAGE_CODE = 'en' -LANGUAGE_CODE = 'zh' +LANGUAGE_CODE = CONFIG.LANGUAGE_CODE -TIME_ZONE = 'Asia/Shanghai' +TIME_ZONE = CONFIG.TIME_ZONE USE_I18N = True From e7031d0ac1a65b415a73992ba60268511c2afec1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 9 Apr 2020 10:33:20 +0800 Subject: [PATCH 002/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9serailizer?= =?UTF-8?q?=20mixin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/mixins/api.py | 2 +- apps/users/api/profile.py | 2 +- apps/users/models/user.py | 2 +- apps/users/serializers/user.py | 36 +++++++++++++++++++++++++++++++--- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/apps/common/mixins/api.py b/apps/common/mixins/api.py index 6440cdd10..613d66eba 100644 --- a/apps/common/mixins/api.py +++ b/apps/common/mixins/api.py @@ -37,7 +37,7 @@ class SerializerMixin: serializer_class = None if hasattr(self, 'serializer_classes') and \ isinstance(self.serializer_classes, dict): - if self.action == 'list' and self.request.query_params.get('draw'): + if self.action in ['list', 'metadata'] and self.request.query_params.get('draw'): serializer_class = self.serializer_classes.get('display') if serializer_class is None: serializer_class = self.serializer_classes.get( diff --git a/apps/users/api/profile.py b/apps/users/api/profile.py index 70e028a85..5ad7c2a00 100644 --- a/apps/users/api/profile.py +++ b/apps/users/api/profile.py @@ -57,7 +57,7 @@ class UserUpdatePKApi(UserQuerysetMixin, generics.UpdateAPIView): class UserProfileApi(generics.RetrieveAPIView): permission_classes = (IsAuthenticated,) - serializer_class = serializers.UserSerializer + serializer_class = serializers.UserProfileSerializer def get_object(self): return self.request.user diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 7e81340fd..169a30a00 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -481,7 +481,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): max_length=30, default='', blank=True, verbose_name=_('Created by') ) source = models.CharField( - max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES, + max_length=30, default=SOURCE_LDAP, choices=SOURCE_CHOICES, verbose_name=_('Source') ) date_password_last_updated = models.DateTimeField( diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index f2b3cc0bc..f4c2ee085 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -23,7 +23,16 @@ class UserOrgSerializer(serializers.Serializer): class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): - admin_orgs = UserOrgSerializer(many=True, read_only=True) + 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 = serializers.ChoiceField( + choices=PASSWORD_STRATEGY_CHOICES, required=True, initial=0, + label=_('Password strategy'), write_only=True + ) class Meta: model = User @@ -33,8 +42,9 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): 'groups', 'role', 'wechat', 'phone', 'mfa_level', 'comment', 'source', 'is_valid', 'is_expired', 'is_active', 'created_by', 'is_first_login', + 'password_strategy', 'date_password_last_updated', 'date_expired', - 'avatar_url', 'admin_orgs', + 'avatar_url', ] extra_kwargs = { 'password': {'write_only': True, 'required': False, 'allow_null': True, 'allow_blank': True}, @@ -46,6 +56,16 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): 'created_by': {'read_only': True, 'allow_blank': True}, } + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_role_choices() + + def set_role_choices(self): + role = self.fields['role'] + choices = role.choices + choices.pop('App', None) + role.choices = choices + def validate_role(self, value): request = self.context.get('request') if not request.user.is_superuser and value != User.ROLE_USER: @@ -92,6 +112,7 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): def validate(self, attrs): attrs = self.change_password_to_raw(attrs) attrs = self.clean_auth_fields(attrs) + attrs.pop('password_strategy', None) return attrs @@ -157,8 +178,17 @@ class ResetOTPSerializer(serializers.Serializer): class UserProfileSerializer(serializers.ModelSerializer): + admin_orgs = UserOrgSerializer(many=True, read_only=True) + class Meta: model = User fields = [ - 'id', 'username', 'name', 'role', 'email' + 'id', 'name', 'username', 'email', + 'role', 'wechat', 'phone', 'mfa_level', + 'comment', 'source', 'is_valid', 'is_expired', + 'is_active', 'created_by', 'is_first_login', + 'date_password_last_updated', 'date_expired', + 'avatar_url', + + 'groups', 'admin_orgs', ] From cd7946f3f015dd3f3f83b045f7cc4ca002dde02e Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 9 Apr 2020 18:58:22 +0800 Subject: [PATCH 003/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E4=B8=80?= =?UTF-8?q?=E4=BA=9Bbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/admin_user.py | 15 +-------------- apps/users/serializers/user.py | 6 +++--- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py index c77da1ae4..3780def5e 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -1,17 +1,4 @@ -# ~*~ coding: utf-8 ~*~ -# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved. -# -# Licensed under the GNU General Public License v2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.gnu.org/licenses/gpl-2.0.html -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. + from django.db import transaction from django.db.models import Count diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index f4c2ee085..c961cccb4 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -30,7 +30,7 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): (1, CUSTOM_PASSWORD) ) password_strategy = serializers.ChoiceField( - choices=PASSWORD_STRATEGY_CHOICES, required=True, initial=0, + choices=PASSWORD_STRATEGY_CHOICES, required=False, initial=0, label=_('Password strategy'), write_only=True ) @@ -62,9 +62,9 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): def set_role_choices(self): role = self.fields['role'] - choices = role.choices + choices = role._choices choices.pop('App', None) - role.choices = choices + role._choices = choices def validate_role(self, value): request = self.context.get('request') From 396bc9b6aee08d03f3a728df295073a559611317 Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 10 Apr 2020 12:47:16 +0800 Subject: [PATCH 004/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7ViewSet=E5=BA=8F=E5=88=97=E7=B1=BB=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E7=9A=84admin=5Forgs=EF=BC=88=E8=A7=A3=E5=86=B3=E7=BB=84?= =?UTF-8?q?=E7=BB=87=E7=AE=A1=E7=90=86=E5=91=98=E7=99=BB=E5=BD=95=E7=9A=84?= =?UTF-8?q?Bug)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/models/user.py | 8 ++++---- apps/users/serializers/user.py | 6 ++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 169a30a00..ce803064a 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -563,10 +563,10 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): user_default = settings.STATIC_URL + "img/avatar/user.png" return user_default - def admin_orgs(self): - from orgs.models import Organization - orgs = Organization.get_user_admin_or_audit_orgs(self) - return orgs + # def admin_orgs(self): + # from orgs.models import Organization + # orgs = Organization.get_user_admin_or_audit_orgs(self) + # return orgs def avatar_url(self): admin_default = settings.STATIC_URL + "img/avatar/admin.png" diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index c961cccb4..87ce80e5d 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -178,7 +178,7 @@ class ResetOTPSerializer(serializers.Serializer): class UserProfileSerializer(serializers.ModelSerializer): - admin_orgs = UserOrgSerializer(many=True, read_only=True) + admin_or_audit_orgs = UserOrgSerializer(many=True, read_only=True) class Meta: model = User @@ -188,7 +188,5 @@ class UserProfileSerializer(serializers.ModelSerializer): 'comment', 'source', 'is_valid', 'is_expired', 'is_active', 'created_by', 'is_first_login', 'date_password_last_updated', 'date_expired', - 'avatar_url', - - 'groups', 'admin_orgs', + 'avatar_url', 'groups', 'admin_or_audit_orgs', ] From 0d2b4d7ca37e7caaaa87d9a9462da2ee0cb151b5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 13 Apr 2020 10:40:13 +0800 Subject: [PATCH 005/146] =?UTF-8?q?[Update]=20=E6=B7=BB=E5=8A=A0ids=20filt?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/filters.py | 21 ++++++++++++++++++++- apps/common/mixins/api.py | 4 ++-- apps/orgs/middleware.py | 5 ++--- apps/orgs/utils.py | 2 +- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/common/drf/filters.py b/apps/common/drf/filters.py index c11a7864d..c5ee0b309 100644 --- a/apps/common/drf/filters.py +++ b/apps/common/drf/filters.py @@ -9,7 +9,7 @@ import logging from common import const -__all__ = ["DatetimeRangeFilter", "IDSpmFilter", "CustomFilter"] +__all__ = ["DatetimeRangeFilter", "IDSpmFilter", 'IDInFilter', "CustomFilter"] class DatetimeRangeFilter(filters.BaseFilterBackend): @@ -68,6 +68,25 @@ class IDSpmFilter(filters.BaseFilterBackend): return queryset +class IDInFilter(filters.BaseFilterBackend): + def get_schema_fields(self, view): + return [ + coreapi.Field( + name='ids', location='query', required=False, + type='string', example='/api/v1/users/users?ids=1,2,3', + description='Filter by id set' + ) + ] + + def filter_queryset(self, request, queryset, view): + ids = request.query_params.get('ids') + if not ids: + return queryset + id_list = [i.strip() for i in ids.split(',')] + queryset = queryset.filter(id__in=id_list) + return queryset + + class CustomFilter(filters.BaseFilterBackend): def get_schema_fields(self, view): diff --git a/apps/common/mixins/api.py b/apps/common/mixins/api.py index 613d66eba..079e9f038 100644 --- a/apps/common/mixins/api.py +++ b/apps/common/mixins/api.py @@ -9,7 +9,7 @@ from django.http import JsonResponse from rest_framework.response import Response from rest_framework.settings import api_settings -from common.drf.filters import IDSpmFilter, CustomFilter +from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter from ..utils import lazyproperty __all__ = [ @@ -49,7 +49,7 @@ class SerializerMixin: class ExtraFilterFieldsMixin: - default_added_filters = [CustomFilter, IDSpmFilter] + default_added_filters = [CustomFilter, IDSpmFilter, IDInFilter] filter_backends = api_settings.DEFAULT_FILTER_BACKENDS extra_filter_fields = [] extra_filter_backends = [] diff --git a/apps/orgs/middleware.py b/apps/orgs/middleware.py index 3e491d3d2..efbee2dde 100644 --- a/apps/orgs/middleware.py +++ b/apps/orgs/middleware.py @@ -34,8 +34,7 @@ class OrgMiddleware: def __call__(self, request): self.set_permed_org_if_need(request) org = get_org_from_request(request) - if org is not None: - request.current_org = org - set_current_org(org) + request.current_org = org + set_current_org(org) response = self.get_response(request) return response diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index d5ea4ca30..6870942da 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -24,7 +24,7 @@ def get_org_from_request(request): oid = Organization.DEFAULT_ID elif oid.lower() == "root": oid = Organization.ROOT_ID - org = Organization.get_instance(oid) + org = Organization.get_instance(oid, True) return org From 5f2c9c38019ea3a013d8a6f817034da08763e7a7 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 22 Apr 2020 15:13:04 +0800 Subject: [PATCH 006/146] =?UTF-8?q?[Update]=20=E6=B7=BB=E5=8A=A0=20authent?= =?UTF-8?q?ication=20backend=20header?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/api.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/authentication/backends/api.py b/apps/authentication/backends/api.py index 599351d0a..b61798695 100644 --- a/apps/authentication/backends/api.py +++ b/apps/authentication/backends/api.py @@ -106,6 +106,9 @@ class AccessKeyAuthentication(authentication.BaseAuthentication): raise exceptions.AuthenticationFailed(_('User disabled.')) return access_key.user, None + def authenticate_header(self, request): + return 'Sign access_key_id:Signature' + class AccessTokenAuthentication(authentication.BaseAuthentication): keyword = 'Bearer' @@ -143,6 +146,9 @@ class AccessTokenAuthentication(authentication.BaseAuthentication): raise exceptions.AuthenticationFailed(msg) return user, None + def authenticate_header(self, request): + return self.keyword + class PrivateTokenAuthentication(authentication.TokenAuthentication): model = PrivateToken From efb5d4135af79566689e0920643f596ff9d14326 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 23 Apr 2020 11:14:02 +0800 Subject: [PATCH 007/146] =?UTF-8?q?[Update]=20=E4=BC=98=E5=8C=96api?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/admin_user.py | 2 +- apps/assets/serializers/asset.py | 42 ++++++---- apps/common/mixins/serializers.py | 122 +++++++++++++++++++++++++++++- apps/orgs/mixins/serializers.py | 4 +- apps/users/api/user.py | 1 - apps/users/serializers/user.py | 54 ++++++------- 6 files changed, 174 insertions(+), 51 deletions(-) diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py index 3780def5e..58fc78f71 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -36,7 +36,7 @@ class AdminUserViewSet(OrgBulkModelViewSet): def get_queryset(self): queryset = super().get_queryset() - queryset = queryset.annotate(_assets_amount=Count('assets')) + queryset = queryset.annotate(assets_amount=Count('assets')) return queryset def destroy(self, request, *args, **kwargs): diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index 82d6964a4..3f9a3eb84 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from rest_framework import serializers -from django.db.models import Prefetch, F +from django.db.models import Prefetch, F, Count from django.utils.translation import ugettext_lazy as _ @@ -73,21 +73,35 @@ class AssetSerializer(BulkOrgResourceModelSerializer): class Meta: model = Asset list_serializer_class = AdaptedBulkListSerializer - fields = [ - 'id', 'ip', 'hostname', 'protocol', 'port', - 'protocols', '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', + fields_mini = ['id', 'hostname', 'ip'] + fields_small = fields_mini + [ + 'protocol', 'port', 'protocols', 'is_active', 'public_ip', + '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', ] - read_only_fields = ( + fields_fk = [ + 'admin_user', 'domain', 'platform' + ] + fk_only_fields = { + 'platform': ['name'] + } + fields_m2m = [ + 'nodes', 'labels', + ] + annotates_fields = { + # 'admin_user_display': 'admin_user__name' + } + fields_as = list(annotates_fields.keys()) + fields = fields_small + fields_fk + fields_m2m + fields_as + read_only_fields = [ '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', - ) + ] + fields_as + extra_kwargs = { 'protocol': {'write_only': True}, 'port': {'write_only': True}, @@ -98,11 +112,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.prefetch_related( - Prefetch('nodes', queryset=Node.objects.all().only('id')), - Prefetch('labels', queryset=Label.objects.all().only('id')), - ).select_related('admin_user', 'domain', 'platform') \ - .annotate(platform_base=F('platform__base')) + queryset = queryset.select_related('admin_user', 'domain', 'platform') return queryset def compatible_with_old_protocol(self, validated_data): diff --git a/apps/common/mixins/serializers.py b/apps/common/mixins/serializers.py index 492a7cf98..7f12ef872 100644 --- a/apps/common/mixins/serializers.py +++ b/apps/common/mixins/serializers.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- # +from collections import Iterable +from django.db.models import Prefetch, F from django.core.exceptions import ObjectDoesNotExist 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, empty -__all__ = ['BulkSerializerMixin', 'BulkListSerializerMixin'] +__all__ = ['BulkSerializerMixin', 'BulkListSerializerMixin', 'CommonSerializerMixin'] class BulkSerializerMixin(object): @@ -113,3 +115,121 @@ class BulkListSerializerMixin(object): raise ValidationError(errors) return ret + + +class BaseDynamicFieldsPlugin: + def __init__(self, serializer): + self.serializer = serializer + + def can_dynamic(self): + try: + request = self.serializer.context['request'] + method = request.method + except (AttributeError, TypeError, KeyError): + # The serializer was not initialized with request context. + return False + + if method != 'GET': + return False + return True + + def get_request(self): + return self.serializer.context['request'] + + def get_query_params(self): + request = self.get_request() + try: + query_params = request.query_params + except AttributeError: + # DRF 2 + query_params = getattr(request, 'QUERY_PARAMS', request.GET) + return query_params + + def get_exclude_field_names(self): + return set() + + +class QueryFieldsMixin(BaseDynamicFieldsPlugin): + # https://github.com/wimglenn/djangorestframework-queryfields/ + + # If using Django filters in the API, these labels mustn't conflict with any model field names. + include_arg_name = 'fields' + exclude_arg_name = 'fields!' + + # Split field names by this string. It doesn't necessarily have to be a single character. + # Avoid RFC 1738 reserved characters i.e. ';', '/', '?', ':', '@', '=' and '&' + delimiter = ',' + + def get_exclude_field_names(self): + query_params = self.get_query_params() + includes = query_params.getlist(self.include_arg_name) + include_field_names = {name for names in includes for name in names.split(self.delimiter) if name} + + excludes = query_params.getlist(self.exclude_arg_name) + exclude_field_names = {name for names in excludes for name in names.split(self.delimiter) if name} + + if not include_field_names and not exclude_field_names: + # No user fields filtering was requested, we have nothing to do here. + return [] + + serializer_field_names = set(self.serializer.fields) + fields_to_drop = serializer_field_names & exclude_field_names + + if include_field_names: + fields_to_drop |= serializer_field_names - include_field_names + return fields_to_drop + + +class SizedModelFieldsMixin(BaseDynamicFieldsPlugin): + arg_name = 'fields_size' + + def can_dynamic(self): + if not hasattr(self.serializer, 'Meta'): + return False + can = super().can_dynamic() + return can + + def get_exclude_field_names(self): + query_params = self.get_query_params() + size = query_params.get(self.arg_name) + if not size: + return [] + size_fields = getattr(self.serializer.Meta, 'fields_{}'.format(size), None) + if not size_fields or not isinstance(size_fields, Iterable): + return [] + serializer_field_names = set(self.serializer.fields) + fields_to_drop = serializer_field_names - set(size_fields) + return fields_to_drop + + +class DynamicFieldsMixin: + dynamic_fields_plugins = [QueryFieldsMixin, SizedModelFieldsMixin] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + exclude_field_names = set() + for cls in self.dynamic_fields_plugins: + plugin = cls(self) + if not plugin.can_dynamic(): + continue + exclude_field_names |= set(plugin.get_exclude_field_names()) + + for field in exclude_field_names or []: + self.fields.pop(field, None) + + +class EagerLoadQuerySetFields: + def setup_eager_loading(self, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.prefetch_related( + Prefetch('nodes'), + Prefetch('labels'), + ).select_related('admin_user', 'domain', 'platform') \ + .annotate(platform_base=F('platform__base')) + return queryset + + +class CommonSerializerMixin(DynamicFieldsMixin): + pass + diff --git a/apps/orgs/mixins/serializers.py b/apps/orgs/mixins/serializers.py index a34f8d9b1..2b415e31b 100644 --- a/apps/orgs/mixins/serializers.py +++ b/apps/orgs/mixins/serializers.py @@ -5,7 +5,7 @@ from rest_framework import serializers from rest_framework.validators import UniqueTogetherValidator from common.validators import ProjectUniqueValidator -from common.mixins import BulkSerializerMixin +from common.mixins import BulkSerializerMixin, CommonSerializerMixin from ..utils import get_current_org_id_for_serializer @@ -16,7 +16,7 @@ __all__ = [ ] -class OrgResourceSerializerMixin(serializers.Serializer): +class OrgResourceSerializerMixin(CommonSerializerMixin, serializers.Serializer): """ 通过API批量操作资源时, 自动给每个资源添加所需属性org_id的值为current_org_id (同时为serializer.is_valid()对Model的unique_together校验做准备) diff --git a/apps/users/api/user.py b/apps/users/api/user.py index fac0fabca..6ca9bb7a1 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -31,7 +31,6 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet): search_fields = filter_fields serializer_classes = { 'default': serializers.UserSerializer, - 'display': serializers.UserDisplaySerializer } permission_classes = (IsOrgAdmin, CanUpdateDeleteUser) diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 35002da44..9cded6e58 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from common.utils import validate_ssh_public_key -from common.mixins import BulkSerializerMixin +from common.mixins import CommonSerializerMixin from common.serializers import AdaptedBulkListSerializer from common.permissions import CanUpdateDeleteUser from ..models import User @@ -13,7 +13,7 @@ from ..models import User __all__ = [ 'UserSerializer', 'UserPKUpdateSerializer', 'ChangeUserPasswordSerializer', 'ResetOTPSerializer', - 'UserProfileSerializer', 'UserDisplaySerializer', + 'UserProfileSerializer', ] @@ -22,7 +22,7 @@ class UserOrgSerializer(serializers.Serializer): name = serializers.CharField() -class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): +class UserSerializer(CommonSerializerMixin, serializers.ModelSerializer): EMAIL_SET_PASSWORD = _('Reset link will be generated and sent to the user') CUSTOM_PASSWORD = _('Set password') PASSWORD_STRATEGY_CHOICES = ( @@ -33,18 +33,27 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): choices=PASSWORD_STRATEGY_CHOICES, required=False, initial=0, label=_('Password strategy'), write_only=True ) + can_update = serializers.SerializerMethodField() + can_delete = serializers.SerializerMethodField() class Meta: model = User list_serializer_class = AdaptedBulkListSerializer - fields = [ - 'id', 'name', 'username', 'password', 'email', 'public_key', - 'groups', 'role', 'wechat', 'phone', 'mfa_level', + # mini 是指能识别对象的最小单元 + fields_mini = ['id', 'name', 'username'] + # small 指的是 不需要计算的直接能从一张表中获取到的数据 + fields_small = fields_mini + [ + 'password', 'email', 'public_key', 'wechat', 'phone', 'mfa_level', 'comment', 'source', 'is_valid', 'is_expired', 'is_active', 'created_by', 'is_first_login', 'password_strategy', 'date_password_last_updated', 'date_expired', - 'avatar_url', + 'avatar_url', 'source_display', ] + fields = fields_small + [ + 'groups', 'role', 'groups_display', 'role_display', + 'can_update', 'can_delete' + ] + extra_kwargs = { 'password': {'write_only': True, 'required': False, 'allow_null': True, 'allow_blank': True}, 'public_key': {'write_only': True}, @@ -53,6 +62,11 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): 'is_expired': {'label': _('Is expired')}, 'avatar_url': {'label': _('Avatar url')}, 'created_by': {'read_only': True, 'allow_blank': True}, + 'can_update': {'read_only': True}, + 'can_delete': {'read_only': True}, + 'groups_display': {'label': _('Groups name')}, + 'source_display': {'label': _('Source name')}, + 'role_display': {'label': _('Role name')}, } def __init__(self, *args, **kwargs): @@ -60,7 +74,9 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): self.set_role_choices() def set_role_choices(self): - role = self.fields['role'] + role = self.fields.get('role') + if not role: + return choices = role._choices choices.pop('App', None) role._choices = choices @@ -114,17 +130,6 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): attrs.pop('password_strategy', None) return attrs - -class UserDisplaySerializer(UserSerializer): - can_update = serializers.SerializerMethodField() - can_delete = serializers.SerializerMethodField() - - class Meta(UserSerializer.Meta): - fields = UserSerializer.Meta.fields + [ - 'groups_display', 'role_display', 'source_display', - 'can_update', 'can_delete', - ] - def get_can_update(self, obj): return CanUpdateDeleteUser.has_update_object_permission( self.context['request'], self.context['view'], obj @@ -135,17 +140,6 @@ class UserDisplaySerializer(UserSerializer): self.context['request'], self.context['view'], obj ) - def get_extra_kwargs(self): - kwargs = super().get_extra_kwargs() - kwargs.update({ - 'can_update': {'read_only': True}, - 'can_delete': {'read_only': True}, - 'groups_display': {'label': _('Groups name')}, - 'source_display': {'label': _('Source name')}, - 'role_display': {'label': _('Role name')}, - }) - return kwargs - class UserPKUpdateSerializer(serializers.ModelSerializer): class Meta: From aa9533eb5b4dafea99c0ee85f81f9d8860107095 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 29 Apr 2020 10:19:25 +0800 Subject: [PATCH 008/146] =?UTF-8?q?[Update]=20Groups=20users=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E5=8F=AF=E4=BB=A5=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/serializers/group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/users/serializers/group.py b/apps/users/serializers/group.py index 67b21668c..078ceb8a8 100644 --- a/apps/users/serializers/group.py +++ b/apps/users/serializers/group.py @@ -18,7 +18,7 @@ __all__ = [ class UserGroupSerializer(BulkOrgResourceModelSerializer): users = serializers.PrimaryKeyRelatedField( required=False, many=True, queryset=User.objects, label=_('User'), - write_only=True + # write_only=True, group can return many to many on detail ) class Meta: @@ -38,7 +38,7 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer): def set_fields_queryset(self): users_field = self.fields['users'] - users_field.child_relation.queryset = utils.get_current_org_members() + users_field.child_relation.queryset = utils.get_current_org_members(exclude=('Auditor',)) def validate_users(self, users): for user in users: From 20cf7c7c52038bd2979568684ac5ad7596049246 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 29 Apr 2020 11:08:09 +0800 Subject: [PATCH 009/146] [Update] Add settings api --- apps/settings/api.py | 64 ++++++++++++++- apps/settings/serializers/__init__.py | 1 + apps/settings/serializers/settings.py | 113 ++++++++++++++++++++++++++ apps/settings/urls/api_urls.py | 6 ++ apps/settings/utils/__init__.py | 1 + apps/settings/utils/common.py | 18 ++++ 6 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 apps/settings/serializers/settings.py create mode 100644 apps/settings/utils/common.py diff --git a/apps/settings/api.py b/apps/settings/api.py index 38167a7e3..69f0129f8 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -12,18 +12,19 @@ from django.utils.translation import ugettext_lazy as _ from .utils import ( LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil, - LDAP_USE_CACHE_FLAGS, LDAPTestUtil, + LDAP_USE_CACHE_FLAGS, LDAPTestUtil, ObjectDict ) from .tasks import sync_ldap_user_task from common.permissions import IsOrgAdmin, IsSuperUser from common.utils import get_logger from .serializers import ( MailTestSerializer, LDAPTestConfigSerializer, LDAPUserSerializer, - PublicSettingSerializer, LDAPTestLoginSerializer, + PublicSettingSerializer, LDAPTestLoginSerializer, BaseSettingSerializer, + BasicSettingSerializer, EmailContentSettingSerializer, EmailSettingSerializer, + SecuritySettingSerializer, LdapSettingSerializer, TerminalSettingSerializer ) from users.models import User - logger = get_logger(__file__) @@ -59,7 +60,7 @@ class MailTestingAPI(APIView): use_tls=email_use_tls, use_ssl=email_use_ssl, ) send_mail( - subject, message, email_from, [email_recipient], + subject, message, email_from, [email_recipient], connection=connection ) except SMTPSenderRefused as e: @@ -275,3 +276,58 @@ class PublicSettingApi(generics.RetrieveAPIView): return instance +class BaseSettingApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsSuperUser,) + serializer_class = BaseSettingSerializer + setting_fields = [] + + def get_object(self): + instance = {field: getattr(settings, field) for field in self.setting_fields} + return ObjectDict(instance) + + def perform_update(self, serializer): + serializer.save() + + +class BasicSettingApi(BaseSettingApi): + serializer_class = BasicSettingSerializer + setting_fields = ['SITE_URL', 'USER_GUIDE_URL', 'EMAIL_SUBJECT_PREFIX'] + + +class EmailSettingApi(BaseSettingApi): + serializer_class = EmailSettingSerializer + setting_fields = ['EMAIL_HOST', 'EMAIL_PORT', 'EMAIL_HOST_USER', + 'EMAIL_HOST_PASSWORD', 'EMAIL_FROM', 'EMAIL_RECIPIENT', + 'EMAIL_USE_SSL', 'EMAIL_USE_TLS'] + + +class EmailContentSettingApi(BaseSettingApi): + serializer_class = EmailContentSettingSerializer + setting_fields = ['EMAIL_CUSTOM_USER_CREATED_SUBJECT', 'EMAIL_CUSTOM_USER_CREATED_HONORIFIC', + 'EMAIL_CUSTOM_USER_CREATED_BODY', 'EMAIL_CUSTOM_USER_CREATED_SIGNATURE', ] + + +class LdapSettingApi(BaseSettingApi): + serializer_class = LdapSettingSerializer + setting_fields = ['AUTH_LDAP_SERVER_URI', 'AUTH_LDAP_BIND_DN', + 'AUTH_LDAP_BIND_PASSWORD', 'AUTH_LDAP_SEARCH_OU', + 'AUTH_LDAP_SEARCH_FILTER', 'AUTH_LDAP_USER_ATTR_MAP', + 'AUTH_LDAP'] + + +class TerminalSettingApi(BaseSettingApi): + serializer_class = TerminalSettingSerializer + setting_fields = ['TERMINAL_PASSWORD_AUTH', 'TERMINAL_PUBLIC_KEY_AUTH', + 'TERMINAL_HEARTBEAT_INTERVAL', 'TERMINAL_ASSET_LIST_SORT_BY', + 'TERMINAL_ASSET_LIST_PAGE_SIZE', 'TERMINAL_SESSION_KEEP_DURATION', + 'TERMINAL_TELNET_REGEX'] + + +class SecuritySettingApi(BaseSettingApi): + serializer_class = SecuritySettingSerializer + setting_fields = ['SECURITY_MFA_AUTH', 'SECURITY_COMMAND_EXECUTION', + 'SECURITY_SERVICE_ACCOUNT_REGISTRATION', 'SECURITY_LOGIN_LIMIT_COUNT', + 'SECURITY_LOGIN_LIMIT_TIME', 'SECURITY_MAX_IDLE_TIME', + 'SECURITY_PASSWORD_EXPIRATION_TIME', 'SECURITY_PASSWORD_MIN_LENGTH', + 'SECURITY_PASSWORD_UPPER_CASE', 'SECURITY_PASSWORD_LOWER_CASE', + 'SECURITY_PASSWORD_NUMBER', 'SECURITY_PASSWORD_SPECIAL_CHAR'] diff --git a/apps/settings/serializers/__init__.py b/apps/settings/serializers/__init__.py index 045763364..5868a76df 100644 --- a/apps/settings/serializers/__init__.py +++ b/apps/settings/serializers/__init__.py @@ -4,3 +4,4 @@ from .email import * from .ldap import * from .public import * +from .settings import * diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py new file mode 100644 index 000000000..de22ba2eb --- /dev/null +++ b/apps/settings/serializers/settings.py @@ -0,0 +1,113 @@ +# coding: utf-8 + +from django.db import transaction +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers +from ..models import Setting + +__all__ = ['BaseSettingSerializer', 'BasicSettingSerializer', 'EmailSettingSerializer', + "EmailContentSettingSerializer", 'LdapSettingSerializer', + 'TerminalSettingSerializer', 'SecuritySettingSerializer'] + + +class BaseSettingSerializer(serializers.Serializer): + encrypt_fields = ["EMAIL_HOST_PASSWORD", "AUTH_LDAP_BIND_PASSWORD"] + + def create(self, validated_data): + pass + + def update(self, instance, validated_data): + self.update_validated_settings(validated_data) + for field_name, field_value in validated_data.items(): + setattr(instance, field_name, field_value) + return instance + + def update_validated_settings(self, validated_data, category='default'): + with transaction.atomic(): + for field_name, field_value in validated_data.items(): + try: + setting = Setting.objects.get(name=field_name) + except Setting.DoesNotExist: + setting = Setting() + encrypted = True if field_name in self.encrypt_fields else False + setting.name = field_name + setting.category = category + setting.encrypted = encrypted + setting.cleaned_value = field_value + setting.save() + + +class BasicSettingSerializer(BaseSettingSerializer): + SITE_URL = serializers.URLField(required=True) + USER_GUIDE_URL = serializers.URLField(required=False) + EMAIL_SUBJECT_PREFIX = serializers.CharField(max_length=1024, required=True) + + +class EmailSettingSerializer(BaseSettingSerializer): + encrypt_fields = ["EMAIL_HOST_PASSWORD", ] + + EMAIL_HOST = serializers.CharField(max_length=1024, required=True) + EMAIL_PORT = serializers.CharField(max_length=5, required=True) + EMAIL_HOST_USER = serializers.CharField(max_length=128, required=True) + EMAIL_HOST_PASSWORD = serializers.CharField(max_length=1024, required=False, write_only=True) + EMAIL_FROM = serializers.CharField(max_length=128, required=False) + EMAIL_RECIPIENT = serializers.CharField(max_length=128, allow_blank='', required=False) + EMAIL_USE_SSL = serializers.BooleanField(required=False) + EMAIL_USE_TLS = serializers.BooleanField(required=False) + + +class EmailContentSettingSerializer(BaseSettingSerializer): + EMAIL_CUSTOM_USER_CREATED_SUBJECT = serializers.CharField(max_length=1024, required=False, ) + EMAIL_CUSTOM_USER_CREATED_HONORIFIC = serializers.CharField(max_length=1024, required=False, ) + EMAIL_CUSTOM_USER_CREATED_BODY = serializers.CharField(max_length=4096, required=False) + EMAIL_CUSTOM_USER_CREATED_SIGNATURE = serializers.CharField(max_length=512, required=False) + + +class LdapSettingSerializer(BaseSettingSerializer): + encrypt_fields = ["AUTH_LDAP_BIND_PASSWORD", ] + + AUTH_LDAP_SERVER_URI = serializers.CharField(required=True) + AUTH_LDAP_BIND_DN = serializers.CharField(required=False) + AUTH_LDAP_BIND_PASSWORD = serializers.CharField(max_length=1024, write_only=True) + AUTH_LDAP_SEARCH_OU = serializers.CharField(max_length=1024, required=False) + AUTH_LDAP_SEARCH_FILTER = serializers.CharField(max_length=1024, required=True) + AUTH_LDAP_USER_ATTR_MAP = serializers.CharField(max_length=1024, required=True) + AUTH_LDAP = serializers.BooleanField(required=False) + + +class TerminalSettingSerializer(BaseSettingSerializer): + SORT_BY_CHOICES = ( + ('hostname', _('Hostname')), + ('ip', _('IP')) + ) + + PAGE_SIZE_CHOICES = ( + ('all', _('All')), + ('auto', _('Auto')), + (10, 10), + (15, 15), + (25, 25), + (50, 50), + ) + TERMINAL_PASSWORD_AUTH = serializers.BooleanField(required=False) + TERMINAL_PUBLIC_KEY_AUTH = serializers.BooleanField(required=False) + TERMINAL_HEARTBEAT_INTERVAL = serializers.IntegerField(min_value=5, max_value=99999, required=True) + TERMINAL_ASSET_LIST_SORT_BY = serializers.ChoiceField(SORT_BY_CHOICES) + TERMINAL_ASSET_LIST_PAGE_SIZE = serializers.ChoiceField(PAGE_SIZE_CHOICES) + TERMINAL_SESSION_KEEP_DURATION = serializers.IntegerField(min_value=1, max_value=99999, required=True) + TERMINAL_TELNET_REGEX = serializers.CharField(required=False) + + +class SecuritySettingSerializer(BaseSettingSerializer): + SECURITY_MFA_AUTH = serializers.BooleanField(required=False) + SECURITY_COMMAND_EXECUTION = serializers.BooleanField(required=False) + SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField(required=True) + SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField(min_value=3, max_value=99999, required=True) + SECURITY_LOGIN_LIMIT_TIME = serializers.IntegerField(min_value=5, max_value=99999, required=True) + SECURITY_MAX_IDLE_TIME = serializers.IntegerField(min_value=5, max_value=99999, required=False) + SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField(min_value=1, max_value=99999, required=True) + SECURITY_PASSWORD_MIN_LENGTH = serializers.IntegerField(min_value=6, max_value=30, required=True) + SECURITY_PASSWORD_UPPER_CASE = serializers.BooleanField(required=False) + SECURITY_PASSWORD_LOWER_CASE = serializers.BooleanField(required=False) + SECURITY_PASSWORD_NUMBER = serializers.BooleanField(required=False) + SECURITY_PASSWORD_SPECIAL_CHAR = serializers.BooleanField(required=False) diff --git a/apps/settings/urls/api_urls.py b/apps/settings/urls/api_urls.py index 689e1ea82..093bc9c15 100644 --- a/apps/settings/urls/api_urls.py +++ b/apps/settings/urls/api_urls.py @@ -14,5 +14,11 @@ urlpatterns = [ path('ldap/users/import/', api.LDAPUserImportAPI.as_view(), name='ldap-user-import'), path('ldap/cache/refresh/', api.LDAPCacheRefreshAPI.as_view(), name='ldap-cache-refresh'), + path('basic/', api.BasicSettingApi.as_view(), name='basic-settings'), + path('email/', api.EmailSettingApi.as_view(), name='email-settings'), + path('email-content/', api.EmailContentSettingApi.as_view(), name='email-content-settings'), + path('ldap/', api.LdapSettingApi.as_view(), name='ldap-settings'), + path('terminal/', api.TerminalSettingApi.as_view(), name='terminal-settings'), + path('security/', api.SecuritySettingApi.as_view(), name='security-settings'), path('public/', api.PublicSettingApi.as_view(), name='public-setting'), ] diff --git a/apps/settings/utils/__init__.py b/apps/settings/utils/__init__.py index 87bc6198f..e17c4e43c 100644 --- a/apps/settings/utils/__init__.py +++ b/apps/settings/utils/__init__.py @@ -2,3 +2,4 @@ # from .ldap import * +from .common import * diff --git a/apps/settings/utils/common.py b/apps/settings/utils/common.py new file mode 100644 index 000000000..e64ceaf7f --- /dev/null +++ b/apps/settings/utils/common.py @@ -0,0 +1,18 @@ +# coding: utf-8 + + +class ObjectDict(dict): + def __getattr__(self, name): + if name in self: + return self[name] + else: + raise AttributeError("No such attribute: " + name) + + def __setattr__(self, name, value): + self[name] = value + + def __delattr__(self, name): + if name in self: + del self[name] + else: + raise AttributeError("No such attribute: " + name) From 5c4dfabc48e0f0f5c67d951e8ffe914b01b1a9e9 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 29 Apr 2020 14:32:51 +0800 Subject: [PATCH 010/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/settings/models.py b/apps/settings/models.py index 75b56bb54..30732a00c 100644 --- a/apps/settings/models.py +++ b/apps/settings/models.py @@ -10,7 +10,8 @@ from common.utils import signer class SettingQuerySet(models.QuerySet): def __getattr__(self, item): - instances = self.filter(name=item) + queryset = list(self) + instances = [i for i in queryset if i.name == item] if len(instances) == 1: return instances[0] else: From f8c323cf5c4cf4834c6648d143482637fe13f308 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 29 Apr 2020 16:01:14 +0800 Subject: [PATCH 011/146] =?UTF-8?q?[Update]=20=E4=BC=98=E5=8C=96user=20gro?= =?UTF-8?q?up=20serializer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/serializers/group.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/users/serializers/group.py b/apps/users/serializers/group.py index 078ceb8a8..01fa104fc 100644 --- a/apps/users/serializers/group.py +++ b/apps/users/serializers/group.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from django.utils.translation import ugettext_lazy as _ - +from django.db.models import Prefetch from rest_framework import serializers from common.serializers import AdaptedBulkListSerializer @@ -18,15 +18,18 @@ __all__ = [ class UserGroupSerializer(BulkOrgResourceModelSerializer): users = serializers.PrimaryKeyRelatedField( required=False, many=True, queryset=User.objects, label=_('User'), - # write_only=True, group can return many to many on detail + # write_only=True, # group can return many to many on detail ) class Meta: model = UserGroup list_serializer_class = AdaptedBulkListSerializer - fields = [ - 'id', 'name', 'users', 'users_amount', 'comment', - 'date_created', 'created_by', + fields_mini = ['id', 'name'] + fields_small = fields_mini + [ + 'comment', 'date_created', 'created_by' + ] + fields = fields_mini + fields_small + [ + 'users', 'users_amount', ] extra_kwargs = { 'created_by': {'label': _('Created by'), 'read_only': True} @@ -50,5 +53,7 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer): @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.annotate(users_amount=Count('users')) + queryset = queryset.prefetch_related( + Prefetch('users', queryset=User.objects.only('id')) + ).annotate(users_amount=Count('users')) return queryset From 2d18acf6f70cad9340d1dd2edb1123dfe4702802 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 29 Apr 2020 17:04:48 +0800 Subject: [PATCH 012/146] [Update] settings api --- apps/settings/api.py | 100 ++++++++++++------------- apps/settings/serializers/settings.py | 101 ++++++++++++++------------ apps/settings/urls/api_urls.py | 7 +- 3 files changed, 107 insertions(+), 101 deletions(-) diff --git a/apps/settings/api.py b/apps/settings/api.py index 69f0129f8..f6021f3f2 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -2,7 +2,7 @@ # import json - +from collections.abc import Iterable from smtplib import SMTPSenderRefused from rest_framework import generics from rest_framework.views import Response, APIView @@ -19,9 +19,7 @@ from common.permissions import IsOrgAdmin, IsSuperUser from common.utils import get_logger from .serializers import ( MailTestSerializer, LDAPTestConfigSerializer, LDAPUserSerializer, - PublicSettingSerializer, LDAPTestLoginSerializer, BaseSettingSerializer, - BasicSettingSerializer, EmailContentSettingSerializer, EmailSettingSerializer, - SecuritySettingSerializer, LdapSettingSerializer, TerminalSettingSerializer + PublicSettingSerializer, LDAPTestLoginSerializer, SettingsSerializer ) from users.models import User @@ -276,58 +274,60 @@ class PublicSettingApi(generics.RetrieveAPIView): return instance -class BaseSettingApi(generics.RetrieveUpdateAPIView): - permission_classes = (IsSuperUser,) - serializer_class = BaseSettingSerializer - setting_fields = [] +class SettingsApi(generics.RetrieveUpdateAPIView): + serializer_class = SettingsSerializer + BASIC_CATEGORY = ['SITE_URL', 'USER_GUIDE_URL', 'EMAIL_SUBJECT_PREFIX'] + + EMAIL_CATEGORY = ['EMAIL_HOST', 'EMAIL_PORT', 'EMAIL_HOST_USER', + 'EMAIL_HOST_PASSWORD', 'EMAIL_FROM', 'EMAIL_RECIPIENT', + 'EMAIL_USE_SSL', 'EMAIL_USE_TLS'] + + EMAIL_CONTENT_CATEGORY = ['EMAIL_CUSTOM_USER_CREATED_SUBJECT', 'EMAIL_CUSTOM_USER_CREATED_HONORIFIC', + 'EMAIL_CUSTOM_USER_CREATED_BODY', 'EMAIL_CUSTOM_USER_CREATED_SIGNATURE', ] + + LDAP_CATEGORY = ['AUTH_LDAP_SERVER_URI', 'AUTH_LDAP_BIND_DN', + 'AUTH_LDAP_BIND_PASSWORD', 'AUTH_LDAP_SEARCH_OU', + 'AUTH_LDAP_SEARCH_FILTER', 'AUTH_LDAP_USER_ATTR_MAP', + 'AUTH_LDAP'] + + TERMINAL_CATEGORY = ['TERMINAL_PASSWORD_AUTH', 'TERMINAL_PUBLIC_KEY_AUTH', + 'TERMINAL_HEARTBEAT_INTERVAL', 'TERMINAL_ASSET_LIST_SORT_BY', + 'TERMINAL_ASSET_LIST_PAGE_SIZE', 'TERMINAL_SESSION_KEEP_DURATION', + 'TERMINAL_TELNET_REGEX'] + + SECURITY_CATEGORY = ['SECURITY_MFA_AUTH', 'SECURITY_COMMAND_EXECUTION', + 'SECURITY_SERVICE_ACCOUNT_REGISTRATION', 'SECURITY_LOGIN_LIMIT_COUNT', + 'SECURITY_LOGIN_LIMIT_TIME', 'SECURITY_MAX_IDLE_TIME', + 'SECURITY_PASSWORD_EXPIRATION_TIME', 'SECURITY_PASSWORD_MIN_LENGTH', + 'SECURITY_PASSWORD_UPPER_CASE', 'SECURITY_PASSWORD_LOWER_CASE', + 'SECURITY_PASSWORD_NUMBER', 'SECURITY_PASSWORD_SPECIAL_CHAR'] + + SETTING_CATEGORIES = { + "basic": BASIC_CATEGORY, + 'email': EMAIL_CATEGORY, + 'email_content': EMAIL_CONTENT_CATEGORY, + 'ldap': LDAP_CATEGORY, + 'terminal': TERMINAL_CATEGORY, + 'security': SECURITY_CATEGORY + } def get_object(self): - instance = {field: getattr(settings, field) for field in self.setting_fields} + instance = {category_name: self._get_setting_fields_obj(category_fields) + for category_name, category_fields in self.SETTING_CATEGORIES.items()} + return ObjectDict(instance) def perform_update(self, serializer): serializer.save() + def _get_setting_fields_obj(self, category_fields): + if isinstance(category_fields, Iterable): + fields_data = {field_name: getattr(settings, field_name) + for field_name in category_fields} + return ObjectDict(fields_data) -class BasicSettingApi(BaseSettingApi): - serializer_class = BasicSettingSerializer - setting_fields = ['SITE_URL', 'USER_GUIDE_URL', 'EMAIL_SUBJECT_PREFIX'] + if isinstance(category_fields, str): + fields_data = {category_fields: getattr(settings, category_fields)} + return ObjectDict(fields_data) - -class EmailSettingApi(BaseSettingApi): - serializer_class = EmailSettingSerializer - setting_fields = ['EMAIL_HOST', 'EMAIL_PORT', 'EMAIL_HOST_USER', - 'EMAIL_HOST_PASSWORD', 'EMAIL_FROM', 'EMAIL_RECIPIENT', - 'EMAIL_USE_SSL', 'EMAIL_USE_TLS'] - - -class EmailContentSettingApi(BaseSettingApi): - serializer_class = EmailContentSettingSerializer - setting_fields = ['EMAIL_CUSTOM_USER_CREATED_SUBJECT', 'EMAIL_CUSTOM_USER_CREATED_HONORIFIC', - 'EMAIL_CUSTOM_USER_CREATED_BODY', 'EMAIL_CUSTOM_USER_CREATED_SIGNATURE', ] - - -class LdapSettingApi(BaseSettingApi): - serializer_class = LdapSettingSerializer - setting_fields = ['AUTH_LDAP_SERVER_URI', 'AUTH_LDAP_BIND_DN', - 'AUTH_LDAP_BIND_PASSWORD', 'AUTH_LDAP_SEARCH_OU', - 'AUTH_LDAP_SEARCH_FILTER', 'AUTH_LDAP_USER_ATTR_MAP', - 'AUTH_LDAP'] - - -class TerminalSettingApi(BaseSettingApi): - serializer_class = TerminalSettingSerializer - setting_fields = ['TERMINAL_PASSWORD_AUTH', 'TERMINAL_PUBLIC_KEY_AUTH', - 'TERMINAL_HEARTBEAT_INTERVAL', 'TERMINAL_ASSET_LIST_SORT_BY', - 'TERMINAL_ASSET_LIST_PAGE_SIZE', 'TERMINAL_SESSION_KEEP_DURATION', - 'TERMINAL_TELNET_REGEX'] - - -class SecuritySettingApi(BaseSettingApi): - serializer_class = SecuritySettingSerializer - setting_fields = ['SECURITY_MFA_AUTH', 'SECURITY_COMMAND_EXECUTION', - 'SECURITY_SERVICE_ACCOUNT_REGISTRATION', 'SECURITY_LOGIN_LIMIT_COUNT', - 'SECURITY_LOGIN_LIMIT_TIME', 'SECURITY_MAX_IDLE_TIME', - 'SECURITY_PASSWORD_EXPIRATION_TIME', 'SECURITY_PASSWORD_MIN_LENGTH', - 'SECURITY_PASSWORD_UPPER_CASE', 'SECURITY_PASSWORD_LOWER_CASE', - 'SECURITY_PASSWORD_NUMBER', 'SECURITY_PASSWORD_SPECIAL_CHAR'] + return ObjectDict() diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index de22ba2eb..d8dc8d708 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -5,77 +5,48 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from ..models import Setting -__all__ = ['BaseSettingSerializer', 'BasicSettingSerializer', 'EmailSettingSerializer', - "EmailContentSettingSerializer", 'LdapSettingSerializer', - 'TerminalSettingSerializer', 'SecuritySettingSerializer'] +__all__ = ['SettingsSerializer'] -class BaseSettingSerializer(serializers.Serializer): - encrypt_fields = ["EMAIL_HOST_PASSWORD", "AUTH_LDAP_BIND_PASSWORD"] - - def create(self, validated_data): - pass - - def update(self, instance, validated_data): - self.update_validated_settings(validated_data) - for field_name, field_value in validated_data.items(): - setattr(instance, field_name, field_value) - return instance - - def update_validated_settings(self, validated_data, category='default'): - with transaction.atomic(): - for field_name, field_value in validated_data.items(): - try: - setting = Setting.objects.get(name=field_name) - except Setting.DoesNotExist: - setting = Setting() - encrypted = True if field_name in self.encrypt_fields else False - setting.name = field_name - setting.category = category - setting.encrypted = encrypted - setting.cleaned_value = field_value - setting.save() - - -class BasicSettingSerializer(BaseSettingSerializer): +class BasicSettingSerializer(serializers.Serializer): SITE_URL = serializers.URLField(required=True) - USER_GUIDE_URL = serializers.URLField(required=False) + USER_GUIDE_URL = serializers.URLField(required=False, allow_blank=True, ) EMAIL_SUBJECT_PREFIX = serializers.CharField(max_length=1024, required=True) -class EmailSettingSerializer(BaseSettingSerializer): +class EmailSettingSerializer(serializers.Serializer): encrypt_fields = ["EMAIL_HOST_PASSWORD", ] EMAIL_HOST = serializers.CharField(max_length=1024, required=True) EMAIL_PORT = serializers.CharField(max_length=5, required=True) EMAIL_HOST_USER = serializers.CharField(max_length=128, required=True) EMAIL_HOST_PASSWORD = serializers.CharField(max_length=1024, required=False, write_only=True) - EMAIL_FROM = serializers.CharField(max_length=128, required=False) - EMAIL_RECIPIENT = serializers.CharField(max_length=128, allow_blank='', required=False) + EMAIL_FROM = serializers.CharField(max_length=128, allow_blank=True, required=False) + EMAIL_RECIPIENT = serializers.CharField(max_length=128, allow_blank=True, required=False) EMAIL_USE_SSL = serializers.BooleanField(required=False) EMAIL_USE_TLS = serializers.BooleanField(required=False) -class EmailContentSettingSerializer(BaseSettingSerializer): - EMAIL_CUSTOM_USER_CREATED_SUBJECT = serializers.CharField(max_length=1024, required=False, ) - EMAIL_CUSTOM_USER_CREATED_HONORIFIC = serializers.CharField(max_length=1024, required=False, ) - EMAIL_CUSTOM_USER_CREATED_BODY = serializers.CharField(max_length=4096, required=False) - EMAIL_CUSTOM_USER_CREATED_SIGNATURE = serializers.CharField(max_length=512, required=False) +class EmailContentSettingSerializer(serializers.Serializer): + EMAIL_CUSTOM_USER_CREATED_SUBJECT = serializers.CharField(max_length=1024, allow_blank=True, required=False, ) + EMAIL_CUSTOM_USER_CREATED_HONORIFIC = serializers.CharField(max_length=1024, allow_blank=True, required=False, ) + EMAIL_CUSTOM_USER_CREATED_BODY = serializers.CharField(max_length=4096, allow_blank=True, required=False) + EMAIL_CUSTOM_USER_CREATED_SIGNATURE = serializers.CharField(max_length=512, allow_blank=True, required=False) -class LdapSettingSerializer(BaseSettingSerializer): +class LdapSettingSerializer(serializers.Serializer): encrypt_fields = ["AUTH_LDAP_BIND_PASSWORD", ] AUTH_LDAP_SERVER_URI = serializers.CharField(required=True) AUTH_LDAP_BIND_DN = serializers.CharField(required=False) AUTH_LDAP_BIND_PASSWORD = serializers.CharField(max_length=1024, write_only=True) - AUTH_LDAP_SEARCH_OU = serializers.CharField(max_length=1024, required=False) + AUTH_LDAP_SEARCH_OU = serializers.CharField(max_length=1024, allow_blank=True, required=False) AUTH_LDAP_SEARCH_FILTER = serializers.CharField(max_length=1024, required=True) AUTH_LDAP_USER_ATTR_MAP = serializers.CharField(max_length=1024, required=True) AUTH_LDAP = serializers.BooleanField(required=False) -class TerminalSettingSerializer(BaseSettingSerializer): +class TerminalSettingSerializer(serializers.Serializer): SORT_BY_CHOICES = ( ('hostname', _('Hostname')), ('ip', _('IP')) @@ -95,10 +66,10 @@ class TerminalSettingSerializer(BaseSettingSerializer): TERMINAL_ASSET_LIST_SORT_BY = serializers.ChoiceField(SORT_BY_CHOICES) TERMINAL_ASSET_LIST_PAGE_SIZE = serializers.ChoiceField(PAGE_SIZE_CHOICES) TERMINAL_SESSION_KEEP_DURATION = serializers.IntegerField(min_value=1, max_value=99999, required=True) - TERMINAL_TELNET_REGEX = serializers.CharField(required=False) + TERMINAL_TELNET_REGEX = serializers.CharField(required=False, allow_blank=True) -class SecuritySettingSerializer(BaseSettingSerializer): +class SecuritySettingSerializer(serializers.Serializer): SECURITY_MFA_AUTH = serializers.BooleanField(required=False) SECURITY_COMMAND_EXECUTION = serializers.BooleanField(required=False) SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField(required=True) @@ -111,3 +82,43 @@ class SecuritySettingSerializer(BaseSettingSerializer): SECURITY_PASSWORD_LOWER_CASE = serializers.BooleanField(required=False) SECURITY_PASSWORD_NUMBER = serializers.BooleanField(required=False) SECURITY_PASSWORD_SPECIAL_CHAR = serializers.BooleanField(required=False) + + +class SettingsSerializer(serializers.Serializer): + basic = BasicSettingSerializer(required=False) + email = EmailSettingSerializer(required=False) + email_content = EmailContentSettingSerializer(required=False) + ldap = LdapSettingSerializer(required=False) + terminal = TerminalSettingSerializer(required=False) + security = SecuritySettingSerializer(required=False) + + encrypt_fields = ["EMAIL_HOST_PASSWORD", "AUTH_LDAP_BIND_PASSWORD"] + + def create(self, validated_data): + pass + + def update(self, instance, validated_data): + for category, category_data in validated_data.items(): + if not category_data: + continue + self.update_validated_settings(category_data) + for field_name, field_value in category_data.items(): + setattr(getattr(instance, category), field_name, field_value) + + return instance + + def update_validated_settings(self, validated_data, category='default'): + if not validated_data: + return + with transaction.atomic(): + for field_name, field_value in validated_data.items(): + try: + setting = Setting.objects.get(name=field_name) + except Setting.DoesNotExist: + setting = Setting() + encrypted = True if field_name in self.encrypt_fields else False + setting.name = field_name + setting.category = category + setting.encrypted = encrypted + setting.cleaned_value = field_value + setting.save() diff --git a/apps/settings/urls/api_urls.py b/apps/settings/urls/api_urls.py index 093bc9c15..0db9c7c54 100644 --- a/apps/settings/urls/api_urls.py +++ b/apps/settings/urls/api_urls.py @@ -14,11 +14,6 @@ urlpatterns = [ path('ldap/users/import/', api.LDAPUserImportAPI.as_view(), name='ldap-user-import'), path('ldap/cache/refresh/', api.LDAPCacheRefreshAPI.as_view(), name='ldap-cache-refresh'), - path('basic/', api.BasicSettingApi.as_view(), name='basic-settings'), - path('email/', api.EmailSettingApi.as_view(), name='email-settings'), - path('email-content/', api.EmailContentSettingApi.as_view(), name='email-content-settings'), - path('ldap/', api.LdapSettingApi.as_view(), name='ldap-settings'), - path('terminal/', api.TerminalSettingApi.as_view(), name='terminal-settings'), - path('security/', api.SecuritySettingApi.as_view(), name='security-settings'), + path('setting/', api.SettingsApi.as_view(), name='settings-setting'), path('public/', api.PublicSettingApi.as_view(), name='public-setting'), ] From 79eb838250a3cfcf5c20dfc6f9cfc2bab7e2dc3b Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 29 Apr 2020 18:10:42 +0800 Subject: [PATCH 013/146] [Update] setting fields automatically generated by serializer --- apps/settings/api.py | 41 +++------------------------ apps/settings/serializers/settings.py | 10 +++---- 2 files changed, 9 insertions(+), 42 deletions(-) diff --git a/apps/settings/api.py b/apps/settings/api.py index f6021f3f2..b8ceaac06 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -9,6 +9,7 @@ from rest_framework.views import Response, APIView from django.conf import settings from django.core.mail import send_mail, get_connection from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers from .utils import ( LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil, @@ -276,45 +277,11 @@ class PublicSettingApi(generics.RetrieveAPIView): class SettingsApi(generics.RetrieveUpdateAPIView): serializer_class = SettingsSerializer - BASIC_CATEGORY = ['SITE_URL', 'USER_GUIDE_URL', 'EMAIL_SUBJECT_PREFIX'] - - EMAIL_CATEGORY = ['EMAIL_HOST', 'EMAIL_PORT', 'EMAIL_HOST_USER', - 'EMAIL_HOST_PASSWORD', 'EMAIL_FROM', 'EMAIL_RECIPIENT', - 'EMAIL_USE_SSL', 'EMAIL_USE_TLS'] - - EMAIL_CONTENT_CATEGORY = ['EMAIL_CUSTOM_USER_CREATED_SUBJECT', 'EMAIL_CUSTOM_USER_CREATED_HONORIFIC', - 'EMAIL_CUSTOM_USER_CREATED_BODY', 'EMAIL_CUSTOM_USER_CREATED_SIGNATURE', ] - - LDAP_CATEGORY = ['AUTH_LDAP_SERVER_URI', 'AUTH_LDAP_BIND_DN', - 'AUTH_LDAP_BIND_PASSWORD', 'AUTH_LDAP_SEARCH_OU', - 'AUTH_LDAP_SEARCH_FILTER', 'AUTH_LDAP_USER_ATTR_MAP', - 'AUTH_LDAP'] - - TERMINAL_CATEGORY = ['TERMINAL_PASSWORD_AUTH', 'TERMINAL_PUBLIC_KEY_AUTH', - 'TERMINAL_HEARTBEAT_INTERVAL', 'TERMINAL_ASSET_LIST_SORT_BY', - 'TERMINAL_ASSET_LIST_PAGE_SIZE', 'TERMINAL_SESSION_KEEP_DURATION', - 'TERMINAL_TELNET_REGEX'] - - SECURITY_CATEGORY = ['SECURITY_MFA_AUTH', 'SECURITY_COMMAND_EXECUTION', - 'SECURITY_SERVICE_ACCOUNT_REGISTRATION', 'SECURITY_LOGIN_LIMIT_COUNT', - 'SECURITY_LOGIN_LIMIT_TIME', 'SECURITY_MAX_IDLE_TIME', - 'SECURITY_PASSWORD_EXPIRATION_TIME', 'SECURITY_PASSWORD_MIN_LENGTH', - 'SECURITY_PASSWORD_UPPER_CASE', 'SECURITY_PASSWORD_LOWER_CASE', - 'SECURITY_PASSWORD_NUMBER', 'SECURITY_PASSWORD_SPECIAL_CHAR'] - - SETTING_CATEGORIES = { - "basic": BASIC_CATEGORY, - 'email': EMAIL_CATEGORY, - 'email_content': EMAIL_CONTENT_CATEGORY, - 'ldap': LDAP_CATEGORY, - 'terminal': TERMINAL_CATEGORY, - 'security': SECURITY_CATEGORY - } def get_object(self): - instance = {category_name: self._get_setting_fields_obj(category_fields) - for category_name, category_fields in self.SETTING_CATEGORIES.items()} - + instance = {category: self._get_setting_fields_obj(list(category_serializer.get_fields())) + for category, category_serializer in self.serializer_class().get_fields().items() + if isinstance(category_serializer, serializers.Serializer)} return ObjectDict(instance) def perform_update(self, serializer): diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index d8dc8d708..2e2fb2f6b 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -20,7 +20,7 @@ class EmailSettingSerializer(serializers.Serializer): EMAIL_HOST = serializers.CharField(max_length=1024, required=True) EMAIL_PORT = serializers.CharField(max_length=5, required=True) EMAIL_HOST_USER = serializers.CharField(max_length=128, required=True) - EMAIL_HOST_PASSWORD = serializers.CharField(max_length=1024, required=False, write_only=True) + EMAIL_HOST_PASSWORD = serializers.CharField(max_length=1024, write_only=True, required=False, ) EMAIL_FROM = serializers.CharField(max_length=128, allow_blank=True, required=False) EMAIL_RECIPIENT = serializers.CharField(max_length=128, allow_blank=True, required=False) EMAIL_USE_SSL = serializers.BooleanField(required=False) @@ -39,7 +39,7 @@ class LdapSettingSerializer(serializers.Serializer): AUTH_LDAP_SERVER_URI = serializers.CharField(required=True) AUTH_LDAP_BIND_DN = serializers.CharField(required=False) - AUTH_LDAP_BIND_PASSWORD = serializers.CharField(max_length=1024, write_only=True) + AUTH_LDAP_BIND_PASSWORD = serializers.CharField(max_length=1024, write_only=True, required=False) AUTH_LDAP_SEARCH_OU = serializers.CharField(max_length=1024, allow_blank=True, required=False) AUTH_LDAP_SEARCH_FILTER = serializers.CharField(max_length=1024, required=True) AUTH_LDAP_USER_ATTR_MAP = serializers.CharField(max_length=1024, required=True) @@ -63,10 +63,10 @@ class TerminalSettingSerializer(serializers.Serializer): TERMINAL_PASSWORD_AUTH = serializers.BooleanField(required=False) TERMINAL_PUBLIC_KEY_AUTH = serializers.BooleanField(required=False) TERMINAL_HEARTBEAT_INTERVAL = serializers.IntegerField(min_value=5, max_value=99999, required=True) - TERMINAL_ASSET_LIST_SORT_BY = serializers.ChoiceField(SORT_BY_CHOICES) - TERMINAL_ASSET_LIST_PAGE_SIZE = serializers.ChoiceField(PAGE_SIZE_CHOICES) + TERMINAL_ASSET_LIST_SORT_BY = serializers.ChoiceField(SORT_BY_CHOICES, required=False) + TERMINAL_ASSET_LIST_PAGE_SIZE = serializers.ChoiceField(PAGE_SIZE_CHOICES, required=False) TERMINAL_SESSION_KEEP_DURATION = serializers.IntegerField(min_value=1, max_value=99999, required=True) - TERMINAL_TELNET_REGEX = serializers.CharField(required=False, allow_blank=True) + TERMINAL_TELNET_REGEX = serializers.CharField(allow_blank=True, required=False) class SecuritySettingSerializer(serializers.Serializer): From f4eca83a4924fcd4f15096865b80853cad09e225 Mon Sep 17 00:00:00 2001 From: xuxinwen Date: Wed, 29 Apr 2020 19:05:56 +0800 Subject: [PATCH 014/146] =?UTF-8?q?[feature]=20=E6=B7=BB=E5=8A=A0=20login-?= =?UTF-8?q?logs=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/api.py | 38 +++++++++++++++++++++++++++--- apps/audits/serializers.py | 7 ++++-- apps/audits/urls/api_urls.py | 1 + apps/common/drf/filters.py | 45 ++++++++++++++++++++++++++++++++---- apps/common/mixins/views.py | 2 -- 5 files changed, 81 insertions(+), 12 deletions(-) diff --git a/apps/audits/api.py b/apps/audits/api.py index 3677a8e8e..5ed7a9cd6 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -1,10 +1,16 @@ # -*- coding: utf-8 -*- # +from rest_framework.viewsets import GenericViewSet +from rest_framework.mixins import ListModelMixin +from django.db.models import Q -from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor +from common.mixins.api import CommonApiMixin +from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsOrgAdmin +from common.drf.filters import DatetimeRangeFilter from orgs.mixins.api import OrgModelViewSet -from .models import FTPLog -from .serializers import FTPLogSerializer +from orgs.utils import current_org +from .models import FTPLog, UserLoginLog +from .serializers import FTPLogSerializer, UserLoginLogSerializer class FTPLogViewSet(OrgModelViewSet): @@ -12,3 +18,29 @@ class FTPLogViewSet(OrgModelViewSet): serializer_class = FTPLogSerializer permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,) http_method_names = ['get', 'post', 'head', 'options'] + + +class UserLoginLogViewSet(CommonApiMixin, + ListModelMixin, + GenericViewSet): + queryset = UserLoginLog.objects.all() + permission_classes = [IsOrgAdmin | IsOrgAuditor] + serializer_class = UserLoginLogSerializer + extra_filter_backends = [DatetimeRangeFilter] + date_range_filter_fields = [ + ('datetime', ('date_from', 'date_to')) + ] + filterset_fields = ['username'] + search_fields = ['ip', 'city', 'username'] + + @staticmethod + def get_org_members(): + users = current_org.get_org_members().values_list('username', flat=True) + return users + + def get_queryset(self): + queryset = super().get_queryset() + if not current_org.is_default(): + users = self.get_org_members() + queryset = queryset.filter(username__in=users) + return queryset diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 7dcd94d73..70ca61dc2 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -14,10 +14,13 @@ class FTPLogSerializer(serializers.ModelSerializer): fields = '__all__' -class LoginLogSerializer(serializers.ModelSerializer): +class UserLoginLogSerializer(serializers.ModelSerializer): class Meta: model = models.UserLoginLog - fields = '__all__' + fields = ( + 'username', 'type', 'ip', 'city', 'user_agent', + 'mfa', 'reason', 'status', 'datetime' + ) class OperateLogSerializer(serializers.ModelSerializer): diff --git a/apps/audits/urls/api_urls.py b/apps/audits/urls/api_urls.py index 249843f24..86312dfe3 100644 --- a/apps/audits/urls/api_urls.py +++ b/apps/audits/urls/api_urls.py @@ -12,6 +12,7 @@ app_name = "audits" router = DefaultRouter() router.register(r'ftp-logs', api.FTPLogViewSet, 'ftp-log') +router.register(r'login-logs', api.UserLoginLogViewSet, 'login-log') urlpatterns = [ ] diff --git a/apps/common/drf/filters.py b/apps/common/drf/filters.py index c11a7864d..bf6ac7197 100644 --- a/apps/common/drf/filters.py +++ b/apps/common/drf/filters.py @@ -4,7 +4,9 @@ import coreapi from rest_framework import filters from rest_framework.fields import DateTimeField from rest_framework.serializers import ValidationError +from rest_framework.compat import coreapi, coreschema from django.core.cache import cache +from django.core.exceptions import ImproperlyConfigured import logging from common import const @@ -13,15 +15,48 @@ __all__ = ["DatetimeRangeFilter", "IDSpmFilter", "CustomFilter"] class DatetimeRangeFilter(filters.BaseFilterBackend): - def filter_queryset(self, request, queryset, view): + def get_schema_fields(self, view): + ret = [] + fields = self._get_date_range_filter_fields(view) + + for attr, date_range_keyword in fields.items(): + if len(date_range_keyword) != 2: + continue + for v in date_range_keyword: + ret.append( + coreapi.Field( + name=v, location='query', required=False, type='string', + schema=coreschema.String( + title=v, + description='%s %s' % (attr, v) + ) + ) + ) + + return ret + + def _get_date_range_filter_fields(self, view): if not hasattr(view, 'date_range_filter_fields'): - return queryset + return {} try: - fields = dict(view.date_range_filter_fields) + return dict(view.date_range_filter_fields) except ValueError: - msg = "View {} datetime_filter_fields set is error".format(view.name) + msg = """ + View {} `date_range_filter_fields` set is improperly. + For example: + ``` + class ExampleView: + date_range_filter_fields = [ + ('db column', ('query param date from', 'query param date to')) + ] + ``` + """.format(view.name) logging.error(msg) - return queryset + raise ImproperlyConfigured(msg) + + def filter_queryset(self, request, queryset, view): + fields = self._get_date_range_filter_fields(view) + kwargs = {} for attr, date_range_keyword in fields.items(): if len(date_range_keyword) != 2: diff --git a/apps/common/mixins/views.py b/apps/common/mixins/views.py index a00535b5b..b6685def5 100644 --- a/apps/common/mixins/views.py +++ b/apps/common/mixins/views.py @@ -36,5 +36,3 @@ class DatetimeSearchMixin: def get(self, request, *args, **kwargs): self.get_date_range() return super().get(request, *args, **kwargs) - - From 6bb13a26f52e15b3ded433a78e6fa55ed7d89918 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 30 Apr 2020 16:58:08 +0800 Subject: [PATCH 015/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7serializer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/utils.py | 8 ++++++++ apps/perms/api/user_permission/mixin.py | 4 +++- apps/perms/utils/asset_permission.py | 4 +++- apps/users/serializers/group.py | 5 +++-- apps/users/serializers/user.py | 4 +++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/assets/utils.py b/apps/assets/utils.py index 8ec20388b..3238e8fca 100644 --- a/apps/assets/utils.py +++ b/apps/assets/utils.py @@ -1,5 +1,6 @@ # ~*~ coding: utf-8 ~*~ # +import time from treelib import Tree from treelib.exceptions import NodeIDAbsentError from collections import defaultdict @@ -77,8 +78,15 @@ class TreeService(Tree): return ancestor_ids def ancestors(self, nid, with_self=False, deep=False, with_assets=True): + # now = time.time() + # print("Start get ancestor_ids") ancestor_ids = self.ancestors_ids(nid, with_self=with_self) + # now2 = time.time() + # interval = (now2 - now) * 1000 + # print("Start get ancestor_ids using: {}".format(interval)) ancestors = [self.get_node(i, deep=deep) for i in ancestor_ids] + # interval = (time.time() - now2) * 1000 + # print("Get ancestors done: {}".format(interval)) if with_assets: return ancestors for n in ancestors: diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index 4b0cc32a5..426f7d34d 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -2,6 +2,7 @@ # from common.utils import lazyproperty from common.tree import TreeNodeSerializer +from django.db.models import QuerySet from ..mixin import UserPermissionMixin from ...utils import AssetPermissionUtil, ParserNode from ...hands import Node, Asset @@ -32,7 +33,8 @@ class UserNodeTreeMixin: nodes_only_fields = ParserNode.nodes_only_fields def parse_nodes_to_queryset(self, nodes): - nodes = nodes.only(*self.nodes_only_fields) + if isinstance(nodes, QuerySet): + nodes = nodes.only(*self.nodes_only_fields) _queryset = [] for node in nodes: diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index 3a1f81a90..cdcf4389f 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -290,11 +290,12 @@ class AssetPermissionUtil(AssetPermissionUtilCacheMixin): def parse_user_tree_to_full_tree(self, user_tree): """ 经过前面两个动作,用户授权的节点已放到树上,但是树不是完整的, - 这里要讲树构造成一个完整的树 + 这里要将树构造成一个完整的树 """ # 开始修正user_tree,保证父节点都在树上 root_children = user_tree.children('') for child in root_children: + # print("child: {}".format(child.identifier)) if child.identifier.isdigit(): continue if child.identifier.startswith('-'): @@ -303,6 +304,7 @@ class AssetPermissionUtil(AssetPermissionUtilCacheMixin): child.identifier, with_self=False, deep=True, with_assets=False, ) + # print("Get ancestors: {}".format(len(ancestors))) if not ancestors: continue user_tree.safe_add_ancestors(child, ancestors) diff --git a/apps/users/serializers/group.py b/apps/users/serializers/group.py index 01fa104fc..a6b9f67b9 100644 --- a/apps/users/serializers/group.py +++ b/apps/users/serializers/group.py @@ -40,8 +40,9 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer): self.set_fields_queryset() def set_fields_queryset(self): - users_field = self.fields['users'] - users_field.child_relation.queryset = utils.get_current_org_members(exclude=('Auditor',)) + users_field = self.fields.get('users') + if users_field: + users_field.child_relation.queryset = utils.get_current_org_members(exclude=('Auditor',)) def validate_users(self, users): for user in users: diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 9cded6e58..ba5c4ec2a 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -33,6 +33,7 @@ class UserSerializer(CommonSerializerMixin, serializers.ModelSerializer): choices=PASSWORD_STRATEGY_CHOICES, required=False, initial=0, label=_('Password strategy'), write_only=True ) + mfa_level_display = serializers.ReadOnlyField(source='get_mfa_level_display') can_update = serializers.SerializerMethodField() can_delete = serializers.SerializerMethodField() @@ -44,10 +45,11 @@ class UserSerializer(CommonSerializerMixin, serializers.ModelSerializer): # small 指的是 不需要计算的直接能从一张表中获取到的数据 fields_small = fields_mini + [ 'password', 'email', 'public_key', 'wechat', 'phone', 'mfa_level', + 'mfa_level_display', 'comment', 'source', 'is_valid', 'is_expired', 'is_active', 'created_by', 'is_first_login', 'password_strategy', 'date_password_last_updated', 'date_expired', - 'avatar_url', 'source_display', + 'avatar_url', 'source_display', 'date_joined', 'last_login' ] fields = fields_small + [ 'groups', 'role', 'groups_display', 'role_display', From 7b339df4306e5f2b92036c71d690028335ce32e8 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 6 May 2020 16:36:36 +0800 Subject: [PATCH 016/146] [Update] save settings --- apps/settings/serializers/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index 2e2fb2f6b..2b9458d8f 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -121,4 +121,4 @@ class SettingsSerializer(serializers.Serializer): setting.category = category setting.encrypted = encrypted setting.cleaned_value = field_value - setting.save() + setting.save() From 8c0bf0b71bb1b87efef2a44e9a485e418d55e10e Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 6 May 2020 19:35:19 +0800 Subject: [PATCH 017/146] =?UTF-8?q?[Update]=20Login=20log=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0display=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/serializers.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 70ca61dc2..797733563 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -15,11 +15,14 @@ class FTPLogSerializer(serializers.ModelSerializer): class UserLoginLogSerializer(serializers.ModelSerializer): + type_display = serializers.ReadOnlyField(source='get_type_display') + status_display = serializers.ReadOnlyField(source='get_status_display') + class Meta: model = models.UserLoginLog fields = ( - 'username', 'type', 'ip', 'city', 'user_agent', - 'mfa', 'reason', 'status', 'datetime' + 'username', 'type', 'type_display', 'ip', 'city', 'user_agent', + 'mfa', 'reason', 'status', 'status_display', 'datetime' ) From 9a39ccd37dc5216083fa20908368030808170a97 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 7 May 2020 16:57:57 +0800 Subject: [PATCH 018/146] [Update] Reponse status code from 401 to 400 --- apps/settings/api.py | 21 +++++++++++---------- apps/settings/serializers/settings.py | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apps/settings/api.py b/apps/settings/api.py index b8ceaac06..b641b0ec4 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -72,13 +72,13 @@ class MailTestingAPI(APIView): continue else: break - return Response({"error": str(resp)}, status=401) + return Response({"error": str(resp)}, status=400) except Exception as e: print(e) - return Response({"error": str(e)}, status=401) + return Response({"error": str(e)}, status=400) return Response({"msg": self.success_message.format(email_recipient)}) else: - return Response({"error": str(serializer.errors)}, status=401) + return Response({"error": str(serializer.errors)}, status=400) class LDAPTestingConfigAPI(APIView): @@ -88,10 +88,10 @@ class LDAPTestingConfigAPI(APIView): def post(self, request): serializer = self.serializer_class(data=request.data) if not serializer.is_valid(): - return Response({"error": str(serializer.errors)}, status=401) + return Response({"error": str(serializer.errors)}, status=400) config = self.get_ldap_config(serializer) ok, msg = LDAPTestUtil(config).test_config() - status = 200 if ok else 401 + status = 200 if ok else 400 return Response(msg, status=status) @staticmethod @@ -124,11 +124,11 @@ class LDAPTestingLoginAPI(APIView): def post(self, request): serializer = self.serializer_class(data=request.data) if not serializer.is_valid(): - return Response({"error": str(serializer.errors)}, status=401) + return Response({"error": str(serializer.errors)}, status=400) username = serializer.validated_data['username'] password = serializer.validated_data['password'] ok, msg = LDAPTestUtil().test_login(username, password) - status = 200 if ok else 401 + status = 200 if ok else 400 return Response(msg, status=status) @@ -236,14 +236,14 @@ class LDAPUserImportAPI(APIView): try: users = self.get_ldap_users() except Exception as e: - return Response({'error': str(e)}, status=401) + return Response({'error': str(e)}, status=400) if users is None: - return Response({'msg': _('Get ldap users is None')}, status=401) + return Response({'msg': _('Get ldap users is None')}, status=400) errors = LDAPImportUtil().perform_import(users) if errors: - return Response({'errors': errors}, status=401) + return Response({'errors': errors}, status=400) count = users if users is None else len(users) return Response({'msg': _('Imported {} users successfully').format(count)}) @@ -276,6 +276,7 @@ class PublicSettingApi(generics.RetrieveAPIView): class SettingsApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsSuperUser,) serializer_class = SettingsSerializer def get_object(self): diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index 2b9458d8f..0f315801b 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -75,7 +75,7 @@ class SecuritySettingSerializer(serializers.Serializer): SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField(required=True) SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField(min_value=3, max_value=99999, required=True) SECURITY_LOGIN_LIMIT_TIME = serializers.IntegerField(min_value=5, max_value=99999, required=True) - SECURITY_MAX_IDLE_TIME = serializers.IntegerField(min_value=5, max_value=99999, required=False) + SECURITY_MAX_IDLE_TIME = serializers.IntegerField(min_value=1, max_value=99999, required=False) SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField(min_value=1, max_value=99999, required=True) SECURITY_PASSWORD_MIN_LENGTH = serializers.IntegerField(min_value=6, max_value=30, required=True) SECURITY_PASSWORD_UPPER_CASE = serializers.BooleanField(required=False) From dec89ae5ee0de919da82a2ad68f58b3f124faf2e Mon Sep 17 00:00:00 2001 From: xinwen Date: Fri, 8 May 2020 10:08:16 +0800 Subject: [PATCH 019/146] =?UTF-8?q?[Update]=20login-logs=20api=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20`mfa=5Fdisplay`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 797733563..2feaed7fe 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -17,12 +17,13 @@ class FTPLogSerializer(serializers.ModelSerializer): class UserLoginLogSerializer(serializers.ModelSerializer): type_display = serializers.ReadOnlyField(source='get_type_display') status_display = serializers.ReadOnlyField(source='get_status_display') + mfa_display = serializers.ReadOnlyField(source='get_mfa_display') class Meta: model = models.UserLoginLog fields = ( 'username', 'type', 'type_display', 'ip', 'city', 'user_agent', - 'mfa', 'reason', 'status', 'status_display', 'datetime' + 'mfa', 'reason', 'status', 'status_display', 'datetime', 'mfa_display' ) From ac902501ec4b5ee431bd247c468957324c12cc83 Mon Sep 17 00:00:00 2001 From: xinwen Date: Fri, 8 May 2020 12:46:18 +0800 Subject: [PATCH 020/146] [Add] ftp-logs api --- apps/audits/api.py | 12 ++++++++---- apps/audits/serializers.py | 5 ++++- apps/orgs/mixins/api.py | 6 +++++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/audits/api.py b/apps/audits/api.py index 5ed7a9cd6..64e308699 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -2,22 +2,26 @@ # from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import ListModelMixin -from django.db.models import Q from common.mixins.api import CommonApiMixin from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsOrgAdmin from common.drf.filters import DatetimeRangeFilter -from orgs.mixins.api import OrgModelViewSet +from orgs.mixins.api import OrgGenericViewSet from orgs.utils import current_org from .models import FTPLog, UserLoginLog from .serializers import FTPLogSerializer, UserLoginLogSerializer -class FTPLogViewSet(OrgModelViewSet): +class FTPLogViewSet(ListModelMixin, OrgGenericViewSet): model = FTPLog serializer_class = FTPLogSerializer permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,) - http_method_names = ['get', 'post', 'head', 'options'] + extra_filter_backends = [DatetimeRangeFilter] + date_range_filter_fields = [ + ('date_start', ('date_from', 'date_to')) + ] + filterset_fields = ['user', 'asset', 'system_user'] + search_fields = ['filename'] class UserLoginLogViewSet(CommonApiMixin, diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 2feaed7fe..2ea0dec0f 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -11,7 +11,10 @@ class FTPLogSerializer(serializers.ModelSerializer): class Meta: model = models.FTPLog - fields = '__all__' + fields = ( + 'id', 'user', 'remote_addr', 'asset', 'system_user', + 'operate', 'filename', 'is_success', 'date_start' + ) class UserLoginLogSerializer(serializers.ModelSerializer): diff --git a/apps/orgs/mixins/api.py b/apps/orgs/mixins/api.py index 7695292c3..f3f376694 100644 --- a/apps/orgs/mixins/api.py +++ b/apps/orgs/mixins/api.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from django.shortcuts import get_object_or_404 -from rest_framework.viewsets import ModelViewSet +from rest_framework.viewsets import ModelViewSet, GenericViewSet from rest_framework_bulk import BulkModelViewSet from common.mixins import CommonApiMixin @@ -44,6 +44,10 @@ class OrgModelViewSet(CommonApiMixin, OrgQuerySetMixin, ModelViewSet): pass +class OrgGenericViewSet(CommonApiMixin, OrgQuerySetMixin, GenericViewSet): + pass + + class OrgBulkModelViewSet(CommonApiMixin, OrgQuerySetMixin, BulkModelViewSet): def allow_bulk_destroy(self, qs, filtered): qs_count = qs.count() From e339ed1fb38bd428d3ab6ecb430b0f33b6d9f91d Mon Sep 17 00:00:00 2001 From: xinwen Date: Fri, 8 May 2020 16:48:26 +0800 Subject: [PATCH 021/146] [Add] audits apis --- apps/audits/api.py | 59 ++++++++++++++++++++++++++++++++---- apps/audits/filters.py | 32 +++++++++++++++++++ apps/audits/serializers.py | 20 ++++++++++-- apps/audits/urls/api_urls.py | 3 ++ apps/common/api.py | 6 ++++ apps/common/drf/filters.py | 8 ++++- 6 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 apps/audits/filters.py diff --git a/apps/audits/api.py b/apps/audits/api.py index 64e308699..7deee860d 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -5,11 +5,15 @@ from rest_framework.mixins import ListModelMixin from common.mixins.api import CommonApiMixin from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsOrgAdmin -from common.drf.filters import DatetimeRangeFilter +from common.drf.filters import DatetimeRangeFilter, current_user_filter +from common.api import CommonGenericViewSet from orgs.mixins.api import OrgGenericViewSet from orgs.utils import current_org -from .models import FTPLog, UserLoginLog -from .serializers import FTPLogSerializer, UserLoginLogSerializer +from ops.models import CommandExecution +from .models import FTPLog, UserLoginLog, OperateLog, PasswordChangeLog +from .serializers import FTPLogSerializer, UserLoginLogSerializer, CommandExecutionSerializer +from .serializers import OperateLogSerializer, PasswordChangeLogSerializer +from .filters import CurrentOrgMembersFilter class FTPLogViewSet(ListModelMixin, OrgGenericViewSet): @@ -24,9 +28,8 @@ class FTPLogViewSet(ListModelMixin, OrgGenericViewSet): search_fields = ['filename'] -class UserLoginLogViewSet(CommonApiMixin, - ListModelMixin, - GenericViewSet): +class UserLoginLogViewSet(ListModelMixin, + CommonGenericViewSet): queryset = UserLoginLog.objects.all() permission_classes = [IsOrgAdmin | IsOrgAuditor] serializer_class = UserLoginLogSerializer @@ -48,3 +51,47 @@ class UserLoginLogViewSet(CommonApiMixin, users = self.get_org_members() queryset = queryset.filter(username__in=users) return queryset + + +class OperateLogViewSet(ListModelMixin, OrgGenericViewSet): + model = OperateLog + serializer_class = OperateLogSerializer + permission_classes = [IsOrgAdmin | IsOrgAuditor] + extra_filter_backends = [DatetimeRangeFilter] + date_range_filter_fields = [ + ('datetime', ('date_from', 'date_to')) + ] + filterset_fields = ['user', 'action', 'resource_type'] + search_fields = ['filename'] + ordering_fields = ['-datetime'] + + +class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet): + queryset = PasswordChangeLog.objects.all() + permission_classes = [IsOrgAdmin | IsOrgAuditor] + serializer_class = PasswordChangeLogSerializer + extra_filter_backends = [DatetimeRangeFilter] + date_range_filter_fields = [ + ('datetime', ('date_from', 'date_to')) + ] + filterset_fields = ['user'] + ordering_fields = ['-datetime'] + + def get_queryset(self): + users = current_org.get_org_members() + queryset = super().get_queryset().filter( + user__in=[user.__str__() for user in users] + ) + return queryset + + +class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): + model = CommandExecution + serializer_class = CommandExecutionSerializer + permission_classes = [IsOrgAdmin | IsOrgAuditor] + extra_filter_backends = [DatetimeRangeFilter, current_user_filter(), CurrentOrgMembersFilter] + date_range_filter_fields = [ + ('date_start', ('date_from', 'date_to')) + ] + search_fields = ['command'] + ordering_fields = ['-date_created'] diff --git a/apps/audits/filters.py b/apps/audits/filters.py new file mode 100644 index 000000000..6db2d9b21 --- /dev/null +++ b/apps/audits/filters.py @@ -0,0 +1,32 @@ +from rest_framework import filters +from rest_framework.compat import coreapi, coreschema + +from orgs.utils import current_org + + +__all__ = ['CurrentOrgMembersFilter'] + + +class CurrentOrgMembersFilter(filters.BaseFilterBackend): + def get_schema_fields(self, view): + return [ + coreapi.Field( + name='user', location='query', required=False, type='string', + schema=coreschema.String( + title='user', + description='user' + ) + ) + ] + + def _get_user_list(self): + users = current_org.get_org_members(exclude=('Auditor',)) + return users + + def filter_queryset(self, request, queryset, view): + user_id = request.GET.get('user') + if user_id: + queryset = queryset.filter(user=user_id) + else: + queryset = queryset.filter(user__in=self._get_user_list()) + return queryset diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 2ea0dec0f..0d3805d85 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -4,6 +4,7 @@ from rest_framework import serializers from terminal.models import Session +from ops.models import CommandExecution from . import models @@ -12,7 +13,7 @@ class FTPLogSerializer(serializers.ModelSerializer): class Meta: model = models.FTPLog fields = ( - 'id', 'user', 'remote_addr', 'asset', 'system_user', + 'user', 'remote_addr', 'asset', 'system_user', 'operate', 'filename', 'is_success', 'date_start' ) @@ -33,13 +34,18 @@ class UserLoginLogSerializer(serializers.ModelSerializer): class OperateLogSerializer(serializers.ModelSerializer): class Meta: model = models.OperateLog - fields = '__all__' + fields = ( + 'user', 'action', 'resource_type', 'resource', + 'remote_addr', 'datetime' + ) class PasswordChangeLogSerializer(serializers.ModelSerializer): class Meta: model = models.PasswordChangeLog - fields = '__all__' + fields = ( + 'user', 'change_by', 'remote_addr', 'datetime' + ) class SessionAuditSerializer(serializers.ModelSerializer): @@ -47,3 +53,11 @@ class SessionAuditSerializer(serializers.ModelSerializer): model = Session fields = '__all__' + +class CommandExecutionSerializer(serializers.ModelSerializer): + class Meta: + model = CommandExecution + fields = ( + 'hosts', 'run_as', 'command', 'user', 'is_finished', + 'date_start', 'result', 'is_success' + ) diff --git a/apps/audits/urls/api_urls.py b/apps/audits/urls/api_urls.py index 86312dfe3..be622a357 100644 --- a/apps/audits/urls/api_urls.py +++ b/apps/audits/urls/api_urls.py @@ -13,6 +13,9 @@ app_name = "audits" router = DefaultRouter() router.register(r'ftp-logs', api.FTPLogViewSet, 'ftp-log') router.register(r'login-logs', api.UserLoginLogViewSet, 'login-log') +router.register(r'operate-logs', api.OperateLogViewSet, 'operate-log') +router.register(r'password-change-logs', api.PasswordChangeLogViewSet, 'password-change-log') +router.register(r'command-execution-logs', api.CommandExecutionViewSet, 'command-execution-log') urlpatterns = [ ] diff --git a/apps/common/api.py b/apps/common/api.py index d69540cfd..7ecd77122 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -9,10 +9,12 @@ from django.views.decorators.csrf import csrf_exempt from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import generics, serializers +from rest_framework.viewsets import GenericViewSet from .http import HttpResponseTemporaryRedirect from .const import KEY_CACHE_RESOURCES_ID from .utils import get_logger +from .mixins import CommonApiMixin __all__ = [ 'LogTailApi', 'ResourcesIDCacheApi', @@ -100,3 +102,7 @@ def redirect_plural_name_api(request, *args, **kwargs): full_path = org_full_path.replace(resource, resource+"s", 1) logger.debug("Redirect {} => {}".format(org_full_path, full_path)) return HttpResponseTemporaryRedirect(full_path) + + +class CommonGenericViewSet(CommonApiMixin, GenericViewSet): + pass diff --git a/apps/common/drf/filters.py b/apps/common/drf/filters.py index 029538e8a..8d091b7d8 100644 --- a/apps/common/drf/filters.py +++ b/apps/common/drf/filters.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # -import coreapi from rest_framework import filters from rest_framework.fields import DateTimeField from rest_framework.serializers import ValidationError @@ -146,3 +145,10 @@ class CustomFilter(filters.BaseFilterBackend): def filter_queryset(self, request, queryset, view): return queryset + + +def current_user_filter(user_field='user'): + class CurrentUserFilter(filters.BaseFilterBackend): + def filter_queryset(self, request, queryset, view): + return queryset.filter(**{user_field: request.user}) + return CurrentUserFilter From 48b71bb11be5c8d84e5b469f8550704348c02312 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 8 May 2020 16:48:36 +0800 Subject: [PATCH 022/146] AUTH_LDAP_USER_ATTR_MAP to Dict field --- apps/settings/serializers/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index 0f315801b..c5baa67da 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -42,7 +42,7 @@ class LdapSettingSerializer(serializers.Serializer): AUTH_LDAP_BIND_PASSWORD = serializers.CharField(max_length=1024, write_only=True, required=False) AUTH_LDAP_SEARCH_OU = serializers.CharField(max_length=1024, allow_blank=True, required=False) AUTH_LDAP_SEARCH_FILTER = serializers.CharField(max_length=1024, required=True) - AUTH_LDAP_USER_ATTR_MAP = serializers.CharField(max_length=1024, required=True) + AUTH_LDAP_USER_ATTR_MAP = serializers.DictField(required=True) AUTH_LDAP = serializers.BooleanField(required=False) From 3ee051303a39f9c13cca9347ca6fc068dea08ecc Mon Sep 17 00:00:00 2001 From: xinwen Date: Sat, 9 May 2020 11:28:39 +0800 Subject: [PATCH 023/146] =?UTF-8?q?[Update]=20audits=20=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E4=B8=BA=E4=B8=80=E4=BA=9B=20models=20=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20verbose=20=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0008_auto_20200508_2105.py | 28 + apps/audits/models.py | 6 +- apps/audits/serializers.py | 6 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 89528 -> 80215 bytes apps/locale/zh/LC_MESSAGES/django.po | 1658 ++++++----------- .../ops/migrations/0018_auto_20200508_2105.py | 50 + apps/ops/models/command.py | 14 +- .../migrations/0026_auto_20200508_2105.py | 18 + 8 files changed, 704 insertions(+), 1076 deletions(-) create mode 100644 apps/audits/migrations/0008_auto_20200508_2105.py create mode 100644 apps/ops/migrations/0018_auto_20200508_2105.py create mode 100644 apps/users/migrations/0026_auto_20200508_2105.py diff --git a/apps/audits/migrations/0008_auto_20200508_2105.py b/apps/audits/migrations/0008_auto_20200508_2105.py new file mode 100644 index 000000000..bde6687ba --- /dev/null +++ b/apps/audits/migrations/0008_auto_20200508_2105.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.10 on 2020-05-08 13:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('audits', '0007_auto_20191202_1010'), + ] + + operations = [ + migrations.AlterField( + model_name='ftplog', + name='date_start', + field=models.DateTimeField(auto_now_add=True, verbose_name='Date start'), + ), + migrations.AlterField( + model_name='operatelog', + name='datetime', + field=models.DateTimeField(auto_now=True, verbose_name='Datetime'), + ), + migrations.AlterField( + model_name='passwordchangelog', + name='datetime', + field=models.DateTimeField(auto_now=True, verbose_name='Datetime'), + ), + ] diff --git a/apps/audits/models.py b/apps/audits/models.py index 81866bb1d..406d0aa44 100644 --- a/apps/audits/models.py +++ b/apps/audits/models.py @@ -22,7 +22,7 @@ class FTPLog(OrgModelMixin): operate = models.CharField(max_length=16, verbose_name=_("Operate")) filename = models.CharField(max_length=1024, verbose_name=_("Filename")) is_success = models.BooleanField(default=True, verbose_name=_("Success")) - date_start = models.DateTimeField(auto_now_add=True) + date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start')) class OperateLog(OrgModelMixin): @@ -40,7 +40,7 @@ class OperateLog(OrgModelMixin): resource_type = models.CharField(max_length=64, verbose_name=_("Resource Type")) resource = models.CharField(max_length=128, verbose_name=_("Resource")) remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) - datetime = models.DateTimeField(auto_now=True) + datetime = models.DateTimeField(auto_now=True, verbose_name=_('Datetime')) def __str__(self): return "<{}> {} <{}>".format(self.user, self.action, self.resource) @@ -51,7 +51,7 @@ class PasswordChangeLog(models.Model): user = models.CharField(max_length=128, verbose_name=_('User')) change_by = models.CharField(max_length=128, verbose_name=_("Change by")) remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True) - datetime = models.DateTimeField(auto_now=True) + datetime = models.DateTimeField(auto_now=True, verbose_name=_('Datetime')) def __str__(self): return "{} change {}'s password".format(self.change_by, self.user) diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 0d3805d85..75ec9b634 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # - +from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from terminal.models import Session @@ -61,3 +61,7 @@ class CommandExecutionSerializer(serializers.ModelSerializer): 'hosts', 'run_as', 'command', 'user', 'is_finished', 'date_start', 'result', 'is_success' ) + extra_kwargs = { + 'result': {'label': _('Result')}, + 'is_success': {'label': _('Is success')}, + } diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 126defb5619feee38247c0be8c684584080455cb..d505bc23db02daef5dcaaccf59ad88b535ab0e1d 100644 GIT binary patch delta 24106 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 ZOZMV+5!$yH-w67l#OczixAV3Ae*hu6isJwP diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 01a3bc023..b875be379 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: 2020-04-17 20:49+0800\n" +"POT-Creation-Date: 2020-05-08 18:40+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -26,7 +26,7 @@ msgstr "自定义" #: applications/templates/applications/remote_app_list.html:27 #: applications/templates/applications/user_remote_app_list.html:18 #: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:353 assets/models/authbook.py:27 +#: assets/models/asset.py:352 assets/models/authbook.py:27 #: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:32 #: assets/serializers/asset_user.py:47 assets/serializers/asset_user.py:84 #: assets/serializers/system_user.py:44 assets/serializers/system_user.py:176 @@ -52,16 +52,6 @@ msgstr "自定义" #: users/templates/users/user_asset_permission.html:40 #: users/templates/users/user_asset_permission.html:70 #: users/templates/users/user_granted_remote_app.html:36 -#: xpack/plugins/change_auth_plan/forms.py:74 -#: xpack/plugins/change_auth_plan/models.py:265 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:40 -#: 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:266 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:47 -#: xpack/plugins/orgs/templates/orgs/org_list.html:17 -#: xpack/plugins/vault/forms.py:13 xpack/plugins/vault/forms.py:15 msgid "Asset" msgstr "资产" @@ -111,7 +101,7 @@ msgstr "运行参数" #: applications/templates/applications/user_database_app_list.html:16 #: applications/templates/applications/user_remote_app_list.html:16 #: assets/forms/asset.py:21 assets/forms/domain.py:77 assets/forms/user.py:74 -#: assets/forms/user.py:96 assets/models/asset.py:146 assets/models/base.py:232 +#: assets/forms/user.py:96 assets/models/asset.py:145 assets/models/base.py:232 #: assets/models/cluster.py:18 assets/models/cmd_filter.py:21 #: assets/models/domain.py:20 assets/models/group.py:20 #: assets/models/label.py:18 assets/templates/assets/_node_detail_modal.html:27 @@ -140,7 +130,7 @@ msgstr "运行参数" #: perms/templates/perms/remote_app_permission_list.html:14 #: perms/templates/perms/remote_app_permission_remote_app.html:49 #: perms/templates/perms/remote_app_permission_user.html:49 -#: settings/models.py:26 +#: settings/models.py:27 #: settings/templates/settings/_ldap_list_users_modal.html:32 #: terminal/models.py:26 terminal/models.py:342 terminal/models.py:374 #: terminal/models.py:411 terminal/templates/terminal/base_storage_list.html:31 @@ -160,17 +150,6 @@ msgstr "运行参数" #: users/templates/users/user_profile.html:51 #: users/templates/users/user_pubkey_update.html:57 #: users/templates/users/user_remote_app_permission.html:36 -#: xpack/plugins/change_auth_plan/forms.py:57 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:59 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 -#: xpack/plugins/cloud/models.py:35 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:47 -#: 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/gathered_user/templates/gathered_user/task_list.html:16 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:47 -#: xpack/plugins/orgs/templates/orgs/org_list.html:12 msgid "Name" msgstr "名称" @@ -202,7 +181,7 @@ msgstr "主机" #: applications/models/database_app.py:27 #: applications/templates/applications/database_app_detail.html:60 #: applications/templates/applications/database_app_list.html:26 -#: assets/forms/asset.py:25 assets/models/asset.py:192 +#: assets/forms/asset.py:25 assets/models/asset.py:191 #: assets/models/domain.py:50 #: assets/templates/assets/domain_gateway_list.html:64 msgid "Port" @@ -225,7 +204,7 @@ msgstr "数据库" #: applications/templates/applications/remote_app_list.html:28 #: applications/templates/applications/user_database_app_list.html:20 #: applications/templates/applications/user_remote_app_list.html:19 -#: assets/models/asset.py:151 assets/models/asset.py:227 +#: assets/models/asset.py:150 assets/models/asset.py:226 #: assets/models/base.py:237 assets/models/cluster.py:29 #: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:56 #: assets/models/domain.py:21 assets/models/domain.py:53 @@ -247,7 +226,7 @@ msgstr "数据库" #: perms/templates/perms/asset_permission_detail.html:97 #: perms/templates/perms/database_app_permission_detail.html:93 #: perms/templates/perms/remote_app_permission_detail.html:89 -#: settings/models.py:31 terminal/models.py:36 terminal/models.py:381 +#: settings/models.py:32 terminal/models.py:36 terminal/models.py:381 #: terminal/models.py:418 terminal/templates/terminal/base_storage_list.html:33 #: terminal/templates/terminal/terminal_detail.html:63 #: tickets/templates/tickets/ticket_detail.html:104 users/models/group.py:16 @@ -257,17 +236,6 @@ msgstr "数据库" #: users/templates/users/user_group_detail.html:62 #: users/templates/users/user_group_list.html:16 #: users/templates/users/user_profile.html:138 -#: xpack/plugins/change_auth_plan/models.py:75 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:115 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 -#: xpack/plugins/cloud/models.py:53 xpack/plugins/cloud/models.py:136 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:67 -#: xpack/plugins/cloud/templates/cloud/account_list.html:15 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:102 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18 -#: xpack/plugins/gathered_user/models.py:26 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:59 -#: xpack/plugins/orgs/templates/orgs/org_list.html:23 msgid "Comment" msgstr "备注" @@ -307,7 +275,7 @@ msgstr "参数" #: applications/models/remote_app.py:39 #: applications/templates/applications/database_app_detail.html:72 #: applications/templates/applications/remote_app_detail.html:68 -#: assets/models/asset.py:225 assets/models/base.py:240 +#: assets/models/asset.py:224 assets/models/base.py:240 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 #: assets/models/cmd_filter.py:59 assets/models/group.py:21 #: assets/templates/assets/admin_user_detail.html:63 @@ -319,12 +287,8 @@ msgstr "参数" #: perms/templates/perms/asset_permission_detail.html:93 #: perms/templates/perms/database_app_permission_detail.html:89 #: perms/templates/perms/remote_app_permission_detail.html:85 -#: users/models/user.py:481 users/serializers/group.py:32 +#: users/models/user.py:481 users/serializers/group.py:35 #: users/templates/users/user_detail.html:97 -#: xpack/plugins/change_auth_plan/models.py:79 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:111 -#: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:142 -#: xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "创建者" @@ -333,14 +297,14 @@ msgstr "创建者" #: applications/models/remote_app.py:42 #: applications/templates/applications/database_app_detail.html:68 #: applications/templates/applications/remote_app_detail.html:64 -#: assets/models/asset.py:226 assets/models/base.py:238 +#: assets/models/asset.py:225 assets/models/base.py:238 #: assets/models/cluster.py:26 assets/models/domain.py:23 #: assets/models/gathered_user.py:19 assets/models/group.py:22 #: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:59 #: assets/templates/assets/cmd_filter_detail.html:64 #: assets/templates/assets/domain_detail.html:63 #: assets/templates/assets/system_user_detail.html:104 -#: common/mixins/models.py:50 ops/models/adhoc.py:38 +#: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:27 #: ops/templates/ops/adhoc_detail.html:88 ops/templates/ops/task_detail.html:62 #: orgs/models.py:17 perms/models/base.py:55 #: perms/templates/perms/asset_permission_detail.html:89 @@ -349,11 +313,6 @@ msgstr "创建者" #: terminal/templates/terminal/terminal_detail.html:59 #: tickets/templates/tickets/ticket_detail.html:52 users/models/group.py:18 #: users/templates/users/user_group_detail.html:58 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:103 -#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:145 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:63 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:98 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:55 msgid "Date created" msgstr "创建日期" @@ -403,13 +362,6 @@ msgstr "远程应用" #: users/templates/users/user_profile_update.html:67 #: users/templates/users/user_pubkey_update.html:74 #: users/templates/users/user_pubkey_update.html:80 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:65 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:29 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:49 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:40 -#: xpack/plugins/interface/templates/interface/interface.html:72 -#: xpack/plugins/orgs/templates/orgs/org_create_update.html:29 -#: xpack/plugins/vault/templates/vault/vault_create.html:41 msgid "Reset" msgstr "重置" @@ -447,10 +399,6 @@ msgstr "重置" #: users/templates/users/user_password_update.html:76 #: users/templates/users/user_profile_update.html:68 #: users/templates/users/user_pubkey_update.html:81 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:66 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67 -#: xpack/plugins/interface/templates/interface/interface.html:74 -#: xpack/plugins/vault/templates/vault/vault_create.html:42 msgid "Submit" msgstr "提交" @@ -478,11 +426,6 @@ msgstr "提交" #: perms/templates/perms/remote_app_permission_detail.html:13 #: perms/templates/perms/remote_app_permission_remote_app.html:13 #: perms/templates/perms/remote_app_permission_user.html:13 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:13 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:18 -#: 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:91 msgid "Detail" msgstr "详情" @@ -529,15 +472,6 @@ msgstr "详情" #: users/templates/users/user_profile.html:191 #: users/templates/users/user_profile.html:201 #: users/templates/users/user_remote_app_permission.html:110 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:27 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:56 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:20 -#: xpack/plugins/cloud/templates/cloud/account_list.html:40 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:26 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:60 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:46 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:20 -#: xpack/plugins/orgs/templates/orgs/org_list.html:93 msgid "Update" msgstr "更新" @@ -581,15 +515,6 @@ msgstr "更新" #: users/templates/users/user_list.html:94 #: users/templates/users/user_list.html:98 #: users/templates/users/user_remote_app_permission.html:111 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:31 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:58 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:24 -#: xpack/plugins/cloud/templates/cloud/account_list.html:42 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:61 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:47 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:24 -#: xpack/plugins/orgs/templates/orgs/org_list.html:95 msgid "Delete" msgstr "删除" @@ -640,14 +565,6 @@ msgstr "创建数据库应用" #: users/templates/users/user_group_list.html:17 #: users/templates/users/user_list.html:20 #: users/templates/users/user_remote_app_permission.html:42 -#: 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_history.html:52 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:19 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:20 -#: xpack/plugins/orgs/templates/orgs/org_list.html:24 msgid "Action" msgstr "动作" @@ -714,7 +631,7 @@ msgstr "远程应用详情" msgid "My RemoteApp" msgstr "我的远程应用" -#: assets/api/admin_user.py:59 +#: assets/api/admin_user.py:46 msgid "Deleted failed, There are related assets" msgstr "删除失败,存在关联资产" @@ -734,24 +651,18 @@ msgstr "不能移除资产的管理用户账号" msgid "Latest version could not be delete" msgstr "最新版本的不能被删除" -#: assets/forms/asset.py:83 assets/models/asset.py:196 +#: assets/forms/asset.py:83 assets/models/asset.py:195 #: assets/models/user.py:109 assets/templates/assets/asset_detail.html:186 #: assets/templates/assets/asset_detail.html:194 #: assets/templates/assets/system_user_assets.html:118 #: perms/models/asset_permission.py:81 -#: xpack/plugins/change_auth_plan/models.py:54 -#: xpack/plugins/gathered_user/models.py:24 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:17 msgid "Nodes" msgstr "节点" -#: assets/forms/asset.py:86 assets/models/asset.py:200 +#: assets/forms/asset.py:86 assets/models/asset.py:199 #: assets/models/cluster.py:19 assets/models/user.py:65 #: assets/templates/assets/admin_user_list.html:62 #: assets/templates/assets/asset_detail.html:72 templates/_nav.html:44 -#: xpack/plugins/cloud/models.py:133 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:65 -#: xpack/plugins/orgs/templates/orgs/org_list.html:19 msgid "Admin user" msgstr "管理用户" @@ -759,20 +670,18 @@ msgstr "管理用户" #: assets/templates/assets/asset_create.html:48 #: assets/templates/assets/asset_create.html:50 #: assets/templates/assets/asset_list.html:13 -#: xpack/plugins/orgs/templates/orgs/org_list.html:21 msgid "Label" msgstr "标签" -#: assets/forms/asset.py:92 assets/models/asset.py:195 +#: assets/forms/asset.py:92 assets/models/asset.py:194 #: assets/models/domain.py:26 assets/models/domain.py:52 #: assets/templates/assets/asset_detail.html:76 #: assets/templates/assets/user_asset_list.html:80 -#: xpack/plugins/orgs/templates/orgs/org_list.html:18 msgid "Domain" msgstr "网域" -#: assets/forms/asset.py:95 assets/models/asset.py:170 -#: assets/models/asset.py:194 assets/serializers/asset.py:67 +#: assets/forms/asset.py:95 assets/models/asset.py:169 +#: assets/models/asset.py:193 assets/serializers/asset.py:67 #: assets/templates/assets/asset_detail.html:100 #: assets/templates/assets/user_asset_list.html:78 msgid "Platform" @@ -788,11 +697,6 @@ msgstr "系统平台" #: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:158 -#: xpack/plugins/change_auth_plan/forms.py:75 -#: 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:129 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:61 msgid "Node" msgstr "节点" @@ -817,8 +721,6 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域, #: assets/forms/domain.py:17 assets/forms/label.py:15 #: assets/templates/assets/system_user_assets.html:102 #: perms/templates/perms/asset_permission_asset.html:74 -#: xpack/plugins/change_auth_plan/forms.py:65 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:70 msgid "Select assets" msgstr "选择资产" @@ -856,28 +758,16 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: users/templates/users/user_detail.html:53 #: users/templates/users/user_list.html:15 #: users/templates/users/user_profile.html:47 -#: xpack/plugins/change_auth_plan/forms.py:59 -#: xpack/plugins/change_auth_plan/models.py:45 -#: xpack/plugins/change_auth_plan/models.py:261 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:63 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:13 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:25 msgid "Username" msgstr "用户名" #: assets/forms/platform.py:20 ops/templates/ops/task_detail.html:85 #: ops/templates/ops/task_detail.html:95 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:82 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:72 msgid "Yes" msgstr "是" #: assets/forms/platform.py:21 ops/templates/ops/task_detail.html:87 #: ops/templates/ops/task_detail.html:97 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:84 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:74 msgid "No" msgstr "否" @@ -914,9 +804,6 @@ msgstr "密码或密钥密码" #: users/templates/users/user_profile_update.html:41 #: users/templates/users/user_pubkey_update.html:41 #: users/templates/users/user_update.html:20 -#: xpack/plugins/change_auth_plan/models.py:66 -#: xpack/plugins/change_auth_plan/models.py:181 -#: xpack/plugins/change_auth_plan/models.py:268 msgid "Password" msgstr "密码" @@ -975,24 +862,24 @@ msgstr "SFTP的起始路径,tmp目录, 用户home目录或者自定义" msgid "Username is dynamic, When connect asset, using current user's username" msgstr "用户名是动态的,登录资产时使用当前用户的用户名登录" -#: assets/models/asset.py:147 xpack/plugins/cloud/providers/base.py:16 +#: assets/models/asset.py:146 msgid "Base" msgstr "基础" -#: assets/models/asset.py:148 assets/templates/assets/platform_detail.html:56 +#: assets/models/asset.py:147 assets/templates/assets/platform_detail.html:56 msgid "Charset" msgstr "编码" -#: assets/models/asset.py:149 assets/templates/assets/platform_detail.html:60 +#: assets/models/asset.py:148 assets/templates/assets/platform_detail.html:60 #: tickets/models/ticket.py:38 msgid "Meta" msgstr "元数据" -#: assets/models/asset.py:150 +#: assets/models/asset.py:149 msgid "Internal" msgstr "内部的" -#: assets/models/asset.py:187 assets/models/domain.py:49 +#: assets/models/asset.py:186 assets/models/domain.py:49 #: assets/serializers/asset_user.py:46 #: assets/templates/assets/_asset_list_modal.html:47 #: assets/templates/assets/_asset_user_list.html:20 @@ -1002,14 +889,13 @@ msgstr "内部的" #: assets/templates/assets/user_asset_list.html:76 #: audits/templates/audits/login_log_list.html:60 #: perms/templates/perms/asset_permission_list.html:187 -#: settings/forms/terminal.py:16 users/templates/users/_granted_assets.html:26 +#: settings/forms/terminal.py:16 settings/serializers/settings.py:52 +#: users/templates/users/_granted_assets.html:26 #: users/templates/users/user_asset_permission.html:156 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:50 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:24 msgid "IP" msgstr "IP" -#: assets/models/asset.py:188 assets/serializers/asset_user.py:45 +#: assets/models/asset.py:187 assets/serializers/asset_user.py:45 #: assets/serializers/gathered_user.py:20 #: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/_asset_user_auth_update_modal.html:9 @@ -1019,14 +905,13 @@ msgstr "IP" #: assets/templates/assets/asset_list.html:24 #: assets/templates/assets/user_asset_list.html:75 #: perms/templates/perms/asset_permission_list.html:188 -#: settings/forms/terminal.py:15 users/templates/users/_granted_assets.html:25 +#: settings/forms/terminal.py:15 settings/serializers/settings.py:51 +#: users/templates/users/_granted_assets.html:25 #: users/templates/users/user_asset_permission.html:157 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:49 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:23 msgid "Hostname" msgstr "主机名" -#: assets/models/asset.py:191 assets/models/domain.py:51 +#: assets/models/asset.py:190 assets/models/domain.py:51 #: assets/models/user.py:114 assets/templates/assets/asset_detail.html:68 #: assets/templates/assets/domain_gateway_list.html:65 #: assets/templates/assets/system_user_detail.html:78 @@ -1038,84 +923,84 @@ msgstr "主机名" msgid "Protocol" msgstr "协议" -#: assets/models/asset.py:193 assets/serializers/asset.py:69 +#: assets/models/asset.py:192 assets/serializers/asset.py:69 #: assets/templates/assets/asset_create.html:24 #: assets/templates/assets/user_asset_list.html:77 #: perms/serializers/user_permission.py:60 msgid "Protocols" msgstr "协议组" -#: assets/models/asset.py:197 assets/models/cmd_filter.py:22 +#: assets/models/asset.py:196 assets/models/cmd_filter.py:22 #: assets/models/domain.py:54 assets/models/label.py:22 #: assets/templates/assets/asset_detail.html:108 authentication/models.py:45 msgid "Is active" msgstr "激活" -#: assets/models/asset.py:203 assets/templates/assets/asset_detail.html:64 +#: assets/models/asset.py:202 assets/templates/assets/asset_detail.html:64 msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:204 assets/templates/assets/asset_detail.html:116 +#: assets/models/asset.py:203 assets/templates/assets/asset_detail.html:116 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:207 assets/templates/assets/asset_detail.html:80 +#: assets/models/asset.py:206 assets/templates/assets/asset_detail.html:80 msgid "Vendor" msgstr "制造商" -#: assets/models/asset.py:208 assets/templates/assets/asset_detail.html:84 +#: assets/models/asset.py:207 assets/templates/assets/asset_detail.html:84 msgid "Model" msgstr "型号" -#: assets/models/asset.py:209 assets/templates/assets/asset_detail.html:112 +#: assets/models/asset.py:208 assets/templates/assets/asset_detail.html:112 msgid "Serial number" msgstr "序列号" -#: assets/models/asset.py:211 +#: assets/models/asset.py:210 msgid "CPU model" msgstr "CPU型号" -#: assets/models/asset.py:212 +#: assets/models/asset.py:211 msgid "CPU count" msgstr "CPU数量" -#: assets/models/asset.py:213 +#: assets/models/asset.py:212 msgid "CPU cores" msgstr "CPU核数" -#: assets/models/asset.py:214 +#: assets/models/asset.py:213 msgid "CPU vcpus" msgstr "CPU总数" -#: assets/models/asset.py:215 assets/templates/assets/asset_detail.html:92 +#: assets/models/asset.py:214 assets/templates/assets/asset_detail.html:92 msgid "Memory" msgstr "内存" -#: assets/models/asset.py:216 +#: assets/models/asset.py:215 msgid "Disk total" msgstr "硬盘大小" -#: assets/models/asset.py:217 +#: assets/models/asset.py:216 msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:219 assets/templates/assets/asset_detail.html:104 +#: assets/models/asset.py:218 assets/templates/assets/asset_detail.html:104 msgid "OS" msgstr "操作系统" -#: assets/models/asset.py:220 +#: assets/models/asset.py:219 msgid "OS version" msgstr "系统版本" -#: assets/models/asset.py:221 +#: assets/models/asset.py:220 msgid "OS arch" msgstr "系统架构" -#: assets/models/asset.py:222 +#: assets/models/asset.py:221 msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:224 assets/templates/assets/asset_create.html:46 +#: assets/models/asset.py:223 assets/templates/assets/asset_create.html:46 #: assets/templates/assets/asset_detail.html:220 templates/_nav.html:46 msgid "Labels" msgstr "标签管理" @@ -1140,23 +1025,17 @@ msgstr "版本" msgid "AuthBook" msgstr "" -#: assets/models/base.py:235 xpack/plugins/change_auth_plan/models.py:70 -#: xpack/plugins/change_auth_plan/models.py:188 -#: xpack/plugins/change_auth_plan/models.py:275 +#: assets/models/base.py:235 msgid "SSH private key" msgstr "ssh密钥" -#: assets/models/base.py:236 xpack/plugins/change_auth_plan/models.py:73 -#: xpack/plugins/change_auth_plan/models.py:184 -#: xpack/plugins/change_auth_plan/models.py:271 +#: assets/models/base.py:236 msgid "SSH public key" msgstr "ssh公钥" #: assets/models/base.py:239 assets/models/gathered_user.py:20 #: assets/templates/assets/cmd_filter_detail.html:68 common/mixins/models.py:51 #: ops/models/adhoc.py:39 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:107 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:29 msgid "Date updated" msgstr "更新日期" @@ -1254,7 +1133,6 @@ msgstr "优先级可选范围为1-100,1最低优先级,100最高优先级" #: assets/models/cmd_filter.py:54 #: assets/templates/assets/cmd_filter_rule_list.html:54 -#: xpack/plugins/license/models.py:29 msgid "Content" msgstr "内容" @@ -1274,17 +1152,14 @@ msgid "Gateway" msgstr "网关" #: assets/models/gathered_user.py:16 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:28 msgid "Present" msgstr "存在" #: assets/models/gathered_user.py:17 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:26 msgid "Date last login" msgstr "最后登录日期" #: assets/models/gathered_user.py:18 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:27 msgid "IP last login" msgstr "最后登录IP" @@ -1307,7 +1182,8 @@ msgstr "默认资产组" #: audits/templates/audits/operate_log_list.html:37 #: audits/templates/audits/password_change_log_list.html:37 #: audits/templates/audits/password_change_log_list.html:54 -#: authentication/models.py:43 ops/templates/ops/command_execution_list.html:41 +#: authentication/models.py:43 ops/models/command.py:25 +#: ops/templates/ops/command_execution_list.html:41 #: ops/templates/ops/command_execution_list.html:66 #: perms/forms/asset_permission.py:83 perms/forms/database_app_permission.py:38 #: perms/forms/remote_app_permission.py:40 perms/models/base.py:49 @@ -1337,14 +1213,12 @@ msgstr "默认资产组" #: users/templates/users/user_group_list.html:15 #: users/templates/users/user_remote_app_permission.html:37 #: users/templates/users/user_remote_app_permission.html:58 -#: users/views/profile/base.py:46 xpack/plugins/orgs/forms.py:27 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:108 -#: xpack/plugins/orgs/templates/orgs/org_list.html:15 +#: users/views/profile/base.py:46 msgid "User" msgstr "用户" #: assets/models/label.py:19 assets/models/node.py:488 -#: assets/templates/assets/label_list.html:15 settings/models.py:27 +#: assets/templates/assets/label_list.html:15 settings/models.py:28 msgid "Value" msgstr "值" @@ -1400,14 +1274,14 @@ msgstr "手动登录" #: assets/views/platform.py:58 assets/views/platform.py:74 #: assets/views/system_user.py:30 assets/views/system_user.py:47 #: assets/views/system_user.py:64 assets/views/system_user.py:80 -#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:50 +#: templates/_nav.html:39 msgid "Assets" msgstr "资产管理" #: assets/models/user.py:111 assets/templates/assets/system_user_users.html:76 #: templates/_nav.html:17 users/views/group.py:28 users/views/group.py:45 #: users/views/group.py:63 users/views/group.py:82 users/views/group.py:99 -#: users/views/login.py:164 users/views/profile/password.py:40 +#: users/views/login.py:163 users/views/profile/password.py:40 #: users/views/profile/pubkey.py:36 users/views/user.py:50 #: users/views/user.py:67 users/views/user.py:111 users/views/user.py:178 #: users/views/user.py:206 users/views/user.py:220 users/views/user.py:234 @@ -1474,7 +1348,6 @@ msgstr "SFTP根路径" #: users/templates/users/user_database_app_permission.html:67 #: users/templates/users/user_remote_app_permission.html:40 #: users/templates/users/user_remote_app_permission.html:67 -#: xpack/plugins/orgs/templates/orgs/org_list.html:20 msgid "System user" msgstr "系统用户" @@ -1504,15 +1377,15 @@ msgstr "协议格式 {}/{}" msgid "Protocol duplicate: {}" msgstr "协议重复: {}" -#: assets/serializers/asset.py:94 +#: assets/serializers/asset.py:108 msgid "Hardware info" msgstr "硬件信息" -#: assets/serializers/asset.py:95 orgs/mixins/serializers.py:27 +#: assets/serializers/asset.py:109 orgs/mixins/serializers.py:27 msgid "Org name" msgstr "组织名称" -#: assets/serializers/asset.py:134 assets/serializers/asset.py:171 +#: assets/serializers/asset.py:144 assets/serializers/asset.py:181 msgid "Connectivity" msgstr "连接" @@ -1524,8 +1397,6 @@ msgstr "连接" #: ops/templates/ops/adhoc_history_detail.html:47 #: ops/templates/ops/task_detail.html:54 #: terminal/templates/terminal/session_list.html:24 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:45 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:44 msgid "ID" msgstr "ID" @@ -1718,7 +1589,7 @@ msgstr "启用多因子认证" #: assets/templates/assets/system_user_assets.html:26 #: assets/templates/assets/system_user_detail.html:18 #: assets/templates/assets/system_user_users.html:25 assets/views/asset.py:38 -#: templates/_nav.html:42 xpack/plugins/change_auth_plan/views.py:118 +#: templates/_nav.html:42 msgid "Asset list" msgstr "资产列表" @@ -1728,7 +1599,6 @@ msgstr "资产列表" #: ops/templates/ops/command_execution_create.html:112 #: settings/templates/settings/_ldap_list_users_modal.html:41 #: users/templates/users/_granted_assets.html:7 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:62 msgid "Loading" msgstr "加载中" @@ -1738,7 +1608,6 @@ msgstr "更新资产用户认证信息" #: assets/templates/assets/_asset_user_auth_update_modal.html:23 #: settings/templates/settings/_ldap_test_user_login_modal.html:18 -#: xpack/plugins/change_auth_plan/forms.py:61 msgid "Please input password" msgstr "请输入密码" @@ -1746,7 +1615,6 @@ msgstr "请输入密码" #: assets/templates/assets/asset_detail.html:300 #: users/templates/users/user_detail.html:356 #: users/templates/users/user_detail.html:383 -#: xpack/plugins/interface/views.py:35 msgid "Update successfully!" msgstr "更新成功" @@ -1863,9 +1731,6 @@ msgstr "重命名成功" #: perms/templates/perms/asset_permission_create_update.html:48 #: perms/templates/perms/database_app_permission_create_update.html:37 #: perms/templates/perms/remote_app_permission_create_update.html:37 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:37 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:23 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:23 msgid "Basic" msgstr "基本" @@ -1887,9 +1752,6 @@ msgstr "自动生成密钥" #: perms/templates/perms/database_app_permission_create_update.html:51 #: perms/templates/perms/remote_app_permission_create_update.html:51 #: terminal/templates/terminal/terminal_update.html:38 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:61 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:44 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:35 msgid "Other" msgstr "其它" @@ -1906,7 +1768,6 @@ msgstr "资产列表" #: assets/templates/assets/admin_user_assets.html:24 #: perms/templates/perms/asset_permission_asset.html:31 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:27 msgid "Asset list of " msgstr "资产列表" @@ -1932,9 +1793,6 @@ msgstr "替换资产的管理员" #: assets/templates/assets/admin_user_detail.html:86 #: assets/templates/assets/system_user_assets.html:126 #: perms/templates/perms/asset_permission_asset.html:99 -#: xpack/plugins/change_auth_plan/forms.py:69 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:95 -#: xpack/plugins/gathered_user/forms.py:33 msgid "Select nodes" msgstr "选择节点" @@ -1956,11 +1814,6 @@ msgstr "选择节点" #: users/templates/users/user_group_create_update.html:28 #: users/templates/users/user_list.html:184 #: users/templates/users/user_password_verify.html:20 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:30 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:50 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:41 -#: xpack/plugins/interface/templates/interface/interface.html:103 -#: xpack/plugins/orgs/templates/orgs/org_create_update.html:30 msgid "Confirm" msgstr "确认" @@ -1996,9 +1849,6 @@ msgstr "资产用户" #: terminal/templates/terminal/session_detail.html:87 #: users/templates/users/user_detail.html:126 #: users/templates/users/user_profile.html:150 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:126 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:129 -#: xpack/plugins/license/templates/license/license_detail.html:80 msgid "Quick modify" msgstr "快速修改" @@ -2121,7 +1971,6 @@ msgstr "显示所有子节点资产" #: users/templates/users/user_detail.html:437 #: users/templates/users/user_detail.html:505 #: users/templates/users/user_list.html:178 -#: xpack/plugins/interface/templates/interface/interface.html:97 msgid "Are you sure?" msgstr "你确认吗?" @@ -2134,7 +1983,6 @@ msgstr "删除选择资产" #: users/templates/users/user_detail.html:441 #: users/templates/users/user_detail.html:509 #: users/templates/users/user_list.html:182 -#: xpack/plugins/interface/templates/interface/interface.html:101 msgid "Cancel" msgstr "取消" @@ -2335,7 +2183,6 @@ msgstr "创建系统用户" #: assets/templates/assets/system_user_users.html:84 users/forms/group.py:19 #: users/forms/user.py:143 users/forms/user.py:148 -#: xpack/plugins/orgs/forms.py:17 msgid "Select users" msgstr "选择用户" @@ -2494,13 +2341,11 @@ msgstr "文件名" #: ops/templates/ops/command_execution_list.html:71 #: ops/templates/ops/task_list.html:14 #: users/templates/users/user_detail.html:487 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:14 msgid "Success" msgstr "成功" #: audits/models.py:33 #: authentication/templates/authentication/_access_key_modal.html:22 -#: xpack/plugins/vault/templates/vault/vault.html:7 msgid "Create" msgstr "创建" @@ -2521,7 +2366,7 @@ msgstr "修改者" msgid "Disabled" msgstr "禁用" -#: audits/models.py:72 settings/models.py:30 +#: audits/models.py:72 settings/models.py:31 #: users/templates/users/user_detail.html:82 msgid "Enabled" msgstr "启用" @@ -2530,7 +2375,7 @@ msgstr "启用" msgid "-" msgstr "" -#: audits/models.py:78 xpack/plugins/cloud/models.py:201 +#: audits/models.py:78 msgid "Failed" msgstr "失败" @@ -2561,9 +2406,6 @@ msgid "MFA" msgstr "多因子认证" #: audits/models.py:87 audits/templates/audits/login_log_list.html:63 -#: xpack/plugins/change_auth_plan/models.py:286 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15 -#: xpack/plugins/cloud/models.py:214 msgid "Reason" msgstr "原因" @@ -2571,9 +2413,6 @@ msgstr "原因" #: tickets/templates/tickets/ticket_detail.html:34 #: tickets/templates/tickets/ticket_list.html:36 #: tickets/templates/tickets/ticket_list.html:104 -#: xpack/plugins/cloud/models.py:211 xpack/plugins/cloud/models.py:269 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:50 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:48 msgid "Status" msgstr "状态" @@ -2581,7 +2420,7 @@ msgstr "状态" msgid "Date login" msgstr "登录日期" -#: audits/templates/audits/ftp_log_list.html:81 +#: audits/templates/audits/ftp_log_list.html:81 ops/models/command.py:28 #: ops/templates/ops/adhoc_history.html:50 #: ops/templates/ops/adhoc_history_detail.html:59 #: ops/templates/ops/command_execution_list.html:72 @@ -2591,11 +2430,6 @@ msgstr "登录日期" #: perms/templates/perms/remote_app_permission_detail.html:73 #: terminal/models.py:199 terminal/templates/terminal/session_detail.html:72 #: terminal/templates/terminal/session_list.html:32 -#: xpack/plugins/change_auth_plan/models.py:167 -#: xpack/plugins/change_auth_plan/models.py:290 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17 -#: xpack/plugins/gathered_user/models.py:76 msgid "Date start" msgstr "开始日期" @@ -2698,20 +2532,20 @@ msgstr "" msgid "User disabled." msgstr "用户已禁用" -#: authentication/backends/api.py:121 +#: authentication/backends/api.py:124 msgid "Invalid token header. No credentials provided." msgstr "" -#: authentication/backends/api.py:124 +#: authentication/backends/api.py:127 msgid "Invalid token header. Sign string should not contain spaces." msgstr "" -#: authentication/backends/api.py:131 +#: authentication/backends/api.py:134 msgid "" "Invalid token header. Sign string should not contain invalid characters." msgstr "" -#: authentication/backends/api.py:142 +#: authentication/backends/api.py:145 msgid "Invalid token or cache refreshed." msgstr "" @@ -3043,25 +2877,14 @@ msgid "Not has host {} permission" msgstr "没有该主机 {} 权限" #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:98 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:88 msgid "Cycle perform" msgstr "周期执行" #: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:111 ops/mixin.py:150 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:90 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:80 msgid "Regularly perform" msgstr "定期执行" #: ops/mixin.py:108 ops/mixin.py:147 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:54 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:79 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:17 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:37 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:28 msgid "Periodic perform" msgstr "定时执行" @@ -3119,8 +2942,6 @@ msgid "Become" msgstr "Become" #: ops/models/adhoc.py:150 users/templates/users/user_group_detail.html:54 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:59 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:51 msgid "Create by" msgstr "创建者" @@ -3142,15 +2963,11 @@ msgstr "完成时间" #: ops/models/adhoc.py:238 ops/templates/ops/adhoc_history.html:55 #: ops/templates/ops/task_history.html:61 ops/templates/ops/task_list.html:16 -#: xpack/plugins/change_auth_plan/models.py:170 -#: xpack/plugins/change_auth_plan/models.py:293 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16 -#: xpack/plugins/gathered_user/models.py:79 msgid "Time" msgstr "时间" -#: ops/models/adhoc.py:239 ops/templates/ops/adhoc_detail.html:104 +#: ops/models/adhoc.py:239 ops/models/command.py:26 +#: ops/templates/ops/adhoc_detail.html:104 #: ops/templates/ops/adhoc_history.html:53 #: ops/templates/ops/adhoc_history_detail.html:67 #: ops/templates/ops/task_detail.html:82 ops/templates/ops/task_history.html:59 @@ -3170,20 +2987,36 @@ msgstr "结果" msgid "Adhoc result summary" msgstr "汇总" -#: ops/models/adhoc.py:282 xpack/plugins/change_auth_plan/utils.py:137 +#: ops/models/adhoc.py:282 msgid "{} Start task: {}" msgstr "{} 任务开始: {}" -#: ops/models/adhoc.py:291 xpack/plugins/change_auth_plan/utils.py:149 +#: ops/models/adhoc.py:291 msgid "{} Task finish" msgstr "{} 任务结束" +#: ops/models/command.py:21 ops/templates/ops/adhoc_detail.html:51 +#: ops/templates/ops/command_execution_list.html:65 +#: ops/templates/ops/task_adhoc.html:57 ops/templates/ops/task_list.html:13 +#: terminal/forms/storage.py:151 +msgid "Hosts" +msgstr "主机" + +#: ops/models/command.py:22 ops/templates/ops/adhoc_detail.html:70 +#: ops/templates/ops/adhoc_detail.html:75 +#: ops/templates/ops/command_execution_list.html:68 +#: ops/templates/ops/task_adhoc.html:59 +msgid "Run as" +msgstr "运行用户" + #: ops/models/command.py:24 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:56 -#: xpack/plugins/cloud/models.py:209 msgid "Result" msgstr "结果" +#: ops/models/command.py:29 +msgid "Date finished" +msgstr "结束日期" + #: ops/models/command.py:64 msgid "Task start" msgstr "任务开始" @@ -3214,23 +3047,7 @@ msgstr "版本详情" msgid "Version run execution" msgstr "执行历史" -#: ops/templates/ops/adhoc_detail.html:51 -#: ops/templates/ops/command_execution_list.html:65 -#: ops/templates/ops/task_adhoc.html:57 ops/templates/ops/task_list.html:13 -#: terminal/forms/storage.py:151 -msgid "Hosts" -msgstr "主机" - -#: ops/templates/ops/adhoc_detail.html:70 -#: ops/templates/ops/adhoc_detail.html:75 -#: ops/templates/ops/command_execution_list.html:68 -#: ops/templates/ops/task_adhoc.html:59 -msgid "Run as" -msgstr "运行用户" - #: ops/templates/ops/adhoc_detail.html:92 ops/templates/ops/task_list.html:12 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:18 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:19 msgid "Run times" msgstr "执行次数" @@ -3358,7 +3175,6 @@ msgid "Pending" msgstr "等待" #: ops/templates/ops/command_execution_list.html:70 -#: xpack/plugins/change_auth_plan/models.py:257 msgid "Finished" msgstr "结束" @@ -3396,11 +3212,6 @@ msgid "Contents" msgstr "内容" #: ops/templates/ops/task_list.html:73 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:135 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:54 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:138 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:58 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:44 msgid "Run" msgstr "执行" @@ -3423,7 +3234,6 @@ msgid "Ops" msgstr "作业中心" #: ops/views/adhoc.py:32 templates/_nav.html:124 -#: xpack/plugins/gathered_user/views.py:35 msgid "Task list" msgstr "任务列表" @@ -3474,7 +3284,6 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件" #: users/templates/users/user_list.html:17 #: users/templates/users/user_remote_app_permission.html:38 #: users/templates/users/user_remote_app_permission.html:61 -#: xpack/plugins/orgs/templates/orgs/org_list.html:16 msgid "User group" msgstr "用户组" @@ -3487,6 +3296,7 @@ msgid "Asset or group at least one required" msgstr "资产和节点至少选一个" #: perms/models/asset_permission.py:31 settings/forms/terminal.py:19 +#: settings/serializers/settings.py:56 msgid "All" msgstr "全部" @@ -3549,9 +3359,6 @@ msgstr "用户或用户组" #: perms/templates/perms/asset_permission_asset.html:23 #: perms/templates/perms/asset_permission_detail.html:22 #: perms/templates/perms/asset_permission_user.html:23 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:16 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:21 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:20 msgid "Assets and node" msgstr "资产或节点" @@ -3572,9 +3379,6 @@ msgstr "添加资产" #: perms/templates/perms/remote_app_permission_user.html:92 #: perms/templates/perms/remote_app_permission_user.html:120 #: users/templates/users/user_group_detail.html:87 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:76 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:88 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:125 msgid "Add" msgstr "添加" @@ -3584,7 +3388,6 @@ msgstr "添加节点" #: perms/templates/perms/asset_permission_asset.html:105 #: users/templates/users/user_detail.html:226 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:101 msgid "Join" msgstr "加入" @@ -3613,7 +3416,6 @@ msgid "User group count" msgstr "用户组数量" #: perms/templates/perms/asset_permission_detail.html:69 -#: xpack/plugins/license/templates/license/license_detail.html:63 msgid "Asset count" msgstr "资产数量" @@ -3645,9 +3447,6 @@ msgstr "刷新授权缓存" #: users/templates/users/user_database_app_permission.html:41 #: users/templates/users/user_list.html:19 #: users/templates/users/user_remote_app_permission.html:41 -#: xpack/plugins/cloud/models.py:50 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:55 -#: xpack/plugins/cloud/templates/cloud/account_list.html:14 msgid "Validity" msgstr "有效" @@ -3753,7 +3552,6 @@ msgstr "添加用户组" #: perms/views/remote_app_permission.py:84 #: perms/views/remote_app_permission.py:116 #: perms/views/remote_app_permission.py:149 templates/_nav.html:75 -#: xpack/plugins/orgs/templates/orgs/org_list.html:22 msgid "Perms" msgstr "权限管理" @@ -4093,7 +3891,7 @@ msgid "" "characters" msgstr "开启后,用户密码修改、重置必须包含特殊字符" -#: settings/forms/terminal.py:20 +#: settings/forms/terminal.py:20 settings/serializers/settings.py:57 msgid "Auto" msgstr "自动" @@ -4142,7 +3940,7 @@ msgid "ex: Last\\s*login|success|成功" msgstr "" "登录telnet服务器成功后的提示正则表达式,如: Last\\s*login|success|成功 " -#: settings/models.py:95 users/templates/users/reset_password.html:29 +#: settings/models.py:96 users/templates/users/reset_password.html:29 #: users/templates/users/user_profile.html:20 msgid "Setting" msgstr "设置" @@ -4177,7 +3975,6 @@ msgstr "当前无勾选用户,请勾选你想要导入的用户" #: settings/templates/settings/_ldap_list_users_modal.html:172 #: templates/_csv_import_export.html:13 templates/_csv_import_modal.html:5 -#: xpack/plugins/license/templates/license/license_detail.html:88 msgid "Import" msgstr "导入" @@ -4356,7 +4153,6 @@ msgid "Update setting successfully" msgstr "更新设置成功" #: templates/_base_only_msg_content.html:28 -#: xpack/plugins/interface/models.py:36 msgid "Welcome to the JumpServer open source fortress" msgstr "欢迎使用JumpServer开源堡垒机" @@ -4590,7 +4386,7 @@ msgstr "工单管理" msgid "XPack" msgstr "" -#: templates/_nav.html:171 xpack/plugins/cloud/views.py:28 +#: templates/_nav.html:171 msgid "Account list" msgstr "账户列表" @@ -4880,10 +4676,7 @@ msgid "" " " msgstr "" -#: terminal/forms/storage.py:136 xpack/plugins/cloud/models.py:263 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:29 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:106 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:46 +#: terminal/forms/storage.py:136 msgid "Region" msgstr "地域" @@ -5300,7 +5093,7 @@ msgstr "工单列表" msgid "Ticket detail" msgstr "工单详情" -#: users/api/user.py:116 +#: users/api/user.py:115 msgid "Could not reset self otp, use profile reset instead" msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置" @@ -5374,7 +5167,7 @@ msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" #: users/forms/profile.py:137 users/forms/user.py:90 -#: users/serializers/user.py:138 +#: users/serializers/user.py:154 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" @@ -5401,24 +5194,20 @@ msgstr "复制用户公钥到这里" msgid "Join user groups" msgstr "添加到用户组" -#: users/forms/user.py:103 users/views/login.py:124 +#: users/forms/user.py:103 users/views/login.py:123 #: users/views/profile/password.py:57 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/forms/user.py:124 +#: users/forms/user.py:124 users/serializers/user.py:26 msgid "Reset link will be generated and sent to the user" msgstr "生成重置密码链接,通过邮件发送给用户" -#: users/forms/user.py:125 +#: users/forms/user.py:125 users/serializers/user.py:27 msgid "Set password" msgstr "设置密码" -#: users/forms/user.py:132 xpack/plugins/change_auth_plan/models.py:59 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:45 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:67 -#: 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 +#: users/forms/user.py:132 users/serializers/user.py:34 msgid "Password strategy" msgstr "密码策略" @@ -5426,8 +5215,7 @@ msgstr "密码策略" msgid "Administrator" msgstr "管理员" -#: users/models/user.py:145 xpack/plugins/orgs/forms.py:29 -#: xpack/plugins/orgs/templates/orgs/org_list.html:14 +#: users/models/user.py:145 msgid "Auditor" msgstr "审计员" @@ -5463,46 +5251,46 @@ msgstr "最后更新密码日期" msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/serializers/group.py:46 +#: users/serializers/group.py:50 msgid "Auditors cannot be join in the user group" msgstr "审计员不能被加入到用户组" -#: users/serializers/user.py:42 +#: users/serializers/user.py:62 msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:43 +#: users/serializers/user.py:63 msgid "Is valid" msgstr "账户是否有效" -#: users/serializers/user.py:44 +#: users/serializers/user.py:64 msgid "Is expired" msgstr " 是否过期" -#: users/serializers/user.py:45 +#: users/serializers/user.py:65 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:53 -msgid "Role limit to {}" -msgstr "角色只能为 {}" - -#: users/serializers/user.py:65 -msgid "Password does not match security rules" -msgstr "密码不满足安全规则" - -#: users/serializers/user.py:123 +#: users/serializers/user.py:69 msgid "Groups name" msgstr "用户组名" -#: users/serializers/user.py:124 +#: users/serializers/user.py:70 msgid "Source name" msgstr "用户来源名" -#: users/serializers/user.py:125 +#: users/serializers/user.py:71 msgid "Role name" msgstr "角色名" +#: users/serializers/user.py:90 +msgid "Role limit to {}" +msgstr "角色只能为 {}" + +#: users/serializers/user.py:102 +msgid "Password does not match security rules" +msgstr "密码不满足安全规则" + #: users/serializers_v2/user.py:36 msgid "name not unique" msgstr "名称重复" @@ -5513,9 +5301,6 @@ msgstr "安全令牌验证" #: users/templates/users/_base_otp.html:14 users/templates/users/_user.html:13 #: users/templates/users/user_profile_update.html:55 -#: xpack/plugins/cloud/models.py:119 -#: 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 "账户" @@ -5781,7 +5566,6 @@ msgid "User group detail" msgstr "用户组详情" #: users/templates/users/user_group_detail.html:81 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:116 msgid "Add user" msgstr "添加用户" @@ -5921,7 +5705,7 @@ msgid "Update user" msgstr "更新用户" #: users/templates/users/user_update.html:22 users/views/login.py:49 -#: users/views/login.py:117 +#: users/views/login.py:116 msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" @@ -6143,28 +5927,28 @@ msgstr "用户组授权资产" msgid "Email address invalid, please input again" msgstr "邮箱地址错误,重新输入" -#: users/views/login.py:63 +#: users/views/login.py:62 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views/login.py:64 +#: users/views/login.py:63 msgid "Send reset password mail success, login your mail box and follow it " msgstr "" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" -#: users/views/login.py:77 +#: users/views/login.py:76 msgid "Reset password success" msgstr "重置密码成功" -#: users/views/login.py:78 +#: users/views/login.py:77 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:102 users/views/login.py:112 +#: users/views/login.py:101 users/views/login.py:111 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/login.py:164 +#: users/views/login.py:163 msgid "First login" msgstr "首次登录" @@ -6220,729 +6004,476 @@ msgstr "用户授权远程应用" msgid "User granted DatabaseApp" msgstr "用户授权数据库应用" -#: xpack/plugins/change_auth_plan/forms.py:21 -msgid "Password length" -msgstr "密码长度" - -#: xpack/plugins/change_auth_plan/forms.py:79 -msgid "" -"Tips: The username of the user on the asset to be modified. if the user " -"exists, change the password; If the user does not exist, create the user." -msgstr "" -"提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如果" -"用户不存在,则创建用户。" - -#: xpack/plugins/change_auth_plan/meta.py:9 -#: xpack/plugins/change_auth_plan/models.py:87 -#: xpack/plugins/change_auth_plan/models.py:174 -#: xpack/plugins/change_auth_plan/views.py:33 -#: xpack/plugins/change_auth_plan/views.py:50 -#: xpack/plugins/change_auth_plan/views.py:74 -#: xpack/plugins/change_auth_plan/views.py:90 -#: xpack/plugins/change_auth_plan/views.py:117 -#: xpack/plugins/change_auth_plan/views.py:132 -#: xpack/plugins/change_auth_plan/views.py:147 -msgid "Change auth plan" -msgstr "改密计划" - -#: xpack/plugins/change_auth_plan/models.py:39 -msgid "Custom password" -msgstr "自定义密码" - -#: xpack/plugins/change_auth_plan/models.py:40 -msgid "All assets use the same random password" -msgstr "所有资产使用相同的随机密码" - -#: xpack/plugins/change_auth_plan/models.py:41 -msgid "All assets use different random password" -msgstr "所有资产使用不同的随机密码" - -#: xpack/plugins/change_auth_plan/models.py:63 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:72 -msgid "Password rules" -msgstr "密码规则" - -#: xpack/plugins/change_auth_plan/models.py:178 -msgid "Change auth plan snapshot" -msgstr "改密计划快照" - -#: xpack/plugins/change_auth_plan/models.py:193 -#: xpack/plugins/change_auth_plan/models.py:279 -msgid "Change auth plan execution" -msgstr "改密计划执行" - -#: xpack/plugins/change_auth_plan/models.py:252 -msgid "Ready" -msgstr "" - -#: xpack/plugins/change_auth_plan/models.py:253 -msgid "Preflight check" -msgstr "" - -#: xpack/plugins/change_auth_plan/models.py:254 -msgid "Change auth" -msgstr "" - -#: xpack/plugins/change_auth_plan/models.py:255 -msgid "Verify auth" -msgstr "" - -#: xpack/plugins/change_auth_plan/models.py:256 -msgid "Keep auth" -msgstr "" - -#: xpack/plugins/change_auth_plan/models.py:283 -msgid "Step" -msgstr "步骤" - -#: xpack/plugins/change_auth_plan/models.py:300 -msgid "Change auth plan task" -msgstr "改密计划任务" - -#: xpack/plugins/change_auth_plan/serializers.py:68 -msgid "* Please enter custom password" -msgstr "* 请输入自定义密码" - -#: xpack/plugins/change_auth_plan/serializers.py:78 -msgid "* Please enter the correct password length" -msgstr "* 请输入正确的密码长度" - -#: xpack/plugins/change_auth_plan/serializers.py:81 -msgid "* Password length range 6-30 bits" -msgstr "* 密码长度范围 6-30 位" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:19 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:24 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:23 -#: xpack/plugins/change_auth_plan/views.py:133 -msgid "Plan execution list" -msgstr "执行列表" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:62 -msgid "Add asset to this plan" -msgstr "添加资产" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:87 -msgid "Add node to this plan" -msgstr "添加节点" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:7 -msgid "" -"When the user password on the asset is changed, the action is performed " -"using the admin user associated with the asset" -msgstr "更改资产上的用户密码时,将会使用与该资产关联的管理用户进行操作" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74 -msgid "Length" -msgstr "长度" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:132 -msgid "Run plan manually" -msgstr "手动执行计划" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:176 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:102 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:90 -msgid "Execute failed" -msgstr "执行失败" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:31 -msgid "Execution list of plan" -msgstr "执行历史列表" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:104 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:106 -msgid "Log" -msgstr "日志" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:61 -msgid "Retry" -msgstr "重试" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:95 -msgid "Run failed" -msgstr "执行失败" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:5 -#: xpack/plugins/change_auth_plan/views.py:51 -msgid "Create plan" -msgstr "创建计划" - -#: xpack/plugins/change_auth_plan/utils.py:437 -msgid "Invalid/incorrect password" -msgstr "无效/错误 密码" - -#: xpack/plugins/change_auth_plan/utils.py:439 -msgid "Failed to connect to the host" -msgstr "连接主机失败" - -#: xpack/plugins/change_auth_plan/utils.py:441 -msgid "Data could not be sent to remote" -msgstr "无法将数据发送到远程" - -#: xpack/plugins/change_auth_plan/views.py:34 -msgid "Plan list" -msgstr "计划列表" - -#: xpack/plugins/change_auth_plan/views.py:75 -msgid "Update plan" -msgstr "更新计划" - -#: xpack/plugins/change_auth_plan/views.py:148 -msgid "Plan execution task list" -msgstr "执行任务列表" - -#: xpack/plugins/cloud/forms.py:15 -msgid "Access Key" -msgstr "" - -#: xpack/plugins/cloud/forms.py:19 -msgid "Secret Key" -msgstr "" - -#: xpack/plugins/cloud/forms.py:56 -msgid "Select account" -msgstr "选择账户" - -#: xpack/plugins/cloud/forms.py:62 -msgid "Select regions" -msgstr "选择地域" - -#: xpack/plugins/cloud/forms.py:68 -msgid "Select instances" -msgstr "选择实例" - -#: xpack/plugins/cloud/forms.py:74 -msgid "Select node" -msgstr "选择节点" - -#: xpack/plugins/cloud/forms.py:80 xpack/plugins/orgs/forms.py:20 -msgid "Select admins" -msgstr "选择管理员" - -#: xpack/plugins/cloud/meta.py:9 xpack/plugins/cloud/views.py:27 -#: xpack/plugins/cloud/views.py:44 xpack/plugins/cloud/views.py:62 -#: xpack/plugins/cloud/views.py:78 xpack/plugins/cloud/views.py:92 -#: xpack/plugins/cloud/views.py:109 xpack/plugins/cloud/views.py:127 -#: xpack/plugins/cloud/views.py:143 xpack/plugins/cloud/views.py:158 -#: xpack/plugins/cloud/views.py:172 -msgid "Cloud center" -msgstr "云管中心" - -#: xpack/plugins/cloud/models.py:29 -msgid "Available" -msgstr "有效" - -#: xpack/plugins/cloud/models.py:30 -msgid "Unavailable" -msgstr "无效" - -#: xpack/plugins/cloud/models.py:39 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:51 -#: xpack/plugins/cloud/templates/cloud/account_list.html:13 -msgid "Provider" -msgstr "云服务商" - -#: xpack/plugins/cloud/models.py:42 -msgid "Access key id" -msgstr "" - -#: xpack/plugins/cloud/models.py:46 -msgid "Access key secret" -msgstr "" - -#: xpack/plugins/cloud/models.py:64 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:26 -msgid "Cloud account" -msgstr "云账号" - -#: xpack/plugins/cloud/models.py:122 -msgid "Regions" -msgstr "地域" - -#: xpack/plugins/cloud/models.py:125 -msgid "Instances" -msgstr "实例" - -#: xpack/plugins/cloud/models.py:139 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:94 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:17 -msgid "Date last sync" -msgstr "最后同步日期" - -#: xpack/plugins/cloud/models.py:150 xpack/plugins/cloud/models.py:207 -msgid "Sync instance task" -msgstr "同步实例任务" - -#: xpack/plugins/cloud/models.py:202 -msgid "Succeed" -msgstr "成功" - -#: xpack/plugins/cloud/models.py:217 xpack/plugins/cloud/models.py:272 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:51 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:49 -msgid "Date sync" -msgstr "同步日期" - -#: xpack/plugins/cloud/models.py:245 -msgid "Unsync" -msgstr "未同步" - -#: xpack/plugins/cloud/models.py:246 xpack/plugins/cloud/models.py:247 -msgid "Synced" -msgstr "已同步" - -#: xpack/plugins/cloud/models.py:248 -msgid "Released" -msgstr "已释放" - -#: xpack/plugins/cloud/models.py:253 -msgid "Sync task" -msgstr "同步任务" - -#: xpack/plugins/cloud/models.py:257 -msgid "Sync instance task history" -msgstr "同步实例任务历史" - -#: xpack/plugins/cloud/models.py:260 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:114 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:45 -msgid "Instance" -msgstr "实例" - -#: xpack/plugins/cloud/providers/aliyun.py:16 -msgid "Alibaba Cloud" -msgstr "阿里云" - -#: xpack/plugins/cloud/providers/aws.py:14 -msgid "AWS (International)" -msgstr "AWS (国际)" - -#: xpack/plugins/cloud/providers/aws_china.py:9 -msgid "AWS (China)" -msgstr "AWS (中国)" - -#: xpack/plugins/cloud/providers/huaweicloud.py:13 -msgid "Huawei Cloud" -msgstr "华为云" - -#: xpack/plugins/cloud/providers/huaweicloud.py:16 -msgid "CN North-Beijing4" -msgstr "华北-北京4" - -#: xpack/plugins/cloud/providers/huaweicloud.py:17 -msgid "CN East-Shanghai1" -msgstr "华东-上海1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:18 -msgid "CN East-Shanghai2" -msgstr "华东-上海2" - -#: xpack/plugins/cloud/providers/huaweicloud.py:19 -msgid "CN South-Guangzhou" -msgstr "华南-广州" - -#: xpack/plugins/cloud/providers/huaweicloud.py:20 -msgid "CN Southwest-Guiyang1" -msgstr "西南-贵阳1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:21 -msgid "AP-Hong-Kong" -msgstr "亚太-香港" - -#: xpack/plugins/cloud/providers/huaweicloud.py:22 -msgid "AP-Bangkok" -msgstr "亚太-曼谷" - -#: xpack/plugins/cloud/providers/huaweicloud.py:23 -msgid "AP-Singapore" -msgstr "亚太-新加坡" - -#: xpack/plugins/cloud/providers/huaweicloud.py:24 -msgid "AF-Johannesburg" -msgstr "非洲-约翰内斯堡" - -#: xpack/plugins/cloud/providers/huaweicloud.py:25 -msgid "LA-Santiago" -msgstr "拉美-圣地亚哥" - -#: xpack/plugins/cloud/providers/qcloud.py:14 -msgid "Tencent Cloud" -msgstr "腾讯云" - -#: xpack/plugins/cloud/templates/cloud/account_detail.html:17 -#: xpack/plugins/cloud/views.py:79 -msgid "Account detail" -msgstr "账户详情" - -#: xpack/plugins/cloud/templates/cloud/account_list.html:5 -#: xpack/plugins/cloud/views.py:45 -msgid "Create account" -msgstr "创建账户" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:33 -msgid "Node & AdminUser" -msgstr "节点 & 管理用户" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:63 -msgid "Load failed" -msgstr "加载失败" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:17 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:19 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:18 -#: xpack/plugins/cloud/views.py:144 -msgid "Sync task detail" -msgstr "同步任务详情" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:20 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:22 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:21 -#: xpack/plugins/cloud/views.py:159 -msgid "Sync task history" -msgstr "同步历史列表" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:23 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:25 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:24 -#: xpack/plugins/cloud/views.py:173 -msgid "Sync instance list" -msgstr "同步实例列表" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:135 -msgid "Run task manually" -msgstr "手动执行任务" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:178 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:102 -msgid "Sync success" -msgstr "同步成功" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:46 -msgid "New count" -msgstr "新增" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:47 -msgid "Unsync count" -msgstr "未同步" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:48 -msgid "Synced count" -msgstr "已同步" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:49 -msgid "Released count" -msgstr "已释放" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 -msgid "Delete released assets" -msgstr "删除已释放的资产" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:5 -msgid "Create sync instance task" -msgstr "创建同步实例任务" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:14 -msgid "Run count" -msgstr "执行次数" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:15 -msgid "Instance count" -msgstr "实例个数" - -#: xpack/plugins/cloud/utils.py:37 -msgid "Account unavailable" -msgstr "账户无效" - -#: xpack/plugins/cloud/views.py:63 -msgid "Update account" -msgstr "更新账户" - -#: xpack/plugins/cloud/views.py:93 -msgid "Sync instance task list" -msgstr "同步实例任务列表" - -#: xpack/plugins/cloud/views.py:110 -msgid "Create sync Instance task" -msgstr "创建同步实例任务" - -#: xpack/plugins/cloud/views.py:128 -msgid "Update sync Instance task" -msgstr "更新同步实例任务" - -#: xpack/plugins/gathered_user/meta.py:11 -#: xpack/plugins/gathered_user/views.py:21 -#: xpack/plugins/gathered_user/views.py:34 -#: xpack/plugins/gathered_user/views.py:49 -#: xpack/plugins/gathered_user/views.py:69 -msgid "Gathered user" -msgstr "收集用户" - -#: xpack/plugins/gathered_user/models.py:39 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:13 -msgid "Gather user task" -msgstr "收集用户任务" - -#: xpack/plugins/gathered_user/models.py:73 -msgid "Task" -msgstr "任务" - -#: xpack/plugins/gathered_user/models.py:85 -msgid "gather user task execution" -msgstr "收集用户执行" - -#: xpack/plugins/gathered_user/models.py:91 -msgid "Assets is empty, please change nodes" -msgstr "资产为空,请更改节点" - -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:170 -msgid "Asset user" -msgstr "资产用户" - -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:7 -#: xpack/plugins/gathered_user/views.py:50 -msgid "Create task" -msgstr "创建任务" - -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:18 -msgid "Periodic" -msgstr "定时执行" - -#: xpack/plugins/gathered_user/views.py:22 -msgid "Gathered user list" -msgstr "收集用户列表" - -#: xpack/plugins/gathered_user/views.py:70 -msgid "Update task" -msgstr "更新任务" - -#: xpack/plugins/interface/forms.py:17 xpack/plugins/interface/models.py:15 -msgid "Title of login page" -msgstr "登录页面标题" - -#: xpack/plugins/interface/forms.py:19 -msgid "" -"Tips: This will be displayed on the enterprise user login page. (eg: Welcome " -"to the JumpServer open source fortress)" -msgstr "提示:将会显示在企业版用户登录页面(eg: 欢迎使用JumpServer开源堡垒机)" - -#: xpack/plugins/interface/forms.py:25 xpack/plugins/interface/models.py:19 -msgid "Image of login page" -msgstr "登录页面图片" - -#: xpack/plugins/interface/forms.py:27 -msgid "" -"Tips: This will be displayed on the enterprise user login page. (suggest " -"image size: 492px*472px)" -msgstr "提示:将会显示在企业版用户登录页面(建议图片大小为: 492*472px)" - -#: xpack/plugins/interface/forms.py:33 xpack/plugins/interface/models.py:23 -msgid "Website icon" -msgstr "网站图标" - -#: xpack/plugins/interface/forms.py:35 -msgid "Tips: website icon. (suggest image size: 16px*16px)" -msgstr "提示:网站图标(建议图片大小为: 16px*16px)" - -#: xpack/plugins/interface/forms.py:40 xpack/plugins/interface/models.py:27 -msgid "Logo of management page" -msgstr "管理页面logo" - -#: xpack/plugins/interface/forms.py:42 -msgid "" -"Tips: This will appear at the top left of the administration page. (suggest " -"image size: 185px*55px)" -msgstr "提示:将会显示在管理页面左上方(建议图片大小为: 185px*55px)" - -#: xpack/plugins/interface/forms.py:48 xpack/plugins/interface/models.py:31 -msgid "Logo of logout page" -msgstr "退出页面logo" - -#: xpack/plugins/interface/forms.py:50 -msgid "" -"Tips: This will be displayed on the enterprise user logout page. (suggest " -"image size: 82px*82px)" -msgstr "提示:将会显示在企业版用户退出页面(建议图片大小为:82px*82px)" - -#: xpack/plugins/interface/meta.py:10 -msgid "Interface settings" -msgstr "界面设置" - -#: xpack/plugins/interface/templates/interface/interface.html:15 -#: xpack/plugins/interface/views.py:24 xpack/plugins/interface/views.py:25 -msgid "Interface setting" -msgstr "界面设置" - -#: xpack/plugins/interface/templates/interface/interface.html:73 -#: xpack/plugins/interface/templates/interface/interface.html:108 -#: xpack/plugins/interface/templates/interface/interface.html:115 -msgid "Restore Default" -msgstr "恢复默认" - -#: xpack/plugins/interface/templates/interface/interface.html:98 -msgid "This will restore default Settings of the interface !!!" -msgstr "您确定要恢复默认初始化吗?" - -#: xpack/plugins/interface/templates/interface/interface.html:107 -#: xpack/plugins/interface/views.py:55 -msgid "Restore default successfully." -msgstr "恢复默认成功!" - -#: xpack/plugins/interface/templates/interface/interface.html:114 -msgid "Restore default failed." -msgstr "恢复默认失败!" - -#: xpack/plugins/interface/views.py:51 -msgid "It is already in the default setting state!" -msgstr "当前已经是初始化状态!" - -#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94 -#: xpack/plugins/license/templates/license/license_detail.html:41 -#: xpack/plugins/license/templates/license/license_detail.html:46 -#: xpack/plugins/license/views.py:32 -msgid "License" -msgstr "许可证" - -#: xpack/plugins/license/models.py:74 -msgid "Standard edition" -msgstr "标准版" - -#: xpack/plugins/license/models.py:76 -msgid "Enterprise edition" -msgstr "企业版" - -#: xpack/plugins/license/models.py:78 -msgid "Ultimate edition" -msgstr "旗舰版" - -#: xpack/plugins/license/templates/license/_license_import_modal.html:4 -#: xpack/plugins/license/templates/license/license_detail.html:86 -msgid "Import license" -msgstr "导入许可证" - -#: xpack/plugins/license/templates/license/_license_import_modal.html:9 -msgid "License file" -msgstr "许可证文件" - -#: xpack/plugins/license/templates/license/license_detail.html:11 -msgid "Please Import License" -msgstr "请导入许可证" - -#: xpack/plugins/license/templates/license/license_detail.html:13 -#: xpack/plugins/license/templates/license/license_detail.html:47 -msgid "License has expired" -msgstr "许可证已经过期" - -#: xpack/plugins/license/templates/license/license_detail.html:15 -msgid "The license will at " -msgstr "许可证将在 " - -#: xpack/plugins/license/templates/license/license_detail.html:15 -msgid " expired." -msgstr " 过期。" - -#: xpack/plugins/license/templates/license/license_detail.html:28 -#: xpack/plugins/license/views.py:33 -msgid "License detail" -msgstr "许可证详情" - -#: xpack/plugins/license/templates/license/license_detail.html:42 -msgid "No license" -msgstr "暂无许可证" - -#: xpack/plugins/license/templates/license/license_detail.html:51 -msgid "Subscription ID" -msgstr "订阅授权ID" - -#: xpack/plugins/license/templates/license/license_detail.html:55 -msgid "Corporation" -msgstr "公司" - -#: xpack/plugins/license/templates/license/license_detail.html:59 -msgid "Expired" -msgstr "过期时间" - -#: xpack/plugins/license/templates/license/license_detail.html:67 -msgid "Edition" -msgstr "版本" - -#: xpack/plugins/license/templates/license/license_detail.html:93 -msgid "Technology consulting" -msgstr "技术咨询" - -#: xpack/plugins/license/templates/license/license_detail.html:96 -msgid "Consult" -msgstr "咨询" - -#: xpack/plugins/license/views.py:47 -msgid "License import successfully" -msgstr "许可证导入成功" - -#: xpack/plugins/license/views.py:49 -msgid "License is invalid" -msgstr "无效的许可证" - -#: xpack/plugins/orgs/forms.py:23 -msgid "Select auditor" -msgstr "选择审计员" - -#: xpack/plugins/orgs/forms.py:28 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:71 -#: xpack/plugins/orgs/templates/orgs/org_list.html:13 -msgid "Admin" -msgstr "管理员" - -#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:26 -#: xpack/plugins/orgs/views.py:43 xpack/plugins/orgs/views.py:61 -#: xpack/plugins/orgs/views.py:79 -msgid "Organizations" -msgstr "组织管理" - -#: xpack/plugins/orgs/templates/orgs/org_detail.html:17 -#: xpack/plugins/orgs/views.py:80 -msgid "Org detail" -msgstr "组织详情" - -#: xpack/plugins/orgs/templates/orgs/org_detail.html:79 -msgid "Add admin" -msgstr "添加管理员" - -#: xpack/plugins/orgs/templates/orgs/org_list.html:5 -msgid "Create organization " -msgstr "创建组织" - -#: xpack/plugins/orgs/views.py:27 -msgid "Org list" -msgstr "组织列表" - -#: xpack/plugins/orgs/views.py:44 -msgid "Create org" -msgstr "创建组织" - -#: xpack/plugins/orgs/views.py:62 -msgid "Update org" -msgstr "更新组织" - -#: xpack/plugins/vault/meta.py:11 xpack/plugins/vault/views.py:23 -#: xpack/plugins/vault/views.py:38 -msgid "Vault" -msgstr "密码匣子" - -#: xpack/plugins/vault/templates/vault/_xpack_import_modal.html:4 -msgid "Import vault" -msgstr "导入密码" - -#: xpack/plugins/vault/templates/vault/vault.html:64 -msgid "vault" -msgstr "密码匣子" - -#: xpack/plugins/vault/views.py:24 -msgid "vault list" -msgstr "密码匣子" - -#: xpack/plugins/vault/views.py:39 -msgid "vault create" -msgstr "创建" +#~ msgid "Password length" +#~ msgstr "密码长度" + +#~ msgid "" +#~ "Tips: The username of the user on the asset to be modified. if the user " +#~ "exists, change the password; If the user does not exist, create the user." +#~ msgstr "" +#~ "提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如" +#~ "果用户不存在,则创建用户。" + +#~ msgid "Change auth plan" +#~ msgstr "改密计划" + +#~ msgid "Custom password" +#~ msgstr "自定义密码" + +#~ msgid "All assets use the same random password" +#~ msgstr "所有资产使用相同的随机密码" + +#~ msgid "All assets use different random password" +#~ msgstr "所有资产使用不同的随机密码" + +#~ msgid "Password rules" +#~ msgstr "密码规则" + +#~ msgid "Change auth plan snapshot" +#~ msgstr "改密计划快照" + +#~ msgid "Change auth plan execution" +#~ msgstr "改密计划执行" + +#~ msgid "Step" +#~ msgstr "步骤" + +#~ msgid "Change auth plan task" +#~ msgstr "改密计划任务" + +#~ msgid "* Please enter custom password" +#~ msgstr "* 请输入自定义密码" + +#~ msgid "* Please enter the correct password length" +#~ msgstr "* 请输入正确的密码长度" + +#~ msgid "* Password length range 6-30 bits" +#~ msgstr "* 密码长度范围 6-30 位" + +#~ msgid "Plan execution list" +#~ msgstr "执行列表" + +#~ msgid "Add asset to this plan" +#~ msgstr "添加资产" + +#~ msgid "Add node to this plan" +#~ msgstr "添加节点" + +#~ msgid "" +#~ "When the user password on the asset is changed, the action is performed " +#~ "using the admin user associated with the asset" +#~ msgstr "更改资产上的用户密码时,将会使用与该资产关联的管理用户进行操作" + +#~ msgid "Length" +#~ msgstr "长度" + +#~ msgid "Run plan manually" +#~ msgstr "手动执行计划" + +#~ msgid "Execute failed" +#~ msgstr "执行失败" + +#~ msgid "Execution list of plan" +#~ msgstr "执行历史列表" + +#~ msgid "Log" +#~ msgstr "日志" + +#~ msgid "Retry" +#~ msgstr "重试" + +#~ msgid "Run failed" +#~ msgstr "执行失败" + +#~ msgid "Create plan" +#~ msgstr "创建计划" + +#~ msgid "Invalid/incorrect password" +#~ msgstr "无效/错误 密码" + +#~ msgid "Failed to connect to the host" +#~ msgstr "连接主机失败" + +#~ msgid "Data could not be sent to remote" +#~ msgstr "无法将数据发送到远程" + +#~ msgid "Plan list" +#~ msgstr "计划列表" + +#~ msgid "Update plan" +#~ msgstr "更新计划" + +#~ msgid "Plan execution task list" +#~ msgstr "执行任务列表" + +#~ msgid "Select account" +#~ msgstr "选择账户" + +#~ msgid "Select regions" +#~ msgstr "选择地域" + +#~ msgid "Select instances" +#~ msgstr "选择实例" + +#~ msgid "Select node" +#~ msgstr "选择节点" + +#~ msgid "Select admins" +#~ msgstr "选择管理员" + +#~ msgid "Cloud center" +#~ msgstr "云管中心" + +#~ msgid "Available" +#~ msgstr "有效" + +#~ msgid "Unavailable" +#~ msgstr "无效" + +#~ msgid "Provider" +#~ msgstr "云服务商" + +#~ msgid "Cloud account" +#~ msgstr "云账号" + +#~ msgid "Regions" +#~ msgstr "地域" + +#~ msgid "Instances" +#~ msgstr "实例" + +#~ msgid "Date last sync" +#~ msgstr "最后同步日期" + +#~ msgid "Sync instance task" +#~ msgstr "同步实例任务" + +#~ msgid "Succeed" +#~ msgstr "成功" + +#~ msgid "Date sync" +#~ msgstr "同步日期" + +#~ msgid "Unsync" +#~ msgstr "未同步" + +#~ msgid "Synced" +#~ msgstr "已同步" + +#~ msgid "Released" +#~ msgstr "已释放" + +#~ msgid "Sync task" +#~ msgstr "同步任务" + +#~ msgid "Sync instance task history" +#~ msgstr "同步实例任务历史" + +#~ msgid "Instance" +#~ msgstr "实例" + +#~ msgid "Alibaba Cloud" +#~ msgstr "阿里云" + +#~ msgid "AWS (International)" +#~ msgstr "AWS (国际)" + +#~ msgid "AWS (China)" +#~ msgstr "AWS (中国)" + +#~ msgid "Huawei Cloud" +#~ msgstr "华为云" + +#~ msgid "CN North-Beijing4" +#~ msgstr "华北-北京4" + +#~ msgid "CN East-Shanghai1" +#~ msgstr "华东-上海1" + +#~ msgid "CN East-Shanghai2" +#~ msgstr "华东-上海2" + +#~ msgid "CN South-Guangzhou" +#~ msgstr "华南-广州" + +#~ msgid "CN Southwest-Guiyang1" +#~ msgstr "西南-贵阳1" + +#~ msgid "AP-Hong-Kong" +#~ msgstr "亚太-香港" + +#~ msgid "AP-Bangkok" +#~ msgstr "亚太-曼谷" + +#~ msgid "AP-Singapore" +#~ msgstr "亚太-新加坡" + +#~ msgid "AF-Johannesburg" +#~ msgstr "非洲-约翰内斯堡" + +#~ msgid "LA-Santiago" +#~ msgstr "拉美-圣地亚哥" + +#~ msgid "Tencent Cloud" +#~ msgstr "腾讯云" + +#~ msgid "Account detail" +#~ msgstr "账户详情" + +#~ msgid "Create account" +#~ msgstr "创建账户" + +#~ msgid "Node & AdminUser" +#~ msgstr "节点 & 管理用户" + +#~ msgid "Load failed" +#~ msgstr "加载失败" + +#~ msgid "Sync task detail" +#~ msgstr "同步任务详情" + +#~ msgid "Sync task history" +#~ msgstr "同步历史列表" + +#~ msgid "Sync instance list" +#~ msgstr "同步实例列表" + +#~ msgid "Run task manually" +#~ msgstr "手动执行任务" + +#~ msgid "Sync success" +#~ msgstr "同步成功" + +#~ msgid "New count" +#~ msgstr "新增" + +#~ msgid "Unsync count" +#~ msgstr "未同步" + +#~ msgid "Synced count" +#~ msgstr "已同步" + +#~ msgid "Released count" +#~ msgstr "已释放" + +#~ msgid "Delete released assets" +#~ msgstr "删除已释放的资产" + +#~ msgid "Create sync instance task" +#~ msgstr "创建同步实例任务" + +#~ msgid "Run count" +#~ msgstr "执行次数" + +#~ msgid "Instance count" +#~ msgstr "实例个数" + +#~ msgid "Account unavailable" +#~ msgstr "账户无效" + +#~ msgid "Update account" +#~ msgstr "更新账户" + +#~ msgid "Sync instance task list" +#~ msgstr "同步实例任务列表" + +#~ msgid "Create sync Instance task" +#~ msgstr "创建同步实例任务" + +#~ msgid "Update sync Instance task" +#~ msgstr "更新同步实例任务" + +#~ msgid "Gathered user" +#~ msgstr "收集用户" + +#~ msgid "Gather user task" +#~ msgstr "收集用户任务" + +#~ msgid "Task" +#~ msgstr "任务" + +#~ msgid "gather user task execution" +#~ msgstr "收集用户执行" + +#~ msgid "Assets is empty, please change nodes" +#~ msgstr "资产为空,请更改节点" + +#~ msgid "Asset user" +#~ msgstr "资产用户" + +#~ msgid "Create task" +#~ msgstr "创建任务" + +#~ msgid "Periodic" +#~ msgstr "定时执行" + +#~ msgid "Gathered user list" +#~ msgstr "收集用户列表" + +#~ msgid "Update task" +#~ msgstr "更新任务" + +#~ msgid "Title of login page" +#~ msgstr "登录页面标题" + +#~ msgid "" +#~ "Tips: This will be displayed on the enterprise user login page. (eg: " +#~ "Welcome to the JumpServer open source fortress)" +#~ msgstr "" +#~ "提示:将会显示在企业版用户登录页面(eg: 欢迎使用JumpServer开源堡垒机)" + +#~ msgid "Image of login page" +#~ msgstr "登录页面图片" + +#~ msgid "" +#~ "Tips: This will be displayed on the enterprise user login page. (suggest " +#~ "image size: 492px*472px)" +#~ msgstr "提示:将会显示在企业版用户登录页面(建议图片大小为: 492*472px)" + +#~ msgid "Website icon" +#~ msgstr "网站图标" + +#~ msgid "Tips: website icon. (suggest image size: 16px*16px)" +#~ msgstr "提示:网站图标(建议图片大小为: 16px*16px)" + +#~ msgid "Logo of management page" +#~ msgstr "管理页面logo" + +#~ msgid "" +#~ "Tips: This will appear at the top left of the administration page. " +#~ "(suggest image size: 185px*55px)" +#~ msgstr "提示:将会显示在管理页面左上方(建议图片大小为: 185px*55px)" + +#~ msgid "Logo of logout page" +#~ msgstr "退出页面logo" + +#~ msgid "" +#~ "Tips: This will be displayed on the enterprise user logout page. (suggest " +#~ "image size: 82px*82px)" +#~ msgstr "提示:将会显示在企业版用户退出页面(建议图片大小为:82px*82px)" + +#~ msgid "Interface settings" +#~ msgstr "界面设置" + +#~ msgid "Interface setting" +#~ msgstr "界面设置" + +#~ msgid "Restore Default" +#~ msgstr "恢复默认" + +#~ msgid "This will restore default Settings of the interface !!!" +#~ msgstr "您确定要恢复默认初始化吗?" + +#~ msgid "Restore default successfully." +#~ msgstr "恢复默认成功!" + +#~ msgid "Restore default failed." +#~ msgstr "恢复默认失败!" + +#~ msgid "It is already in the default setting state!" +#~ msgstr "当前已经是初始化状态!" + +#~ msgid "License" +#~ msgstr "许可证" + +#~ msgid "Standard edition" +#~ msgstr "标准版" + +#~ msgid "Enterprise edition" +#~ msgstr "企业版" + +#~ msgid "Ultimate edition" +#~ msgstr "旗舰版" + +#~ msgid "Import license" +#~ msgstr "导入许可证" + +#~ msgid "License file" +#~ msgstr "许可证文件" + +#~ msgid "Please Import License" +#~ msgstr "请导入许可证" + +#~ msgid "License has expired" +#~ msgstr "许可证已经过期" + +#~ msgid "The license will at " +#~ msgstr "许可证将在 " + +#~ msgid " expired." +#~ msgstr " 过期。" + +#~ msgid "License detail" +#~ msgstr "许可证详情" + +#~ msgid "No license" +#~ msgstr "暂无许可证" + +#~ msgid "Subscription ID" +#~ msgstr "订阅授权ID" + +#~ msgid "Corporation" +#~ msgstr "公司" + +#~ msgid "Expired" +#~ msgstr "过期时间" + +#~ msgid "Edition" +#~ msgstr "版本" + +#~ msgid "Technology consulting" +#~ msgstr "技术咨询" + +#~ msgid "Consult" +#~ msgstr "咨询" + +#~ msgid "License import successfully" +#~ msgstr "许可证导入成功" + +#~ msgid "License is invalid" +#~ msgstr "无效的许可证" + +#~ msgid "Select auditor" +#~ msgstr "选择审计员" + +#~ msgid "Admin" +#~ msgstr "管理员" + +#~ msgid "Organizations" +#~ msgstr "组织管理" + +#~ msgid "Org detail" +#~ msgstr "组织详情" + +#~ msgid "Add admin" +#~ msgstr "添加管理员" + +#~ msgid "Create organization " +#~ msgstr "创建组织" + +#~ msgid "Org list" +#~ msgstr "组织列表" + +#~ msgid "Create org" +#~ msgstr "创建组织" + +#~ msgid "Update org" +#~ msgstr "更新组织" + +#~ msgid "Vault" +#~ msgstr "密码匣子" + +#~ msgid "Import vault" +#~ msgstr "导入密码" + +#~ msgid "vault" +#~ msgstr "密码匣子" + +#~ msgid "vault list" +#~ msgstr "密码匣子" + +#~ msgid "vault create" +#~ msgstr "创建" #~ msgid "Tips: The asset information is always covered" #~ msgstr "提示:资产信息总是被覆盖" @@ -7439,9 +6970,6 @@ msgstr "创建" #~ msgid "Update assets hardware info period" #~ msgstr "定期更新资产硬件信息" -#~ msgid "Date finished" -#~ msgstr "结束日期" - #~ msgid "User id" #~ msgstr "用户" diff --git a/apps/ops/migrations/0018_auto_20200508_2105.py b/apps/ops/migrations/0018_auto_20200508_2105.py new file mode 100644 index 000000000..52c95646e --- /dev/null +++ b/apps/ops/migrations/0018_auto_20200508_2105.py @@ -0,0 +1,50 @@ +# Generated by Django 2.2.10 on 2020-05-08 13:05 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('ops', '0017_auto_20200306_1747'), + ] + + operations = [ + migrations.AlterField( + model_name='commandexecution', + name='date_created', + field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'), + ), + migrations.AlterField( + model_name='commandexecution', + name='date_finished', + field=models.DateTimeField(null=True, verbose_name='Date finished'), + ), + migrations.AlterField( + model_name='commandexecution', + name='date_start', + field=models.DateTimeField(null=True, verbose_name='Date start'), + ), + migrations.AlterField( + model_name='commandexecution', + name='hosts', + field=models.ManyToManyField(to='assets.Asset', verbose_name='Hosts'), + ), + migrations.AlterField( + model_name='commandexecution', + name='is_finished', + field=models.BooleanField(default=False, verbose_name='Is finished'), + ), + migrations.AlterField( + model_name='commandexecution', + name='run_as', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser', verbose_name='Run as'), + ), + migrations.AlterField( + model_name='commandexecution', + name='user', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'), + ), + ] diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py index 662131bc5..6c3339513 100644 --- a/apps/ops/models/command.py +++ b/apps/ops/models/command.py @@ -18,15 +18,15 @@ from ..inventory import JMSInventory class CommandExecution(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - hosts = models.ManyToManyField('assets.Asset') - run_as = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE) + hosts = models.ManyToManyField('assets.Asset', verbose_name=_('Hosts')) + run_as = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, verbose_name=_('Run as')) command = models.TextField(verbose_name=_("Command")) _result = models.TextField(blank=True, null=True, verbose_name=_('Result')) - user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True) - is_finished = models.BooleanField(default=False) - date_created = models.DateTimeField(auto_now_add=True) - date_start = models.DateTimeField(null=True) - date_finished = models.DateTimeField(null=True) + user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True, verbose_name=_('User')) + is_finished = models.BooleanField(default=False, verbose_name=_('Is finished')) + date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) + date_start = models.DateTimeField(null=True, verbose_name=_('Date start')) + date_finished = models.DateTimeField(null=True, verbose_name=_('Date finished')) def __str__(self): return self.command[:10] diff --git a/apps/users/migrations/0026_auto_20200508_2105.py b/apps/users/migrations/0026_auto_20200508_2105.py new file mode 100644 index 000000000..0ccd09e09 --- /dev/null +++ b/apps/users/migrations/0026_auto_20200508_2105.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-05-08 13:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0025_auto_20200206_1216'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='source', + field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS')], default='ldap', max_length=30, verbose_name='Source'), + ), + ] From c6e0e9a79a08139c9c9338afafb9d3ac61cbfdc2 Mon Sep 17 00:00:00 2001 From: xinwen Date: Sat, 9 May 2020 14:50:22 +0800 Subject: [PATCH 024/146] =?UTF-8?q?[Update]=20=E9=87=8D=E5=BB=BA=20ops=200?= =?UTF-8?q?018=20=E8=BF=81=E7=A7=BB=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/serializers.py | 5 +- apps/locale/zh/LC_MESSAGES/django.po | 82 +++++++++---------- ...508_2105.py => 0018_auto_20200509_1434.py} | 19 +---- apps/ops/models/command.py | 6 +- 4 files changed, 49 insertions(+), 63 deletions(-) rename apps/ops/migrations/{0018_auto_20200508_2105.py => 0018_auto_20200509_1434.py} (54%) diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 75ec9b634..a23efe534 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -62,6 +62,9 @@ class CommandExecutionSerializer(serializers.ModelSerializer): 'date_start', 'result', 'is_success' ) extra_kwargs = { - 'result': {'label': _('Result')}, + 'result': {'label': _('Result')}, # model 上的方法,只能在这修改 'is_success': {'label': _('Is success')}, + 'hosts': {'label': _('Hosts')}, # 外键,会生成 sql。不在 model 上修改 + 'run_as': {'label': _('Run as')}, + 'user': {'label': _('User')}, } diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index b875be379..1ff08d362 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: 2020-05-08 18:40+0800\n" +"POT-Creation-Date: 2020-05-09 14:47+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -1177,13 +1177,12 @@ msgstr "默认资产组" #: assets/models/label.py:15 assets/templates/assets/system_user_users.html:63 #: audits/models.py:18 audits/models.py:38 audits/models.py:51 -#: audits/templates/audits/ftp_log_list.html:37 +#: audits/serializers.py:69 audits/templates/audits/ftp_log_list.html:37 #: audits/templates/audits/ftp_log_list.html:74 #: audits/templates/audits/operate_log_list.html:37 #: audits/templates/audits/password_change_log_list.html:37 #: audits/templates/audits/password_change_log_list.html:54 -#: authentication/models.py:43 ops/models/command.py:25 -#: ops/templates/ops/command_execution_list.html:41 +#: authentication/models.py:43 ops/templates/ops/command_execution_list.html:41 #: ops/templates/ops/command_execution_list.html:66 #: perms/forms/asset_permission.py:83 perms/forms/database_app_permission.py:38 #: perms/forms/remote_app_permission.py:40 perms/models/base.py:49 @@ -1643,8 +1642,8 @@ msgstr "获取认证信息错误" msgid "Close" msgstr "关闭" -#: assets/templates/assets/_asset_user_list.html:24 -#: audits/templates/audits/operate_log_list.html:75 +#: assets/templates/assets/_asset_user_list.html:24 audits/models.py:43 +#: audits/models.py:54 audits/templates/audits/operate_log_list.html:75 #: audits/templates/audits/password_change_log_list.html:57 #: ops/templates/ops/task_adhoc.html:61 #: terminal/templates/terminal/command_list.html:34 @@ -2344,6 +2343,19 @@ msgstr "文件名" msgid "Success" msgstr "成功" +#: audits/models.py:25 audits/templates/audits/ftp_log_list.html:81 +#: ops/models/command.py:28 ops/templates/ops/adhoc_history.html:50 +#: ops/templates/ops/adhoc_history_detail.html:59 +#: ops/templates/ops/command_execution_list.html:72 +#: ops/templates/ops/task_history.html:56 perms/models/base.py:52 +#: perms/templates/perms/asset_permission_detail.html:81 +#: perms/templates/perms/database_app_permission_detail.html:77 +#: perms/templates/perms/remote_app_permission_detail.html:73 +#: terminal/models.py:199 terminal/templates/terminal/session_detail.html:72 +#: terminal/templates/terminal/session_list.html:32 +msgid "Date start" +msgstr "开始日期" + #: audits/models.py:33 #: authentication/templates/authentication/_access_key_modal.html:22 msgid "Create" @@ -2420,18 +2432,29 @@ msgstr "状态" msgid "Date login" msgstr "登录日期" -#: audits/templates/audits/ftp_log_list.html:81 ops/models/command.py:28 -#: ops/templates/ops/adhoc_history.html:50 -#: ops/templates/ops/adhoc_history_detail.html:59 -#: ops/templates/ops/command_execution_list.html:72 -#: ops/templates/ops/task_history.html:56 perms/models/base.py:52 -#: perms/templates/perms/asset_permission_detail.html:81 -#: perms/templates/perms/database_app_permission_detail.html:77 -#: perms/templates/perms/remote_app_permission_detail.html:73 -#: terminal/models.py:199 terminal/templates/terminal/session_detail.html:72 -#: terminal/templates/terminal/session_list.html:32 -msgid "Date start" -msgstr "开始日期" +#: audits/serializers.py:65 ops/models/command.py:24 +msgid "Result" +msgstr "结果" + +#: audits/serializers.py:66 ops/models/adhoc.py:240 +#: ops/templates/ops/adhoc_history.html:54 +#: ops/templates/ops/task_history.html:60 +msgid "Is success" +msgstr "是否成功" + +#: audits/serializers.py:67 ops/templates/ops/adhoc_detail.html:51 +#: ops/templates/ops/command_execution_list.html:65 +#: ops/templates/ops/task_adhoc.html:57 ops/templates/ops/task_list.html:13 +#: terminal/forms/storage.py:151 +msgid "Hosts" +msgstr "主机" + +#: audits/serializers.py:68 ops/templates/ops/adhoc_detail.html:70 +#: ops/templates/ops/adhoc_detail.html:75 +#: ops/templates/ops/command_execution_list.html:68 +#: ops/templates/ops/task_adhoc.html:59 +msgid "Run as" +msgstr "运行用户" #: audits/templates/audits/login_log_list.html:34 #: perms/templates/perms/asset_permission_user.html:74 @@ -2974,11 +2997,6 @@ msgstr "时间" msgid "Is finished" msgstr "是否完成" -#: ops/models/adhoc.py:240 ops/templates/ops/adhoc_history.html:54 -#: ops/templates/ops/task_history.html:60 -msgid "Is success" -msgstr "是否成功" - #: ops/models/adhoc.py:241 msgid "Adhoc raw result" msgstr "结果" @@ -2995,24 +3013,6 @@ msgstr "{} 任务开始: {}" msgid "{} Task finish" msgstr "{} 任务结束" -#: ops/models/command.py:21 ops/templates/ops/adhoc_detail.html:51 -#: ops/templates/ops/command_execution_list.html:65 -#: ops/templates/ops/task_adhoc.html:57 ops/templates/ops/task_list.html:13 -#: terminal/forms/storage.py:151 -msgid "Hosts" -msgstr "主机" - -#: ops/models/command.py:22 ops/templates/ops/adhoc_detail.html:70 -#: ops/templates/ops/adhoc_detail.html:75 -#: ops/templates/ops/command_execution_list.html:68 -#: ops/templates/ops/task_adhoc.html:59 -msgid "Run as" -msgstr "运行用户" - -#: ops/models/command.py:24 -msgid "Result" -msgstr "结果" - #: ops/models/command.py:29 msgid "Date finished" msgstr "结束日期" diff --git a/apps/ops/migrations/0018_auto_20200508_2105.py b/apps/ops/migrations/0018_auto_20200509_1434.py similarity index 54% rename from apps/ops/migrations/0018_auto_20200508_2105.py rename to apps/ops/migrations/0018_auto_20200509_1434.py index 52c95646e..5bbf87610 100644 --- a/apps/ops/migrations/0018_auto_20200508_2105.py +++ b/apps/ops/migrations/0018_auto_20200509_1434.py @@ -1,8 +1,6 @@ -# Generated by Django 2.2.10 on 2020-05-08 13:05 +# Generated by Django 2.2.10 on 2020-05-09 06:34 -from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): @@ -27,24 +25,9 @@ class Migration(migrations.Migration): name='date_start', field=models.DateTimeField(null=True, verbose_name='Date start'), ), - migrations.AlterField( - model_name='commandexecution', - name='hosts', - field=models.ManyToManyField(to='assets.Asset', verbose_name='Hosts'), - ), migrations.AlterField( model_name='commandexecution', name='is_finished', field=models.BooleanField(default=False, verbose_name='Is finished'), ), - migrations.AlterField( - model_name='commandexecution', - name='run_as', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser', verbose_name='Run as'), - ), - migrations.AlterField( - model_name='commandexecution', - name='user', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'), - ), ] diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py index 6c3339513..fb6642f85 100644 --- a/apps/ops/models/command.py +++ b/apps/ops/models/command.py @@ -18,11 +18,11 @@ from ..inventory import JMSInventory class CommandExecution(OrgModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - hosts = models.ManyToManyField('assets.Asset', verbose_name=_('Hosts')) - run_as = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, verbose_name=_('Run as')) + hosts = models.ManyToManyField('assets.Asset') + run_as = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE) command = models.TextField(verbose_name=_("Command")) _result = models.TextField(blank=True, null=True, verbose_name=_('Result')) - user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True, verbose_name=_('User')) + user = models.ForeignKey('users.User', on_delete=models.CASCADE, null=True) is_finished = models.BooleanField(default=False, verbose_name=_('Is finished')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created')) date_start = models.DateTimeField(null=True, verbose_name=_('Date start')) From 0a7f63cc5efb3190df2dce7f3b0365b4dce63116 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 9 May 2020 14:51:19 +0800 Subject: [PATCH 025/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9remote=20a?= =?UTF-8?q?pp=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/api/asset_permission.py | 5 +-- apps/perms/api/remote_app_permission.py | 6 +-- apps/perms/models/asset_permission.py | 14 ++++++- apps/perms/models/base.py | 10 ++++- apps/perms/serializers/asset_permission.py | 40 ++++++++++--------- .../serializers/remote_app_permission.py | 26 ++++-------- apps/users/serializers/user.py | 11 ++++- 7 files changed, 62 insertions(+), 50 deletions(-) diff --git a/apps/perms/api/asset_permission.py b/apps/perms/api/asset_permission.py index ff477f5af..18061f236 100644 --- a/apps/perms/api/asset_permission.py +++ b/apps/perms/api/asset_permission.py @@ -22,10 +22,7 @@ class AssetPermissionViewSet(OrgModelViewSet): 资产授权列表的增删改查api """ model = AssetPermission - serializer_classes = { - 'default': serializers.AssetPermissionCreateUpdateSerializer, - 'display': serializers.AssetPermissionListSerializer - } + serializer_class = serializers.AssetPermissionSerializer filter_fields = ['name'] permission_classes = (IsOrgAdmin,) diff --git a/apps/perms/api/remote_app_permission.py b/apps/perms/api/remote_app_permission.py index b7fa6de19..6ced7f0ae 100644 --- a/apps/perms/api/remote_app_permission.py +++ b/apps/perms/api/remote_app_permission.py @@ -11,7 +11,6 @@ from ..serializers import ( RemoteAppPermissionSerializer, RemoteAppPermissionUpdateUserSerializer, RemoteAppPermissionUpdateRemoteAppSerializer, - RemoteAppPermissionListSerializer, ) @@ -26,10 +25,7 @@ class RemoteAppPermissionViewSet(OrgModelViewSet): model = RemoteAppPermission filter_fields = ('name', ) search_fields = filter_fields - serializer_classes = { - 'default': RemoteAppPermissionSerializer, - 'display': RemoteAppPermissionListSerializer, - } + serializer_class = RemoteAppPermissionSerializer permission_classes = (IsOrgAdmin,) diff --git a/apps/perms/models/asset_permission.py b/apps/perms/models/asset_permission.py index 1d92b9852..8552edc74 100644 --- a/apps/perms/models/asset_permission.py +++ b/apps/perms/models/asset_permission.py @@ -3,9 +3,9 @@ import logging from functools import reduce from django.db import models -from django.db.models import Q from django.utils.translation import ugettext_lazy as _ +from common.utils import lazyproperty from orgs.models import Organization from orgs.utils import get_current_org from assets.models import Asset, SystemUser, Node @@ -87,6 +87,18 @@ class AssetPermission(BasePermission): verbose_name = _("Asset permission") ordering = ('name',) + @lazyproperty + def assets_amount(self): + return self.assets.count() + + @lazyproperty + def nodes_amount(self): + return self.nodes.count() + + @lazyproperty + def system_users_amount(self): + return self.system_users.count() + @classmethod def get_queryset_with_prefetch(cls): return cls.objects.all().valid().prefetch_related( diff --git a/apps/perms/models/base.py b/apps/perms/models/base.py index da40ced9d..2467a31b8 100644 --- a/apps/perms/models/base.py +++ b/apps/perms/models/base.py @@ -8,7 +8,7 @@ from django.db.models import Q from django.utils import timezone from orgs.mixins.models import OrgModelMixin -from common.utils import date_expired_default +from common.utils import date_expired_default, lazyproperty from orgs.mixins.models import OrgManager @@ -87,3 +87,11 @@ class BasePermission(OrgModelMixin): Q(id__in=users_id) | Q(groups__id__in=groups_id) ).distinct() return users + + @lazyproperty + def users_amount(self): + return self.users.count() + + @lazyproperty + def user_groups_amount(self): + return self.user_groups.count() diff --git a/apps/perms/serializers/asset_permission.py b/apps/perms/serializers/asset_permission.py index 73612a7e6..96fa58781 100644 --- a/apps/perms/serializers/asset_permission.py +++ b/apps/perms/serializers/asset_permission.py @@ -3,12 +3,12 @@ from rest_framework import serializers -from common.fields import StringManyToManyField +from django.db.models import Count from orgs.mixins.serializers import BulkOrgResourceModelSerializer from perms.models import AssetPermission, Action __all__ = [ - 'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer', + 'AssetPermissionSerializer', 'ActionsField', ] @@ -34,27 +34,29 @@ class ActionsDisplayField(ActionsField): return [choices.get(i) for i in values] -class AssetPermissionCreateUpdateSerializer(BulkOrgResourceModelSerializer): +class AssetPermissionSerializer(BulkOrgResourceModelSerializer): actions = ActionsField(required=False, allow_null=True) - - class Meta: - model = AssetPermission - exclude = ('created_by', 'date_created') - - -class AssetPermissionListSerializer(BulkOrgResourceModelSerializer): - users = StringManyToManyField(many=True, read_only=True) - user_groups = StringManyToManyField(many=True, read_only=True) - assets = StringManyToManyField(many=True, read_only=True) - nodes = StringManyToManyField(many=True, read_only=True) - system_users = StringManyToManyField(many=True, read_only=True) - actions = ActionsDisplayField() is_valid = serializers.BooleanField() is_expired = serializers.BooleanField() class Meta: model = AssetPermission - fields = '__all__' - - + mini_fields = ['id', 'name'] + small_fields = [ + 'is_active', 'is_expired', 'is_valid', 'actions', 'created_by', 'date_created' + ] + m2m_fields = [ + 'users', 'user_groups', 'assets', 'nodes', 'system_users', + 'users_amount', 'user_groups_amount', 'assets_amount', 'nodes_amount', 'system_users_amount', + ] + fields = small_fields + m2m_fields + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.annotate( + users_amount=Count('users'), user_groups_amount=Count('user_groups'), + assets_amount=Count('assets'), nodes_amount=Count('nodes'), + system_users_amount=Count('system_users') + ) + return queryset diff --git a/apps/perms/serializers/remote_app_permission.py b/apps/perms/serializers/remote_app_permission.py index 41c5d7022..1700fca14 100644 --- a/apps/perms/serializers/remote_app_permission.py +++ b/apps/perms/serializers/remote_app_permission.py @@ -1,9 +1,7 @@ # coding: utf-8 # - from rest_framework import serializers -from common.fields import StringManyToManyField from common.serializers import AdaptedBulkListSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import RemoteAppPermission @@ -13,7 +11,6 @@ __all__ = [ 'RemoteAppPermissionSerializer', 'RemoteAppPermissionUpdateUserSerializer', 'RemoteAppPermissionUpdateRemoteAppSerializer', - 'RemoteAppPermissionListSerializer', ] @@ -21,27 +18,18 @@ class RemoteAppPermissionSerializer(BulkOrgResourceModelSerializer): class Meta: model = RemoteAppPermission list_serializer_class = AdaptedBulkListSerializer - fields = [ - 'id', 'name', 'users', 'user_groups', 'remote_apps', 'system_users', + mini_fields = ['id', 'name'] + small_fields = mini_fields + [ 'comment', 'is_active', 'date_start', 'date_expired', 'is_valid', - 'created_by', 'date_created', + 'create_by', 'date_created' ] + m2m_fields = [ + 'users', 'user_groups', 'remote_apps', 'system_users', + ] + fields = small_fields + m2m_fields read_only_fields = ['created_by', 'date_created'] -class RemoteAppPermissionListSerializer(BulkOrgResourceModelSerializer): - users = StringManyToManyField(many=True, read_only=True) - user_groups = StringManyToManyField(many=True, read_only=True) - remote_apps = StringManyToManyField(many=True, read_only=True) - system_users = StringManyToManyField(many=True, read_only=True) - is_valid = serializers.BooleanField() - is_expired = serializers.BooleanField() - - class Meta: - model = RemoteAppPermission - fields = '__all__' - - class RemoteAppPermissionUpdateUserSerializer(serializers.ModelSerializer): class Meta: model = RemoteAppPermission diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index ba5c4ec2a..62a487559 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +from django.core.cache import cache from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers @@ -34,9 +35,12 @@ class UserSerializer(CommonSerializerMixin, serializers.ModelSerializer): label=_('Password strategy'), write_only=True ) mfa_level_display = serializers.ReadOnlyField(source='get_mfa_level_display') + login_blocked = serializers.SerializerMethodField() can_update = serializers.SerializerMethodField() can_delete = serializers.SerializerMethodField() + key_prefix_block = "_LOGIN_BLOCK_{}" + class Meta: model = User list_serializer_class = AdaptedBulkListSerializer @@ -53,7 +57,7 @@ class UserSerializer(CommonSerializerMixin, serializers.ModelSerializer): ] fields = fields_small + [ 'groups', 'role', 'groups_display', 'role_display', - 'can_update', 'can_delete' + 'can_update', 'can_delete', 'login_blocked', ] extra_kwargs = { @@ -142,6 +146,11 @@ class UserSerializer(CommonSerializerMixin, serializers.ModelSerializer): self.context['request'], self.context['view'], obj ) + def get_login_blocked(self, obj): + key_block = self.key_prefix_block.format(obj.username) + blocked = bool(cache.get(key_block)) + return blocked + class UserPKUpdateSerializer(serializers.ModelSerializer): class Meta: From e39d8dce3c5639d256474aa877eb316dc861da0d Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 9 May 2020 14:52:44 +0800 Subject: [PATCH 026/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9fields?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/serializers/asset_permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/perms/serializers/asset_permission.py b/apps/perms/serializers/asset_permission.py index 96fa58781..8d9235633 100644 --- a/apps/perms/serializers/asset_permission.py +++ b/apps/perms/serializers/asset_permission.py @@ -42,7 +42,7 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer): class Meta: model = AssetPermission mini_fields = ['id', 'name'] - small_fields = [ + small_fields = mini_fields + [ 'is_active', 'is_expired', 'is_valid', 'actions', 'created_by', 'date_created' ] m2m_fields = [ From 227f97c2f52e99847c6e2b72826985401d13ded1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 9 May 2020 16:08:09 +0800 Subject: [PATCH 027/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9remote=20a?= =?UTF-8?q?pps=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/serializers/remote_app_permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/perms/serializers/remote_app_permission.py b/apps/perms/serializers/remote_app_permission.py index 1700fca14..9a541d0c0 100644 --- a/apps/perms/serializers/remote_app_permission.py +++ b/apps/perms/serializers/remote_app_permission.py @@ -21,7 +21,7 @@ class RemoteAppPermissionSerializer(BulkOrgResourceModelSerializer): mini_fields = ['id', 'name'] small_fields = mini_fields + [ 'comment', 'is_active', 'date_start', 'date_expired', 'is_valid', - 'create_by', 'date_created' + 'created_by', 'date_created' ] m2m_fields = [ 'users', 'user_groups', 'remote_apps', 'system_users', From 5571651c029d40413a9d7afe8e3534f950d7ac99 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 9 May 2020 16:13:06 +0800 Subject: [PATCH 028/146] =?UTF-8?q?[Update]=20=E5=8D=87=E7=BA=A7requiremen?= =?UTF-8?q?ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index f459ae2fa..06b420565 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,5 +1,5 @@ amqp==2.1.4 -ansible==2.8.2 +ansible==2.8.8 asn1crypto==0.24.0 bcrypt==3.1.4 billiard==3.5.0.3 @@ -49,7 +49,7 @@ olefile==0.44 openapi-codec==1.3.2 paramiko==2.4.2 passlib==1.7.1 -Pillow==6.2.0 +Pillow==6.2.2 pyasn1==0.4.8 pycparser==2.19 pycrypto==2.6.1 @@ -91,7 +91,7 @@ flower==0.9.3 channels-redis==2.4.0 channels==2.3.0 daphne==2.3.0 -psutil==5.6.5 +psutil==5.6.6 django-cas-ng==4.0.1 python-cas==1.5.0 ipython From cda677a30f6d2087410e3dea91aea589c2cf01e3 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 11 May 2020 12:35:33 +0800 Subject: [PATCH 029/146] =?UTF-8?q?[update]=20=E4=BF=AE=E6=94=B9remote=20a?= =?UTF-8?q?pps=E7=9A=84serializer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/models/remote_app_permission.py | 10 +++++++++- apps/perms/serializers/asset_permission.py | 1 + apps/perms/serializers/remote_app_permission.py | 12 ++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/perms/models/remote_app_permission.py b/apps/perms/models/remote_app_permission.py index 57a806b80..c9a7e4463 100644 --- a/apps/perms/models/remote_app_permission.py +++ b/apps/perms/models/remote_app_permission.py @@ -1,9 +1,9 @@ # coding: utf-8 # - from django.db import models from django.utils.translation import ugettext_lazy as _ +from common.utils import lazyproperty from .base import BasePermission __all__ = [ @@ -22,3 +22,11 @@ class RemoteAppPermission(BasePermission): def get_all_remote_apps(self): return set(self.remote_apps.all()) + + @lazyproperty + def remote_apps_amount(self): + return self.remote_apps.count() + + @lazyproperty + def system_users_amount(self): + return self.system_users.count() diff --git a/apps/perms/serializers/asset_permission.py b/apps/perms/serializers/asset_permission.py index 8d9235633..d7b72c22a 100644 --- a/apps/perms/serializers/asset_permission.py +++ b/apps/perms/serializers/asset_permission.py @@ -50,6 +50,7 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer): 'users_amount', 'user_groups_amount', 'assets_amount', 'nodes_amount', 'system_users_amount', ] fields = small_fields + m2m_fields + read_only_fields = ['created_by', 'date_created'] @classmethod def setup_eager_loading(cls, queryset): diff --git a/apps/perms/serializers/remote_app_permission.py b/apps/perms/serializers/remote_app_permission.py index 9a541d0c0..2c347386c 100644 --- a/apps/perms/serializers/remote_app_permission.py +++ b/apps/perms/serializers/remote_app_permission.py @@ -1,6 +1,7 @@ # coding: utf-8 # from rest_framework import serializers +from django.db.models import Count from common.serializers import AdaptedBulkListSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer @@ -25,10 +26,21 @@ class RemoteAppPermissionSerializer(BulkOrgResourceModelSerializer): ] m2m_fields = [ 'users', 'user_groups', 'remote_apps', 'system_users', + 'users_amount', 'user_groups_amount', 'remote_apps_amount', + 'system_users_amount' ] fields = small_fields + m2m_fields read_only_fields = ['created_by', 'date_created'] + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.annotate( + users_amount=Count('users'), user_groups_amount=Count('user_groups'), + remote_apps_amount=Count('remote_apps'), system_users_amount=Count('system_users') + ) + return queryset + class RemoteAppPermissionUpdateUserSerializer(serializers.ModelSerializer): class Meta: From efc66cc7ee1ff106f58adfe453835786bec5dd24 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 11 May 2020 19:28:02 +0800 Subject: [PATCH 030/146] =?UTF-8?q?[Update]=20=E9=BB=98=E8=AE=A4=E5=85=B3?= =?UTF-8?q?=E9=97=ADdebug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index cd0db863d..04988d1d9 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -87,7 +87,7 @@ class Config(dict): # Django Config, Must set before start 'SECRET_KEY': '', 'BOOTSTRAP_TOKEN': '', - 'DEBUG': True, + 'DEBUG': False, 'LOG_LEVEL': 'DEBUG', 'LOG_DIR': os.path.join(PROJECT_DIR, 'logs'), 'DB_ENGINE': 'mysql', From dd5bf546df33069c3e59cc2318e771a1a71b8891 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 12 May 2020 15:37:37 +0800 Subject: [PATCH 031/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9serialzier?= =?UTF-8?q?=5Fclass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/api/user.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/users/api/user.py b/apps/users/api/user.py index 6ca9bb7a1..97857c436 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -29,9 +29,7 @@ __all__ = [ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet): filter_fields = ('username', 'email', 'name', 'id') search_fields = filter_fields - serializer_classes = { - 'default': serializers.UserSerializer, - } + serializer_class = serializers.UserSerializer permission_classes = (IsOrgAdmin, CanUpdateDeleteUser) def get_queryset(self): From 4dd6d4498b2c805b0180cd0fb0a8710808afb789 Mon Sep 17 00:00:00 2001 From: xinwen Date: Tue, 12 May 2020 17:09:36 +0800 Subject: [PATCH 032/146] =?UTF-8?q?[Update]=20Asset=20filter=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20platform=5F=5Fname=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 6eb8ebeaf..4bafe2fc2 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -33,7 +33,7 @@ class AssetViewSet(OrgBulkModelViewSet): API endpoint that allows Asset to be viewed or edited. """ model = Asset - filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id") + filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id", "platform__name") search_fields = ("hostname", "ip") ordering_fields = ("hostname", "ip", "port", "cpu_cores") serializer_classes = { From eb74d13059a3d277373ceb1af58b07c9db878b2d Mon Sep 17 00:00:00 2001 From: xinwen Date: Tue, 12 May 2020 17:09:36 +0800 Subject: [PATCH 033/146] =?UTF-8?q?[Update]=20Asset=20filter=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20platform=5F=5Fbase=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 4bafe2fc2..0d7a42454 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -33,7 +33,7 @@ class AssetViewSet(OrgBulkModelViewSet): API endpoint that allows Asset to be viewed or edited. """ model = Asset - filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id", "platform__name") + filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id", "platform__base") search_fields = ("hostname", "ip") ordering_fields = ("hostname", "ip", "port", "cpu_cores") serializer_classes = { From 195cbbbe4286ac01dd2fd451b6ec7ed209287d02 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 12 May 2020 17:57:35 +0800 Subject: [PATCH 034/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9profile=20?= =?UTF-8?q?serialzier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/serializers/user.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 62a487559..1f76c8955 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -181,16 +181,19 @@ class ResetOTPSerializer(serializers.Serializer): pass -class UserProfileSerializer(serializers.ModelSerializer): +class UserProfileSerializer(UserSerializer): admin_or_audit_orgs = UserOrgSerializer(many=True, read_only=True) - class Meta: - model = User - fields = [ - 'id', 'name', 'username', 'email', - 'role', 'wechat', 'phone', 'mfa_level', - 'comment', 'source', 'is_valid', 'is_expired', - 'is_active', 'created_by', 'is_first_login', - 'date_password_last_updated', 'date_expired', - 'avatar_url', 'groups', 'admin_or_audit_orgs', + class Meta(UserSerializer.Meta): + # fields = [ + # 'id', 'name', 'username', 'email', + # 'role', 'wechat', 'phone', 'mfa_level', + # 'comment', 'source', 'is_valid', 'is_expired', + # 'is_active', 'created_by', 'is_first_login', + # 'date_password_last_updated', 'date_expired', + # 'avatar_url', 'groups', 'admin_or_audit_orgs', + # ] + fields = UserSerializer.Meta.fields + [ + 'admin_or_audit_orgs' ] + From 9341ce9f844e20e0c7e56784ffd09e711f595382 Mon Sep 17 00:00:00 2001 From: Bai Date: Tue, 12 May 2020 20:31:32 +0800 Subject: [PATCH 035/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E5=A4=8DAssetUserV?= =?UTF-8?q?iewSet=20=E4=BD=BF=E7=94=A8Option=E6=96=B9=E6=B3=95=E6=97=B6err?= =?UTF-8?q?or?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset_user.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/assets/api/asset_user.py b/apps/assets/api/asset_user.py index c0d834737..6e6af72ac 100644 --- a/apps/assets/api/asset_user.py +++ b/apps/assets/api/asset_user.py @@ -84,12 +84,15 @@ class AssetUserViewSet(CommonApiMixin, BulkModelViewSet): def get_object(self): pk = self.kwargs.get("pk") + if pk is None: + return queryset = self.get_queryset() obj = queryset.get(id=pk) return obj def get_exception_handler(self): def handler(e, context): + logger.error(e, exc_info=True) return Response({"error": str(e)}, status=400) return handler From 17163dd909983b2ba23105cb08bec65bdccfe816 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 13 May 2020 11:05:58 +0800 Subject: [PATCH 036/146] =?UTF-8?q?[Update]=20=E7=94=A8=E6=88=B7Profile?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=9C=A8=E5=BD=93=E5=89=8D=E7=BB=84=E7=BB=87?= =?UTF-8?q?=E7=9A=84=E8=A7=92=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/models/user.py | 8 ++++++++ apps/users/serializers/user.py | 16 +++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index ce803064a..963b64b21 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -159,6 +159,14 @@ class RoleMixin: roles.append(str(_('User'))) return " | ".join(roles) + def current_org_roles(self): + roles = [] + if self.can_admin_current_org: + roles.append('Admin') + if self.can_audit_current_org: + roles.append('Auditor') + return roles + @property def is_superuser(self): if self.role == 'Admin': diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 1f76c8955..fc511a941 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -181,19 +181,17 @@ class ResetOTPSerializer(serializers.Serializer): pass +class UserRoleSerializer(serializers.Serializer): + name = serializers.CharField(max_length=24) + display = serializers.CharField(max_length=64) + + class UserProfileSerializer(UserSerializer): admin_or_audit_orgs = UserOrgSerializer(many=True, read_only=True) + current_org_roles = serializers.ListField() class Meta(UserSerializer.Meta): - # fields = [ - # 'id', 'name', 'username', 'email', - # 'role', 'wechat', 'phone', 'mfa_level', - # 'comment', 'source', 'is_valid', 'is_expired', - # 'is_active', 'created_by', 'is_first_login', - # 'date_password_last_updated', 'date_expired', - # 'avatar_url', 'groups', 'admin_or_audit_orgs', - # ] fields = UserSerializer.Meta.fields + [ - 'admin_or_audit_orgs' + 'admin_or_audit_orgs', 'current_org_roles' ] From d06ea2944e013cdcf9ad94cdfb2c0f8afe291e56 Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 14 May 2020 10:53:30 +0800 Subject: [PATCH 037/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9Dashboard?= =?UTF-8?q?=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/api.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index fd580093e..6d23fef8e 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -234,11 +234,23 @@ class IndexApi(TotalCountMixin, WeekSessionMetricMixin, MonthLoginMetricMixin, A _all = query_params.get('all') - if _all or query_params.get('total_count'): + if _all or query_params.get('total_count') or query_params.get('total_count_users'): + data.update({ + 'total_count_users': self.get_total_count_users(), + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_assets'): data.update({ 'total_count_assets': self.get_total_count_assets(), - 'total_count_users': self.get_total_count_users(), + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_online_users'): + data.update({ 'total_count_online_users': self.get_total_count_online_users(), + }) + + if _all or query_params.get('total_count') or query_params.get('total_count_online_sessions'): + data.update({ 'total_count_online_sessions': self.get_total_count_online_sessions(), }) From b5291274614803f699112ed7d30453a4d3436ea1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 14 May 2020 14:49:54 +0800 Subject: [PATCH 038/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9user=20rol?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/models/user.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 963b64b21..bb9d82f64 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -165,6 +165,8 @@ class RoleMixin: roles.append('Admin') if self.can_audit_current_org: roles.append('Auditor') + else: + roles.append('User') return roles @property From 1540cbdcaad8cfef94a74dff34689ea490a4c32a Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 18 May 2020 11:52:50 +0800 Subject: [PATCH 039/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9csv=20rend?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/renders/csv.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/common/drf/renders/csv.py b/apps/common/drf/renders/csv.py index d4ae9e6b8..fd862460f 100644 --- a/apps/common/drf/renders/csv.py +++ b/apps/common/drf/renders/csv.py @@ -21,7 +21,9 @@ class JMSCSVRender(BaseRenderer): @staticmethod def _get_show_fields(fields, template): - if template in ('import', 'update'): + if template == 'import': + return [v for k, v in fields.items() if not v.read_only and k != "org_id" and k != 'id'] + elif template == 'update': return [v for k, v in fields.items() if not v.read_only and k != "org_id"] else: return [v for k, v in fields.items() if not v.write_only and k != "org_id"] From 76ef9b292b5633183d56984a0821f8a91476dd5e Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 18 May 2020 14:55:16 +0800 Subject: [PATCH 040/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9public=20a?= =?UTF-8?q?pi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/mixins/serializers.py | 5 ++++- apps/jumpserver/conf.py | 9 +++++++++ apps/jumpserver/settings/custom.py | 4 ++++ apps/settings/api.py | 2 ++ apps/users/serializers/user.py | 7 +++++-- apps/users/templates/users/user_list.html | 2 +- 6 files changed, 25 insertions(+), 4 deletions(-) diff --git a/apps/common/mixins/serializers.py b/apps/common/mixins/serializers.py index 7f12ef872..ed650c38e 100644 --- a/apps/common/mixins/serializers.py +++ b/apps/common/mixins/serializers.py @@ -9,7 +9,7 @@ from rest_framework.settings import api_settings from rest_framework.exceptions import ValidationError from rest_framework.fields import SkipField, empty -__all__ = ['BulkSerializerMixin', 'BulkListSerializerMixin', 'CommonSerializerMixin'] +__all__ = ['BulkSerializerMixin', 'BulkListSerializerMixin', 'CommonSerializerMixin', 'CommonBulkSerializerMixin'] class BulkSerializerMixin(object): @@ -233,3 +233,6 @@ class EagerLoadQuerySetFields: class CommonSerializerMixin(DynamicFieldsMixin): pass + +class CommonBulkSerializerMixin(BulkSerializerMixin, CommonSerializerMixin): + pass diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 04002a87f..a39de2e82 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -437,6 +437,15 @@ class DynamicConfig: backends.insert(0, 'authentication.backends.radius.RadiusBackend') return backends + def XPACK_LICENSE_IS_VALID(self): + if not HAS_XPACK: + return False + try: + from xpack.plugins.license.models import License + return bool(License.is_valid) + except: + return False + def get_from_db(self, item): if self.db_setting is not None: value = self.db_setting.get(item) diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index a55693c55..3bada5469 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -85,3 +85,7 @@ LOGIN_LOG_KEEP_DAYS = DYNAMIC.LOGIN_LOG_KEEP_DAYS TASK_LOG_KEEP_DAYS = CONFIG.TASK_LOG_KEEP_DAYS ORG_CHANGE_TO_URL = CONFIG.ORG_CHANGE_TO_URL WINDOWS_SKIP_ALL_MANUAL_PASSWORD = CONFIG.WINDOWS_SKIP_ALL_MANUAL_PASSWORD + +# XPACK +XPACK_LICENSE_IS_VALID = DYNAMIC.XPACK_LICENSE_IS_VALID + diff --git a/apps/settings/api.py b/apps/settings/api.py index b641b0ec4..fb3740c1f 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -270,6 +270,8 @@ class PublicSettingApi(generics.RetrieveAPIView): "data": { "WINDOWS_SKIP_ALL_MANUAL_PASSWORD": settings.WINDOWS_SKIP_ALL_MANUAL_PASSWORD, "SECURITY_MAX_IDLE_TIME": settings.SECURITY_MAX_IDLE_TIME, + "XPACK_ENABLED": settings.XPACK_ENABLED, + "XPACK_LICENSE_IS_VALID": settings.XPACK_LICENSE_IS_VALID } } return instance diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index fc511a941..a0c56e8e6 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from common.utils import validate_ssh_public_key -from common.mixins import CommonSerializerMixin +from common.mixins import CommonBulkSerializerMixin from common.serializers import AdaptedBulkListSerializer from common.permissions import CanUpdateDeleteUser from ..models import User @@ -23,7 +23,7 @@ class UserOrgSerializer(serializers.Serializer): name = serializers.CharField() -class UserSerializer(CommonSerializerMixin, serializers.ModelSerializer): +class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): EMAIL_SET_PASSWORD = _('Reset link will be generated and sent to the user') CUSTOM_PASSWORD = _('Set password') PASSWORD_STRATEGY_CHOICES = ( @@ -108,6 +108,9 @@ class UserSerializer(CommonSerializerMixin, serializers.ModelSerializer): return password def validate_groups(self, groups): + """ + 审计员不能加入到组中 + """ role = self.initial_data.get('role') if self.instance: role = role or self.instance.role diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index 63dd981be..28b30f73d 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -132,7 +132,7 @@ function initTable() { $(document).ready(function(){ usersTable = initTable(); - initCsvImportExport(usersTable, "{% trans 'User groups' %}") + initCsvImportExport(usersTable, "{% trans 'User' %}") }).on('click', '#btn_bulk_update', function(){ var action = $('#slct_bulk_update').val(); var id_list = usersTable.selected; From f224e49de7d8b89c32299fb78237d7ed3e77cc44 Mon Sep 17 00:00:00 2001 From: Bai Date: Tue, 19 May 2020 20:36:17 +0800 Subject: [PATCH 041/146] =?UTF-8?q?[Update]=20=E5=8E=BB=E6=8E=89=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E6=8E=88=E6=9D=83=E5=BA=8F=E5=88=97=E7=B1=BB=E4=B8=AD?= =?UTF-8?q?=E4=B8=8D=E5=AD=98=E5=9C=A8=E7=9A=84=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/serializers/asset_permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/perms/serializers/asset_permission.py b/apps/perms/serializers/asset_permission.py index d7b72c22a..aa4b51cb2 100644 --- a/apps/perms/serializers/asset_permission.py +++ b/apps/perms/serializers/asset_permission.py @@ -43,7 +43,7 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer): model = AssetPermission mini_fields = ['id', 'name'] small_fields = mini_fields + [ - 'is_active', 'is_expired', 'is_valid', 'actions', 'created_by', 'date_created' + 'is_active', 'actions', 'created_by', 'date_created' ] m2m_fields = [ 'users', 'user_groups', 'assets', 'nodes', 'system_users', From 75f4f6d0a29f1ee883b9279546f27277206087eb Mon Sep 17 00:00:00 2001 From: Bai Date: Tue, 19 May 2020 20:50:33 +0800 Subject: [PATCH 042/146] =?UTF-8?q?[Update]=20=E8=B5=84=E4=BA=A7=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E5=BA=8F=E5=88=97=E7=B1=BBis=5Fexpired,=20is=5Fvalid?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E4=B8=BAread=5Fonly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/serializers/asset_permission.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/perms/serializers/asset_permission.py b/apps/perms/serializers/asset_permission.py index aa4b51cb2..757fe2853 100644 --- a/apps/perms/serializers/asset_permission.py +++ b/apps/perms/serializers/asset_permission.py @@ -36,14 +36,14 @@ class ActionsDisplayField(ActionsField): class AssetPermissionSerializer(BulkOrgResourceModelSerializer): actions = ActionsField(required=False, allow_null=True) - is_valid = serializers.BooleanField() - is_expired = serializers.BooleanField() + is_valid = serializers.BooleanField(read_only=True) + is_expired = serializers.BooleanField(read_only=True) class Meta: model = AssetPermission mini_fields = ['id', 'name'] small_fields = mini_fields + [ - 'is_active', 'actions', 'created_by', 'date_created' + 'is_active', 'is_expired', 'is_valid', 'actions', 'created_by', 'date_created' ] m2m_fields = [ 'users', 'user_groups', 'assets', 'nodes', 'system_users', From 245d28b03dfb97247af0b7dc59f31302c3e0cf06 Mon Sep 17 00:00:00 2001 From: xinwen Date: Wed, 20 May 2020 17:45:50 +0800 Subject: [PATCH 043/146] =?UTF-8?q?[Update]=20=E4=B8=80=E4=BA=9B=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/gathered_user.py | 6 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 89985 -> 90024 bytes apps/locale/zh/LC_MESSAGES/django.po | 225 ++++++++++++----------- 3 files changed, 121 insertions(+), 110 deletions(-) diff --git a/apps/assets/serializers/gathered_user.py b/apps/assets/serializers/gathered_user.py index c055e25bd..2629a8327 100644 --- a/apps/assets/serializers/gathered_user.py +++ b/apps/assets/serializers/gathered_user.py @@ -16,7 +16,7 @@ class GatheredUserSerializer(OrgResourceModelSerializerMixin): 'present', 'date_created', 'date_updated' ] read_only_fields = fields - labels = { - 'hostname': _("Hostname"), - 'ip': "IP" + extra_kwargs = { + 'hostname': {'label': _("Hostname")}, + 'ip': {'label': 'IP'}, } diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 34a68aacde44dd40af2a76cf30a02eb1fb35f405..35c02c6c356af5e3abc91a53f2716604d6e01f9c 100644 GIT binary patch delta 26049 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 b(BwD1A93T;HJjVy3p$x(b8La2)^Yy_ZY2{n delta 26048 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 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 0b752ed50..a559e5902 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: 2020-05-11 16:53+0800\n" +"POT-Creation-Date: 2020-05-20 17:43+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -26,7 +26,7 @@ msgstr "自定义" #: applications/templates/applications/remote_app_list.html:27 #: applications/templates/applications/user_remote_app_list.html:18 #: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:353 assets/models/authbook.py:27 +#: assets/models/asset.py:352 assets/models/authbook.py:27 #: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:32 #: assets/serializers/asset_user.py:47 assets/serializers/asset_user.py:84 #: assets/serializers/system_user.py:44 assets/serializers/system_user.py:176 @@ -112,7 +112,7 @@ msgstr "运行参数" #: applications/templates/applications/user_database_app_list.html:16 #: applications/templates/applications/user_remote_app_list.html:16 #: assets/forms/asset.py:21 assets/forms/domain.py:77 assets/forms/user.py:74 -#: assets/forms/user.py:96 assets/models/asset.py:146 assets/models/base.py:232 +#: assets/forms/user.py:96 assets/models/asset.py:145 assets/models/base.py:232 #: assets/models/cluster.py:18 assets/models/cmd_filter.py:21 #: assets/models/domain.py:20 assets/models/group.py:20 #: assets/models/label.py:18 assets/templates/assets/_node_detail_modal.html:27 @@ -147,7 +147,7 @@ msgstr "运行参数" #: terminal/models.py:411 terminal/templates/terminal/base_storage_list.html:31 #: terminal/templates/terminal/terminal_detail.html:43 #: terminal/templates/terminal/terminal_list.html:30 users/forms/profile.py:20 -#: users/models/group.py:15 users/models/user.py:440 +#: users/models/group.py:15 users/models/user.py:450 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -204,7 +204,7 @@ msgstr "主机" #: applications/models/database_app.py:27 #: applications/templates/applications/database_app_detail.html:60 #: applications/templates/applications/database_app_list.html:26 -#: assets/forms/asset.py:25 assets/models/asset.py:192 +#: assets/forms/asset.py:25 assets/models/asset.py:191 #: assets/models/domain.py:50 #: assets/templates/assets/domain_gateway_list.html:64 msgid "Port" @@ -227,7 +227,7 @@ msgstr "数据库" #: applications/templates/applications/remote_app_list.html:28 #: applications/templates/applications/user_database_app_list.html:20 #: applications/templates/applications/user_remote_app_list.html:19 -#: assets/models/asset.py:151 assets/models/asset.py:227 +#: assets/models/asset.py:150 assets/models/asset.py:226 #: assets/models/base.py:237 assets/models/cluster.py:29 #: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:56 #: assets/models/domain.py:21 assets/models/domain.py:53 @@ -253,7 +253,7 @@ msgstr "数据库" #: terminal/models.py:418 terminal/templates/terminal/base_storage_list.html:33 #: terminal/templates/terminal/terminal_detail.html:63 #: tickets/templates/tickets/ticket_detail.html:104 users/models/group.py:16 -#: users/models/user.py:473 users/templates/users/user_detail.html:115 +#: users/models/user.py:483 users/templates/users/user_detail.html:115 #: users/templates/users/user_granted_database_app.html:38 #: users/templates/users/user_granted_remote_app.html:37 #: users/templates/users/user_group_detail.html:62 @@ -309,7 +309,7 @@ msgstr "参数" #: applications/models/remote_app.py:39 #: applications/templates/applications/database_app_detail.html:72 #: applications/templates/applications/remote_app_detail.html:68 -#: assets/models/asset.py:225 assets/models/base.py:240 +#: assets/models/asset.py:224 assets/models/base.py:240 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 #: assets/models/cmd_filter.py:59 assets/models/group.py:21 #: assets/templates/assets/admin_user_detail.html:63 @@ -321,7 +321,7 @@ msgstr "参数" #: perms/templates/perms/asset_permission_detail.html:93 #: perms/templates/perms/database_app_permission_detail.html:89 #: perms/templates/perms/remote_app_permission_detail.html:85 -#: users/models/user.py:481 users/serializers/group.py:35 +#: users/models/user.py:491 users/serializers/group.py:35 #: users/templates/users/user_detail.html:97 #: xpack/plugins/change_auth_plan/models.py:79 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:111 @@ -335,7 +335,7 @@ msgstr "创建者" #: applications/models/remote_app.py:42 #: applications/templates/applications/database_app_detail.html:68 #: applications/templates/applications/remote_app_detail.html:64 -#: assets/models/asset.py:226 assets/models/base.py:238 +#: assets/models/asset.py:225 assets/models/base.py:238 #: assets/models/cluster.py:26 assets/models/domain.py:23 #: assets/models/gathered_user.py:19 assets/models/group.py:22 #: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:59 @@ -737,7 +737,7 @@ msgstr "不能移除资产的管理用户账号" msgid "Latest version could not be delete" msgstr "最新版本的不能被删除" -#: assets/forms/asset.py:83 assets/models/asset.py:196 +#: assets/forms/asset.py:83 assets/models/asset.py:195 #: assets/models/user.py:109 assets/templates/assets/asset_detail.html:186 #: assets/templates/assets/asset_detail.html:194 #: assets/templates/assets/system_user_assets.html:118 @@ -748,7 +748,7 @@ msgstr "最新版本的不能被删除" msgid "Nodes" msgstr "节点" -#: assets/forms/asset.py:86 assets/models/asset.py:200 +#: assets/forms/asset.py:86 assets/models/asset.py:199 #: assets/models/cluster.py:19 assets/models/user.py:65 #: assets/templates/assets/admin_user_list.html:62 #: assets/templates/assets/asset_detail.html:72 templates/_nav.html:44 @@ -766,7 +766,7 @@ msgstr "管理用户" msgid "Label" msgstr "标签" -#: assets/forms/asset.py:92 assets/models/asset.py:195 +#: assets/forms/asset.py:92 assets/models/asset.py:194 #: assets/models/domain.py:26 assets/models/domain.py:52 #: assets/templates/assets/asset_detail.html:76 #: assets/templates/assets/user_asset_list.html:80 @@ -774,8 +774,8 @@ msgstr "标签" msgid "Domain" msgstr "网域" -#: assets/forms/asset.py:95 assets/models/asset.py:170 -#: assets/models/asset.py:194 assets/serializers/asset.py:67 +#: assets/forms/asset.py:95 assets/models/asset.py:169 +#: assets/models/asset.py:193 assets/serializers/asset.py:67 #: assets/templates/assets/asset_detail.html:100 #: assets/templates/assets/user_asset_list.html:78 msgid "Platform" @@ -854,7 +854,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: perms/templates/perms/remote_app_permission_user.html:50 #: settings/templates/settings/_ldap_list_users_modal.html:31 #: settings/templates/settings/_ldap_test_user_login_modal.html:10 -#: users/forms/profile.py:19 users/models/user.py:438 +#: users/forms/profile.py:19 users/models/user.py:448 #: users/templates/users/_select_user_modal.html:14 #: users/templates/users/user_detail.html:53 #: users/templates/users/user_list.html:15 @@ -927,7 +927,7 @@ msgstr "密码" #: assets/forms/user.py:29 assets/serializers/asset_user.py:79 #: assets/templates/assets/_asset_user_auth_update_modal.html:27 -#: users/models/user.py:467 +#: users/models/user.py:477 msgid "Private key" msgstr "ssh私钥" @@ -980,24 +980,24 @@ msgstr "SFTP的起始路径,tmp目录, 用户home目录或者自定义" msgid "Username is dynamic, When connect asset, using current user's username" msgstr "用户名是动态的,登录资产时使用当前用户的用户名登录" -#: assets/models/asset.py:147 xpack/plugins/cloud/providers/base.py:16 +#: assets/models/asset.py:146 xpack/plugins/cloud/providers/base.py:16 msgid "Base" msgstr "基础" -#: assets/models/asset.py:148 assets/templates/assets/platform_detail.html:56 +#: assets/models/asset.py:147 assets/templates/assets/platform_detail.html:56 msgid "Charset" msgstr "编码" -#: assets/models/asset.py:149 assets/templates/assets/platform_detail.html:60 +#: assets/models/asset.py:148 assets/templates/assets/platform_detail.html:60 #: tickets/models/ticket.py:38 msgid "Meta" msgstr "元数据" -#: assets/models/asset.py:150 +#: assets/models/asset.py:149 msgid "Internal" msgstr "内部的" -#: assets/models/asset.py:187 assets/models/domain.py:49 +#: assets/models/asset.py:186 assets/models/domain.py:49 #: assets/serializers/asset_user.py:46 #: assets/templates/assets/_asset_list_modal.html:47 #: assets/templates/assets/_asset_user_list.html:20 @@ -1015,7 +1015,7 @@ msgstr "内部的" msgid "IP" msgstr "IP" -#: assets/models/asset.py:188 assets/serializers/asset_user.py:45 +#: assets/models/asset.py:187 assets/serializers/asset_user.py:45 #: assets/serializers/gathered_user.py:20 #: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/_asset_user_auth_update_modal.html:9 @@ -1033,7 +1033,7 @@ msgstr "IP" msgid "Hostname" msgstr "主机名" -#: assets/models/asset.py:191 assets/models/domain.py:51 +#: assets/models/asset.py:190 assets/models/domain.py:51 #: assets/models/user.py:114 assets/templates/assets/asset_detail.html:68 #: assets/templates/assets/domain_gateway_list.html:65 #: assets/templates/assets/system_user_detail.html:78 @@ -1045,84 +1045,84 @@ msgstr "主机名" msgid "Protocol" msgstr "协议" -#: assets/models/asset.py:193 assets/serializers/asset.py:69 +#: assets/models/asset.py:192 assets/serializers/asset.py:69 #: assets/templates/assets/asset_create.html:24 #: assets/templates/assets/user_asset_list.html:77 #: perms/serializers/user_permission.py:60 msgid "Protocols" msgstr "协议组" -#: assets/models/asset.py:197 assets/models/cmd_filter.py:22 +#: assets/models/asset.py:196 assets/models/cmd_filter.py:22 #: assets/models/domain.py:54 assets/models/label.py:22 #: assets/templates/assets/asset_detail.html:108 authentication/models.py:45 msgid "Is active" msgstr "激活" -#: assets/models/asset.py:203 assets/templates/assets/asset_detail.html:64 +#: assets/models/asset.py:202 assets/templates/assets/asset_detail.html:64 msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:204 assets/templates/assets/asset_detail.html:116 +#: assets/models/asset.py:203 assets/templates/assets/asset_detail.html:116 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:207 assets/templates/assets/asset_detail.html:80 +#: assets/models/asset.py:206 assets/templates/assets/asset_detail.html:80 msgid "Vendor" msgstr "制造商" -#: assets/models/asset.py:208 assets/templates/assets/asset_detail.html:84 +#: assets/models/asset.py:207 assets/templates/assets/asset_detail.html:84 msgid "Model" msgstr "型号" -#: assets/models/asset.py:209 assets/templates/assets/asset_detail.html:112 +#: assets/models/asset.py:208 assets/templates/assets/asset_detail.html:112 msgid "Serial number" msgstr "序列号" -#: assets/models/asset.py:211 +#: assets/models/asset.py:210 msgid "CPU model" msgstr "CPU型号" -#: assets/models/asset.py:212 +#: assets/models/asset.py:211 msgid "CPU count" msgstr "CPU数量" -#: assets/models/asset.py:213 +#: assets/models/asset.py:212 msgid "CPU cores" msgstr "CPU核数" -#: assets/models/asset.py:214 +#: assets/models/asset.py:213 msgid "CPU vcpus" msgstr "CPU总数" -#: assets/models/asset.py:215 assets/templates/assets/asset_detail.html:92 +#: assets/models/asset.py:214 assets/templates/assets/asset_detail.html:92 msgid "Memory" msgstr "内存" -#: assets/models/asset.py:216 +#: assets/models/asset.py:215 msgid "Disk total" msgstr "硬盘大小" -#: assets/models/asset.py:217 +#: assets/models/asset.py:216 msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:219 assets/templates/assets/asset_detail.html:104 +#: assets/models/asset.py:218 assets/templates/assets/asset_detail.html:104 msgid "OS" msgstr "操作系统" -#: assets/models/asset.py:220 +#: assets/models/asset.py:219 msgid "OS version" msgstr "系统版本" -#: assets/models/asset.py:221 +#: assets/models/asset.py:220 msgid "OS arch" msgstr "系统架构" -#: assets/models/asset.py:222 +#: assets/models/asset.py:221 msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:224 assets/templates/assets/asset_create.html:46 +#: assets/models/asset.py:223 assets/templates/assets/asset_create.html:46 #: assets/templates/assets/asset_detail.html:220 templates/_nav.html:46 msgid "Labels" msgstr "标签管理" @@ -1175,7 +1175,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:459 +#: assets/models/cluster.py:22 users/models/user.py:469 #: users/templates/users/user_detail.html:62 msgid "Phone" msgstr "手机" @@ -1201,7 +1201,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:600 +#: users/models/user.py:610 msgid "System" msgstr "系统" @@ -1334,7 +1334,7 @@ msgstr "默认资产组" #: tickets/models/ticket.py:128 tickets/templates/tickets/ticket_detail.html:32 #: tickets/templates/tickets/ticket_list.html:34 #: tickets/templates/tickets/ticket_list.html:103 users/forms/group.py:15 -#: users/models/user.py:143 users/models/user.py:159 users/models/user.py:588 +#: users/models/user.py:143 users/models/user.py:159 users/models/user.py:598 #: users/serializers/group.py:20 #: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:64 @@ -1342,6 +1342,7 @@ msgstr "默认资产组" #: users/templates/users/user_database_app_permission.html:58 #: users/templates/users/user_group_detail.html:73 #: users/templates/users/user_group_list.html:15 +#: users/templates/users/user_list.html:135 #: users/templates/users/user_remote_app_permission.html:37 #: users/templates/users/user_remote_app_permission.html:58 #: users/views/profile/base.py:46 xpack/plugins/orgs/forms.py:27 @@ -1422,7 +1423,6 @@ msgid "Users" msgstr "用户管理" #: assets/models/user.py:112 users/templates/users/user_group_list.html:90 -#: users/templates/users/user_list.html:135 #: users/templates/users/user_profile.html:124 msgid "User groups" msgstr "用户组" @@ -1540,7 +1540,7 @@ msgid "Backend" msgstr "后端" #: assets/serializers/asset_user.py:75 users/forms/profile.py:148 -#: users/models/user.py:470 users/templates/users/first_login.html:42 +#: users/models/user.py:480 users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:49 #: users/templates/users/user_profile.html:69 #: users/templates/users/user_profile_update.html:46 @@ -2578,7 +2578,7 @@ msgstr "Agent" #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: authentication/templates/authentication/login_otp.html:6 #: settings/forms/security.py:16 users/forms/profile.py:52 -#: users/models/user.py:462 users/templates/users/first_login.html:45 +#: users/models/user.py:472 users/templates/users/first_login.html:45 #: users/templates/users/user_detail.html:77 #: users/templates/users/user_profile.html:87 msgid "MFA" @@ -2860,7 +2860,7 @@ msgid "Show" msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: users/models/user.py:360 users/templates/users/user_profile.html:94 +#: users/models/user.py:370 users/templates/users/user_profile.html:94 #: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:166 #: users/templates/users/user_verify_mfa.html:32 @@ -2868,7 +2868,7 @@ msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:361 users/templates/users/user_profile.html:92 +#: users/models/user.py:371 users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:170 msgid "Enable" msgstr "启用" @@ -2909,8 +2909,10 @@ msgid "More login options" msgstr "更多登录方式" #: authentication/templates/authentication/login.html:61 -msgid "Keycloak" -msgstr "" +#, fuzzy +#| msgid "Open" +msgid "OpenID" +msgstr "开启" #: authentication/templates/authentication/login_otp.html:17 msgid "One-time password" @@ -3478,7 +3480,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件" #: perms/templates/perms/database_app_permission_list.html:16 #: perms/templates/perms/remote_app_permission_list.html:16 #: templates/_nav.html:21 users/forms/user.py:168 users/models/group.py:31 -#: users/models/user.py:446 users/templates/users/_select_user_modal.html:16 +#: users/models/user.py:456 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_asset_permission.html:39 #: users/templates/users/user_asset_permission.html:67 #: users/templates/users/user_database_app_permission.html:38 @@ -3531,7 +3533,7 @@ msgstr "资产授权" #: perms/templates/perms/asset_permission_detail.html:85 #: perms/templates/perms/database_app_permission_detail.html:81 #: perms/templates/perms/remote_app_permission_detail.html:77 -#: users/models/user.py:478 users/templates/users/user_detail.html:93 +#: users/models/user.py:488 users/templates/users/user_detail.html:93 #: users/templates/users/user_profile.html:120 msgid "Date expired" msgstr "失效日期" @@ -4176,7 +4178,7 @@ msgid "Refresh cache" msgstr "刷新缓存" #: settings/templates/settings/_ldap_list_users_modal.html:33 -#: users/forms/profile.py:89 users/models/user.py:442 +#: users/forms/profile.py:89 users/models/user.py:452 #: users/templates/users/user_detail.html:57 #: users/templates/users/user_profile.html:59 msgid "Email" @@ -5320,7 +5322,7 @@ msgstr "工单列表" msgid "Ticket detail" msgstr "工单详情" -#: users/api/user.py:115 +#: users/api/user.py:113 msgid "Could not reset self otp, use profile reset instead" msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置" @@ -5394,11 +5396,11 @@ msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" #: users/forms/profile.py:137 users/forms/user.py:90 -#: users/serializers/user.py:163 +#: users/serializers/user.py:166 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" -#: users/forms/user.py:27 users/models/user.py:450 +#: users/forms/user.py:27 users/models/user.py:460 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:73 #: users/templates/users/user_list.html:16 @@ -5406,7 +5408,7 @@ msgstr "ssh密钥不合法" msgid "Role" msgstr "角色" -#: users/forms/user.py:31 users/models/user.py:485 +#: users/forms/user.py:31 users/models/user.py:495 #: users/templates/users/user_detail.html:89 #: users/templates/users/user_list.html:18 #: users/templates/users/user_profile.html:102 @@ -5443,7 +5445,7 @@ msgstr "设置密码" msgid "Password strategy" msgstr "密码策略" -#: users/models/user.py:142 users/models/user.py:596 +#: users/models/user.py:142 users/models/user.py:606 msgid "Administrator" msgstr "管理员" @@ -5461,27 +5463,27 @@ msgstr "组织管理员" msgid "Org auditor" msgstr "组织审计员" -#: users/models/user.py:362 users/templates/users/user_profile.html:90 +#: users/models/user.py:372 users/templates/users/user_profile.html:90 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:429 +#: users/models/user.py:439 msgid "Local" msgstr "数据库" -#: users/models/user.py:453 +#: users/models/user.py:463 msgid "Avatar" msgstr "头像" -#: users/models/user.py:456 users/templates/users/user_detail.html:68 +#: users/models/user.py:466 users/templates/users/user_detail.html:68 msgid "Wechat" msgstr "微信" -#: users/models/user.py:489 +#: users/models/user.py:499 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:599 +#: users/models/user.py:609 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -6539,11 +6541,11 @@ msgstr "同步实例任务历史" msgid "Instance" msgstr "实例" -#: xpack/plugins/cloud/providers/aliyun.py:19 +#: xpack/plugins/cloud/providers/aliyun.py:16 msgid "Alibaba Cloud" msgstr "阿里云" -#: xpack/plugins/cloud/providers/aws.py:15 +#: xpack/plugins/cloud/providers/aws.py:14 msgid "AWS (International)" msgstr "AWS (国际)" @@ -6551,59 +6553,53 @@ msgstr "AWS (国际)" msgid "AWS (China)" msgstr "AWS (中国)" -#: xpack/plugins/cloud/providers/huaweicloud.py:17 +#: xpack/plugins/cloud/providers/huaweicloud.py:13 msgid "Huawei Cloud" msgstr "华为云" +#: xpack/plugins/cloud/providers/huaweicloud.py:16 +msgid "CN North-Beijing4" +msgstr "华北-北京4" + +#: xpack/plugins/cloud/providers/huaweicloud.py:17 +msgid "CN East-Shanghai1" +msgstr "华东-上海1" + +#: xpack/plugins/cloud/providers/huaweicloud.py:18 +msgid "CN East-Shanghai2" +msgstr "华东-上海2" + +#: xpack/plugins/cloud/providers/huaweicloud.py:19 +msgid "CN South-Guangzhou" +msgstr "华南-广州" + #: xpack/plugins/cloud/providers/huaweicloud.py:20 -msgid "AF-Johannesburg" -msgstr "非洲-约翰内斯堡" +msgid "CN Southwest-Guiyang1" +msgstr "西南-贵阳1" #: xpack/plugins/cloud/providers/huaweicloud.py:21 -msgid "AP-Bangkok" -msgstr "亚太-曼谷" +#, fuzzy +#| msgid "AP-Hong Kong" +msgid "AP-Hong-Kong" +msgstr "亚太-香港" #: xpack/plugins/cloud/providers/huaweicloud.py:22 -msgid "AP-Hong Kong" -msgstr "亚太-香港" +msgid "AP-Bangkok" +msgstr "亚太-曼谷" #: xpack/plugins/cloud/providers/huaweicloud.py:23 msgid "AP-Singapore" msgstr "亚太-新加坡" #: xpack/plugins/cloud/providers/huaweicloud.py:24 -msgid "CN East-Shanghai1" -msgstr "华东-上海1" +msgid "AF-Johannesburg" +msgstr "非洲-约翰内斯堡" #: xpack/plugins/cloud/providers/huaweicloud.py:25 -msgid "CN East-Shanghai2" -msgstr "华东-上海2" +msgid "LA-Santiago" +msgstr "拉美-圣地亚哥" -#: xpack/plugins/cloud/providers/huaweicloud.py:26 -msgid "CN North-Beijing1" -msgstr "华北-北京1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:27 -msgid "CN North-Beijing4" -msgstr "华北-北京4" - -#: xpack/plugins/cloud/providers/huaweicloud.py:28 -msgid "CN Northeast-Dalian" -msgstr "东北-大连" - -#: xpack/plugins/cloud/providers/huaweicloud.py:29 -msgid "CN South-Guangzhou" -msgstr "华南-广州" - -#: xpack/plugins/cloud/providers/huaweicloud.py:30 -msgid "CN Southwest-Guiyang1" -msgstr "西南-贵阳1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:31 -msgid "EU-Paris" -msgstr "" - -#: xpack/plugins/cloud/providers/qcloud.py:17 +#: xpack/plugins/cloud/providers/qcloud.py:14 msgid "Tencent Cloud" msgstr "腾讯云" @@ -6732,6 +6728,18 @@ msgstr "收集用户执行" msgid "Assets is empty, please change nodes" msgstr "资产为空,请更改节点" +#: xpack/plugins/gathered_user/serializers.py:20 +#, fuzzy +#| msgid "Periodic" +msgid "Periodic display" +msgstr "定时执行" + +#: xpack/plugins/gathered_user/serializers.py:21 +#, fuzzy +#| msgid "Execute failed" +msgid "Executed times" +msgstr "执行次数" + #: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:170 msgid "Asset user" msgstr "资产用户" @@ -6999,8 +7007,11 @@ msgstr "密码匣子" msgid "vault create" msgstr "创建" -#~ msgid "LA-Santiago" -#~ msgstr "拉美-圣地亚哥" +#~ msgid "CN North-Beijing1" +#~ msgstr "华北-北京1" + +#~ msgid "CN Northeast-Dalian" +#~ msgstr "东北-大连" #~ msgid "Total hosts" #~ msgstr "主机总数" From 0cac8d66b3367eb29f0f36c49eb910363f9df237 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 20 May 2020 19:35:33 +0800 Subject: [PATCH 044/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9index=20ap?= =?UTF-8?q?i?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/api.py | 158 +++++++++++++++++----------------- apps/jumpserver/middleware.py | 2 +- apps/templates/index.html | 78 ++++++++--------- 3 files changed, 119 insertions(+), 119 deletions(-) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index 6d23fef8e..716e625b0 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -16,27 +16,36 @@ from common.utils import lazyproperty __all__ = ['IndexApi'] -class MonthLoginMetricMixin: +class DatesLoginMetricMixin: + @lazyproperty + def days(self): + query_params = self.request.query_params + if query_params.get('monthly'): + return 30 + return 7 @lazyproperty - def session_month(self): - month_ago = timezone.now() - timezone.timedelta(days=30) - session_month = Session.objects.filter(date_start__gt=month_ago) - return session_month + def sessions_queryset(self): + days = timezone.now() - timezone.timedelta(days=self.days) + sessions_queryset = Session.objects.filter(date_start__gt=days) + return sessions_queryset @lazyproperty - def session_month_dates(self): - dates = self.session_month.dates('date_start', 'day') + def session_dates_list(self): + now = timezone.now() + dates = [(now - timezone.timedelta(days=i)).date() for i in range(self.days)] + dates.reverse() + # dates = self.sessions_queryset.dates('date_start', 'day') return dates - def get_month_metrics_date(self): - month_metrics_date = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0'] - return month_metrics_date + def get_dates_metrics_date(self): + dates_metrics_date = [d.strftime('%m-%d') for d in self.session_dates_list] or ['0'] + return dates_metrics_date @staticmethod def get_cache_key(date, tp): date_str = date.strftime("%Y%m%d") - key = "SESSION_MONTH_{}_{}_{}".format(current_org.id, tp, date_str) + key = "SESSION_DATE_{}_{}_{}".format(current_org.id, tp, date_str) return key def __get_data_from_cache(self, date, tp): @@ -69,9 +78,9 @@ class MonthLoginMetricMixin: self.__set_data_to_cache(date, tp, count) return count - def get_month_metrics_total_count_login(self): + def get_dates_metrics_total_count_login(self): data = [] - for d in self.session_month_dates: + for d in self.session_dates_list: count = self.get_date_login_count(d) data.append(count) if len(data) == 0: @@ -88,9 +97,9 @@ class MonthLoginMetricMixin: self.__set_data_to_cache(date, tp, count) return count - def get_month_metrics_total_count_active_users(self): + def get_dates_metrics_total_count_active_users(self): data = [] - for d in self.session_month_dates: + for d in self.session_dates_list: count = self.get_date_user_count(d) data.append(count) return data @@ -105,90 +114,81 @@ class MonthLoginMetricMixin: self.__set_data_to_cache(date, tp, count) return count - def get_month_metrics_total_count_active_assets(self): + def get_dates_metrics_total_count_active_assets(self): data = [] - for d in self.session_month_dates: + for d in self.session_dates_list: count = self.get_date_asset_count(d) data.append(count) return data @lazyproperty - def month_total_count_active_users(self): - count = len(set(self.session_month.values_list('user', flat=True))) + def dates_total_count_active_users(self): + count = len(set(self.sessions_queryset.values_list('user', flat=True))) return count @lazyproperty - def month_total_count_inactive_users(self): + def dates_total_count_inactive_users(self): total = current_org.get_org_members().count() - active = self.month_total_count_active_users + active = self.dates_total_count_active_users count = total - active if count < 0: count = 0 return count @lazyproperty - def month_total_count_disabled_users(self): + def dates_total_count_disabled_users(self): return current_org.get_org_members().filter(is_active=False).count() @lazyproperty - def month_total_count_active_assets(self): - return len(set(self.session_month.values_list('asset', flat=True))) + def dates_total_count_active_assets(self): + return len(set(self.sessions_queryset.values_list('asset', flat=True))) @lazyproperty - def month_total_count_inactive_assets(self): + def dates_total_count_inactive_assets(self): total = Asset.objects.all().count() - active = self.month_total_count_active_assets + active = self.dates_total_count_active_assets count = total - active if count < 0: count = 0 return count @lazyproperty - def month_total_count_disabled_assets(self): + def dates_total_count_disabled_assets(self): return Asset.objects.filter(is_active=False).count() - - -class WeekSessionMetricMixin: - session_week = None - - @lazyproperty - def session_week(self): - week_ago = timezone.now() - timezone.timedelta(weeks=1) - session_week = Session.objects.filter(date_start__gt=week_ago) - return session_week - - def get_week_login_times_top5_users(self): - users = self.session_week.values_list('user', flat=True) + + # 以下是从week中而来 + def get_dates_login_times_top5_users(self): + users = self.sessions_queryset.values_list('user', flat=True) users = [ {'user': user, 'total': total} for user, total in Counter(users).most_common(5) ] return users - def get_week_total_count_login_users(self): - return len(set(self.session_week.values_list('user', flat=True))) + def get_dates_total_count_login_users(self): + return len(set(self.sessions_queryset.values_list('user', flat=True))) - def get_week_total_count_login_times(self): - return self.session_week.count() + def get_dates_total_count_login_times(self): + return self.sessions_queryset.count() - def get_week_login_times_top10_assets(self): - assets = self.session_week.values("asset")\ - .annotate(total=Count("asset"))\ - .annotate(last=Max("date_start")).order_by("-total")[:10] + def get_dates_login_times_top10_assets(self): + assets = self.sessions_queryset.values("asset") \ + .annotate(total=Count("asset")) \ + .annotate(last=Max("date_start")).order_by("-total")[:10] for asset in assets: asset['last'] = str(asset['last']) return list(assets) - def get_week_login_times_top10_users(self): - users = self.session_week.values("user") \ - .annotate(total=Count("user")) \ - .annotate(last=Max("date_start")).order_by("-total")[:10] + def get_dates_login_times_top10_users(self): + users = self.sessions_queryset.values("user") \ + .annotate(total=Count("user")) \ + .annotate(last=Max("date_start")).order_by("-total")[:10] for user in users: user['last'] = str(user['last']) return list(users) - def get_week_login_record_top10_sessions(self): - sessions = self.session_week.order_by('-date_start')[:10] + def get_dates_login_record_top10_sessions(self): + sessions = self.sessions_queryset.order_by('-date_start')[:10] for session in sessions: session.avatar_url = User.get_avatar_url("") sessions = [ @@ -223,7 +223,7 @@ class TotalCountMixin: return Session.objects.filter(is_finished=False).count() -class IndexApi(TotalCountMixin, WeekSessionMetricMixin, MonthLoginMetricMixin, APIView): +class IndexApi(TotalCountMixin, DatesLoginMetricMixin, APIView): permission_classes = (IsOrgAdmin,) http_method_names = ['get'] @@ -254,52 +254,52 @@ class IndexApi(TotalCountMixin, WeekSessionMetricMixin, MonthLoginMetricMixin, A 'total_count_online_sessions': self.get_total_count_online_sessions(), }) - if _all or query_params.get('month_metrics'): + if _all or query_params.get('dates_metrics'): data.update({ - 'month_metrics_date': self.get_month_metrics_date(), - 'month_metrics_total_count_login': self.get_month_metrics_total_count_login(), - 'month_metrics_total_count_active_users': self.get_month_metrics_total_count_active_users(), - 'month_metrics_total_count_active_assets': self.get_month_metrics_total_count_active_assets(), + 'dates_metrics_date': self.get_dates_metrics_date(), + 'dates_metrics_total_count_login': self.get_dates_metrics_total_count_login(), + 'dates_metrics_total_count_active_users': self.get_dates_metrics_total_count_active_users(), + 'dates_metrics_total_count_active_assets': self.get_dates_metrics_total_count_active_assets(), }) - if _all or query_params.get('month_total_count_users'): + if _all or query_params.get('dates_total_count_users'): data.update({ - 'month_total_count_active_users': self.month_total_count_active_users, - 'month_total_count_inactive_users': self.month_total_count_inactive_users, - 'month_total_count_disabled_users': self.month_total_count_disabled_users, + 'dates_total_count_active_users': self.dates_total_count_active_users, + 'dates_total_count_inactive_users': self.dates_total_count_inactive_users, + 'dates_total_count_disabled_users': self.dates_total_count_disabled_users, }) - if _all or query_params.get('month_total_count_assets'): + if _all or query_params.get('dates_total_count_assets'): data.update({ - 'month_total_count_active_assets': self.month_total_count_active_assets, - 'month_total_count_inactive_assets': self.month_total_count_inactive_assets, - 'month_total_count_disabled_assets': self.month_total_count_disabled_assets, + 'dates_total_count_active_assets': self.dates_total_count_active_assets, + 'dates_total_count_inactive_assets': self.dates_total_count_inactive_assets, + 'dates_total_count_disabled_assets': self.dates_total_count_disabled_assets, }) - if _all or query_params.get('week_total_count'): + if _all or query_params.get('dates_total_count'): data.update({ - 'week_total_count_login_users': self.get_week_total_count_login_users(), - 'week_total_count_login_times': self.get_week_total_count_login_times(), + 'dates_total_count_login_users': self.get_dates_total_count_login_users(), + 'dates_total_count_login_times': self.get_dates_total_count_login_times(), }) - if _all or query_params.get('week_login_times_top5_users'): + if _all or query_params.get('dates_login_times_top5_users'): data.update({ - 'week_login_times_top5_users': self.get_week_login_times_top5_users(), + 'dates_login_times_top5_users': self.get_dates_login_times_top5_users(), }) - if _all or query_params.get('week_login_times_top10_assets'): + if _all or query_params.get('dates_login_times_top10_assets'): data.update({ - 'week_login_times_top10_assets': self.get_week_login_times_top10_assets(), + 'dates_login_times_top10_assets': self.get_dates_login_times_top10_assets(), }) - if _all or query_params.get('week_login_times_top10_users'): + if _all or query_params.get('dates_login_times_top10_users'): data.update({ - 'week_login_times_top10_users': self.get_week_login_times_top10_users(), + 'dates_login_times_top10_users': self.get_dates_login_times_top10_users(), }) - if _all or query_params.get('week_login_record_top10_sessions'): + if _all or query_params.get('dates_login_record_top10_sessions'): data.update({ - 'week_login_record_top10_sessions': self.get_week_login_record_top10_sessions() + 'dates_login_record_top10_sessions': self.get_dates_login_record_top10_sessions() }) return JsonResponse(data, status=200) diff --git a/apps/jumpserver/middleware.py b/apps/jumpserver/middleware.py index 775041b9d..f1589003d 100644 --- a/apps/jumpserver/middleware.py +++ b/apps/jumpserver/middleware.py @@ -15,7 +15,7 @@ class TimezoneMiddleware: self.get_response = get_response def __call__(self, request): - tzname = request.META.get('TZ') + tzname = request.META.get('HTTP_X_TZ') if tzname: timezone.activate(pytz.timezone(tzname)) else: diff --git a/apps/templates/index.html b/apps/templates/index.html index 1942ad2cb..a216ddac8 100644 --- a/apps/templates/index.html +++ b/apps/templates/index.html @@ -58,11 +58,11 @@
- {% trans 'In the past week, a total of ' %}{% trans ' users have logged in ' %}{% trans ' times asset.' %} -
    + {% trans 'In the past week, a total of ' %}{% trans ' users have logged in ' %}{% trans ' times asset.' %} +
-
+

@@ -73,12 +73,12 @@

-
+
{% trans 'User' %}
-
+
{% trans 'Asset' %}
@@ -112,7 +112,7 @@

{% trans 'Top 10 assets in a week'%}

{% trans 'Login frequency and last login record.' %}
-
+

@@ -130,7 +130,7 @@
-
+
@@ -158,7 +158,7 @@

{% trans 'Top 10 users in a week' %}

{% trans 'User login frequency and last login record.' %}
-
+
@@ -178,7 +178,7 @@ function requireMonthMetricsECharts(data){ 'echarts/chart/line' ], function (ec) { - var monthMetricsECharts = ec.init(document.getElementById('month_metrics_echarts')); + var monthMetricsECharts = ec.init(document.getElementById('dates_metrics_echarts')); var option = { title : { text: "{% trans 'Monthly data overview' %}", @@ -204,7 +204,7 @@ function requireMonthMetricsECharts(data){ { type : 'category', boundaryGap : false, - data : data['month_metrics_date'], + data : data['dates_metrics_date'], } ], yAxis : [ @@ -218,21 +218,21 @@ function requireMonthMetricsECharts(data){ type:'line', smooth: true, itemStyle: {normal: {areaStyle: {type: 'default'}}}, - data: data['month_metrics_total_count_login'] + data: data['dates_metrics_total_count_login'] }, { name: "{% trans 'Active users' %}", type: 'line', smooth: true, itemStyle: {normal: {areaStyle: {type: 'default'}}}, - data: data['month_metrics_total_count_active_users'] + data: data['dates_metrics_total_count_active_users'] }, { name:"{% trans 'Active assets' %}", type:'line', smooth:true, itemStyle: {normal: {areaStyle: {type: 'default'}}}, - data: data['month_metrics_total_count_active_assets'] + data: data['dates_metrics_total_count_active_assets'] } ] }; @@ -249,7 +249,7 @@ function requireMonthTotalCountUsersPie(data){ 'echarts/chart/pie' ], function (ec) { - var monthTotalCountUsersPie = ec.init(document.getElementById('month_total_count_users_pie')); + var monthTotalCountUsersPie = ec.init(document.getElementById('dates_total_count_users_pie')); var option = { tooltip : { trigger: 'item', @@ -310,9 +310,9 @@ function requireMonthTotalCountUsersPie(data){ } }, data:[ - {value:data['month_total_count_active_users'], name:"{% trans 'Monthly active users' %}"}, - {value:data['month_total_count_disabled_users'], name:"{% trans 'Disable user' %}"}, - {value:data['month_total_count_inactive_users'], name:"{% trans 'Month not logged in user' %}"} + {value:data['dates_total_count_active_users'], name:"{% trans 'Monthly active users' %}"}, + {value:data['dates_total_count_disabled_users'], name:"{% trans 'Disable user' %}"}, + {value:data['dates_total_count_inactive_users'], name:"{% trans 'Month not logged in user' %}"} ] } ] @@ -329,7 +329,7 @@ function requireMonthTotalCountAssetsPie(data){ 'echarts/chart/pie' ], function (ec) { - var monthTotalCountAssetsPie = ec.init(document.getElementById('month_total_count_assets_pie')); + var monthTotalCountAssetsPie = ec.init(document.getElementById('dates_total_count_assets_pie')); var option = { tooltip : { trigger: 'item', @@ -389,9 +389,9 @@ function requireMonthTotalCountAssetsPie(data){ } }, data:[ - {value:data['month_total_count_active_assets'], name:"{% trans 'Month is logged into the host' %}"}, - {value:data['month_total_count_disabled_assets'], name:"{% trans 'Disable host' %}"}, - {value:data['month_total_count_inactive_assets'], name:"{% trans 'Month not logged on host' %}"} + {value:data['dates_total_count_active_assets'], name:"{% trans 'Month is logged into the host' %}"}, + {value:data['dates_total_count_disabled_assets'], name:"{% trans 'Disable host' %}"}, + {value:data['dates_total_count_inactive_assets'], name:"{% trans 'Month not logged on host' %}"} ] } ] @@ -431,14 +431,14 @@ function renderMonthMetricsECharts(){ var success = function (data) { requireMonthMetricsECharts(data) }; - renderRequestApi('month_metrics=1', success) + renderRequestApi('dates_metrics=1', success) } function renderMonthTotalCountUsersPie(){ var success = function (data) { requireMonthTotalCountUsersPie(data) }; - renderRequestApi('month_total_count_users=1', success) + renderRequestApi('dates_total_count_users=1', success) } @@ -446,15 +446,15 @@ function renderMonthTotalCountAssetsPie(){ var success = function (data) { requireMonthTotalCountAssetsPie(data) }; - renderRequestApi('month_total_count_assets=1', success) + renderRequestApi('dates_total_count_assets=1', success) } function renderWeekTotalCount(){ var success = function (data) { - $('#week_total_count_login_users').html(data['week_total_count_login_users']); - $('#week_total_count_login_times').html(data['week_total_count_login_times']) + $('#dates_total_count_login_users').html(data['dates_total_count_login_users']); + $('#dates_total_count_login_times').html(data['dates_total_count_login_times']) }; - renderRequestApi('week_total_count=1', success) + renderRequestApi('dates_total_count=1', success) } function renderWeekLoginTimesTop5Users(){ @@ -468,14 +468,14 @@ function renderWeekLoginTimesTop5Users(){ "{INDEX} {USER}" + ""; - $.each(data['week_login_times_top5_users'], function(index, value){ + $.each(data['dates_login_times_top5_users'], function(index, value){ html += html_cell.replace('{TOTAL}', value['total']) .replace('{USER}', value['user']) .replace('{INDEX}', index+1) }); - $('#week_login_times_top5_users').html(html) + $('#dates_login_times_top5_users').html(html) }; - renderRequestApi('week_login_times_top5_users=1', success) + renderRequestApi('dates_login_times_top5_users=1', success) } function renderWeekLoginTimesTop10Assets(){ @@ -497,7 +497,7 @@ function renderWeekLoginTimesTop10Assets(){ "" + ""; - var assets = data['week_login_times_top10_assets']; + var assets = data['dates_login_times_top10_assets']; if (assets.length !== 0){ $.each(assets, function(index, value){ html += html_cell @@ -509,9 +509,9 @@ function renderWeekLoginTimesTop10Assets(){ else{ html += "

{% trans '(No)' %}

" } - $('#week_login_times_top10_assets').html(html) + $('#dates_login_times_top10_assets').html(html) }; - renderRequestApi('week_login_times_top10_assets=1', success) + renderRequestApi('dates_login_times_top10_assets=1', success) } function renderWeekLoginTimesTop10Users(){ @@ -533,7 +533,7 @@ function renderWeekLoginTimesTop10Users(){ "" + ""; - var users = data['week_login_times_top10_users']; + var users = data['dates_login_times_top10_users']; if (users.length !== 0){ $.each(users, function(index, value){ html += html_cell.replaceAll('{USER}', value['user']) @@ -544,9 +544,9 @@ function renderWeekLoginTimesTop10Users(){ else{ html += "

{% trans '(No)' %}

" } - $('#week_login_times_top10_users').html(html) + $('#dates_login_times_top10_users').html(html) }; - renderRequestApi('week_login_times_top10_users=1', success) + renderRequestApi('dates_login_times_top10_users=1', success) } function renderWeekLoginRecordTop10Sessions(){ @@ -564,7 +564,7 @@ function renderWeekLoginRecordTop10Sessions(){ "" + ""; - var users = data['week_login_record_top10_sessions']; + var users = data['dates_login_record_top10_sessions']; if (users.length !== 0){ $.each(users, function(index, value){ console.log(value['is_finished']) @@ -579,10 +579,10 @@ function renderWeekLoginRecordTop10Sessions(){ else{ html += "

{% trans '(No)' %}

" } - $('#week_login_record_top10_sessions').html(html) + $('#dates_login_record_top10_sessions').html(html) }; - renderRequestApi('week_login_record_top10_sessions=1', success) + renderRequestApi('dates_login_record_top10_sessions=1', success) } function renderData(){ From 701582fe38da8d563f6e148b7a0c1a08255ba62f Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 21 May 2020 14:47:00 +0800 Subject: [PATCH 045/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9license?= =?UTF-8?q?=E7=9A=84=E5=88=A4=E6=96=AD=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/conf.py | 2 +- apps/users/api/group.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index a39de2e82..1caf019c6 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -442,7 +442,7 @@ class DynamicConfig: return False try: from xpack.plugins.license.models import License - return bool(License.is_valid) + return License.has_valid_license() except: return False diff --git a/apps/users/api/group.py b/apps/users/api/group.py index 860ca36b4..f91b1d3bd 100644 --- a/apps/users/api/group.py +++ b/apps/users/api/group.py @@ -16,4 +16,3 @@ class UserGroupViewSet(OrgBulkModelViewSet): search_fields = filter_fields permission_classes = (IsOrgAdmin,) serializer_class = UserGroupSerializer - From 7b0993959e9d3e102348616c1ffe5f8c5aa1611b Mon Sep 17 00:00:00 2001 From: xinwen Date: Thu, 21 May 2020 15:40:41 +0800 Subject: [PATCH 046/146] [Update] orgs.serializers.OrgReadSerializer add `id` --- apps/orgs/serializers.py | 7 ++++--- apps/users/serializers/user.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py index 5ff3b5f4d..fbff6106c 100644 --- a/apps/orgs/serializers.py +++ b/apps/orgs/serializers.py @@ -6,6 +6,7 @@ from users.models import User, UserGroup from assets.models import Asset, Domain, AdminUser, SystemUser, Label from perms.models import AssetPermission from common.serializers import AdaptedBulkListSerializer +from users.serializers import UserOrgSerializer from .utils import set_current_org, get_current_org from .models import Organization from .mixins.serializers import OrgMembershipSerializerMixin @@ -20,9 +21,9 @@ class OrgSerializer(ModelSerializer): class OrgReadSerializer(ModelSerializer): - admins = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) - auditors = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) - users = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) + admins = UserOrgSerializer(many=True, read_only=True) + auditors = UserOrgSerializer(many=True, read_only=True) + users = UserOrgSerializer(many=True, read_only=True) user_groups = serializers.SerializerMethodField() assets = serializers.SerializerMethodField() domains = serializers.SerializerMethodField() diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index a0c56e8e6..99e1d23f4 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -14,7 +14,7 @@ from ..models import User __all__ = [ 'UserSerializer', 'UserPKUpdateSerializer', 'ChangeUserPasswordSerializer', 'ResetOTPSerializer', - 'UserProfileSerializer', + 'UserProfileSerializer', 'UserOrgSerializer' ] From 492b1c43118ab3c4e950e163eefadabb7f7b0ad1 Mon Sep 17 00:00:00 2001 From: xinwen Date: Thu, 21 May 2020 16:09:42 +0800 Subject: [PATCH 047/146] [Update] org retrieve api --- apps/orgs/api.py | 11 ++++++----- apps/orgs/serializers.py | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/apps/orgs/api.py b/apps/orgs/api.py index 63eebb25d..466ac7254 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -10,7 +10,7 @@ from common.permissions import IsSuperUserOrAppUser from .models import Organization from .serializers import OrgSerializer, OrgReadSerializer, \ OrgMembershipUserSerializer, OrgMembershipAdminSerializer, \ - OrgAllUserSerializer + OrgAllUserSerializer, OrgRetrieveSerializer from users.models import User, UserGroup from assets.models import Asset, Domain, AdminUser, SystemUser, Label from perms.models import AssetPermission @@ -28,10 +28,11 @@ class OrgViewSet(BulkModelViewSet): org = None def get_serializer_class(self): - if self.action in ('list', 'retrieve'): - return OrgReadSerializer - else: - return super().get_serializer_class() + mapper = { + 'list': OrgReadSerializer, + 'retrieve': OrgRetrieveSerializer + } + return mapper.get(self.action, super().get_serializer_class()) def get_data_from_model(self, model): if model == User: diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py index fbff6106c..ca5bdddba 100644 --- a/apps/orgs/serializers.py +++ b/apps/orgs/serializers.py @@ -1,12 +1,10 @@ -import re from rest_framework.serializers import ModelSerializer from rest_framework import serializers -from users.models import User, UserGroup +from users.models import UserGroup from assets.models import Asset, Domain, AdminUser, SystemUser, Label from perms.models import AssetPermission from common.serializers import AdaptedBulkListSerializer -from users.serializers import UserOrgSerializer from .utils import set_current_org, get_current_org from .models import Organization from .mixins.serializers import OrgMembershipSerializerMixin @@ -21,9 +19,9 @@ class OrgSerializer(ModelSerializer): class OrgReadSerializer(ModelSerializer): - admins = UserOrgSerializer(many=True, read_only=True) - auditors = UserOrgSerializer(many=True, read_only=True) - users = UserOrgSerializer(many=True, read_only=True) + admins = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) + auditors = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) + users = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) user_groups = serializers.SerializerMethodField() assets = serializers.SerializerMethodField() domains = serializers.SerializerMethodField() @@ -93,3 +91,12 @@ class OrgAllUserSerializer(serializers.Serializer): @staticmethod def get_user_display(obj): return str(obj) + + +class OrgRetrieveSerializer(OrgReadSerializer): + admins = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + auditors = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + users = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + + class Meta(OrgReadSerializer.Meta): + pass From 3320e6105c150d91732edf448682ed59566603b2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 21 May 2020 19:00:47 +0800 Subject: [PATCH 048/146] =?UTF-8?q?[Update]=20=E5=88=A0=E6=8E=89=E6=B2=A1?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/mixins/api.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/apps/common/mixins/api.py b/apps/common/mixins/api.py index 079e9f038..544e24852 100644 --- a/apps/common/mixins/api.py +++ b/apps/common/mixins/api.py @@ -14,7 +14,7 @@ from ..utils import lazyproperty __all__ = [ "JSONResponseMixin", "CommonApiMixin", - "IDSpmFilterMixin", 'AsyncApiMixin', + 'AsyncApiMixin', ] @@ -25,30 +25,27 @@ class JSONResponseMixin(object): return JsonResponse(context) -class IDSpmFilterMixin: - def get_filter_backends(self): - backends = super().get_filter_backends() - backends.append(IDSpmFilter) - return backends - - class SerializerMixin: def get_serializer_class(self): + if not hasattr(self, 'serializer_classes') or isinstance(self.serializer_classes, dict): + return super().get_serializer_class() + serializer_class = None - if hasattr(self, 'serializer_classes') and \ - isinstance(self.serializer_classes, dict): - if self.action in ['list', 'metadata'] and self.request.query_params.get('draw'): - serializer_class = self.serializer_classes.get('display') - if serializer_class is None: - serializer_class = self.serializer_classes.get( - self.action, self.serializer_classes.get('default') - ) + if self.action in ['list', 'metadata'] and self.request.query_params.get('draw'): + serializer_class = self.serializer_classes.get('display') + if serializer_class is None: + serializer_class = self.serializer_classes.get( + self.action, self.serializer_classes.get('default') + ) if serializer_class: return serializer_class return super().get_serializer_class() class ExtraFilterFieldsMixin: + """ + 额外的 api filter + """ default_added_filters = [CustomFilter, IDSpmFilter, IDInFilter] filter_backends = api_settings.DEFAULT_FILTER_BACKENDS extra_filter_fields = [] @@ -57,9 +54,10 @@ class ExtraFilterFieldsMixin: def get_filter_backends(self): if self.filter_backends != self.__class__.filter_backends: return self.filter_backends - return list(self.filter_backends) + \ - self.default_added_filters + \ - list(self.extra_filter_backends) + backends = list(self.filter_backends) + \ + list(self.default_added_filters) + \ + list(self.extra_filter_backends) + return backends def filter_queryset(self, queryset): for backend in self.get_filter_backends(): @@ -72,6 +70,9 @@ class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin): class InterceptMixin: + """ + Hack默认的dispatch, 让用户可以实现 self.do + """ def dispatch(self, request, *args, **kwargs): self.args = args self.kwargs = kwargs From 2ff226641734ca05897b28d22ac4b19bbd2c0abf Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 21 May 2020 20:35:44 +0800 Subject: [PATCH 049/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=20Seriali?= =?UTF-8?q?zerMixin=20=E8=BF=98=E5=8E=9F=E4=B9=8B=E5=89=8D=E7=9A=84?= =?UTF-8?q?=E6=9B=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/mixins/api.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/common/mixins/api.py b/apps/common/mixins/api.py index 544e24852..c49535ad6 100644 --- a/apps/common/mixins/api.py +++ b/apps/common/mixins/api.py @@ -27,16 +27,15 @@ class JSONResponseMixin(object): class SerializerMixin: def get_serializer_class(self): - if not hasattr(self, 'serializer_classes') or isinstance(self.serializer_classes, dict): - return super().get_serializer_class() - serializer_class = None - if self.action in ['list', 'metadata'] and self.request.query_params.get('draw'): - serializer_class = self.serializer_classes.get('display') - if serializer_class is None: - serializer_class = self.serializer_classes.get( - self.action, self.serializer_classes.get('default') - ) + if hasattr(self, 'serializer_classes') and isinstance(self.serializer_classes, dict): + if self.action in ['list', 'metadata'] and self.request.query_params.get('draw'): + serializer_class = self.serializer_classes.get('display') + if serializer_class is None: + serializer_class = self.serializer_classes.get( + self.action, self.serializer_classes.get('default') + ) + print(serializer_class) if serializer_class: return serializer_class return super().get_serializer_class() From 11527b903324c1d8a88fbcf77e9eefde689b95b2 Mon Sep 17 00:00:00 2001 From: xinwen Date: Fri, 22 May 2020 18:18:40 +0800 Subject: [PATCH 050/146] [Update] i18n --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 90024 -> 89801 bytes apps/locale/zh/LC_MESSAGES/django.po | 216 +++++++++++++-------------- 2 files changed, 107 insertions(+), 109 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 35c02c6c356af5e3abc91a53f2716604d6e01f9c..0b9b710a1074a17e97be112aab89f116638d45f3 100644 GIT binary patch delta 26429 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\n" "Language-Team: JumpServer team\n" @@ -53,7 +53,7 @@ msgstr "自定义" #: users/templates/users/user_asset_permission.html:70 #: users/templates/users/user_granted_remote_app.html:36 #: xpack/plugins/change_auth_plan/forms.py:74 -#: xpack/plugins/change_auth_plan/models.py:265 +#: xpack/plugins/change_auth_plan/models.py:274 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:40 #: 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 @@ -170,9 +170,8 @@ msgstr "运行参数" #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:16 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:51 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:47 #: xpack/plugins/orgs/templates/orgs/org_list.html:12 -#: xpack/plugins/orgs/templates/orgs/org_users.html:46 msgid "Name" msgstr "名称" @@ -259,7 +258,7 @@ msgstr "数据库" #: users/templates/users/user_group_detail.html:62 #: users/templates/users/user_group_list.html:16 #: users/templates/users/user_profile.html:138 -#: xpack/plugins/change_auth_plan/models.py:75 +#: xpack/plugins/change_auth_plan/models.py:76 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:115 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 #: xpack/plugins/cloud/models.py:53 xpack/plugins/cloud/models.py:139 @@ -268,7 +267,7 @@ msgstr "数据库" #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:128 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18 #: xpack/plugins/gathered_user/models.py:26 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:63 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:59 #: xpack/plugins/orgs/templates/orgs/org_list.html:23 msgid "Comment" msgstr "备注" @@ -323,7 +322,7 @@ msgstr "参数" #: perms/templates/perms/remote_app_permission_detail.html:85 #: users/models/user.py:491 users/serializers/group.py:35 #: users/templates/users/user_detail.html:97 -#: xpack/plugins/change_auth_plan/models.py:79 +#: xpack/plugins/change_auth_plan/models.py:80 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:111 #: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:145 #: xpack/plugins/gathered_user/models.py:30 @@ -355,7 +354,7 @@ msgstr "创建者" #: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:148 #: xpack/plugins/cloud/templates/cloud/account_detail.html:63 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:108 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:59 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:55 msgid "Date created" msgstr "创建日期" @@ -538,7 +537,7 @@ msgstr "详情" #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:26 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:60 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:46 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:24 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:20 #: xpack/plugins/orgs/templates/orgs/org_list.html:93 msgid "Update" msgstr "更新" @@ -590,7 +589,7 @@ msgstr "更新" #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:61 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:47 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:28 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:24 #: xpack/plugins/orgs/templates/orgs/org_list.html:95 msgid "Delete" msgstr "删除" @@ -650,7 +649,6 @@ msgstr "创建数据库应用" #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:19 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:20 #: xpack/plugins/orgs/templates/orgs/org_list.html:24 -#: xpack/plugins/orgs/templates/orgs/org_users.html:47 msgid "Action" msgstr "动作" @@ -742,7 +740,7 @@ msgstr "最新版本的不能被删除" #: assets/templates/assets/asset_detail.html:194 #: assets/templates/assets/system_user_assets.html:118 #: perms/models/asset_permission.py:81 -#: xpack/plugins/change_auth_plan/models.py:54 +#: xpack/plugins/change_auth_plan/models.py:55 #: xpack/plugins/gathered_user/models.py:24 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:17 msgid "Nodes" @@ -860,8 +858,8 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: users/templates/users/user_list.html:15 #: users/templates/users/user_profile.html:47 #: xpack/plugins/change_auth_plan/forms.py:59 -#: xpack/plugins/change_auth_plan/models.py:45 -#: xpack/plugins/change_auth_plan/models.py:261 +#: xpack/plugins/change_auth_plan/models.py:46 +#: xpack/plugins/change_auth_plan/models.py:270 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:63 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12 @@ -919,9 +917,9 @@ msgstr "密码或密钥密码" #: users/templates/users/user_profile_update.html:41 #: users/templates/users/user_pubkey_update.html:41 #: users/templates/users/user_update.html:20 -#: xpack/plugins/change_auth_plan/models.py:66 -#: xpack/plugins/change_auth_plan/models.py:181 -#: xpack/plugins/change_auth_plan/models.py:268 +#: xpack/plugins/change_auth_plan/models.py:67 +#: xpack/plugins/change_auth_plan/models.py:190 +#: xpack/plugins/change_auth_plan/models.py:277 msgid "Password" msgstr "密码" @@ -1147,15 +1145,15 @@ msgstr "版本" msgid "AuthBook" msgstr "" -#: assets/models/base.py:235 xpack/plugins/change_auth_plan/models.py:70 -#: xpack/plugins/change_auth_plan/models.py:188 -#: xpack/plugins/change_auth_plan/models.py:275 +#: assets/models/base.py:235 xpack/plugins/change_auth_plan/models.py:71 +#: xpack/plugins/change_auth_plan/models.py:197 +#: xpack/plugins/change_auth_plan/models.py:284 msgid "SSH private key" msgstr "ssh密钥" -#: assets/models/base.py:236 xpack/plugins/change_auth_plan/models.py:73 -#: xpack/plugins/change_auth_plan/models.py:184 -#: xpack/plugins/change_auth_plan/models.py:271 +#: assets/models/base.py:236 xpack/plugins/change_auth_plan/models.py:74 +#: xpack/plugins/change_auth_plan/models.py:193 +#: xpack/plugins/change_auth_plan/models.py:280 msgid "SSH public key" msgstr "ssh公钥" @@ -1346,6 +1344,7 @@ msgstr "默认资产组" #: users/templates/users/user_remote_app_permission.html:37 #: users/templates/users/user_remote_app_permission.html:58 #: users/views/profile/base.py:46 xpack/plugins/orgs/forms.py:27 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:108 #: xpack/plugins/orgs/templates/orgs/org_list.html:15 msgid "User" msgstr "用户" @@ -1407,7 +1406,7 @@ msgstr "手动登录" #: assets/views/platform.py:58 assets/views/platform.py:74 #: assets/views/system_user.py:30 assets/views/system_user.py:47 #: assets/views/system_user.py:64 assets/views/system_user.py:80 -#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:50 +#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:51 msgid "Assets" msgstr "资产管理" @@ -2514,8 +2513,8 @@ msgstr "成功" #: perms/templates/perms/remote_app_permission_detail.html:73 #: terminal/models.py:199 terminal/templates/terminal/session_detail.html:72 #: terminal/templates/terminal/session_list.html:32 -#: xpack/plugins/change_auth_plan/models.py:167 -#: xpack/plugins/change_auth_plan/models.py:290 +#: xpack/plugins/change_auth_plan/models.py:176 +#: xpack/plugins/change_auth_plan/models.py:299 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17 #: xpack/plugins/gathered_user/models.py:76 @@ -2585,7 +2584,7 @@ msgid "MFA" msgstr "多因子认证" #: audits/models.py:87 audits/templates/audits/login_log_list.html:63 -#: xpack/plugins/change_auth_plan/models.py:286 +#: xpack/plugins/change_auth_plan/models.py:295 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15 #: xpack/plugins/cloud/models.py:217 msgid "Reason" @@ -2635,7 +2634,6 @@ msgstr "运行用户" #: perms/templates/perms/asset_permission_user.html:74 #: perms/templates/perms/database_app_permission_user.html:74 #: perms/templates/perms/remote_app_permission_user.html:83 -#: xpack/plugins/orgs/templates/orgs/org_users.html:67 msgid "Select user" msgstr "选择用户" @@ -3090,6 +3088,7 @@ msgid "Regularly perform" msgstr "定期执行" #: ops/mixin.py:108 ops/mixin.py:147 +#: xpack/plugins/change_auth_plan/serializers.py:53 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:54 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:79 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:17 @@ -3155,7 +3154,7 @@ msgstr "Become" #: ops/models/adhoc.py:150 users/templates/users/user_group_detail.html:54 #: xpack/plugins/cloud/templates/cloud/account_detail.html:59 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:55 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:51 msgid "Create by" msgstr "创建者" @@ -3177,8 +3176,8 @@ msgstr "完成时间" #: ops/models/adhoc.py:238 ops/templates/ops/adhoc_history.html:55 #: ops/templates/ops/task_history.html:61 ops/templates/ops/task_list.html:16 -#: xpack/plugins/change_auth_plan/models.py:170 -#: xpack/plugins/change_auth_plan/models.py:293 +#: xpack/plugins/change_auth_plan/models.py:179 +#: xpack/plugins/change_auth_plan/models.py:302 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16 #: xpack/plugins/gathered_user/models.py:79 @@ -3244,6 +3243,7 @@ msgid "Version run execution" msgstr "执行历史" #: ops/templates/ops/adhoc_detail.html:92 ops/templates/ops/task_list.html:12 +#: xpack/plugins/change_auth_plan/serializers.py:54 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:18 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:19 msgid "Run times" @@ -3373,7 +3373,7 @@ msgid "Pending" msgstr "等待" #: ops/templates/ops/command_execution_list.html:70 -#: xpack/plugins/change_auth_plan/models.py:257 +#: xpack/plugins/change_auth_plan/models.py:266 msgid "Finished" msgstr "结束" @@ -3589,9 +3589,8 @@ msgstr "添加资产" #: perms/templates/perms/remote_app_permission_user.html:120 #: users/templates/users/user_group_detail.html:87 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:76 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:89 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:123 -#: xpack/plugins/orgs/templates/orgs/org_users.html:73 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:88 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:125 msgid "Add" msgstr "添加" @@ -3662,7 +3661,7 @@ msgstr "刷新授权缓存" #: users/templates/users/user_database_app_permission.html:41 #: users/templates/users/user_list.html:19 #: users/templates/users/user_remote_app_permission.html:41 -#: xpack/plugins/cloud/models.py:50 +#: xpack/plugins/cloud/models.py:50 xpack/plugins/cloud/serializers.py:32 #: xpack/plugins/cloud/templates/cloud/account_detail.html:55 #: xpack/plugins/cloud/templates/cloud/account_list.html:14 msgid "Validity" @@ -3690,7 +3689,6 @@ msgstr "刷新成功" #: perms/templates/perms/asset_permission_user.html:31 #: perms/templates/perms/database_app_permission_user.html:31 #: perms/templates/perms/remote_app_permission_user.html:30 -#: xpack/plugins/orgs/templates/orgs/org_users.html:24 msgid "User list of " msgstr "用户列表" @@ -4373,7 +4371,7 @@ msgstr "系统设置" msgid "Update setting successfully" msgstr "更新设置成功" -#: templates/_base_only_msg_content.html:28 +#: templates/_base_only_msg_content.html:28 xpack/plugins/interface/api.py:17 #: xpack/plugins/interface/models.py:36 msgid "Welcome to the JumpServer open source fortress" msgstr "欢迎使用JumpServer开源堡垒机" @@ -4851,7 +4849,7 @@ msgstr "风险等级" msgid "Container name" msgstr "容器名称" -#: terminal/forms/storage.py:44 +#: terminal/forms/storage.py:44 xpack/plugins/cloud/serializers.py:75 msgid "Account name" msgstr "账户名称" @@ -5437,7 +5435,8 @@ msgid "Set password" msgstr "设置密码" #: users/forms/user.py:132 users/serializers/user.py:35 -#: xpack/plugins/change_auth_plan/models.py:59 +#: xpack/plugins/change_auth_plan/models.py:60 +#: xpack/plugins/change_auth_plan/serializers.py:30 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:45 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:67 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57 @@ -5450,7 +5449,6 @@ msgid "Administrator" msgstr "管理员" #: users/models/user.py:145 xpack/plugins/orgs/forms.py:29 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:109 #: xpack/plugins/orgs/templates/orgs/org_list.html:14 msgid "Auditor" msgstr "审计员" @@ -5785,6 +5783,7 @@ msgid "User group detail" msgstr "用户组详情" #: users/templates/users/user_group_detail.html:81 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:116 msgid "Add user" msgstr "添加用户" @@ -6256,8 +6255,8 @@ msgstr "" "用户不存在,则创建用户。" #: xpack/plugins/change_auth_plan/meta.py:9 -#: xpack/plugins/change_auth_plan/models.py:87 -#: xpack/plugins/change_auth_plan/models.py:174 +#: xpack/plugins/change_auth_plan/models.py:88 +#: xpack/plugins/change_auth_plan/models.py:183 #: xpack/plugins/change_auth_plan/views.py:33 #: xpack/plugins/change_auth_plan/views.py:50 #: xpack/plugins/change_auth_plan/views.py:74 @@ -6268,69 +6267,69 @@ msgstr "" msgid "Change auth plan" msgstr "改密计划" -#: xpack/plugins/change_auth_plan/models.py:39 +#: xpack/plugins/change_auth_plan/models.py:40 msgid "Custom password" msgstr "自定义密码" -#: xpack/plugins/change_auth_plan/models.py:40 +#: xpack/plugins/change_auth_plan/models.py:41 msgid "All assets use the same random password" msgstr "所有资产使用相同的随机密码" -#: xpack/plugins/change_auth_plan/models.py:41 +#: xpack/plugins/change_auth_plan/models.py:42 msgid "All assets use different random password" msgstr "所有资产使用不同的随机密码" -#: xpack/plugins/change_auth_plan/models.py:63 +#: xpack/plugins/change_auth_plan/models.py:64 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:72 msgid "Password rules" msgstr "密码规则" -#: xpack/plugins/change_auth_plan/models.py:178 +#: xpack/plugins/change_auth_plan/models.py:187 msgid "Change auth plan snapshot" msgstr "改密计划快照" -#: xpack/plugins/change_auth_plan/models.py:193 -#: xpack/plugins/change_auth_plan/models.py:279 +#: xpack/plugins/change_auth_plan/models.py:202 +#: xpack/plugins/change_auth_plan/models.py:288 msgid "Change auth plan execution" msgstr "改密计划执行" -#: xpack/plugins/change_auth_plan/models.py:252 +#: xpack/plugins/change_auth_plan/models.py:261 msgid "Ready" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:253 +#: xpack/plugins/change_auth_plan/models.py:262 msgid "Preflight check" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:254 +#: xpack/plugins/change_auth_plan/models.py:263 msgid "Change auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:255 +#: xpack/plugins/change_auth_plan/models.py:264 msgid "Verify auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:256 +#: xpack/plugins/change_auth_plan/models.py:265 msgid "Keep auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:283 +#: xpack/plugins/change_auth_plan/models.py:292 msgid "Step" msgstr "步骤" -#: xpack/plugins/change_auth_plan/models.py:300 +#: xpack/plugins/change_auth_plan/models.py:309 msgid "Change auth plan task" msgstr "改密计划任务" -#: xpack/plugins/change_auth_plan/serializers.py:68 +#: xpack/plugins/change_auth_plan/serializers.py:70 msgid "* Please enter custom password" msgstr "* 请输入自定义密码" -#: xpack/plugins/change_auth_plan/serializers.py:78 +#: xpack/plugins/change_auth_plan/serializers.py:80 msgid "* Please enter the correct password length" msgstr "* 请输入正确的密码长度" -#: xpack/plugins/change_auth_plan/serializers.py:81 +#: xpack/plugins/change_auth_plan/serializers.py:83 msgid "* Password length range 6-30 bits" msgstr "* 密码长度范围 6-30 位" @@ -6464,7 +6463,7 @@ msgstr "有效" msgid "Unavailable" msgstr "无效" -#: xpack/plugins/cloud/models.py:39 +#: xpack/plugins/cloud/models.py:39 xpack/plugins/cloud/serializers.py:31 #: xpack/plugins/cloud/templates/cloud/account_detail.html:51 #: xpack/plugins/cloud/templates/cloud/account_list.html:13 msgid "Provider" @@ -6603,6 +6602,24 @@ msgstr "拉美-圣地亚哥" msgid "Tencent Cloud" msgstr "腾讯云" +#: xpack/plugins/cloud/serializers.py:76 +#, fuzzy +#| msgid "History of " +msgid "History count" +msgstr "执行次数" + +#: xpack/plugins/cloud/serializers.py:77 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:15 +msgid "Instance count" +msgstr "实例个数" + +#: xpack/plugins/cloud/serializers.py:78 +#: xpack/plugins/gathered_user/serializers.py:20 +#, fuzzy +#| msgid "Periodic" +msgid "Periodic display" +msgstr "定时执行" + #: xpack/plugins/cloud/templates/cloud/account_detail.html:17 #: xpack/plugins/cloud/views.py:79 msgid "Account detail" @@ -6679,10 +6696,6 @@ msgstr "创建同步实例任务" msgid "Run count" msgstr "执行次数" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:15 -msgid "Instance count" -msgstr "实例个数" - #: xpack/plugins/cloud/utils.py:38 msgid "Account unavailable" msgstr "账户无效" @@ -6728,12 +6741,6 @@ msgstr "收集用户执行" msgid "Assets is empty, please change nodes" msgstr "资产为空,请更改节点" -#: xpack/plugins/gathered_user/serializers.py:20 -#, fuzzy -#| msgid "Periodic" -msgid "Periodic display" -msgstr "定时执行" - #: xpack/plugins/gathered_user/serializers.py:21 #, fuzzy #| msgid "Execute failed" @@ -6841,6 +6848,14 @@ msgstr "恢复默认失败!" msgid "It is already in the default setting state!" msgstr "当前已经是初始化状态!" +#: xpack/plugins/license/api.py:46 xpack/plugins/license/views.py:47 +msgid "License import successfully" +msgstr "许可证导入成功" + +#: xpack/plugins/license/api.py:47 xpack/plugins/license/views.py:49 +msgid "License is invalid" +msgstr "无效的许可证" + #: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94 #: xpack/plugins/license/templates/license/license_detail.html:41 #: xpack/plugins/license/templates/license/license_detail.html:46 @@ -6919,73 +6934,47 @@ msgstr "技术咨询" msgid "Consult" msgstr "咨询" -#: xpack/plugins/license/views.py:47 -msgid "License import successfully" -msgstr "许可证导入成功" - -#: xpack/plugins/license/views.py:49 -msgid "License is invalid" -msgstr "无效的许可证" - #: xpack/plugins/orgs/forms.py:23 msgid "Select auditor" msgstr "选择审计员" #: xpack/plugins/orgs/forms.py:28 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:75 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:71 #: xpack/plugins/orgs/templates/orgs/org_list.html:13 msgid "Admin" msgstr "管理员" -#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:27 -#: xpack/plugins/orgs/views.py:44 xpack/plugins/orgs/views.py:62 -#: xpack/plugins/orgs/views.py:85 xpack/plugins/orgs/views.py:116 +#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:26 +#: xpack/plugins/orgs/views.py:43 xpack/plugins/orgs/views.py:61 +#: xpack/plugins/orgs/views.py:79 msgid "Organizations" msgstr "组织管理" #: xpack/plugins/orgs/templates/orgs/org_detail.html:17 -#: xpack/plugins/orgs/templates/orgs/org_users.html:13 -#: xpack/plugins/orgs/views.py:86 +#: xpack/plugins/orgs/views.py:80 msgid "Org detail" msgstr "组织详情" -#: xpack/plugins/orgs/templates/orgs/org_detail.html:20 -#: xpack/plugins/orgs/templates/orgs/org_users.html:16 -msgid "Org users" -msgstr "组织用户" - -#: xpack/plugins/orgs/templates/orgs/org_detail.html:83 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:79 msgid "Add admin" msgstr "添加管理员" -#: xpack/plugins/orgs/templates/orgs/org_detail.html:117 -msgid "Add auditor" -msgstr "添加审计员" - #: xpack/plugins/orgs/templates/orgs/org_list.html:5 msgid "Create organization " msgstr "创建组织" -#: xpack/plugins/orgs/templates/orgs/org_users.html:59 -msgid "Add user to organization" -msgstr "添加用户" - -#: xpack/plugins/orgs/views.py:28 +#: xpack/plugins/orgs/views.py:27 msgid "Org list" msgstr "组织列表" -#: xpack/plugins/orgs/views.py:45 +#: xpack/plugins/orgs/views.py:44 msgid "Create org" msgstr "创建组织" -#: xpack/plugins/orgs/views.py:63 +#: xpack/plugins/orgs/views.py:62 msgid "Update org" msgstr "更新组织" -#: xpack/plugins/orgs/views.py:117 -msgid "Org user list" -msgstr "组织用户列表" - #: xpack/plugins/vault/meta.py:11 xpack/plugins/vault/views.py:23 #: xpack/plugins/vault/views.py:38 msgid "Vault" @@ -7007,6 +6996,18 @@ msgstr "密码匣子" msgid "vault create" msgstr "创建" +#~ msgid "Org users" +#~ msgstr "组织用户" + +#~ msgid "Add auditor" +#~ msgstr "添加审计员" + +#~ msgid "Add user to organization" +#~ msgstr "添加用户" + +#~ msgid "Org user list" +#~ msgstr "组织用户列表" + #~ msgid "CN North-Beijing1" #~ msgstr "华北-北京1" @@ -7043,9 +7044,6 @@ msgstr "创建" #~ msgid "History detail of" #~ msgstr "执行历史详情" -#~ msgid "History of " -#~ msgstr "执行历史" - #~ msgid "Assets count: {}" #~ msgstr "资产数量" From 93453cc8c3c56f5c63aad2a51c72a627e70ea623 Mon Sep 17 00:00:00 2001 From: xinwen Date: Mon, 25 May 2020 14:50:07 +0800 Subject: [PATCH 051/146] =?UTF-8?q?[Update]=20perms.serializers.asset=5Fpe?= =?UTF-8?q?rmission.AssetPermissionSerializer=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/serializers/asset_permission.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/perms/serializers/asset_permission.py b/apps/perms/serializers/asset_permission.py index 757fe2853..d60ce28b9 100644 --- a/apps/perms/serializers/asset_permission.py +++ b/apps/perms/serializers/asset_permission.py @@ -43,7 +43,8 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer): model = AssetPermission mini_fields = ['id', 'name'] small_fields = mini_fields + [ - 'is_active', 'is_expired', 'is_valid', 'actions', 'created_by', 'date_created' + 'is_active', 'is_expired', 'is_valid', 'actions', 'created_by', 'date_created', + 'date_expired', 'date_start', 'comment' ] m2m_fields = [ 'users', 'user_groups', 'assets', 'nodes', 'system_users', From 3c95c6fe116ed44d3e0b3e500bfed05abde273f2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 26 May 2020 18:56:03 +0800 Subject: [PATCH 052/146] =?UTF-8?q?[Feat]=20=E6=B7=BB=E5=8A=A0=E5=A4=B1?= =?UTF-8?q?=E6=95=88=E7=94=A8=E6=88=B7=E6=9D=83=E9=99=90=E7=9A=84api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/mixins/api.py | 1 - apps/perms/api/user_permission/common.py | 10 +++++++++- apps/perms/tests.py | 1 - apps/perms/urls/asset_permission.py | 5 +++++ apps/users/api/user.py | 2 +- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/common/mixins/api.py b/apps/common/mixins/api.py index c49535ad6..bbabe9951 100644 --- a/apps/common/mixins/api.py +++ b/apps/common/mixins/api.py @@ -35,7 +35,6 @@ class SerializerMixin: serializer_class = self.serializer_classes.get( self.action, self.serializer_classes.get('default') ) - print(serializer_class) if serializer_class: return serializer_class return super().get_serializer_class() diff --git a/apps/perms/api/user_permission/common.py b/apps/perms/api/user_permission/common.py index 6af3bb6ba..17900a6bc 100644 --- a/apps/perms/api/user_permission/common.py +++ b/apps/perms/api/user_permission/common.py @@ -5,7 +5,7 @@ import uuid from django.shortcuts import get_object_or_404 from rest_framework.views import APIView, Response from rest_framework.generics import ( - ListAPIView, get_object_or_404, RetrieveAPIView + ListAPIView, get_object_or_404, RetrieveAPIView, DestroyAPIView ) from common.permissions import IsOrgAdminOrAppUser, IsOrgAdmin @@ -25,6 +25,7 @@ __all__ = [ 'UserGrantedAssetSystemUsersApi', 'ValidateUserAssetPermissionApi', 'GetUserAssetPermissionActionsApi', + 'UserAssetPermissionsCacheApi', ] @@ -117,3 +118,10 @@ class UserGrantedAssetSystemUsersApi(UserAssetPermissionMixin, ListAPIView): system_user.actions = actions return system_users + +class UserAssetPermissionsCacheApi(UserAssetPermissionMixin, DestroyAPIView): + permission_classes = (IsOrgAdmin,) + + def destroy(self, request, *args, **kwargs): + self.util.expire_user_tree_cache() + return Response(status=204) diff --git a/apps/perms/tests.py b/apps/perms/tests.py index 4fd76b0f2..344266b19 100644 --- a/apps/perms/tests.py +++ b/apps/perms/tests.py @@ -1,4 +1,3 @@ from django.test import TestCase from django.contrib.sessions.backends import file, db, cache -from django.contrib.auth.views import login \ No newline at end of file diff --git a/apps/perms/urls/asset_permission.py b/apps/perms/urls/asset_permission.py index c14db7c3e..ff8ac0c48 100644 --- a/apps/perms/urls/asset_permission.py +++ b/apps/perms/urls/asset_permission.py @@ -51,6 +51,11 @@ user_permission_urlpatterns = [ # Asset System users path('/assets//system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='user-asset-system-users'), path('assets//system-users/', api.UserGrantedAssetSystemUsersApi.as_view(), name='my-asset-system-users'), + + # Expire user permission cache + path('/asset-permissions/cache/', api.UserAssetPermissionsCacheApi.as_view(), + name='user-asset-permission-cache'), + path('asset-permissions/cache/', api.UserAssetPermissionsCacheApi.as_view(), name='my-asset-permission-cache'), ] user_group_permission_urlpatterns = [ diff --git a/apps/users/api/user.py b/apps/users/api/user.py index 97857c436..8729f4dcc 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -27,7 +27,7 @@ __all__ = [ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet): - filter_fields = ('username', 'email', 'name', 'id') + filter_fields = ('username', 'email', 'name', 'id', 'source') search_fields = filter_fields serializer_class = serializers.UserSerializer permission_classes = (IsOrgAdmin, CanUpdateDeleteUser) From 7b362bfc76c212ff061f5b3d235b9e84fb1630a7 Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 27 May 2020 17:27:28 +0800 Subject: [PATCH 053/146] =?UTF-8?q?[Update]=20=E7=94=A8=E6=88=B7=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E7=B1=BB=E6=B7=BB=E5=8A=A0mfa=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/serializers/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 99e1d23f4..ef4c8a34f 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -48,8 +48,8 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): fields_mini = ['id', 'name', 'username'] # small 指的是 不需要计算的直接能从一张表中获取到的数据 fields_small = fields_mini + [ - 'password', 'email', 'public_key', 'wechat', 'phone', 'mfa_level', - 'mfa_level_display', + 'password', 'email', 'public_key', 'wechat', 'phone', 'mfa_level', 'mfa_enabled', + 'mfa_level_display', 'mfa_force_enabled', 'comment', 'source', 'is_valid', 'is_expired', 'is_active', 'created_by', 'is_first_login', 'password_strategy', 'date_password_last_updated', 'date_expired', From a840e611cdc1d3a16a25b4a41491940c0c9d470f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 27 May 2020 18:02:18 +0800 Subject: [PATCH 054/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9core=20?= =?UTF-8?q?=E7=9A=84base=20url?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/urls/view_urls.py | 1 + apps/jumpserver/urls.py | 17 ++++++++--------- apps/static/js/jumpserver.js | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py index bee5f8517..62283f1f5 100644 --- a/apps/authentication/urls/view_urls.py +++ b/apps/authentication/urls/view_urls.py @@ -18,4 +18,5 @@ urlpatterns = [ # openid path('cas/', include(('authentication.backends.cas.urls', 'authentication'), namespace='cas')), path('openid/', include(('jms_oidc_rp.urls', 'authentication'), namespace='openid')), + path('captcha/', include('captcha.urls')), ] diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index ade854397..eb6d41a65 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -43,6 +43,11 @@ app_view_patterns = [ path('applications/', include('applications.urls.views_urls', namespace='applications')), path('tickets/', include('tickets.urls.views_urls', namespace='tickets')), re_path(r'flower/(?P.*)', views.celery_flower_view, name='flower-view'), + re_path('luna/.*', views.LunaView.as_view(), name='luna-view'), + re_path('koko/.*', views.KokoView.as_view(), name='koko-view'), + re_path('ws/.*', views.WsView.as_view(), name='ws-view'), + path('i18n//', views.I18NView.as_view(), name='i18n-switch'), + path('settings/', include('settings.urls.view_urls', namespace='settings')), ] @@ -65,17 +70,11 @@ urlpatterns = [ path('api/v2/', include(api_v2)), re_path('api/(?P\w+)/(?Pv\d)/.*', views.redirect_format_api), path('api/health/', views.HealthCheckView.as_view(), name="health"), - re_path('luna/.*', views.LunaView.as_view(), name='luna-view'), - re_path('koko/.*', views.KokoView.as_view(), name='koko-view'), - re_path('ws/.*', views.WsView.as_view(), name='ws-view'), - path('i18n//', views.I18NView.as_view(), name='i18n-switch'), - path('settings/', include('settings.urls.view_urls', namespace='settings')), - # External apps url - path('captcha/', include('captcha.urls')), + path('core/auth/captcha/', include('captcha.urls')), + path('core/', include(app_view_patterns)), ] -urlpatterns += app_view_patterns urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += js_i18n_patterns @@ -84,7 +83,7 @@ handler404 = 'jumpserver.views.handler404' handler500 = 'jumpserver.views.handler500' if settings.DEBUG: - urlpatterns += [ + app_view_patterns += [ re_path('^swagger(?P\.json|\.yaml)$', views.get_swagger_view().without_ui(cache_timeout=1), name='schema-json'), path('docs/', views.get_swagger_view().with_ui('swagger', cache_timeout=1), name="docs"), diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 9081d3a94..b930154ff 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -138,11 +138,11 @@ function setAjaxCSRFToken() { } function activeNav(prefix) { - var path = document.location.pathname; - if (prefix) { - path = path.replace(prefix, ''); - console.log(path); + if (!prefix) { + prefix = '/core' } + var path = document.location.pathname; + path = path.replace(prefix, ''); var urlArray = path.split("/"); var app = urlArray[1]; var resource = urlArray[2]; From a463f632e8fcd3ccda944ab090c53f62cb2cc1a0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 27 May 2020 20:33:09 +0800 Subject: [PATCH 055/146] =?UTF-8?q?[Update]=20=E4=BC=98=E5=8C=96=E9=87=8D?= =?UTF-8?q?=E5=AE=9A=E5=90=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/urls.py | 12 ++++++++++++ apps/jumpserver/views/other.py | 12 ++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index eb6d41a65..a43a9ea6b 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -64,6 +64,14 @@ js_i18n_patterns = i18n_patterns( ) +apps = [ + 'users', 'assets', 'perms', 'terminal', 'ops', 'audits', 'orgs', 'auth', + 'applications', 'tickets', 'settings', 'xpack' + 'flower', 'luna', 'koko', 'ws', 'i18n', 'jsi18n', 'docs', 'redocs', + 'zh-hans' +] + + urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('api/v1/', include(api_v1)), @@ -79,6 +87,10 @@ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += js_i18n_patterns +# 兼容之前的 +old_app_pattern = '|'.join(apps) +urlpatterns += [re_path(old_app_pattern, views.redirect_old_apps_view)] + handler404 = 'jumpserver.views.handler404' handler500 = 'jumpserver.views.handler500' diff --git a/apps/jumpserver/views/other.py b/apps/jumpserver/views/other.py index a1db94e77..17a7dfd6c 100644 --- a/apps/jumpserver/views/other.py +++ b/apps/jumpserver/views/other.py @@ -3,7 +3,7 @@ import re import time -from django.http import HttpResponseRedirect, JsonResponse +from django.http import HttpResponseRedirect, JsonResponse, Http404 from django.conf import settings from django.views.generic import View from django.utils.translation import ugettext_lazy as _ @@ -16,7 +16,7 @@ from common.http import HttpResponseTemporaryRedirect __all__ = [ 'LunaView', 'I18NView', 'KokoView', 'WsView', 'HealthCheckView', - 'redirect_format_api' + 'redirect_format_api', 'redirect_old_apps_view' ] @@ -51,6 +51,14 @@ def redirect_format_api(request, *args, **kwargs): return JsonResponse({"msg": "Redirect url failed: {}".format(_path)}, status=404) +def redirect_old_apps_view(request, *args, **kwargs): + path = request.get_full_path() + if path.find('/core') != -1: + raise Http404() + new_path = '/core{}'.format(path) + return HttpResponseTemporaryRedirect(new_path) + + class HealthCheckView(APIView): permission_classes = () From caf0d8593949da77315fed62d7ad8c71fdfdb954 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 28 May 2020 14:47:30 +0800 Subject: [PATCH 056/146] =?UTF-8?q?[feat]=20=E4=BF=AE=E6=94=B9remote=20app?= =?UTF-8?q?,=20database=20app=E7=9A=84=E8=BF=87=E6=BB=A4=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/api/remote_app.py | 2 +- apps/perms/api/user_database_app_permission.py | 4 ++-- apps/perms/api/user_remote_app_permission.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/applications/api/remote_app.py b/apps/applications/api/remote_app.py index 79beef8fc..4bd9109fb 100644 --- a/apps/applications/api/remote_app.py +++ b/apps/applications/api/remote_app.py @@ -15,7 +15,7 @@ __all__ = [ class RemoteAppViewSet(OrgBulkModelViewSet): model = RemoteApp - filter_fields = ('name',) + filter_fields = ('name', 'type', 'comment') search_fields = filter_fields permission_classes = (IsOrgAdmin,) serializer_class = RemoteAppSerializer diff --git a/apps/perms/api/user_database_app_permission.py b/apps/perms/api/user_database_app_permission.py index 3a973b8c1..19885d2ef 100644 --- a/apps/perms/api/user_database_app_permission.py +++ b/apps/perms/api/user_database_app_permission.py @@ -26,8 +26,8 @@ __all__ = [ class UserGrantedDatabaseAppsApi(generics.ListAPIView): permission_classes = (IsOrgAdminOrAppUser,) serializer_class = DatabaseAppSerializer - filter_fields = ['id', 'name'] - search_fields = ['name'] + filter_fields = ['id', 'name', 'type', 'comment'] + search_fields = ['name', 'comment'] def get_object(self): user_id = self.kwargs.get('pk', '') diff --git a/apps/perms/api/user_remote_app_permission.py b/apps/perms/api/user_remote_app_permission.py index 51a9217ce..8dca299a3 100644 --- a/apps/perms/api/user_remote_app_permission.py +++ b/apps/perms/api/user_remote_app_permission.py @@ -26,8 +26,8 @@ __all__ = [ class UserGrantedRemoteAppsApi(generics.ListAPIView): permission_classes = (IsOrgAdminOrAppUser,) serializer_class = RemoteAppSerializer - filter_fields = ['name', 'id'] - search_fields = ['name'] + filter_fields = ['name', 'id', 'type', 'comment'] + search_fields = ['name', 'comment'] def get_object(self): user_id = self.kwargs.get('pk', '') From f3bc6c0b227c72fd87fdd0c9df8d199a06f698be Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 28 May 2020 15:02:53 +0800 Subject: [PATCH 057/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7profile=E5=BA=8F=E5=88=97=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/api/profile.py | 2 +- apps/users/serializers/user.py | 40 ++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/apps/users/api/profile.py b/apps/users/api/profile.py index 5ad7c2a00..3f1f04e84 100644 --- a/apps/users/api/profile.py +++ b/apps/users/api/profile.py @@ -55,7 +55,7 @@ class UserUpdatePKApi(UserQuerysetMixin, generics.UpdateAPIView): user.save() -class UserProfileApi(generics.RetrieveAPIView): +class UserProfileApi(generics.RetrieveUpdateAPIView): permission_classes = (IsAuthenticated,) serializer_class = serializers.UserProfileSerializer diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index ef4c8a34f..e6e55a18b 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -191,10 +191,46 @@ class UserRoleSerializer(serializers.Serializer): class UserProfileSerializer(UserSerializer): admin_or_audit_orgs = UserOrgSerializer(many=True, read_only=True) - current_org_roles = serializers.ListField() + current_org_roles = serializers.ListField(read_only=True) + public_key_comment = serializers.SerializerMethodField() + public_key_hash_md5 = serializers.SerializerMethodField() class Meta(UserSerializer.Meta): fields = UserSerializer.Meta.fields + [ - 'admin_or_audit_orgs', 'current_org_roles' + 'public_key_comment', 'public_key_hash_md5', 'admin_or_audit_orgs', 'current_org_roles' ] + extra_kwargs = dict(UserSerializer.Meta.extra_kwargs) + extra_kwargs.update({ + 'name': {'read_only': True, 'max_length': 128}, + 'username': {'read_only': True, 'max_length': 128}, + 'email': {'read_only': True}, + 'mfa_level': {'read_only': True}, + 'source': {'read_only': True}, + 'is_valid': {'read_only': True}, + 'is_active': {'read_only': True}, + 'groups': {'read_only': True}, + 'roles': {'read_only': True}, + 'password_strategy': {'read_only': True}, + 'date_expired': {'read_only': True}, + 'date_joined': {'read_only': True}, + 'last_login': {'read_only': True}, + 'role': {'read_only': True}, + }) + if 'password' in fields: + fields.remove('password') + extra_kwargs.pop('password', None) + + if 'public_key' in fields: + fields.remove('public_key') + extra_kwargs.pop('public_key', None) + + @staticmethod + def get_public_key_comment(obj): + return obj.public_key_obj.comment + + @staticmethod + def get_public_key_hash_md5(obj): + if callable(obj.public_key_obj.hash_md5): + return obj.public_key_obj.hash_md5() + return '' From 3e5d94961070b114b0cb7e37c2559f56f46b457c Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 28 May 2020 16:10:28 +0800 Subject: [PATCH 058/146] =?UTF-8?q?[Update]=20=E6=B7=BB=E5=8A=A0=E7=94=A8?= =?UTF-8?q?=E6=88=B7profile=20password=E5=BA=8F=E5=88=97=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/api/profile.py | 22 ++++++++++++++++++ apps/users/serializers/user.py | 42 +++++++++++++++++++++++++++++++++- apps/users/urls/api_urls.py | 2 ++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/apps/users/api/profile.py b/apps/users/api/profile.py index 3f1f04e84..321ef54e1 100644 --- a/apps/users/api/profile.py +++ b/apps/users/api/profile.py @@ -14,6 +14,7 @@ from .mixins import UserQuerysetMixin __all__ = [ 'UserResetPasswordApi', 'UserResetPKApi', 'UserProfileApi', 'UserUpdatePKApi', + 'UserPasswordApi', 'UserPublicKeyApi' ] @@ -66,3 +67,24 @@ class UserProfileApi(generics.RetrieveUpdateAPIView): age = request.session.get_expiry_age() request.session.set_expiry(age) return super().retrieve(request, *args, **kwargs) + + +class UserPasswordApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsAuthenticated,) + serializer_class = serializers.UserUpdatePasswordSerializer + + def get_object(self): + return self.request.user + + +class UserPublicKeyApi(generics.RetrieveUpdateAPIView): + permission_classes = (IsAuthenticated,) + serializer_class = serializers.UserUpdatePublicKeySerializer + + def get_object(self): + return self.request.user + + def perform_update(self, serializer): + user = self.get_object() + user.public_key = serializer.validated_data['public_key'] + user.save() diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index e6e55a18b..20c412d32 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -14,7 +14,8 @@ from ..models import User __all__ = [ 'UserSerializer', 'UserPKUpdateSerializer', 'ChangeUserPasswordSerializer', 'ResetOTPSerializer', - 'UserProfileSerializer', 'UserOrgSerializer' + 'UserProfileSerializer', 'UserOrgSerializer', + 'UserUpdatePasswordSerializer', 'UserUpdatePublicKeySerializer' ] @@ -234,3 +235,42 @@ class UserProfileSerializer(UserSerializer): if callable(obj.public_key_obj.hash_md5): return obj.public_key_obj.hash_md5() return '' + + +class UserUpdatePasswordSerializer(serializers.ModelSerializer): + old_password = serializers.CharField(required=True, max_length=128, write_only=True) + new_password = serializers.CharField(required=True, max_length=128, write_only=True) + new_password_again = serializers.CharField(required=True, max_length=128, write_only=True) + + class Meta: + model = User + fields = ['old_password', 'new_password', 'new_password_again'] + + def validate_old_password(self, value): + if not self.instance.check_password(value): + msg = 'The old password is incorrect' + raise serializers.ValidationError(msg) + return value + + @staticmethod + def validate_new_password(value): + from ..utils import check_password_rules + if not check_password_rules(value): + msg = _('Password does not match security rules') + raise serializers.ValidationError(msg) + return value + + def validate_new_password_again(self, value): + if value != self.initial_data.get('new_password', ''): + msg = 'The newly set password is inconsistent' + raise serializers.ValidationError(msg) + return value + + def update(self, instance, validated_data): + new_password = self.validated_data.get('new_password') + instance.reset_password(new_password) + return instance + + +class UserUpdatePublicKeySerializer(serializers.ModelSerializer): + pass diff --git a/apps/users/urls/api_urls.py b/apps/users/urls/api_urls.py index b3a75f9ba..ea1101eae 100644 --- a/apps/users/urls/api_urls.py +++ b/apps/users/urls/api_urls.py @@ -21,6 +21,8 @@ urlpatterns = [ path('connection-token/', auth_api.UserConnectionTokenApi.as_view(), name='connection-token'), path('profile/', api.UserProfileApi.as_view(), name='user-profile'), + path('profile/password/', api.UserPasswordApi.as_view(), name='user-password'), + path('profile/public-key/', api.UserPublicKeyApi.as_view(), name='user-public-key'), path('otp/reset/', api.UserResetOTPApi.as_view(), name='my-otp-reset'), path('users//otp/reset/', api.UserResetOTPApi.as_view(), name='user-reset-otp'), path('users//password/', api.UserChangePasswordApi.as_view(), name='change-user-password'), From 7dde15cb044fabb41b7f35b9d72ac94fe5b0a47c Mon Sep 17 00:00:00 2001 From: xinwen Date: Thu, 28 May 2020 17:00:42 +0800 Subject: [PATCH 059/146] =?UTF-8?q?[Feature]=20=E6=9D=83=E9=99=90=E7=AE=A1?= =?UTF-8?q?=E7=90=86->=20=E8=BF=9C=E7=A8=8B=E5=BA=94=E7=94=A8=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20api=20(#4040)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Feature] 权限管理-> 远程应用 添加 api * [Feature] 添加 RelationMixin --- apps/common/{const.py => const/__init__.py} | 0 apps/common/const/http.py | 7 ++ apps/common/mixins/api.py | 48 ++++++++++- apps/orgs/mixins/api.py | 12 ++- apps/perms/api/__init__.py | 1 + apps/perms/api/base.py | 15 ++++ .../api/database_app_permission_relation.py | 30 +++---- apps/perms/api/remote_app_permission.py | 1 - .../api/remote_app_permission_relation.py | 79 +++++++++++++++++++ apps/perms/models/base.py | 17 ++++ apps/perms/models/database_app_permission.py | 9 +++ apps/perms/models/remote_app_permission.py | 4 + apps/perms/serializers/__init__.py | 2 + apps/perms/serializers/base.py | 13 +++ .../serializers/database_app_permission.py | 23 ++++-- .../database_app_permission_relation.py | 15 +--- .../serializers/remote_app_permission.py | 4 +- .../remote_app_permission_relation.py | 49 ++++++++++++ apps/perms/urls/remote_app_permission.py | 7 +- 19 files changed, 291 insertions(+), 45 deletions(-) rename apps/common/{const.py => const/__init__.py} (100%) create mode 100644 apps/common/const/http.py create mode 100644 apps/perms/api/base.py create mode 100644 apps/perms/api/remote_app_permission_relation.py create mode 100644 apps/perms/serializers/base.py create mode 100644 apps/perms/serializers/remote_app_permission_relation.py diff --git a/apps/common/const.py b/apps/common/const/__init__.py similarity index 100% rename from apps/common/const.py rename to apps/common/const/__init__.py diff --git a/apps/common/const/http.py b/apps/common/const/http.py new file mode 100644 index 000000000..4717d38c9 --- /dev/null +++ b/apps/common/const/http.py @@ -0,0 +1,7 @@ + +GET = 'GET' +POST = 'POST' +PUT = 'PUT' +PATCH = 'PATCH' +DELETE = 'DELETE' +OPTIONS = 'OPTIONS' diff --git a/apps/common/mixins/api.py b/apps/common/mixins/api.py index bbabe9951..0b7b5aed6 100644 --- a/apps/common/mixins/api.py +++ b/apps/common/mixins/api.py @@ -3,7 +3,9 @@ import time from hashlib import md5 from threading import Thread +from collections import defaultdict +from django.db.models.signals import m2m_changed from django.core.cache import cache from django.http import JsonResponse from rest_framework.response import Response @@ -14,7 +16,7 @@ from ..utils import lazyproperty __all__ = [ "JSONResponseMixin", "CommonApiMixin", - 'AsyncApiMixin', + 'AsyncApiMixin', 'RelationMixin' ] @@ -187,3 +189,47 @@ class AsyncApiMixin(InterceptMixin): data["error"] = str(e) data["status"] = "error" cache.set(key, data, 600) + + +class RelationMixin: + m2m_field = None + from_field = None + to_field = None + to_model = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + assert self.m2m_field is not None, ''' + `m2m_field` should not be `None` + ''' + + self.from_field = self.m2m_field.m2m_field_name() + self.to_field = self.m2m_field.m2m_reverse_field_name() + self.to_model = self.m2m_field.related_model + self.through = getattr(self.m2m_field.model, self.m2m_field.attname).through + + def get_queryset(self): + queryset = self.through.objects.all() + return queryset + + def send_post_add_signal(self, instances): + if not isinstance(instances, list): + instances = [instances] + + from_to_mapper = defaultdict(list) + + for i in instances: + to_id = getattr(i, self.to_field).id + from_obj = getattr(i, self.from_field) + from_to_mapper[from_obj].append(to_id) + + for from_obj, to_ids in from_to_mapper.items(): + m2m_changed.send( + sender=self.through, instance=from_obj, action='post_add', + reverse=False, model=self.to_model, pk_set=to_ids + ) + + def perform_create(self, serializer): + instance = serializer.save() + self.send_post_add_signal(instance) diff --git a/apps/orgs/mixins/api.py b/apps/orgs/mixins/api.py index f3f376694..2ad34831c 100644 --- a/apps/orgs/mixins/api.py +++ b/apps/orgs/mixins/api.py @@ -3,7 +3,8 @@ from django.shortcuts import get_object_or_404 from rest_framework.viewsets import ModelViewSet, GenericViewSet from rest_framework_bulk import BulkModelViewSet -from common.mixins import CommonApiMixin +from common.mixins import CommonApiMixin, RelationMixin +from orgs.utils import current_org from ..utils import set_to_root_org, filter_org_queryset from ..models import Organization @@ -80,3 +81,12 @@ class OrgMembershipModelViewSetMixin: def get_queryset(self): queryset = self.membership_class.objects.filter(organization=self.org) return queryset + + +class OrgRelationMixin(RelationMixin): + def get_queryset(self): + queryset = super().get_queryset() + org_id = current_org.org_id() + if org_id is not None: + queryset = queryset.filter(**{f'{self.from_field}__org_id': org_id}) + return queryset diff --git a/apps/perms/api/__init__.py b/apps/perms/api/__init__.py index 61cbd7d58..f12a8cc38 100644 --- a/apps/perms/api/__init__.py +++ b/apps/perms/api/__init__.py @@ -6,6 +6,7 @@ from .user_permission import * from .asset_permission_relation import * from .user_group_permission import * from .remote_app_permission import * +from .remote_app_permission_relation import * from .user_remote_app_permission import * from .database_app_permission import * from .database_app_permission_relation import * diff --git a/apps/perms/api/base.py b/apps/perms/api/base.py new file mode 100644 index 000000000..d4ffc9246 --- /dev/null +++ b/apps/perms/api/base.py @@ -0,0 +1,15 @@ +from django.db.models import F +from orgs.mixins.api import OrgBulkModelViewSet +from orgs.mixins.api import OrgRelationMixin + + +__all__ = [ + 'RelationViewSet' +] + + +class RelationViewSet(OrgRelationMixin, OrgBulkModelViewSet): + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.annotate(**{f'{self.from_field}_display': F(f'{self.from_field}__name')}) + return queryset diff --git a/apps/perms/api/database_app_permission_relation.py b/apps/perms/api/database_app_permission_relation.py index 32ab34355..887b723dd 100644 --- a/apps/perms/api/database_app_permission_relation.py +++ b/apps/perms/api/database_app_permission_relation.py @@ -1,14 +1,12 @@ # coding: utf-8 # - from rest_framework import generics from django.db.models import F, Value from django.db.models.functions import Concat from django.shortcuts import get_object_or_404 -from orgs.mixins.api import OrgBulkModelViewSet -from orgs.utils import current_org from common.permissions import IsOrgAdmin +from .base import RelationViewSet from .. import models, serializers __all__ = [ @@ -21,19 +19,9 @@ __all__ = [ ] -class RelationMixin(OrgBulkModelViewSet): - def get_queryset(self): - queryset = self.model.objects.all() - org_id = current_org.org_id() - if org_id is not None: - queryset = queryset.filter(databaseapppermission__org_id=org_id) - queryset = queryset.annotate(databaseapppermission_display=F('databaseapppermission__name')) - return queryset - - -class DatabaseAppPermissionUserRelationViewSet(RelationMixin): +class DatabaseAppPermissionUserRelationViewSet(RelationViewSet): serializer_class = serializers.DatabaseAppPermissionUserRelationSerializer - model = models.DatabaseAppPermission.users.through + m2m_field = models.DatabaseAppPermission.users.field permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', 'user', 'databaseapppermission' @@ -46,9 +34,9 @@ class DatabaseAppPermissionUserRelationViewSet(RelationMixin): return queryset -class DatabaseAppPermissionUserGroupRelationViewSet(RelationMixin): +class DatabaseAppPermissionUserGroupRelationViewSet(RelationViewSet): serializer_class = serializers.DatabaseAppPermissionUserGroupRelationSerializer - model = models.DatabaseAppPermission.user_groups.through + m2m_field = models.DatabaseAppPermission.user_groups.field permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', "usergroup", "databaseapppermission" @@ -77,9 +65,9 @@ class DatabaseAppPermissionAllUserListApi(generics.ListAPIView): return users -class DatabaseAppPermissionDatabaseAppRelationViewSet(RelationMixin): +class DatabaseAppPermissionDatabaseAppRelationViewSet(RelationViewSet): serializer_class = serializers.DatabaseAppPermissionDatabaseAppRelationSerializer - model = models.DatabaseAppPermission.database_apps.through + m2m_field = models.DatabaseAppPermission.database_apps.field permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', 'databaseapp', 'databaseapppermission', @@ -110,9 +98,9 @@ class DatabaseAppPermissionAllDatabaseAppListApi(generics.ListAPIView): return database_apps -class DatabaseAppPermissionSystemUserRelationViewSet(RelationMixin): +class DatabaseAppPermissionSystemUserRelationViewSet(RelationViewSet): serializer_class = serializers.DatabaseAppPermissionSystemUserRelationSerializer - model = models.DatabaseAppPermission.system_users.through + m2m_field = models.DatabaseAppPermission.system_users.field permission_classes = (IsOrgAdmin,) filterset_fields = [ 'id', 'systemuser', 'databaseapppermission' diff --git a/apps/perms/api/remote_app_permission.py b/apps/perms/api/remote_app_permission.py index 6ced7f0ae..cb1998675 100644 --- a/apps/perms/api/remote_app_permission.py +++ b/apps/perms/api/remote_app_permission.py @@ -13,7 +13,6 @@ from ..serializers import ( RemoteAppPermissionUpdateRemoteAppSerializer, ) - __all__ = [ 'RemoteAppPermissionViewSet', 'RemoteAppPermissionAddUserApi', 'RemoteAppPermissionAddRemoteAppApi', diff --git a/apps/perms/api/remote_app_permission_relation.py b/apps/perms/api/remote_app_permission_relation.py new file mode 100644 index 000000000..5cbb7dbf9 --- /dev/null +++ b/apps/perms/api/remote_app_permission_relation.py @@ -0,0 +1,79 @@ +# coding: utf-8 +# +from perms.api.base import RelationViewSet +from rest_framework import generics +from django.db.models import F +from django.shortcuts import get_object_or_404 + +from common.permissions import IsOrgAdmin +from .. import models, serializers + +__all__ = [ + 'RemoteAppPermissionUserRelationViewSet', + 'RemoteAppPermissionRemoteAppRelationViewSet', + 'RemoteAppPermissionAllRemoteAppListApi', + 'RemoteAppPermissionAllUserListApi', +] + + +class RemoteAppPermissionAllUserListApi(generics.ListAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = serializers.PermissionAllUserSerializer + filter_fields = ("username", "name") + search_fields = filter_fields + + def get_queryset(self): + pk = self.kwargs.get("pk") + perm = get_object_or_404(models.RemoteAppPermission, pk=pk) + users = perm.all_users.only( + *self.serializer_class.Meta.only_fields + ) + return users + + +class RemoteAppPermissionUserRelationViewSet(RelationViewSet): + serializer_class = serializers.RemoteAppPermissionUserRelationSerializer + m2m_field = models.RemoteAppPermission.users.field + permission_classes = (IsOrgAdmin,) + filterset_fields = [ + 'id', 'user', 'remoteapppermission' + ] + search_fields = ('user__name', 'user__username', 'remoteapppermission__name') + + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.annotate(user_display=F('user__name')) + return queryset + + +class RemoteAppPermissionRemoteAppRelationViewSet(RelationViewSet): + serializer_class = serializers.RemoteAppPermissionRemoteAppRelationSerializer + m2m_field = models.RemoteAppPermission.remote_apps.field + permission_classes = (IsOrgAdmin,) + filterset_fields = [ + 'id', 'remoteapp', 'remoteapppermission', + ] + search_fields = [ + "id", "remoteapp__name", "remoteapppermission__name" + ] + + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset \ + .annotate(remoteapp_display=F('remoteapp__name')) + return queryset + + +class RemoteAppPermissionAllRemoteAppListApi(generics.ListAPIView): + permission_classes = (IsOrgAdmin,) + serializer_class = serializers.RemoteAppPermissionAllRemoteAppSerializer + filter_fields = ("name",) + search_fields = filter_fields + + def get_queryset(self): + pk = self.kwargs.get("pk") + perm = get_object_or_404(models.RemoteAppPermission, pk=pk) + remote_apps = perm.all_remote_apps.only( + *self.serializer_class.Meta.only_fields + ) + return remote_apps diff --git a/apps/perms/models/base.py b/apps/perms/models/base.py index 2467a31b8..4ad52b2ce 100644 --- a/apps/perms/models/base.py +++ b/apps/perms/models/base.py @@ -79,6 +79,23 @@ class BasePermission(OrgModelMixin): return True return False + @property + def all_users(self): + from users.models import User + + users_query = self._meta.get_field('users').related_query_name() + user_groups_query = self._meta.get_field('user_groups').related_query_name() + + users_q = Q(**{ + f'{users_query}': self + }) + + user_groups_q = Q(**{ + f'groups__{user_groups_query}': self + }) + + return User.objects.filter(users_q | user_groups_q).distinct() + def get_all_users(self): from users.models import User users_id = self.users.all().values_list('id', flat=True) diff --git a/apps/perms/models/database_app_permission.py b/apps/perms/models/database_app_permission.py index de2693274..91b989128 100644 --- a/apps/perms/models/database_app_permission.py +++ b/apps/perms/models/database_app_permission.py @@ -4,6 +4,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ +from common.utils import lazyproperty from .base import BasePermission __all__ = [ @@ -28,3 +29,11 @@ class DatabaseAppPermission(BasePermission): def get_all_database_apps(self): return self.database_apps.all() + + @lazyproperty + def database_apps_amount(self): + return self.database_apps.count() + + @lazyproperty + def system_users_amount(self): + return self.system_users.count() diff --git a/apps/perms/models/remote_app_permission.py b/apps/perms/models/remote_app_permission.py index c9a7e4463..40114875c 100644 --- a/apps/perms/models/remote_app_permission.py +++ b/apps/perms/models/remote_app_permission.py @@ -23,6 +23,10 @@ class RemoteAppPermission(BasePermission): def get_all_remote_apps(self): return set(self.remote_apps.all()) + @property + def all_remote_apps(self): + return self.remote_apps.all() + @lazyproperty def remote_apps_amount(self): return self.remote_apps.count() diff --git a/apps/perms/serializers/__init__.py b/apps/perms/serializers/__init__.py index 7f83bae9b..7b8945827 100644 --- a/apps/perms/serializers/__init__.py +++ b/apps/perms/serializers/__init__.py @@ -4,6 +4,8 @@ from .asset_permission import * from .user_permission import * from .remote_app_permission import * +from .remote_app_permission_relation import * from .asset_permission_relation import * from .database_app_permission import * from .database_app_permission_relation import * +from .base import * diff --git a/apps/perms/serializers/base.py b/apps/perms/serializers/base.py new file mode 100644 index 000000000..33de4980b --- /dev/null +++ b/apps/perms/serializers/base.py @@ -0,0 +1,13 @@ +from rest_framework import serializers + + +class PermissionAllUserSerializer(serializers.Serializer): + user = serializers.UUIDField(read_only=True, source='id') + user_display = serializers.SerializerMethodField() + + class Meta: + only_fields = ['id', 'username', 'name'] + + @staticmethod + def get_user_display(obj): + return str(obj) diff --git a/apps/perms/serializers/database_app_permission.py b/apps/perms/serializers/database_app_permission.py index a8b8bafcd..a8813d8a6 100644 --- a/apps/perms/serializers/database_app_permission.py +++ b/apps/perms/serializers/database_app_permission.py @@ -1,6 +1,6 @@ # coding: utf-8 # - +from django.db.models import Count from rest_framework import serializers from common.fields import StringManyToManyField @@ -27,13 +27,22 @@ class DatabaseAppPermissionSerializer(BulkOrgResourceModelSerializer): class DatabaseAppPermissionListSerializer(BulkOrgResourceModelSerializer): - users = StringManyToManyField(many=True, read_only=True) - user_groups = StringManyToManyField(many=True, read_only=True) - database_apps = StringManyToManyField(many=True, read_only=True) - system_users = StringManyToManyField(many=True, read_only=True) - is_valid = serializers.BooleanField() is_expired = serializers.BooleanField() class Meta: model = models.DatabaseAppPermission - fields = '__all__' + fields = [ + 'id', 'name', 'comment', 'is_active', 'users_amount', 'user_groups_amount', + 'date_start', 'date_expired', 'is_valid', 'database_apps_amount', 'system_users_amount', + 'created_by', 'date_created', 'is_expired' + ] + + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.annotate( + users_amount=Count('users', distinct=True), user_groups_amount=Count('user_groups', distinct=True), + database_apps_amount=Count('database_apps', distinct=True), + system_users_amount=Count('system_users', distinct=True) + ) + return queryset diff --git a/apps/perms/serializers/database_app_permission_relation.py b/apps/perms/serializers/database_app_permission_relation.py index 1a8263cda..deb761853 100644 --- a/apps/perms/serializers/database_app_permission_relation.py +++ b/apps/perms/serializers/database_app_permission_relation.py @@ -1,8 +1,8 @@ # coding: utf-8 # +from perms.serializers.base import PermissionAllUserSerializer from rest_framework import serializers -from applications.models import DatabaseApp from common.mixins import BulkSerializerMixin from common.serializers import AdaptedBulkListSerializer @@ -50,16 +50,9 @@ class DatabaseAppPermissionUserGroupRelationSerializer(RelationMixin, serializer ] -class DatabaseAppPermissionAllUserSerializer(serializers.Serializer): - user = serializers.UUIDField(read_only=True, source='id') - user_display = serializers.SerializerMethodField() - - class Meta: - only_fields = ['id', 'username', 'name'] - - @staticmethod - def get_user_display(obj): - return str(obj) +class DatabaseAppPermissionAllUserSerializer(PermissionAllUserSerializer): + class Meta(PermissionAllUserSerializer.Meta): + pass class DatabaseAppPermissionDatabaseAppRelationSerializer(RelationMixin, serializers.ModelSerializer): diff --git a/apps/perms/serializers/remote_app_permission.py b/apps/perms/serializers/remote_app_permission.py index 2c347386c..a0bd7c410 100644 --- a/apps/perms/serializers/remote_app_permission.py +++ b/apps/perms/serializers/remote_app_permission.py @@ -36,8 +36,8 @@ class RemoteAppPermissionSerializer(BulkOrgResourceModelSerializer): def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ queryset = queryset.annotate( - users_amount=Count('users'), user_groups_amount=Count('user_groups'), - remote_apps_amount=Count('remote_apps'), system_users_amount=Count('system_users') + users_amount=Count('users', distinct=True), user_groups_amount=Count('user_groups', distinct=True), + remote_apps_amount=Count('remote_apps', distinct=True), system_users_amount=Count('system_users', distinct=True) ) return queryset diff --git a/apps/perms/serializers/remote_app_permission_relation.py b/apps/perms/serializers/remote_app_permission_relation.py new file mode 100644 index 000000000..05d06a9da --- /dev/null +++ b/apps/perms/serializers/remote_app_permission_relation.py @@ -0,0 +1,49 @@ +# coding: utf-8 +# +from rest_framework import serializers + +from common.serializers import AdaptedBulkListSerializer +from ..models import RemoteAppPermission + + +__all__ = [ + 'RemoteAppPermissionRemoteAppRelationSerializer', + 'RemoteAppPermissionAllRemoteAppSerializer', + 'RemoteAppPermissionUserRelationSerializer', +] + + +class RemoteAppPermissionRemoteAppRelationSerializer(serializers.ModelSerializer): + remoteapp_display = serializers.ReadOnlyField() + remoteapppermission_display = serializers.ReadOnlyField() + + class Meta: + model = RemoteAppPermission.remote_apps.through + list_serializer_class = AdaptedBulkListSerializer + fields = [ + 'id', 'remoteapp', 'remoteapp_display', 'remoteapppermission', 'remoteapppermission_display' + ] + + +class RemoteAppPermissionAllRemoteAppSerializer(serializers.Serializer): + remoteapp = serializers.UUIDField(read_only=True, source='id') + remoteapp_display = serializers.SerializerMethodField() + + class Meta: + only_fields = ['id', 'name'] + + @staticmethod + def get_remoteapp_display(obj): + return str(obj) + + +class RemoteAppPermissionUserRelationSerializer(serializers.ModelSerializer): + user_display = serializers.ReadOnlyField() + remoteapppermission_display = serializers.ReadOnlyField() + + class Meta: + model = RemoteAppPermission.users.through + list_serializer_class = AdaptedBulkListSerializer + fields = [ + 'id', 'user', 'user_display', 'remoteapppermission', 'remoteapppermission_display' + ] diff --git a/apps/perms/urls/remote_app_permission.py b/apps/perms/urls/remote_app_permission.py index 8f83d72d0..798ca9639 100644 --- a/apps/perms/urls/remote_app_permission.py +++ b/apps/perms/urls/remote_app_permission.py @@ -7,6 +7,9 @@ from .. import api router = BulkRouter() router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remote-app-permission') +router.register('remote-app-permissions-users-relations', api.RemoteAppPermissionUserRelationViewSet, 'remote-app-permissions-users-relation') +router.register('remote-app-permissions-remote-apps-relations', api.RemoteAppPermissionRemoteAppRelationViewSet, 'remote-app-permissions-remote-apps-relation') + remote_app_permission_urlpatterns = [ # 查询用户授权的RemoteApp @@ -32,7 +35,9 @@ remote_app_permission_urlpatterns = [ path('remote-app-permissions//users/remove/', api.RemoteAppPermissionRemoveUserApi.as_view(), name='remote-app-permission-remove-user'), path('remote-app-permissions//remote-apps/remove/', api.RemoteAppPermissionRemoveRemoteAppApi.as_view(), name='remote-app-permission-remove-remote-app'), path('remote-app-permissions//remote-apps/add/', api.RemoteAppPermissionAddRemoteAppApi.as_view(), name='remote-app-permission-add-remote-app'), + + path('remote-app-permissions//remote-apps/all/', api.RemoteAppPermissionAllRemoteAppListApi.as_view(), name='remote-app-permission-all-remote-apps'), + path('remote-app-permissions//users/all/', api.RemoteAppPermissionAllUserListApi.as_view(), name='remote-app-permission-all-users'), ] remote_app_permission_urlpatterns += router.urls - From 3b8a24eeb792f63d3545eddd84f620eafcedbe5b Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 28 May 2020 17:47:18 +0800 Subject: [PATCH 060/146] =?UTF-8?q?[Update]=20=E6=B7=BB=E5=8A=A0=E7=94=A8?= =?UTF-8?q?=E6=88=B7profile=20public-key=E5=BA=8F=E5=88=97=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/models/user.py | 12 ++++++++++ apps/users/serializers/user.py | 43 ++++++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index bb9d82f64..3e38cbb18 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -47,6 +47,10 @@ class AuthMixin: post_user_change_password.send(self.__class__, user=self) super().set_password(raw_password) + def set_public_key(self, public_key): + self.public_key = public_key + self.save() + def can_update_password(self): return self.is_local @@ -79,6 +83,14 @@ class AuthMixin: pass return PubKey() + def get_public_key_comment(self): + return self.public_key_obj.comment + + def get_public_key_hash_md5(self): + if not callable(self.public_key_obj.hash_md5): + return '' + return self.public_key_obj.hash_md5() + def reset_password(self, new_password): self.set_password(new_password) self.save() diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 20c412d32..437ec9302 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -193,8 +193,12 @@ class UserRoleSerializer(serializers.Serializer): class UserProfileSerializer(UserSerializer): admin_or_audit_orgs = UserOrgSerializer(many=True, read_only=True) current_org_roles = serializers.ListField(read_only=True) - public_key_comment = serializers.SerializerMethodField() - public_key_hash_md5 = serializers.SerializerMethodField() + public_key_comment = serializers.CharField( + source='get_public_key_comment', required=False, read_only=True, max_length=128 + ) + public_key_hash_md5 = serializers.CharField( + source='get_public_key_hash_md5', required=False, read_only=True, max_length=128 + ) class Meta(UserSerializer.Meta): fields = UserSerializer.Meta.fields + [ @@ -226,16 +230,6 @@ class UserProfileSerializer(UserSerializer): fields.remove('public_key') extra_kwargs.pop('public_key', None) - @staticmethod - def get_public_key_comment(obj): - return obj.public_key_obj.comment - - @staticmethod - def get_public_key_hash_md5(obj): - if callable(obj.public_key_obj.hash_md5): - return obj.public_key_obj.hash_md5() - return '' - class UserUpdatePasswordSerializer(serializers.ModelSerializer): old_password = serializers.CharField(required=True, max_length=128, write_only=True) @@ -273,4 +267,27 @@ class UserUpdatePasswordSerializer(serializers.ModelSerializer): class UserUpdatePublicKeySerializer(serializers.ModelSerializer): - pass + public_key_comment = serializers.CharField( + source='get_public_key_comment', required=False, read_only=True, max_length=128 + ) + public_key_hash_md5 = serializers.CharField( + source='get_public_key_hash_md5', required=False, read_only=True, max_length=128 + ) + + class Meta: + model = User + fields = ['public_key_comment', 'public_key_hash_md5', 'public_key'] + extra_kwargs = { + 'public_key': {'required': True, 'write_only': True, 'max_length': 2048} + } + + @staticmethod + def validate_public_key(value): + if not validate_ssh_public_key(value): + raise serializers.ValidationError(_('Not a valid ssh public key')) + return value + + def update(self, instance, validated_data): + new_public_key = self.validated_data.get('public_key') + instance.set_public_key(new_public_key) + return instance From f8142e23cdc4e6a29a12773c5df4b27904a3f8fc Mon Sep 17 00:00:00 2001 From: xinwen Date: Thu, 28 May 2020 19:08:48 +0800 Subject: [PATCH 061/146] =?UTF-8?q?[Fix]=20=E6=9D=83=E9=99=90=E7=AE=A1?= =?UTF-8?q?=E7=90=86->=20=E8=B5=84=E4=BA=A7=E4=B8=8E=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=20Bug=20(#4049)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/serializers/asset_permission.py | 6 +-- .../serializers/database_app_permission.py | 53 ++++++++++--------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/apps/perms/serializers/asset_permission.py b/apps/perms/serializers/asset_permission.py index d60ce28b9..a256a7a3c 100644 --- a/apps/perms/serializers/asset_permission.py +++ b/apps/perms/serializers/asset_permission.py @@ -57,8 +57,8 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer): def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ queryset = queryset.annotate( - users_amount=Count('users'), user_groups_amount=Count('user_groups'), - assets_amount=Count('assets'), nodes_amount=Count('nodes'), - system_users_amount=Count('system_users') + users_amount=Count('users', distinct=True), user_groups_amount=Count('user_groups', distinct=True), + assets_amount=Count('assets', distinct=True), nodes_amount=Count('nodes', distinct=True), + system_users_amount=Count('system_users', distinct=True) ) return queryset diff --git a/apps/perms/serializers/database_app_permission.py b/apps/perms/serializers/database_app_permission.py index a8813d8a6..0442a6122 100644 --- a/apps/perms/serializers/database_app_permission.py +++ b/apps/perms/serializers/database_app_permission.py @@ -13,30 +13,7 @@ __all__ = [ ] -class DatabaseAppPermissionSerializer(BulkOrgResourceModelSerializer): - class Meta: - model = models.DatabaseAppPermission - list_serializer_class = AdaptedBulkListSerializer - fields = [ - 'id', 'name', 'users', 'user_groups', - 'database_apps', 'system_users', 'comment', 'is_active', - 'date_start', 'date_expired', 'is_valid', - 'created_by', 'date_created' - ] - read_only_fields = ['created_by', 'date_created'] - - -class DatabaseAppPermissionListSerializer(BulkOrgResourceModelSerializer): - is_expired = serializers.BooleanField() - - class Meta: - model = models.DatabaseAppPermission - fields = [ - 'id', 'name', 'comment', 'is_active', 'users_amount', 'user_groups_amount', - 'date_start', 'date_expired', 'is_valid', 'database_apps_amount', 'system_users_amount', - 'created_by', 'date_created', 'is_expired' - ] - +class AmountMixin: @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ @@ -46,3 +23,31 @@ class DatabaseAppPermissionListSerializer(BulkOrgResourceModelSerializer): system_users_amount=Count('system_users', distinct=True) ) return queryset + + +class DatabaseAppPermissionSerializer(AmountMixin, BulkOrgResourceModelSerializer): + class Meta: + model = models.DatabaseAppPermission + list_serializer_class = AdaptedBulkListSerializer + fields = [ + 'id', 'name', 'users', 'user_groups', 'database_apps', 'system_users', + 'comment', 'is_active', 'date_start', 'date_expired', 'is_valid', + 'created_by', 'date_created', 'users_amount', 'user_groups_amount', + 'database_apps_amount', 'system_users_amount', + ] + read_only_fields = [ + 'created_by', 'date_created', 'users_amount', 'user_groups_amount', + 'database_apps_amount', 'system_users_amount', + ] + + +class DatabaseAppPermissionListSerializer(AmountMixin, BulkOrgResourceModelSerializer): + is_expired = serializers.BooleanField() + + class Meta: + model = models.DatabaseAppPermission + fields = [ + 'id', 'name', 'comment', 'is_active', 'users_amount', 'user_groups_amount', + 'date_start', 'date_expired', 'is_valid', 'database_apps_amount', 'system_users_amount', + 'created_by', 'date_created', 'is_expired' + ] From 1f4fc9b6f03a4c69cff96aac840710df5293490e Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 28 May 2020 20:46:17 +0800 Subject: [PATCH 062/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9API-Key?= =?UTF-8?q?=E5=BA=8F=E5=88=97=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/serializers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/authentication/serializers.py b/apps/authentication/serializers.py index f000c3438..25620fbd1 100644 --- a/apps/authentication/serializers.py +++ b/apps/authentication/serializers.py @@ -15,9 +15,11 @@ __all__ = [ class AccessKeySerializer(serializers.ModelSerializer): + id_display = serializers.UUIDField(source='id', read_only=True) + class Meta: model = AccessKey - fields = ['id', 'secret', 'is_active', 'date_created'] + fields = ['id', 'secret', 'is_active', 'date_created', 'id_display'] read_only_fields = ['id', 'secret', 'date_created'] From 5ee8519274ffbfad41c6e2a36638d27ff84a348b Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 29 May 2020 14:04:50 +0800 Subject: [PATCH 063/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9AccessKey?= =?UTF-8?q?=E5=BA=8F=E5=88=97=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/serializers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/authentication/serializers.py b/apps/authentication/serializers.py index 25620fbd1..f000c3438 100644 --- a/apps/authentication/serializers.py +++ b/apps/authentication/serializers.py @@ -15,11 +15,9 @@ __all__ = [ class AccessKeySerializer(serializers.ModelSerializer): - id_display = serializers.UUIDField(source='id', read_only=True) - class Meta: model = AccessKey - fields = ['id', 'secret', 'is_active', 'date_created', 'id_display'] + fields = ['id', 'secret', 'is_active', 'date_created'] read_only_fields = ['id', 'secret', 'date_created'] From 3a79bfd5f62dbedf03b285710aad2092bcc9f3b0 Mon Sep 17 00:00:00 2001 From: xinwen Date: Fri, 29 May 2020 15:18:25 +0800 Subject: [PATCH 064/146] =?UTF-8?q?[Update]=20=E6=B7=BB=E5=8A=A0=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=20i18n=20(#4052)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 90188 -> 89943 bytes apps/locale/zh/LC_MESSAGES/django.po | 296 +++++++++++++-------------- apps/ops/mixin.py | 2 +- 3 files changed, 144 insertions(+), 154 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index c571399ffa1ba0018dea6ccb0df9315e62d59fc1..6ea8518e0bfd89937321f69201137f40b0bd5009 100644 GIT binary patch delta 26549 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 YWahRfbEfSWlhUtJ>>ckj`_;|=f1s{DIRF3v delta 26768 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\n" "Language-Team: JumpServer team\n" @@ -53,7 +53,7 @@ msgstr "自定义" #: users/templates/users/user_asset_permission.html:70 #: users/templates/users/user_granted_remote_app.html:36 #: xpack/plugins/change_auth_plan/forms.py:74 -#: xpack/plugins/change_auth_plan/models.py:282 +#: xpack/plugins/change_auth_plan/models.py:274 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:40 #: 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 @@ -147,7 +147,7 @@ msgstr "运行参数" #: terminal/models.py:411 terminal/templates/terminal/base_storage_list.html:31 #: terminal/templates/terminal/terminal_detail.html:43 #: terminal/templates/terminal/terminal_list.html:30 users/forms/profile.py:20 -#: users/models/group.py:15 users/models/user.py:450 +#: users/models/group.py:15 users/models/user.py:462 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -170,9 +170,8 @@ msgstr "运行参数" #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:53 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:12 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:16 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:51 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:47 #: xpack/plugins/orgs/templates/orgs/org_list.html:12 -#: xpack/plugins/orgs/templates/orgs/org_users.html:46 msgid "Name" msgstr "名称" @@ -254,7 +253,7 @@ msgstr "数据库" #: terminal/models.py:418 terminal/templates/terminal/base_storage_list.html:33 #: terminal/templates/terminal/terminal_detail.html:63 #: tickets/templates/tickets/ticket_detail.html:104 users/models/group.py:16 -#: users/models/user.py:483 users/templates/users/user_detail.html:115 +#: users/models/user.py:495 users/templates/users/user_detail.html:115 #: users/templates/users/user_granted_database_app.html:38 #: users/templates/users/user_granted_remote_app.html:37 #: users/templates/users/user_group_detail.html:62 @@ -269,14 +268,14 @@ msgstr "数据库" #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:128 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18 #: xpack/plugins/gathered_user/models.py:26 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:63 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:59 #: xpack/plugins/orgs/templates/orgs/org_list.html:23 msgid "Comment" msgstr "备注" #: applications/models/database_app.py:41 #: perms/forms/database_app_permission.py:44 -#: perms/models/database_app_permission.py:17 +#: perms/models/database_app_permission.py:18 #: perms/templates/perms/database_app_permission_create_update.html:46 #: perms/templates/perms/database_app_permission_database_app.html:23 #: perms/templates/perms/database_app_permission_database_app.html:53 @@ -322,7 +321,7 @@ msgstr "参数" #: perms/templates/perms/asset_permission_detail.html:93 #: perms/templates/perms/database_app_permission_detail.html:89 #: perms/templates/perms/remote_app_permission_detail.html:85 -#: users/models/user.py:491 users/serializers/group.py:35 +#: users/models/user.py:503 users/serializers/group.py:35 #: users/templates/users/user_detail.html:97 #: xpack/plugins/change_auth_plan/models.py:80 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:111 @@ -356,7 +355,7 @@ msgstr "创建者" #: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:148 #: xpack/plugins/cloud/templates/cloud/account_detail.html:63 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:108 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:59 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:55 msgid "Date created" msgstr "创建日期" @@ -539,7 +538,7 @@ msgstr "详情" #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:26 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:60 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:46 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:24 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:20 #: xpack/plugins/orgs/templates/orgs/org_list.html:93 msgid "Update" msgstr "更新" @@ -591,7 +590,7 @@ msgstr "更新" #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:61 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:47 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:28 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:24 #: xpack/plugins/orgs/templates/orgs/org_list.html:95 msgid "Delete" msgstr "删除" @@ -651,7 +650,6 @@ msgstr "创建数据库应用" #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:19 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:20 #: xpack/plugins/orgs/templates/orgs/org_list.html:24 -#: xpack/plugins/orgs/templates/orgs/org_users.html:47 msgid "Action" msgstr "动作" @@ -679,7 +677,7 @@ msgstr "创建远程应用" msgid "Connect" msgstr "连接" -#: applications/views/database_app.py:26 users/models/user.py:144 +#: applications/views/database_app.py:26 users/models/user.py:156 msgid "Application" msgstr "应用程序" @@ -855,14 +853,14 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: perms/templates/perms/remote_app_permission_user.html:50 #: settings/templates/settings/_ldap_list_users_modal.html:31 #: settings/templates/settings/_ldap_test_user_login_modal.html:10 -#: users/forms/profile.py:19 users/models/user.py:448 +#: users/forms/profile.py:19 users/models/user.py:460 #: users/templates/users/_select_user_modal.html:14 #: users/templates/users/user_detail.html:53 #: users/templates/users/user_list.html:15 #: users/templates/users/user_profile.html:47 #: xpack/plugins/change_auth_plan/forms.py:59 #: xpack/plugins/change_auth_plan/models.py:46 -#: xpack/plugins/change_auth_plan/models.py:278 +#: xpack/plugins/change_auth_plan/models.py:270 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:63 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12 @@ -922,13 +920,13 @@ msgstr "密码或密钥密码" #: users/templates/users/user_update.html:20 #: xpack/plugins/change_auth_plan/models.py:67 #: xpack/plugins/change_auth_plan/models.py:190 -#: xpack/plugins/change_auth_plan/models.py:285 +#: xpack/plugins/change_auth_plan/models.py:277 msgid "Password" msgstr "密码" #: assets/forms/user.py:29 assets/serializers/asset_user.py:79 #: assets/templates/assets/_asset_user_auth_update_modal.html:27 -#: users/models/user.py:477 +#: users/models/user.py:489 msgid "Private key" msgstr "ssh私钥" @@ -1150,13 +1148,13 @@ msgstr "" #: assets/models/base.py:235 xpack/plugins/change_auth_plan/models.py:71 #: xpack/plugins/change_auth_plan/models.py:197 -#: xpack/plugins/change_auth_plan/models.py:292 +#: xpack/plugins/change_auth_plan/models.py:284 msgid "SSH private key" msgstr "ssh密钥" #: assets/models/base.py:236 xpack/plugins/change_auth_plan/models.py:74 #: xpack/plugins/change_auth_plan/models.py:193 -#: xpack/plugins/change_auth_plan/models.py:288 +#: xpack/plugins/change_auth_plan/models.py:280 msgid "SSH public key" msgstr "ssh公钥" @@ -1176,7 +1174,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:469 +#: assets/models/cluster.py:22 users/models/user.py:481 #: users/templates/users/user_detail.html:62 msgid "Phone" msgstr "手机" @@ -1202,7 +1200,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:610 +#: users/models/user.py:622 msgid "System" msgstr "系统" @@ -1335,7 +1333,7 @@ msgstr "默认资产组" #: tickets/models/ticket.py:128 tickets/templates/tickets/ticket_detail.html:32 #: tickets/templates/tickets/ticket_list.html:34 #: tickets/templates/tickets/ticket_list.html:103 users/forms/group.py:15 -#: users/models/user.py:143 users/models/user.py:159 users/models/user.py:598 +#: users/models/user.py:155 users/models/user.py:171 users/models/user.py:610 #: users/serializers/group.py:20 #: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:64 @@ -1347,6 +1345,7 @@ msgstr "默认资产组" #: users/templates/users/user_remote_app_permission.html:37 #: users/templates/users/user_remote_app_permission.html:58 #: users/views/profile/base.py:46 xpack/plugins/orgs/forms.py:27 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:108 #: xpack/plugins/orgs/templates/orgs/org_list.html:15 msgid "User" msgstr "用户" @@ -1456,7 +1455,7 @@ msgstr "SFTP根路径" #: audits/templates/audits/ftp_log_list.html:76 #: perms/forms/asset_permission.py:95 perms/forms/remote_app_permission.py:49 #: perms/models/asset_permission.py:82 -#: perms/models/database_app_permission.py:21 +#: perms/models/database_app_permission.py:22 #: perms/models/remote_app_permission.py:16 #: perms/templates/perms/asset_permission_asset.html:124 #: perms/templates/perms/asset_permission_list.html:37 @@ -1540,7 +1539,7 @@ msgid "Backend" msgstr "后端" #: assets/serializers/asset_user.py:75 users/forms/profile.py:148 -#: users/models/user.py:480 users/templates/users/first_login.html:42 +#: users/models/user.py:492 users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:49 #: users/templates/users/user_profile.html:69 #: users/templates/users/user_profile_update.html:46 @@ -2516,7 +2515,7 @@ msgstr "成功" #: terminal/models.py:199 terminal/templates/terminal/session_detail.html:72 #: terminal/templates/terminal/session_list.html:32 #: xpack/plugins/change_auth_plan/models.py:176 -#: xpack/plugins/change_auth_plan/models.py:307 +#: xpack/plugins/change_auth_plan/models.py:299 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17 #: xpack/plugins/gathered_user/models.py:76 @@ -2579,14 +2578,14 @@ msgstr "Agent" #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: authentication/templates/authentication/login_otp.html:6 #: settings/forms/security.py:16 users/forms/profile.py:52 -#: users/models/user.py:472 users/templates/users/first_login.html:45 +#: users/models/user.py:484 users/templates/users/first_login.html:45 #: users/templates/users/user_detail.html:77 #: users/templates/users/user_profile.html:87 msgid "MFA" msgstr "多因子认证" #: audits/models.py:87 audits/templates/audits/login_log_list.html:63 -#: xpack/plugins/change_auth_plan/models.py:303 +#: xpack/plugins/change_auth_plan/models.py:295 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15 #: xpack/plugins/cloud/models.py:217 msgid "Reason" @@ -2636,7 +2635,6 @@ msgstr "运行用户" #: perms/templates/perms/asset_permission_user.html:74 #: perms/templates/perms/database_app_permission_user.html:74 #: perms/templates/perms/remote_app_permission_user.html:83 -#: xpack/plugins/orgs/templates/orgs/org_users.html:67 msgid "Select user" msgstr "选择用户" @@ -2861,7 +2859,7 @@ msgid "Show" msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: users/models/user.py:370 users/templates/users/user_profile.html:94 +#: users/models/user.py:382 users/templates/users/user_profile.html:94 #: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:166 #: users/templates/users/user_verify_mfa.html:32 @@ -2869,7 +2867,7 @@ msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:371 users/templates/users/user_profile.html:92 +#: users/models/user.py:383 users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:170 msgid "Enable" msgstr "启用" @@ -2971,12 +2969,12 @@ msgstr "退出登录成功" msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: common/const.py:6 +#: common/const/__init__.py:6 #, python-format msgid "%(name)s was created successfully" msgstr "%(name)s 创建成功" -#: common/const.py:7 +#: common/const/__init__.py:7 #, python-format msgid "%(name)s was updated successfully" msgstr "%(name)s 更新成功" @@ -3054,11 +3052,11 @@ msgstr "" "
Luna是单独部署的一个程序,你需要部署luna,koko,
如果你看到了" "这个页面,证明你访问的不是nginx监听的端口,祝你好运
" -#: jumpserver/views/other.py:65 +#: jumpserver/views/other.py:73 msgid "Websocket server run on port: {}, you should proxy it on nginx" msgstr "Websocket 服务运行在端口: {}, 请检查nginx是否代理是否设置" -#: jumpserver/views/other.py:73 +#: jumpserver/views/other.py:81 msgid "" "
Koko is a separately deployed program, you need to deploy Koko, " "configure nginx for url distribution,
If you see this page, " @@ -3100,6 +3098,10 @@ msgstr "定期执行" msgid "Periodic perform" msgstr "定时执行" +#: ops/mixin.py:113 +msgid "Interval" +msgstr "间隔" + #: ops/mixin.py:122 msgid "* Please enter a valid crontab expression" msgstr "* 请输入有效的 crontab 表达式" @@ -3155,7 +3157,7 @@ msgstr "Become" #: ops/models/adhoc.py:150 users/templates/users/user_group_detail.html:54 #: xpack/plugins/cloud/templates/cloud/account_detail.html:59 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:55 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:51 msgid "Create by" msgstr "创建者" @@ -3178,7 +3180,7 @@ msgstr "完成时间" #: ops/models/adhoc.py:238 ops/templates/ops/adhoc_history.html:55 #: ops/templates/ops/task_history.html:61 ops/templates/ops/task_list.html:16 #: xpack/plugins/change_auth_plan/models.py:179 -#: xpack/plugins/change_auth_plan/models.py:310 +#: xpack/plugins/change_auth_plan/models.py:302 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16 #: xpack/plugins/gathered_user/models.py:79 @@ -3374,7 +3376,7 @@ msgid "Pending" msgstr "等待" #: ops/templates/ops/command_execution_list.html:70 -#: xpack/plugins/change_auth_plan/models.py:274 +#: xpack/plugins/change_auth_plan/models.py:266 msgid "Finished" msgstr "结束" @@ -3481,7 +3483,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件" #: perms/templates/perms/database_app_permission_list.html:16 #: perms/templates/perms/remote_app_permission_list.html:16 #: templates/_nav.html:21 users/forms/user.py:168 users/models/group.py:31 -#: users/models/user.py:456 users/templates/users/_select_user_modal.html:16 +#: users/models/user.py:468 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_asset_permission.html:39 #: users/templates/users/user_asset_permission.html:67 #: users/templates/users/user_database_app_permission.html:38 @@ -3534,12 +3536,12 @@ msgstr "资产授权" #: perms/templates/perms/asset_permission_detail.html:85 #: perms/templates/perms/database_app_permission_detail.html:81 #: perms/templates/perms/remote_app_permission_detail.html:77 -#: users/models/user.py:488 users/templates/users/user_detail.html:93 +#: users/models/user.py:500 users/templates/users/user_detail.html:93 #: users/templates/users/user_profile.html:120 msgid "Date expired" msgstr "失效日期" -#: perms/models/database_app_permission.py:26 +#: perms/models/database_app_permission.py:27 #: users/templates/users/_user_detail_nav_header.html:61 #: users/views/user.py:277 msgid "DatabaseApp permission" @@ -3590,9 +3592,8 @@ msgstr "添加资产" #: perms/templates/perms/remote_app_permission_user.html:120 #: users/templates/users/user_group_detail.html:87 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:76 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:89 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:123 -#: xpack/plugins/orgs/templates/orgs/org_users.html:73 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:88 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:125 msgid "Add" msgstr "添加" @@ -3691,7 +3692,6 @@ msgstr "刷新成功" #: perms/templates/perms/asset_permission_user.html:31 #: perms/templates/perms/database_app_permission_user.html:31 #: perms/templates/perms/remote_app_permission_user.html:30 -#: xpack/plugins/orgs/templates/orgs/org_users.html:24 msgid "User list of " msgstr "用户列表" @@ -4179,7 +4179,7 @@ msgid "Refresh cache" msgstr "刷新缓存" #: settings/templates/settings/_ldap_list_users_modal.html:33 -#: users/forms/profile.py:89 users/models/user.py:452 +#: users/forms/profile.py:89 users/models/user.py:464 #: users/templates/users/user_detail.html:57 #: users/templates/users/user_profile.html:59 msgid "Email" @@ -5397,11 +5397,11 @@ msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" #: users/forms/profile.py:137 users/forms/user.py:90 -#: users/serializers/user.py:166 +#: users/serializers/user.py:167 users/serializers/user.py:287 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" -#: users/forms/user.py:27 users/models/user.py:460 +#: users/forms/user.py:27 users/models/user.py:472 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:73 #: users/templates/users/user_list.html:16 @@ -5409,7 +5409,7 @@ msgstr "ssh密钥不合法" msgid "Role" msgstr "角色" -#: users/forms/user.py:31 users/models/user.py:495 +#: users/forms/user.py:31 users/models/user.py:507 #: users/templates/users/user_detail.html:89 #: users/templates/users/user_list.html:18 #: users/templates/users/user_profile.html:102 @@ -5429,15 +5429,15 @@ msgstr "添加到用户组" msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/forms/user.py:124 users/serializers/user.py:27 +#: users/forms/user.py:124 users/serializers/user.py:28 msgid "Reset link will be generated and sent to the user" msgstr "生成重置密码链接,通过邮件发送给用户" -#: users/forms/user.py:125 users/serializers/user.py:28 +#: users/forms/user.py:125 users/serializers/user.py:29 msgid "Set password" msgstr "设置密码" -#: users/forms/user.py:132 users/serializers/user.py:35 +#: users/forms/user.py:132 users/serializers/user.py:36 #: xpack/plugins/change_auth_plan/models.py:60 #: xpack/plugins/change_auth_plan/serializers.py:30 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:45 @@ -5447,45 +5447,44 @@ msgstr "设置密码" msgid "Password strategy" msgstr "密码策略" -#: users/models/user.py:142 users/models/user.py:606 +#: users/models/user.py:154 users/models/user.py:618 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:145 xpack/plugins/orgs/forms.py:29 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:109 +#: users/models/user.py:157 xpack/plugins/orgs/forms.py:29 #: xpack/plugins/orgs/templates/orgs/org_list.html:14 msgid "Auditor" msgstr "审计员" -#: users/models/user.py:155 +#: users/models/user.py:167 msgid "Org admin" msgstr "组织管理员" -#: users/models/user.py:157 +#: users/models/user.py:169 msgid "Org auditor" msgstr "组织审计员" -#: users/models/user.py:372 users/templates/users/user_profile.html:90 +#: users/models/user.py:384 users/templates/users/user_profile.html:90 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:439 +#: users/models/user.py:451 msgid "Local" msgstr "数据库" -#: users/models/user.py:463 +#: users/models/user.py:475 msgid "Avatar" msgstr "头像" -#: users/models/user.py:466 users/templates/users/user_detail.html:68 +#: users/models/user.py:478 users/templates/users/user_detail.html:68 msgid "Wechat" msgstr "微信" -#: users/models/user.py:499 +#: users/models/user.py:511 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:609 +#: users/models/user.py:621 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -5493,39 +5492,39 @@ msgstr "Administrator是初始的超级管理员" msgid "Auditors cannot be join in the user group" msgstr "审计员不能被加入到用户组" -#: users/serializers/user.py:66 +#: users/serializers/user.py:67 msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:67 +#: users/serializers/user.py:68 msgid "Is valid" msgstr "账户是否有效" -#: users/serializers/user.py:68 +#: users/serializers/user.py:69 msgid "Is expired" msgstr " 是否过期" -#: users/serializers/user.py:69 +#: users/serializers/user.py:70 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:73 +#: users/serializers/user.py:74 msgid "Groups name" msgstr "用户组名" -#: users/serializers/user.py:74 +#: users/serializers/user.py:75 msgid "Source name" msgstr "用户来源名" -#: users/serializers/user.py:75 +#: users/serializers/user.py:76 msgid "Role name" msgstr "角色名" -#: users/serializers/user.py:94 +#: users/serializers/user.py:95 msgid "Role limit to {}" msgstr "角色只能为 {}" -#: users/serializers/user.py:106 +#: users/serializers/user.py:107 users/serializers/user.py:253 msgid "Password does not match security rules" msgstr "密码不满足安全规则" @@ -5787,6 +5786,7 @@ msgid "User group detail" msgstr "用户组详情" #: users/templates/users/user_group_detail.html:81 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:116 msgid "Add user" msgstr "添加用户" @@ -6292,35 +6292,35 @@ msgid "Change auth plan snapshot" msgstr "改密计划快照" #: xpack/plugins/change_auth_plan/models.py:202 -#: xpack/plugins/change_auth_plan/models.py:296 +#: xpack/plugins/change_auth_plan/models.py:288 msgid "Change auth plan execution" msgstr "改密计划执行" -#: xpack/plugins/change_auth_plan/models.py:269 +#: xpack/plugins/change_auth_plan/models.py:261 msgid "Ready" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:270 +#: xpack/plugins/change_auth_plan/models.py:262 msgid "Preflight check" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:271 +#: xpack/plugins/change_auth_plan/models.py:263 msgid "Change auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:272 +#: xpack/plugins/change_auth_plan/models.py:264 msgid "Verify auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:273 +#: xpack/plugins/change_auth_plan/models.py:265 msgid "Keep auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:300 +#: xpack/plugins/change_auth_plan/models.py:292 msgid "Step" msgstr "步骤" -#: xpack/plugins/change_auth_plan/models.py:317 +#: xpack/plugins/change_auth_plan/models.py:309 msgid "Change auth plan task" msgstr "改密计划任务" @@ -6485,7 +6485,7 @@ msgstr "" msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:122 +#: xpack/plugins/cloud/models.py:122 xpack/plugins/cloud/serializers.py:55 msgid "Regions" msgstr "地域" @@ -6493,10 +6493,10 @@ msgstr "地域" msgid "Instances" msgstr "实例" -#: xpack/plugins/cloud/models.py:136 +#: xpack/plugins/cloud/models.py:136 xpack/plugins/cloud/serializers.py:77 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69 msgid "Covered always" -msgstr "" +msgstr "总是被覆盖" #: xpack/plugins/cloud/models.py:142 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:104 @@ -6543,11 +6543,11 @@ msgstr "同步实例任务历史" msgid "Instance" msgstr "实例" -#: xpack/plugins/cloud/providers/aliyun.py:19 +#: xpack/plugins/cloud/providers/aliyun.py:16 msgid "Alibaba Cloud" msgstr "阿里云" -#: xpack/plugins/cloud/providers/aws.py:15 +#: xpack/plugins/cloud/providers/aws.py:14 msgid "AWS (International)" msgstr "AWS (国际)" @@ -6555,63 +6555,53 @@ msgstr "AWS (国际)" msgid "AWS (China)" msgstr "AWS (中国)" -#: xpack/plugins/cloud/providers/huaweicloud.py:17 +#: xpack/plugins/cloud/providers/huaweicloud.py:13 msgid "Huawei Cloud" msgstr "华为云" +#: xpack/plugins/cloud/providers/huaweicloud.py:16 +msgid "CN North-Beijing4" +msgstr "华北-北京4" + +#: xpack/plugins/cloud/providers/huaweicloud.py:17 +msgid "CN East-Shanghai1" +msgstr "华东-上海1" + +#: xpack/plugins/cloud/providers/huaweicloud.py:18 +msgid "CN East-Shanghai2" +msgstr "华东-上海2" + +#: xpack/plugins/cloud/providers/huaweicloud.py:19 +msgid "CN South-Guangzhou" +msgstr "华南-广州" + #: xpack/plugins/cloud/providers/huaweicloud.py:20 -msgid "AF-Johannesburg" -msgstr "非洲-约翰内斯堡" +msgid "CN Southwest-Guiyang1" +msgstr "西南-贵阳1" #: xpack/plugins/cloud/providers/huaweicloud.py:21 -msgid "AP-Bangkok" -msgstr "亚太-曼谷" +#, fuzzy +#| msgid "AP-Hong Kong" +msgid "AP-Hong-Kong" +msgstr "亚太-香港" #: xpack/plugins/cloud/providers/huaweicloud.py:22 -msgid "AP-Hong Kong" -msgstr "亚太-香港" +msgid "AP-Bangkok" +msgstr "亚太-曼谷" #: xpack/plugins/cloud/providers/huaweicloud.py:23 msgid "AP-Singapore" msgstr "亚太-新加坡" #: xpack/plugins/cloud/providers/huaweicloud.py:24 -msgid "CN East-Shanghai1" -msgstr "华东-上海1" +msgid "AF-Johannesburg" +msgstr "非洲-约翰内斯堡" #: xpack/plugins/cloud/providers/huaweicloud.py:25 -msgid "CN East-Shanghai2" -msgstr "华东-上海2" - -#: xpack/plugins/cloud/providers/huaweicloud.py:26 -msgid "CN North-Beijing1" -msgstr "华北-北京1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:27 -msgid "CN North-Beijing4" -msgstr "华北-北京4" - -#: xpack/plugins/cloud/providers/huaweicloud.py:28 -msgid "CN Northeast-Dalian" -msgstr "华北-大连" - -#: xpack/plugins/cloud/providers/huaweicloud.py:29 -msgid "CN South-Guangzhou" -msgstr "华南-广州" - -#: xpack/plugins/cloud/providers/huaweicloud.py:30 -msgid "CN Southwest-Guiyang1" -msgstr "西南-贵阳1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:31 -msgid "EU-Paris" -msgstr "欧洲-巴黎" - -#: xpack/plugins/cloud/providers/huaweicloud.py:32 msgid "LA-Santiago" msgstr "拉美-圣地亚哥" -#: xpack/plugins/cloud/providers/qcloud.py:17 +#: xpack/plugins/cloud/providers/qcloud.py:14 msgid "Tencent Cloud" msgstr "腾讯云" @@ -6946,60 +6936,42 @@ msgid "Select auditor" msgstr "选择审计员" #: xpack/plugins/orgs/forms.py:28 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:75 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:71 #: xpack/plugins/orgs/templates/orgs/org_list.html:13 msgid "Admin" msgstr "管理员" -#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:27 -#: xpack/plugins/orgs/views.py:44 xpack/plugins/orgs/views.py:62 -#: xpack/plugins/orgs/views.py:85 xpack/plugins/orgs/views.py:116 +#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:26 +#: xpack/plugins/orgs/views.py:43 xpack/plugins/orgs/views.py:61 +#: xpack/plugins/orgs/views.py:79 msgid "Organizations" msgstr "组织管理" #: xpack/plugins/orgs/templates/orgs/org_detail.html:17 -#: xpack/plugins/orgs/templates/orgs/org_users.html:13 -#: xpack/plugins/orgs/views.py:86 +#: xpack/plugins/orgs/views.py:80 msgid "Org detail" msgstr "组织详情" -#: xpack/plugins/orgs/templates/orgs/org_detail.html:20 -#: xpack/plugins/orgs/templates/orgs/org_users.html:16 -msgid "Org users" -msgstr "组织用户" - -#: xpack/plugins/orgs/templates/orgs/org_detail.html:83 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:79 msgid "Add admin" msgstr "添加管理员" -#: xpack/plugins/orgs/templates/orgs/org_detail.html:117 -msgid "Add auditor" -msgstr "添加审计员" - #: xpack/plugins/orgs/templates/orgs/org_list.html:5 msgid "Create organization " msgstr "创建组织" -#: xpack/plugins/orgs/templates/orgs/org_users.html:59 -msgid "Add user to organization" -msgstr "添加用户" - -#: xpack/plugins/orgs/views.py:28 +#: xpack/plugins/orgs/views.py:27 msgid "Org list" msgstr "组织列表" -#: xpack/plugins/orgs/views.py:45 +#: xpack/plugins/orgs/views.py:44 msgid "Create org" msgstr "创建组织" -#: xpack/plugins/orgs/views.py:63 +#: xpack/plugins/orgs/views.py:62 msgid "Update org" msgstr "更新组织" -#: xpack/plugins/orgs/views.py:117 -msgid "Org user list" -msgstr "组织用户列表" - #: xpack/plugins/vault/meta.py:11 xpack/plugins/vault/views.py:23 #: xpack/plugins/vault/views.py:38 msgid "Vault" @@ -7021,6 +6993,27 @@ msgstr "密码匣子" msgid "vault create" msgstr "创建" +#~ msgid "CN North-Beijing1" +#~ msgstr "华北-北京1" + +#~ msgid "CN Northeast-Dalian" +#~ msgstr "华北-大连" + +#~ msgid "EU-Paris" +#~ msgstr "欧洲-巴黎" + +#~ msgid "Org users" +#~ msgstr "组织用户" + +#~ msgid "Add auditor" +#~ msgstr "添加审计员" + +#~ msgid "Add user to organization" +#~ msgstr "添加用户" + +#~ msgid "Org user list" +#~ msgstr "组织用户列表" + #~ msgid "Total hosts" #~ msgstr "主机总数" @@ -7033,9 +7026,6 @@ msgstr "创建" #~ msgid "Region & Instance" #~ msgstr "地域 & 实例" -#~ msgid "Interval" -#~ msgstr "间隔" - #~ msgid "Crontab" #~ msgstr "Crontab" diff --git a/apps/ops/mixin.py b/apps/ops/mixin.py index d3d397220..fd6f9cf27 100644 --- a/apps/ops/mixin.py +++ b/apps/ops/mixin.py @@ -110,7 +110,7 @@ class PeriodTaskSerializerMixin(serializers.Serializer): max_length=128, allow_blank=True, allow_null=True, required=False, label=_('Regularly perform') ) - interval = serializers.IntegerField(allow_null=True, required=False) + interval = serializers.IntegerField(allow_null=True, required=False, label=_('Interval')) INTERVAL_MAX = 65535 INTERVAL_MIN = 1 From ad1c17aa7bf90978e636490efb7c278d4c542854 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 1 Jun 2020 10:10:28 +0800 Subject: [PATCH 065/146] =?UTF-8?q?feat:=20=E4=BB=85=E6=94=AF=E6=8C=81fiel?= =?UTF-8?q?ds=5Fsize=3Dmini,small?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/mixins/serializers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/common/mixins/serializers.py b/apps/common/mixins/serializers.py index ed650c38e..5c3a243cf 100644 --- a/apps/common/mixins/serializers.py +++ b/apps/common/mixins/serializers.py @@ -194,6 +194,8 @@ class SizedModelFieldsMixin(BaseDynamicFieldsPlugin): size = query_params.get(self.arg_name) if not size: return [] + if size not in ['mini', 'small']: + return [] size_fields = getattr(self.serializer.Meta, 'fields_{}'.format(size), None) if not size_fields or not isinstance(size_fields, Iterable): return [] From b460e4abaa9f00617d62b551ba71fc1d6035083a Mon Sep 17 00:00:00 2001 From: xinwen Date: Tue, 2 Jun 2020 15:40:07 +0800 Subject: [PATCH 066/146] =?UTF-8?q?[Fix]=20=E6=97=A5=E5=BF=97=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=20=E6=97=A5=E6=9C=9F=E8=BF=87=E6=BB=A4=E4=B8=8E?= =?UTF-8?q?=E6=8E=92=E5=BA=8Fbug=20(#4063)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/api.py | 6 +++--- apps/audits/serializers.py | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/audits/api.py b/apps/audits/api.py index 7deee860d..c7a0dd957 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -63,7 +63,7 @@ class OperateLogViewSet(ListModelMixin, OrgGenericViewSet): ] filterset_fields = ['user', 'action', 'resource_type'] search_fields = ['filename'] - ordering_fields = ['-datetime'] + ordering = ['-datetime'] class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet): @@ -75,7 +75,7 @@ class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet): ('datetime', ('date_from', 'date_to')) ] filterset_fields = ['user'] - ordering_fields = ['-datetime'] + ordering = ['-datetime'] def get_queryset(self): users = current_org.get_org_members() @@ -94,4 +94,4 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): ('date_start', ('date_from', 'date_to')) ] search_fields = ['command'] - ordering_fields = ['-date_created'] + ordering = ['-date_created'] diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index a23efe534..192329e05 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -13,7 +13,7 @@ class FTPLogSerializer(serializers.ModelSerializer): class Meta: model = models.FTPLog fields = ( - 'user', 'remote_addr', 'asset', 'system_user', + 'id', 'user', 'remote_addr', 'asset', 'system_user', 'operate', 'filename', 'is_success', 'date_start' ) @@ -26,7 +26,7 @@ class UserLoginLogSerializer(serializers.ModelSerializer): class Meta: model = models.UserLoginLog fields = ( - 'username', 'type', 'type_display', 'ip', 'city', 'user_agent', + 'id', 'username', 'type', 'type_display', 'ip', 'city', 'user_agent', 'mfa', 'reason', 'status', 'status_display', 'datetime', 'mfa_display' ) @@ -35,7 +35,7 @@ class OperateLogSerializer(serializers.ModelSerializer): class Meta: model = models.OperateLog fields = ( - 'user', 'action', 'resource_type', 'resource', + 'id', 'user', 'action', 'resource_type', 'resource', 'remote_addr', 'datetime' ) @@ -44,7 +44,7 @@ class PasswordChangeLogSerializer(serializers.ModelSerializer): class Meta: model = models.PasswordChangeLog fields = ( - 'user', 'change_by', 'remote_addr', 'datetime' + 'id', 'user', 'change_by', 'remote_addr', 'datetime' ) @@ -58,7 +58,7 @@ class CommandExecutionSerializer(serializers.ModelSerializer): class Meta: model = CommandExecution fields = ( - 'hosts', 'run_as', 'command', 'user', 'is_finished', + 'id', 'hosts', 'run_as', 'command', 'user', 'is_finished', 'date_start', 'result', 'is_success' ) extra_kwargs = { From a4ece2b2713526fe4c22472211ae12a5b0684439 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 2 Jun 2020 15:41:27 +0800 Subject: [PATCH 067/146] =?UTF-8?q?feat:=20audits=E4=B8=AD=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0id=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/api.py | 8 ++++---- apps/audits/serializers.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/audits/api.py b/apps/audits/api.py index 7deee860d..2c10024c2 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -37,8 +37,8 @@ class UserLoginLogViewSet(ListModelMixin, date_range_filter_fields = [ ('datetime', ('date_from', 'date_to')) ] - filterset_fields = ['username'] - search_fields = ['ip', 'city', 'username'] + filter_fields = ['username', 'ip', 'city', 'type', 'status', 'mfa'] + search_fields =['username', 'ip', 'city'] @staticmethod def get_org_members(): @@ -61,8 +61,8 @@ class OperateLogViewSet(ListModelMixin, OrgGenericViewSet): date_range_filter_fields = [ ('datetime', ('date_from', 'date_to')) ] - filterset_fields = ['user', 'action', 'resource_type'] - search_fields = ['filename'] + filter_fields = ['user', 'action', 'resource_type', 'resource'] + search_fields = ['resource'] ordering_fields = ['-datetime'] diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index a23efe534..dbb072d0b 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -13,7 +13,7 @@ class FTPLogSerializer(serializers.ModelSerializer): class Meta: model = models.FTPLog fields = ( - 'user', 'remote_addr', 'asset', 'system_user', + 'id', 'user', 'remote_addr', 'asset', 'system_user', 'operate', 'filename', 'is_success', 'date_start' ) @@ -26,7 +26,7 @@ class UserLoginLogSerializer(serializers.ModelSerializer): class Meta: model = models.UserLoginLog fields = ( - 'username', 'type', 'type_display', 'ip', 'city', 'user_agent', + 'id', 'username', 'type', 'type_display', 'ip', 'city', 'user_agent', 'mfa', 'reason', 'status', 'status_display', 'datetime', 'mfa_display' ) @@ -35,7 +35,7 @@ class OperateLogSerializer(serializers.ModelSerializer): class Meta: model = models.OperateLog fields = ( - 'user', 'action', 'resource_type', 'resource', + 'id', 'user', 'action', 'resource_type', 'resource', 'remote_addr', 'datetime' ) @@ -44,7 +44,7 @@ class PasswordChangeLogSerializer(serializers.ModelSerializer): class Meta: model = models.PasswordChangeLog fields = ( - 'user', 'change_by', 'remote_addr', 'datetime' + 'id', 'user', 'change_by', 'remote_addr', 'datetime' ) @@ -58,7 +58,7 @@ class CommandExecutionSerializer(serializers.ModelSerializer): class Meta: model = CommandExecution fields = ( - 'hosts', 'run_as', 'command', 'user', 'is_finished', + 'id', 'hosts', 'run_as', 'command', 'user', 'is_finished', 'date_start', 'result', 'is_success' ) extra_kwargs = { From 9442acfb74d7eb32e99e4168942d5e437caeab91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Tue, 2 Jun 2020 02:44:45 -0500 Subject: [PATCH 068/146] Celery version (#4064) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 升级celery版本 * [Update] 修改redis-cache 版本 --- requirements/requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 741e66ed8..741313ae8 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -5,7 +5,7 @@ bcrypt==3.1.4 billiard==3.5.0.3 boto3==1.12.14 botocore==1.15.26 -celery==4.1.1 +celery==4.4.2 certifi==2018.1.18 cffi==1.13.2 chardet==3.0.4 @@ -21,7 +21,7 @@ django-celery-beat==1.4.0 django-filter==2.0.0 django-formtools==2.1 django-ranged-response==0.2.0 -django-redis-cache==1.7.1 +django-redis-cache==2.1.1 django-rest-swagger==2.1.2 django-simple-captcha==0.5.6 django-timezone-field==3.1 @@ -41,7 +41,7 @@ itsdangerous==0.24 itypes==1.1.0 Jinja2==2.10.1 jmespath==0.9.3 -kombu==4.2.1 +kombu==4.6.8 ldap3==2.4 MarkupSafe==1.1.1 mysqlclient==1.3.14 @@ -59,7 +59,7 @@ python-dateutil==2.6.1 python-gssapi==0.6.4 pytz==2018.3 PyYAML==5.1 -redis==2.10.6 +redis==3.2.0 requests==2.22.0 jms-storage==0.0.29 s3transfer==0.3.3 From c87b9f203f4922bcf2fdde7d0c907ed9bb6cb19c Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 2 Jun 2020 15:51:24 +0800 Subject: [PATCH 069/146] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E5=BA=93=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 741313ae8..64aa9d0dc 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,8 +1,8 @@ -amqp==2.1.4 +amqp==2.5.2 ansible==2.8.8 asn1crypto==0.24.0 bcrypt==3.1.4 -billiard==3.5.0.3 +billiard==3.6.3.0 boto3==1.12.14 botocore==1.15.26 celery==4.4.2 @@ -68,7 +68,7 @@ six==1.11.0 sshpubkeys==3.1.0 uritemplate==3.0.0 urllib3==1.25.2 -vine==1.1.4 +vine==1.3.0 drf-yasg==1.9.1 Werkzeug==0.15.3 drf-nested-routers==0.91 From 1a84661ca9ea56550ea5e5c45e70ff911b8957b4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 2 Jun 2020 20:02:22 +0800 Subject: [PATCH 070/146] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9filterset=5Ff?= =?UTF-8?q?ields=20=3D>=20filter=5Ffields=EF=BC=8Coption=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E4=B8=8D=E6=94=AF=E6=8C=81filterset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset.py | 2 +- apps/assets/api/system_user_relation.py | 6 +++--- apps/audits/api.py | 11 +++++------ apps/perms/api/asset_permission_relation.py | 10 +++++----- apps/perms/api/database_app_permission_relation.py | 8 ++++---- apps/perms/api/remote_app_permission_relation.py | 4 ++-- apps/terminal/api/session.py | 2 +- apps/terminal/serializers/session.py | 5 +++++ 8 files changed, 26 insertions(+), 22 deletions(-) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 0d7a42454..15b9ec75f 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -74,7 +74,7 @@ class AssetPlatformViewSet(ModelViewSet): queryset = Platform.objects.all() permission_classes = (IsSuperUser,) serializer_class = serializers.PlatformSerializer - filterset_fields = ['name', 'base'] + filter_fields = ['name', 'base'] search_fields = ['name'] def check_object_permissions(self, request, obj): diff --git a/apps/assets/api/system_user_relation.py b/apps/assets/api/system_user_relation.py index 69605b74a..bffa283ec 100644 --- a/apps/assets/api/system_user_relation.py +++ b/apps/assets/api/system_user_relation.py @@ -65,7 +65,7 @@ class SystemUserAssetRelationViewSet(BaseRelationViewSet): serializer_class = serializers.SystemUserAssetRelationSerializer model = models.SystemUser.assets.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'asset', 'systemuser', ] search_fields = [ @@ -91,7 +91,7 @@ class SystemUserNodeRelationViewSet(BaseRelationViewSet): serializer_class = serializers.SystemUserNodeRelationSerializer model = models.SystemUser.nodes.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'node', 'systemuser', ] search_fields = [ @@ -112,7 +112,7 @@ class SystemUserUserRelationViewSet(BaseRelationViewSet): serializer_class = serializers.SystemUserUserRelationSerializer model = models.SystemUser.users.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'user', 'systemuser', ] search_fields = [ diff --git a/apps/audits/api.py b/apps/audits/api.py index 564fdbb81..ecfc7f1d1 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -24,12 +24,11 @@ class FTPLogViewSet(ListModelMixin, OrgGenericViewSet): date_range_filter_fields = [ ('date_start', ('date_from', 'date_to')) ] - filter_fields = ['user', 'asset', 'system_user'] - search_fields = ['filename'] + filter_fields = ['user', 'asset', 'system_user', 'filename'] + search_fields = filter_fields -class UserLoginLogViewSet(ListModelMixin, - CommonGenericViewSet): +class UserLoginLogViewSet(ListModelMixin, CommonGenericViewSet): queryset = UserLoginLog.objects.all() permission_classes = [IsOrgAdmin | IsOrgAuditor] serializer_class = UserLoginLogSerializer @@ -61,7 +60,7 @@ class OperateLogViewSet(ListModelMixin, OrgGenericViewSet): date_range_filter_fields = [ ('datetime', ('date_from', 'date_to')) ] - filter_fields = ['user', 'action', 'resource_type', 'resource'] + filter_fields = ['user', 'action', 'resource_type', 'resource', 'remote_addr'] search_fields = ['resource'] ordering = ['-datetime'] @@ -74,7 +73,7 @@ class PasswordChangeLogViewSet(ListModelMixin, CommonGenericViewSet): date_range_filter_fields = [ ('datetime', ('date_from', 'date_to')) ] - filterset_fields = ['user'] + filter_fields = ['user', 'change_by', 'remote_addr'] ordering = ['-datetime'] def get_queryset(self): diff --git a/apps/perms/api/asset_permission_relation.py b/apps/perms/api/asset_permission_relation.py index 3b68af775..4207a995c 100644 --- a/apps/perms/api/asset_permission_relation.py +++ b/apps/perms/api/asset_permission_relation.py @@ -33,7 +33,7 @@ class AssetPermissionUserRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionUserRelationSerializer model = models.AssetPermission.users.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', "user", "assetpermission", ] search_fields = ("user__name", "user__username", "assetpermission__name") @@ -64,7 +64,7 @@ class AssetPermissionUserGroupRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionUserGroupRelationSerializer model = models.AssetPermission.user_groups.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', "usergroup", "assetpermission" ] search_fields = ["usergroup__name", "assetpermission__name"] @@ -80,7 +80,7 @@ class AssetPermissionAssetRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionAssetRelationSerializer model = models.AssetPermission.assets.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'asset', 'assetpermission', ] search_fields = ["id", "asset__hostname", "asset__ip", "assetpermission__name"] @@ -111,7 +111,7 @@ class AssetPermissionNodeRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionNodeRelationSerializer model = models.AssetPermission.nodes.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'node', 'assetpermission', ] search_fields = ["node__value", "assetpermission__name"] @@ -127,7 +127,7 @@ class AssetPermissionSystemUserRelationViewSet(RelationMixin): serializer_class = serializers.AssetPermissionSystemUserRelationSerializer model = models.AssetPermission.system_users.through permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'systemuser', 'assetpermission', ] search_fields = [ diff --git a/apps/perms/api/database_app_permission_relation.py b/apps/perms/api/database_app_permission_relation.py index 887b723dd..572896e34 100644 --- a/apps/perms/api/database_app_permission_relation.py +++ b/apps/perms/api/database_app_permission_relation.py @@ -23,7 +23,7 @@ class DatabaseAppPermissionUserRelationViewSet(RelationViewSet): serializer_class = serializers.DatabaseAppPermissionUserRelationSerializer m2m_field = models.DatabaseAppPermission.users.field permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'user', 'databaseapppermission' ] search_fields = ('user__name', 'user__username', 'databaseapppermission__name') @@ -38,7 +38,7 @@ class DatabaseAppPermissionUserGroupRelationViewSet(RelationViewSet): serializer_class = serializers.DatabaseAppPermissionUserGroupRelationSerializer m2m_field = models.DatabaseAppPermission.user_groups.field permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', "usergroup", "databaseapppermission" ] search_fields = ["usergroup__name", "databaseapppermission__name"] @@ -69,7 +69,7 @@ class DatabaseAppPermissionDatabaseAppRelationViewSet(RelationViewSet): serializer_class = serializers.DatabaseAppPermissionDatabaseAppRelationSerializer m2m_field = models.DatabaseAppPermission.database_apps.field permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'databaseapp', 'databaseapppermission', ] search_fields = [ @@ -102,7 +102,7 @@ class DatabaseAppPermissionSystemUserRelationViewSet(RelationViewSet): serializer_class = serializers.DatabaseAppPermissionSystemUserRelationSerializer m2m_field = models.DatabaseAppPermission.system_users.field permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'systemuser', 'databaseapppermission' ] search_fields = [ diff --git a/apps/perms/api/remote_app_permission_relation.py b/apps/perms/api/remote_app_permission_relation.py index 5cbb7dbf9..fe0ab7355 100644 --- a/apps/perms/api/remote_app_permission_relation.py +++ b/apps/perms/api/remote_app_permission_relation.py @@ -35,7 +35,7 @@ class RemoteAppPermissionUserRelationViewSet(RelationViewSet): serializer_class = serializers.RemoteAppPermissionUserRelationSerializer m2m_field = models.RemoteAppPermission.users.field permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'user', 'remoteapppermission' ] search_fields = ('user__name', 'user__username', 'remoteapppermission__name') @@ -50,7 +50,7 @@ class RemoteAppPermissionRemoteAppRelationViewSet(RelationViewSet): serializer_class = serializers.RemoteAppPermissionRemoteAppRelationSerializer m2m_field = models.RemoteAppPermission.remote_apps.field permission_classes = (IsOrgAdmin,) - filterset_fields = [ + filter_fields = [ 'id', 'remoteapp', 'remoteapppermission', ] search_fields = [ diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index d2365bef0..8cbf4daa8 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -32,7 +32,7 @@ class SessionViewSet(OrgBulkModelViewSet): 'display': serializers.SessionDisplaySerializer, } permission_classes = (IsOrgAdminOrAppUser, ) - filterset_fields = [ + filter_fields = [ "user", "asset", "system_user", "remote_addr", "protocol", "terminal", "is_finished", ] diff --git a/apps/terminal/serializers/session.py b/apps/terminal/serializers/session.py index bed6525b5..65f933351 100644 --- a/apps/terminal/serializers/session.py +++ b/apps/terminal/serializers/session.py @@ -1,5 +1,6 @@ from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.serializers import AdaptedBulkListSerializer from ..models import Session @@ -24,6 +25,10 @@ class SessionSerializer(BulkOrgResourceModelSerializer): "can_join", "protocol", "date_start", "date_end", "terminal", ] + extra_kwargs = { + "protocol": {'label': _('Protocol')}, + 'is_finished': {'label': _('Is finished')} + } class SessionDisplaySerializer(SessionSerializer): From b1f5cc7728a9cf7c0e5484eb63fa927f7a948af0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 3 Jun 2020 10:38:44 +0800 Subject: [PATCH 071/146] =?UTF-8?q?[Update]=20=E7=A6=81=E7=94=A8view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/urls.py | 14 ++++++++++---- apps/jumpserver/views/index.py | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index a43a9ea6b..bb1782a26 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -1,5 +1,6 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals +import os from django.urls import path, include, re_path from django.conf import settings @@ -30,8 +31,7 @@ api_v2 = [ path('users/', include('users.urls.api_urls_v2', namespace='api-users-v2')), ] - -app_view_patterns = [ +disabled_view_pattern = [ path('users/', include('users.urls.views_urls', namespace='users')), path('assets/', include('assets.urls.views_urls', namespace='assets')), path('perms/', include('perms.urls.views_urls', namespace='perms')), @@ -39,7 +39,6 @@ app_view_patterns = [ path('ops/', include('ops.urls.view_urls', namespace='ops')), 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')), path('tickets/', include('tickets.urls.views_urls', namespace='tickets')), re_path(r'flower/(?P.*)', views.celery_flower_view, name='flower-view'), @@ -51,6 +50,14 @@ app_view_patterns = [ ] +app_view_patterns = [ + path('auth/', include('authentication.urls.view_urls'), name='auth'), +] + +if os.environ.get('ENABLE_OLD_VIEW'): + app_view_patterns += disabled_view_pattern + + if settings.XPACK_ENABLED: app_view_patterns.append( path('xpack/', include('xpack.urls.view_urls', namespace='xpack')) @@ -68,7 +75,6 @@ apps = [ 'users', 'assets', 'perms', 'terminal', 'ops', 'audits', 'orgs', 'auth', 'applications', 'tickets', 'settings', 'xpack' 'flower', 'luna', 'koko', 'ws', 'i18n', 'jsi18n', 'docs', 'redocs', - 'zh-hans' ] diff --git a/apps/jumpserver/views/index.py b/apps/jumpserver/views/index.py index ffcb10493..19380466f 100644 --- a/apps/jumpserver/views/index.py +++ b/apps/jumpserver/views/index.py @@ -10,6 +10,9 @@ class IndexView(PermissionsMixin, TemplateView): template_name = 'index.html' permission_classes = [IsValidUser] + def get(self, request, *args, **kwargs): + return redirect('/ui/') + def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return self.handle_no_permission() From 96551856a248316955c61e122c603296b958d825 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 3 Jun 2020 11:06:44 +0800 Subject: [PATCH 072/146] =?UTF-8?q?[Update]=20=E9=83=A8=E5=88=86view?= =?UTF-8?q?=E6=94=BE=E5=88=B0auth=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/authentication/login.html | 2 +- .../templates/authentication/xpack_login.html | 2 +- apps/authentication/urls/view_urls.py | 19 +++++++++++++++++++ apps/jumpserver/urls.py | 3 ++- apps/jumpserver/views/other.py | 8 +++++++- apps/users/models/user.py | 2 +- .../users/user_otp_enable_install_app.html | 2 +- apps/users/templates/users/user_profile.html | 6 +++--- apps/users/utils.py | 10 +++++----- apps/users/views/login.py | 4 ++-- apps/users/views/profile/otp.py | 10 +++++----- 11 files changed, 47 insertions(+), 21 deletions(-) diff --git a/apps/authentication/templates/authentication/login.html b/apps/authentication/templates/authentication/login.html index 9cacdf4ff..8812f582b 100644 --- a/apps/authentication/templates/authentication/login.html +++ b/apps/authentication/templates/authentication/login.html @@ -46,7 +46,7 @@
diff --git a/apps/authentication/templates/authentication/xpack_login.html b/apps/authentication/templates/authentication/xpack_login.html index ba3f92308..8c1cb24f8 100644 --- a/apps/authentication/templates/authentication/xpack_login.html +++ b/apps/authentication/templates/authentication/xpack_login.html @@ -112,7 +112,7 @@
diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py index 62283f1f5..6972ae9cd 100644 --- a/apps/authentication/urls/view_urls.py +++ b/apps/authentication/urls/view_urls.py @@ -4,6 +4,7 @@ from django.urls import path, include from .. import views +from users import views as users_view app_name = 'authentication' @@ -15,6 +16,24 @@ urlpatterns = [ path('login/guard/', views.UserLoginGuardView.as_view(), name='login-guard'), path('logout/', views.UserLogoutView.as_view(), name='logout'), + # 原来在users中的 + path('password/forgot/', users_view.UserForgotPasswordView.as_view(), name='forgot-password'), + path('password/forgot/sendmail-success/', users_view.UserForgotPasswordSendmailSuccessView.as_view(), + name='forgot-password-sendmail-success'), + path('password/reset/', users_view.UserResetPasswordView.as_view(), name='reset-password'), + path('password/reset/success/', users_view.UserResetPasswordSuccessView.as_view(), name='reset-password-success'), + path('password/verify/', users_view.UserVerifyPasswordView.as_view(), name='user-verify-password'), + + # Profile + path('profile/otp/enable/start/', users_view.UserOtpEnableStartView.as_view(), name='user-otp-enable-start'), + path('profile/otp/enable/install-app/', users_view.UserOtpEnableInstallAppView.as_view(), + name='user-otp-enable-install-app'), + path('profile/otp/enable/bind/', users_view.UserOtpEnableBindView.as_view(), name='user-otp-enable-bind'), + path('profile/otp/disable/authentication/', users_view.UserDisableMFAView.as_view(), + name='user-otp-disable-authentication'), + path('profile/otp/update/', users_view.UserOtpUpdateView.as_view(), name='user-otp-update'), + path('profile/otp/settings-success/', users_view.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'), + # openid path('cas/', include(('authentication.backends.cas.urls', 'authentication'), namespace='cas')), path('openid/', include(('jms_oidc_rp.urls', 'authentication'), namespace='openid')), diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index bb1782a26..14379b924 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -67,7 +67,7 @@ if settings.XPACK_ENABLED: ) js_i18n_patterns = i18n_patterns( - path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'), + # path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'), ) @@ -87,6 +87,7 @@ urlpatterns = [ # External apps url path('core/auth/captcha/', include('captcha.urls')), path('core/', include(app_view_patterns)), + path('ui/', views.UIView.as_view()) ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ diff --git a/apps/jumpserver/views/other.py b/apps/jumpserver/views/other.py index 17a7dfd6c..ca58f70ac 100644 --- a/apps/jumpserver/views/other.py +++ b/apps/jumpserver/views/other.py @@ -16,7 +16,7 @@ from common.http import HttpResponseTemporaryRedirect __all__ = [ 'LunaView', 'I18NView', 'KokoView', 'WsView', 'HealthCheckView', - 'redirect_format_api', 'redirect_old_apps_view' + 'redirect_format_api', 'redirect_old_apps_view', 'UIView' ] @@ -75,6 +75,12 @@ class WsView(APIView): return JsonResponse({"msg": msg}) +class UIView(View): + def get(self, request): + msg = "如果你能看到这个页面,证明你的配置是有问题的,请参考文档设置好nginx, UI由Lina项目提供" + return HttpResponse(msg) + + class KokoView(View): def get(self, request): msg = _( diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 3e38cbb18..473f62197 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -437,7 +437,7 @@ class MFAMixin: if not self.mfa_enabled: return False, None if self.mfa_is_otp() and not self.otp_secret_key: - return True, reverse('users:user-otp-enable-start') + return True, reverse('authentication:user-otp-enable-start') return False, None diff --git a/apps/users/templates/users/user_otp_enable_install_app.html b/apps/users/templates/users/user_otp_enable_install_app.html index e3462cd3a..37b3e612b 100644 --- a/apps/users/templates/users/user_otp_enable_install_app.html +++ b/apps/users/templates/users/user_otp_enable_install_app.html @@ -26,7 +26,7 @@

{% trans 'After installation, click the next step to enter the binding page (if installed, go to the next step directly).' %}

- + -{% endblock %} \ No newline at end of file diff --git a/apps/applications/templates/applications/database_app_detail.html b/apps/applications/templates/applications/database_app_detail.html deleted file mode 100644 index 153bfa98a..000000000 --- a/apps/applications/templates/applications/database_app_detail.html +++ /dev/null @@ -1,103 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ database_app.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ database_app.name }}
{% trans 'Type' %}:{{ database_app.get_type_display }}
{% trans 'Host' %}:{{ database_app.host }}
{% trans 'Port' %}:{{ database_app.port }}
{% trans 'Database' %}:{{ database_app.database }}
{% trans 'Date created' %}:{{ database_app.date_created }}
{% trans 'Created by' %}:{{ database_app.created_by }}
{% trans 'Comment' %}:{{ database_app.comment }}
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/applications/templates/applications/database_app_list.html b/apps/applications/templates/applications/database_app_list.html deleted file mode 100644 index 74a5c907e..000000000 --- a/apps/applications/templates/applications/database_app_list.html +++ /dev/null @@ -1,88 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block help_message %} -{% endblock %} -{% block table_search %}{% endblock %} -{% block table_container %} -
- - -
- - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Type' %}{% trans 'Host' %}{% trans 'Port' %}{% trans 'Database' %}{% trans 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/applications/templates/applications/remote_app_create_update.html b/apps/applications/templates/applications/remote_app_create_update.html deleted file mode 100644 index 440219936..000000000 --- a/apps/applications/templates/applications/remote_app_create_update.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends '_base_create_update.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} - -{% block form %} -
- {% bootstrap_form form 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 deleted file mode 100644 index da7c2e72d..000000000 --- a/apps/applications/templates/applications/remote_app_detail.html +++ /dev/null @@ -1,100 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ remote_app.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ remote_app.name }}
{% trans 'Asset' %}:{{ remote_app.asset.hostname }}
{% 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 %} diff --git a/apps/applications/templates/applications/remote_app_list.html b/apps/applications/templates/applications/remote_app_list.html deleted file mode 100644 index 709152489..000000000 --- a/apps/applications/templates/applications/remote_app_list.html +++ /dev/null @@ -1,92 +0,0 @@ -{% 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 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/applications/templates/applications/user_database_app_list.html b/apps/applications/templates/applications/user_database_app_list.html deleted file mode 100644 index 1edaacd76..000000000 --- a/apps/applications/templates/applications/user_database_app_list.html +++ /dev/null @@ -1,83 +0,0 @@ -{% extends 'base.html' %} -{% load i18n static %} - -{% block custom_head_css_js %} - -{% endblock %} - -{% block content %} -
- - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Type' %}{% trans 'Host' %}{% trans 'Database' %}{% trans 'Comment' %}{% trans 'Action' %}
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/applications/templates/applications/user_remote_app_list.html b/apps/applications/templates/applications/user_remote_app_list.html deleted file mode 100644 index 576c7ed14..000000000 --- a/apps/applications/templates/applications/user_remote_app_list.html +++ /dev/null @@ -1,73 +0,0 @@ -{% extends 'base.html' %} -{% load i18n static %} - -{% block custom_head_css_js %} - -{% endblock %} - -{% block content %} -
- - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'App type' %}{% trans 'Asset' %}{% trans 'Comment' %}{% trans 'Action' %}
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/applications/urls/views_urls.py b/apps/applications/urls/views_urls.py index 663b0878d..2b09baf9f 100644 --- a/apps/applications/urls/views_urls.py +++ b/apps/applications/urls/views_urls.py @@ -1,23 +1,7 @@ # 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'), - - path('database-app/', views.DatabaseAppListView.as_view(), name='database-app-list'), - path('database-app/create/', views.DatabaseAppCreateView.as_view(), name='database-app-create'), - path('database-app//update/', views.DatabaseAppUpdateView.as_view(), name='database-app-update'), - path('database-app//', views.DatabaseAppDetailView.as_view(), name='database-app-detail'), - # User DatabaseApp view - path('user-database-app/', views.UserDatabaseAppListView.as_view(), name='user-database-app-list'), - ] diff --git a/apps/applications/views/__init__.py b/apps/applications/views/__init__.py deleted file mode 100644 index a707cfde6..000000000 --- a/apps/applications/views/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .remote_app import * -from .database_app import * diff --git a/apps/applications/views/database_app.py b/apps/applications/views/database_app.py deleted file mode 100644 index 21d2b4f7c..000000000 --- a/apps/applications/views/database_app.py +++ /dev/null @@ -1,115 +0,0 @@ -# coding: utf-8 -# - -from django.http import Http404 -from django.views.generic import TemplateView -from django.views.generic.edit import CreateView, UpdateView -from django.utils.translation import ugettext_lazy as _ -from django.views.generic.detail import DetailView - -from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser - -from .. import models, const, forms - -__all__ = [ - 'DatabaseAppListView', 'DatabaseAppCreateView', 'DatabaseAppUpdateView', - 'DatabaseAppDetailView', 'UserDatabaseAppListView', -] - - -class DatabaseAppListView(PermissionsMixin, TemplateView): - template_name = 'applications/database_app_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _("Application"), - 'action': _('DatabaseApp list'), - 'type_choices': const.DATABASE_APP_TYPE_CHOICES - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class BaseDatabaseAppCreateUpdateView: - template_name = 'applications/database_app_create_update.html' - model = models.DatabaseApp - permission_classes = [IsOrgAdmin] - default_type = const.DATABASE_APP_TYPE_MYSQL - form_class = forms.DatabaseAppMySQLForm - form_class_choices = { - const.DATABASE_APP_TYPE_MYSQL: forms.DatabaseAppMySQLForm, - } - - def get_initial(self): - return {'type': self.get_type()} - - def get_type(self): - return self.default_type - - def get_form_class(self): - tp = self.get_type() - form_class = self.form_class_choices.get(tp) - if not form_class: - raise Http404() - return form_class - - -class DatabaseAppCreateView(BaseDatabaseAppCreateUpdateView, CreateView): - - def get_type(self): - tp = self.request.GET.get("type") - if tp: - return tp.lower() - return super().get_type() - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('Create DatabaseApp'), - 'api_action': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppUpdateView(BaseDatabaseAppCreateUpdateView, UpdateView): - - def get_type(self): - return self.object.type - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('Create DatabaseApp'), - 'api_action': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppDetailView(PermissionsMixin, DetailView): - template_name = 'applications/database_app_detail.html' - model = models.DatabaseApp - context_object_name = 'database_app' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('DatabaseApp detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserDatabaseAppListView(PermissionsMixin, TemplateView): - template_name = 'applications/user_database_app_list.html' - permission_classes = [IsValidUser] - - def get_context_data(self, **kwargs): - context = { - 'action': _('My DatabaseApp'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/applications/views/remote_app.py b/apps/applications/views/remote_app.py deleted file mode 100644 index 92b436005..000000000 --- a/apps/applications/views/remote_app.py +++ /dev/null @@ -1,128 +0,0 @@ -# coding: utf-8 -# - -from django.http import Http404 -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 common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser - -from ..models import RemoteApp -from .. import forms, const - - -__all__ = [ - 'RemoteAppListView', 'RemoteAppCreateView', 'RemoteAppUpdateView', - 'RemoteAppDetailView', 'UserRemoteAppListView', -] - - -class RemoteAppListView(PermissionsMixin, TemplateView): - template_name = 'applications/remote_app_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('RemoteApp list'), - 'type_choices': const.REMOTE_APP_TYPE_CHOICES, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class BaseRemoteAppCreateUpdateView: - template_name = 'applications/remote_app_create_update.html' - model = RemoteApp - permission_classes = [IsOrgAdmin] - default_type = const.REMOTE_APP_TYPE_CHROME - form_class = forms.RemoteAppChromeForm - form_class_choices = { - const.REMOTE_APP_TYPE_CHROME: forms.RemoteAppChromeForm, - const.REMOTE_APP_TYPE_MYSQL_WORKBENCH: forms.RemoteAppMySQLWorkbenchForm, - const.REMOTE_APP_TYPE_VMWARE_CLIENT: forms.RemoteAppVMwareForm, - const.REMOTE_APP_TYPE_CUSTOM: forms.RemoteAppCustomForm - } - - def get_initial(self): - return {'type': self.get_type()} - - def get_type(self): - return self.default_type - - def get_form_class(self): - tp = self.get_type() - form_class = self.form_class_choices.get(tp) - if not form_class: - raise Http404() - return form_class - - -class RemoteAppCreateView(BaseRemoteAppCreateUpdateView, - PermissionsMixin, CreateView): - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('Create RemoteApp'), - 'api_action': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def get_type(self): - tp = self.request.GET.get("type") - if tp: - return tp.lower() - return super().get_type() - - -class RemoteAppUpdateView(BaseRemoteAppCreateUpdateView, - PermissionsMixin, UpdateView): - - def get_initial(self): - initial_data = super().get_initial() - params = {k: v for k, v in self.object.params.items()} - initial_data.update(params) - return initial_data - - def get_type(self): - return self.object.type - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('Update RemoteApp'), - 'api_action': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class RemoteAppDetailView(PermissionsMixin, DetailView): - template_name = 'applications/remote_app_detail.html' - model = RemoteApp - context_object_name = 'remote_app' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Applications'), - 'action': _('RemoteApp detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserRemoteAppListView(PermissionsMixin, TemplateView): - template_name = 'applications/user_remote_app_list.html' - permission_classes = [IsValidUser] - - def get_context_data(self, **kwargs): - context = { - 'action': _('My RemoteApp'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/assets/forms/__init__.py b/apps/assets/forms/__init__.py deleted file mode 100644 index 39b39a45a..000000000 --- a/apps/assets/forms/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 -*- -# -from .asset import * -from .label import * -from .user import * -from .domain import * -from .cmd_filter import * -from .platform import * diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py deleted file mode 100644 index 54d97f036..000000000 --- a/apps/assets/forms/asset.py +++ /dev/null @@ -1,172 +0,0 @@ -# -*- coding: utf-8 -*- -# -from itertools import groupby -from django import forms -from django.utils.translation import gettext_lazy as _ - -from common.utils import get_logger -from orgs.mixins.forms import OrgModelForm - -from ..models import Asset, Platform - - -logger = get_logger(__file__) -__all__ = [ - 'AssetCreateUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm', -] - - -class ProtocolForm(forms.Form): - name = forms.ChoiceField( - choices=Asset.PROTOCOL_CHOICES, label=_("Name"), initial='ssh', - widget=forms.Select(attrs={'class': 'form-control protocol-name'}) - ) - port = forms.IntegerField( - max_value=65534, min_value=1, label=_("Port"), initial=22, - widget=forms.TextInput(attrs={'class': 'form-control protocol-port'}) - ) - - -class AssetCreateUpdateForm(OrgModelForm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_platform_to_name() - self.set_fields_queryset() - - def set_fields_queryset(self): - nodes_field = self.fields['nodes'] - nodes_choices = [] - if self.instance: - nodes_choices = [ - (n.id, n.full_value) for n in - self.instance.nodes.all() - ] - nodes_field.choices = nodes_choices - - @staticmethod - def sorted_platform(platform): - if platform['base'] == 'Other': - return 'zz' - return platform['base'] - - def set_platform_to_name(self): - choices = [] - platforms = Platform.objects.all().values('name', 'base') - platforms_sorted = sorted(platforms, key=self.sorted_platform) - platforms_grouped = groupby(platforms_sorted, key=lambda x: x['base']) - for i in platforms_grouped: - base = i[0] - grouped = sorted(i[1], key=lambda x: x['name']) - grouped = [(j['name'], j['name']) for j in grouped] - choices.append( - (base, grouped) - ) - platform_field = self.fields['platform'] - platform_field.choices = choices - if self.instance: - self.initial['platform'] = self.instance.platform.name - - def add_nodes_initial(self, node): - nodes_field = self.fields['nodes'] - nodes_field.choices.append((node.id, node.full_value)) - nodes_field.initial = [node] - - class Meta: - model = Asset - fields = [ - 'hostname', 'ip', 'public_ip', 'protocols', 'comment', - 'nodes', 'is_active', 'admin_user', 'labels', 'platform', - 'domain', 'number', - ] - widgets = { - 'nodes': forms.SelectMultiple(attrs={ - 'class': 'nodes-select2', 'data-placeholder': _('Nodes') - }), - 'admin_user': forms.Select(attrs={ - 'class': 'select2', 'data-placeholder': _('Admin user') - }), - 'labels': forms.SelectMultiple(attrs={ - 'class': 'select2', 'data-placeholder': _('Label') - }), - 'domain': forms.Select(attrs={ - 'class': 'select2', 'data-placeholder': _('Domain') - }), - 'platform': forms.Select(attrs={ - 'class': 'select2', 'data-placeholder': _('Platform') - }), - } - labels = { - 'nodes': _("Node"), - } - help_texts = { - 'admin_user': _( - 'root or other NOPASSWD sudo privilege user existed in asset,' - 'If asset is windows or other set any one, more see admin user left menu' - ), - 'platform': _("Windows 2016 RDP protocol is different, If is window 2016, set it"), - 'domain': _("If your have some network not connect with each other, you can set domain") - } - - -class AssetBulkUpdateForm(OrgModelForm): - assets = forms.ModelMultipleChoiceField( - required=True, - label=_('Select assets'), queryset=Asset.objects, - widget=forms.SelectMultiple( - attrs={ - 'class': 'select2', - 'data-placeholder': _('Select assets') - } - ) - ) - - class Meta: - model = Asset - fields = [ - 'assets', 'admin_user', 'labels', 'platform', - 'domain', - ] - widgets = { - 'labels': forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Label')} - ), - 'nodes': forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Node')} - ), - } - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_fields_queryset() - - # 重写其他字段为不再required - for name, field in self.fields.items(): - if name != 'assets': - field.required = False - - def set_fields_queryset(self): - assets_field = self.fields['assets'] - if hasattr(self, 'data'): - assets_field.queryset = Asset.objects.all() - - def save(self, commit=True): - changed_fields = [] - for field in self._meta.fields: - if self.data.get(field) not in [None, '']: - changed_fields.append(field) - - cleaned_data = {k: v for k, v in self.cleaned_data.items() - if k in changed_fields} - assets = cleaned_data.pop('assets') - labels = cleaned_data.pop('labels', []) - nodes = cleaned_data.pop('nodes', None) - assets = Asset.objects.filter(id__in=[asset.id for asset in assets]) - assets.update(**cleaned_data) - - if labels: - for asset in assets: - asset.labels.set(labels) - if nodes: - for asset in assets: - asset.nodes.set(nodes) - return assets diff --git a/apps/assets/forms/cmd_filter.py b/apps/assets/forms/cmd_filter.py deleted file mode 100644 index 46f8fc244..000000000 --- a/apps/assets/forms/cmd_filter.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django import forms -from django.core.exceptions import ValidationError -from django.utils.translation import ugettext_lazy as _ -import re - -from orgs.mixins.forms import OrgModelForm -from ..models import CommandFilter, CommandFilterRule - -__all__ = ['CommandFilterForm', 'CommandFilterRuleForm'] - - -class CommandFilterForm(OrgModelForm): - class Meta: - model = CommandFilter - fields = ['name', 'comment'] - - -class CommandFilterRuleForm(OrgModelForm): - invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]') - - class Meta: - model = CommandFilterRule - fields = [ - 'filter', 'type', 'content', 'priority', 'action', 'comment' - ] - widgets = { - 'content': forms.Textarea(attrs={ - 'placeholder': 'eg:\r\nreboot\r\nrm -rf' - }), - } - - def clean_content(self): - content = self.cleaned_data.get("content") - if self.invalid_pattern.search(content): - invalid_char = self.invalid_pattern.pattern.replace('\\', '') - msg = _("Content should not be contain: {}").format(invalid_char) - raise ValidationError(msg) - return content diff --git a/apps/assets/forms/domain.py b/apps/assets/forms/domain.py deleted file mode 100644 index 71d9f6cfa..000000000 --- a/apps/assets/forms/domain.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django import forms -from django.utils.translation import gettext_lazy as _ - -from orgs.mixins.forms import OrgModelForm -from ..models import Domain, Asset, Gateway -from .user import PasswordAndKeyAuthForm - -__all__ = ['DomainForm', 'GatewayForm'] - - -class DomainForm(forms.ModelForm): - assets = forms.ModelMultipleChoiceField( - queryset=Asset.objects, label=_('Asset'), required=False, - widget=forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Select assets')} - ) - ) - - class Meta: - model = Domain - fields = ['name', 'comment', 'assets'] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_fields_queryset() - - def set_fields_queryset(self): - assets_field = self.fields.get('assets') - - # 没有data代表是渲染表单, 有data代表是提交创建/更新表单 - if not self.data: - # 有instance 代表渲染更新表单, 否则是创建表单 - # 前端渲染优化, 防止过多资产, 设置assets queryset为none - if self.instance: - assets_field.initial = self.instance.assets.all() - assets_field.queryset = self.instance.assets.all() - else: - assets_field.queryset = Asset.objects.none() - else: - assets_field.queryset = Asset.objects.all() - - def save(self, commit=True): - instance = super().save(commit=commit) - assets = self.cleaned_data['assets'] - instance.assets.set(assets) - return instance - - -class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - password_field = self.fields.get('password') - password_field.help_text = _('Password should not contain special characters') - protocol_field = self.fields.get('protocol') - protocol_field.choices = [Gateway.PROTOCOL_CHOICES[0]] - - def save(self, commit=True): - # Because we define custom field, so we need rewrite :method: `save` - instance = super().save() - password = self.cleaned_data.get('password') - private_key, public_key = super().gen_keys() - instance.set_auth(password=password, private_key=private_key) - return instance - - class Meta: - model = Gateway - fields = [ - 'name', 'ip', 'port', 'username', 'protocol', 'domain', 'password', - 'private_key', 'is_active', 'comment', - ] - help_texts = { - 'protocol': _("SSH gateway support proxy SSH,RDP,VNC") - } - widgets = { - 'name': forms.TextInput(attrs={'placeholder': _('Name')}), - 'username': forms.TextInput(attrs={'placeholder': _('Username')}), - } diff --git a/apps/assets/forms/label.py b/apps/assets/forms/label.py deleted file mode 100644 index 1f2f13987..000000000 --- a/apps/assets/forms/label.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django import forms -from django.utils.translation import gettext_lazy as _ - -from ..models import Label, Asset - -__all__ = ['LabelForm'] - - -class LabelForm(forms.ModelForm): - assets = forms.ModelMultipleChoiceField( - queryset=Asset.objects.none(), label=_('Asset'), required=False, - widget=forms.SelectMultiple( - attrs={'class': 'select2', 'data-placeholder': _('Select assets')} - ) - ) - - class Meta: - model = Label - fields = ['name', 'value', 'assets'] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_fields_queryset() - - def set_fields_queryset(self): - assets_field = self.fields.get('assets') - - # 没有data代表是渲染表单, 有data代表是提交创建/更新表单 - if not self.data: - # 有instance 代表渲染更新表单, 否则是创建表单 - # 前端渲染优化, 防止过多资产, 设置assets queryset为none - if self.instance: - assets_field.initial = self.instance.assets.all() - assets_field.queryset = self.instance.assets.all() - else: - assets_field.queryset = Asset.objects.none() - else: - assets_field.queryset = Asset.objects.all() - - def save(self, commit=True): - label = super().save(commit=commit) - assets = self.cleaned_data['assets'] - label.assets.set(assets) - return label diff --git a/apps/assets/forms/platform.py b/apps/assets/forms/platform.py deleted file mode 100644 index 88c4365d4..000000000 --- a/apps/assets/forms/platform.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from ..models import Platform - - -__all__ = ['PlatformForm', 'PlatformMetaForm'] - - -class PlatformMetaForm(forms.Form): - SECURITY_CHOICES = ( - ('rdp', "RDP"), - ('nla', "NLA"), - ('tls', 'TLS'), - ('any', "Any"), - ) - CONSOLE_CHOICES = ( - (True, _('Yes')), - (False, _('No')), - ) - security = forms.ChoiceField( - choices=SECURITY_CHOICES, initial='any', label=_("RDP security"), - required=False, - ) - console = forms.ChoiceField( - choices=CONSOLE_CHOICES, initial=False, label=_("RDP console"), - required=False, - ) - - -class PlatformForm(forms.ModelForm): - class Meta: - model = Platform - fields = [ - 'name', 'base', 'comment', - ] - labels = { - 'base': _("Base platform") - } - diff --git a/apps/assets/forms/user.py b/apps/assets/forms/user.py deleted file mode 100644 index ff8d27dfd..000000000 --- a/apps/assets/forms/user.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django import forms -from django.utils.translation import gettext_lazy as _ - -from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger -from orgs.mixins.forms import OrgModelForm -from ..models import AdminUser, SystemUser - -logger = get_logger(__file__) -__all__ = [ - 'FileForm', 'SystemUserForm', 'AdminUserForm', 'PasswordAndKeyAuthForm', -] - - -class FileForm(forms.Form): - file = forms.FileField() - - -class PasswordAndKeyAuthForm(forms.ModelForm): - # Form field name can not start with `_`, so redefine it, - password = forms.CharField( - widget=forms.PasswordInput, max_length=128, - strip=True, required=False, - help_text=_('Password or private key passphrase'), - label=_("Password"), - ) - # Need use upload private key file except paste private key content - private_key = forms.FileField(required=False, label=_("Private key")) - - def clean_private_key(self): - private_key_f = self.cleaned_data['private_key'] - password = self.cleaned_data['password'] - - if private_key_f: - key_string = private_key_f.read() - private_key_f.seek(0) - key_string = key_string.decode() - - if not validate_ssh_private_key(key_string, password): - msg = _('Invalid private key, Only support ' - 'RSA/DSA format key') - raise forms.ValidationError(msg) - return private_key_f - - def validate_password_key(self): - password = self.cleaned_data['password'] - private_key_f = self.cleaned_data.get('private_key', '') - - if not password and not private_key_f: - raise forms.ValidationError(_( - 'Password and private key file must be input one' - )) - - def gen_keys(self): - password = self.cleaned_data.get('password', '') or None - private_key_f = self.cleaned_data['private_key'] - public_key = private_key = None - - if private_key_f: - private_key = private_key_f.read().strip().decode('utf-8') - public_key = ssh_pubkey_gen(private_key=private_key, password=password) - return private_key, public_key - - -class AdminUserForm(PasswordAndKeyAuthForm): - def save(self, commit=True): - raise forms.ValidationError("Use api to save") - - class Meta: - model = AdminUser - fields = ['name', 'username', 'password', 'private_key', 'comment'] - widgets = { - 'name': forms.TextInput(attrs={'placeholder': _('Name')}), - 'username': forms.TextInput(attrs={'placeholder': _('Username')}), - } - - -class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm): - # Admin user assets define, let user select, save it in form not in view - auto_generate_key = forms.BooleanField(initial=True, required=False) - - def save(self, commit=True): - raise forms.ValidationError("Use api to save") - - class Meta: - model = SystemUser - fields = [ - 'name', 'username', 'protocol', 'auto_generate_key', - 'password', 'private_key', 'auto_push', 'sudo', - 'username_same_with_user', - 'comment', 'shell', 'priority', 'login_mode', 'cmd_filters', - 'sftp_root', - ] - widgets = { - 'name': forms.TextInput(attrs={'placeholder': _('Name')}), - 'username': forms.TextInput(attrs={'placeholder': _('Username')}), - 'cmd_filters': forms.SelectMultiple(attrs={ - 'class': 'select2', 'data-placeholder': _('Command filter') - }), - } - labels = { - 'username_same_with_user': _("Username same with user"), - } - help_texts = { - 'auto_push': _('Auto push system user to asset'), - 'priority': _('1-100, High level will be using login asset as default, ' - 'if user was granted more than 2 system user'), - 'login_mode': _('If you choose manual login mode, you do not ' - 'need to fill in the username and password.'), - 'sudo': _("Use comma split multi command, ex: /bin/whoami,/bin/ifconfig"), - 'sftp_root': _("SFTP root dir, tmp, home or custom"), - 'username_same_with_user': _("Username is dynamic, When connect asset, using current user's username"), - # 'username_same_with_user': _("用户名是动态的,登录资产时使用当前用户的用户名登录"), - } diff --git a/apps/assets/templates/assets/_asset_group_bulk_update_modal.html b/apps/assets/templates/assets/_asset_group_bulk_update_modal.html deleted file mode 100644 index 7df6c4ede..000000000 --- a/apps/assets/templates/assets/_asset_group_bulk_update_modal.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% block modal_id %}asset_group_bulk_update_modal{% endblock %} -{% block modal_class %}modal-lg{% endblock %} -{% block modal_title%}{% trans "Update asset group" %}{% endblock %} -{% block modal_body %} -{% load bootstrap3 %} -

{% trans "Hint: only change the field you want to update." %}

-
-
- -
- -
-
-
- -
- -
-
- -
-
-
- -
-
-
- -
-{% endblock %} -{% block modal_confirm_id %}btn_asset_group_bulk_update{% endblock %} diff --git a/apps/assets/templates/assets/_asset_list_modal.html b/apps/assets/templates/assets/_asset_list_modal.html deleted file mode 100644 index dea2c3e1e..000000000 --- a/apps/assets/templates/assets/_asset_list_modal.html +++ /dev/null @@ -1,224 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% load static %} - -{% block modal_class %}modal-lg{% endblock %} -{% block modal_id %}asset_list_modal{% endblock %} -{% block modal_title%}{% trans "Asset list" %}{% endblock %} -{% block modal_body %} - - - - - -
-
-
-
-
-
-
- {% trans 'Loading' %} ... -
-
-
-
-
-
-
-
- - - - - - - - - - -
{% trans 'Hostname' %}{% trans 'IP' %}
-
-
-
-
- - -{% endblock %} - -{% block modal_button %} - {{ block.super }} -{% endblock %} -{% block modal_confirm_id %}btn_asset_modal_confirm{% endblock %} - - - diff --git a/apps/assets/templates/assets/_asset_user_auth_update_modal.html b/apps/assets/templates/assets/_asset_user_auth_update_modal.html deleted file mode 100644 index fe88b7426..000000000 --- a/apps/assets/templates/assets/_asset_user_auth_update_modal.html +++ /dev/null @@ -1,87 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% block modal_id %}asset_user_auth_update_modal{% endblock %} -{% block modal_title%}{% trans "Update asset user auth" %}{% endblock %} -{% block modal_body %} -
- {% csrf_token %} -
- -
-

-
-
-
- -
-

-
-
-
- -
- -
-
-
- -
-
-
- -
-
-
-
-
- -{% endblock %} -{% block modal_confirm_id %}btn_asset_user_auth_update_modal_confirm{% endblock %} diff --git a/apps/assets/templates/assets/_asset_user_auth_view_modal.html b/apps/assets/templates/assets/_asset_user_auth_view_modal.html deleted file mode 100644 index 8cc3a78de..000000000 --- a/apps/assets/templates/assets/_asset_user_auth_view_modal.html +++ /dev/null @@ -1,102 +0,0 @@ -{% 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 %} - -
-
-
- -
-

-
-
-
- -
-

-
-
-
- -
- -
-
- - -
-
-
-
- - -{% endblock %} -{% block modal_button %} - -{% endblock %} diff --git a/apps/assets/templates/assets/_asset_user_list.html b/apps/assets/templates/assets/_asset_user_list.html deleted file mode 100644 index d43cea10f..000000000 --- a/apps/assets/templates/assets/_asset_user_list.html +++ /dev/null @@ -1,188 +0,0 @@ -{% load i18n %} - - - - - - - - - - -{# #} - - - - - - -
- - {% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Username' %}{% trans 'Version' %}{% trans 'Connectivity'%}{% trans 'Datetime' %}{% trans 'Action' %}
-{% include 'assets/_asset_user_auth_update_modal.html' %} -{% include 'assets/_asset_user_auth_view_modal.html' %} -{% include 'authentication/_mfa_confirm_modal.html' %} - - diff --git a/apps/assets/templates/assets/_gateway_test_modal.html b/apps/assets/templates/assets/_gateway_test_modal.html deleted file mode 100644 index 2eef52c7e..000000000 --- a/apps/assets/templates/assets/_gateway_test_modal.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% block modal_id %}gateway_test{% endblock %} -{% block modal_title%}{% trans "Test gateway test connection" %}{% endblock %} -{% block modal_body %} -{% load bootstrap3 %} -
-
- - -
- - {% trans 'If use nat, set the ssh real port' %} -
-
-
-{% endblock %} -{% block modal_confirm_id %}btn_gateway_test{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/_node_detail_modal.html b/apps/assets/templates/assets/_node_detail_modal.html deleted file mode 100644 index f1f6f2dda..000000000 --- a/apps/assets/templates/assets/_node_detail_modal.html +++ /dev/null @@ -1,68 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% load static %} - -{% block modal_id %}node_detail_modal{% endblock %} - -{% block modal_title %}{% trans "Node detail" %}{% endblock %} - - -{% block modal_body %} -
-
-
- -
-

-
-
- -
-
-
- -
-

-
-
-
- -
-

-
-
-
- -
-

-
-
-
-
- - - -{% endblock %} - -{% block modal_button %} - -{% endblock %} diff --git a/apps/assets/templates/assets/_node_tree.html b/apps/assets/templates/assets/_node_tree.html deleted file mode 100644 index c500f55f8..000000000 --- a/apps/assets/templates/assets/_node_tree.html +++ /dev/null @@ -1,339 +0,0 @@ -{% load static %} -{% load i18n %} - -{# #} - - - -
-
-
-
- {% trans 'Loading' %} ... -
-
-
-
-
- - - diff --git a/apps/assets/templates/assets/_system_user.html b/apps/assets/templates/assets/_system_user.html deleted file mode 100644 index e05a9448b..000000000 --- a/apps/assets/templates/assets/_system_user.html +++ /dev/null @@ -1,282 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} - -{% block content %} -
-
-
-
-
-
{{ action }}
- -
-
-
- {% csrf_token %} - {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} -

{% trans 'Basic' %}

- {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.login_mode layout="horizontal" %} - {% bootstrap_field form.username layout="horizontal" %} - {% bootstrap_field form.username_same_with_user layout="horizontal" %} - {% bootstrap_field form.priority layout="horizontal" %} - {% bootstrap_field form.protocol layout="horizontal" %} - -

{% trans 'Auth' %}

- {% block auth %} -
-
- -
- {{ form.auto_generate_key}} -
-
-
-
- {% bootstrap_field form.password layout="horizontal" %} - {% bootstrap_field form.private_key layout="horizontal" %} -
-
- -
- {{ form.auto_push}} -
-
- {% endblock %} -
-

{% trans 'Command filter' %}

- {% bootstrap_field form.cmd_filters layout="horizontal" %} -
-

{% trans 'Other' %}

- {% bootstrap_field form.sftp_root layout="horizontal" %} - {% bootstrap_field form.sudo layout="horizontal" %} - {% bootstrap_field form.shell layout="horizontal" %} - {% bootstrap_field form.comment layout="horizontal" %} -
-
- - -
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/_user_asset_detail_modal.html b/apps/assets/templates/assets/_user_asset_detail_modal.html deleted file mode 100644 index ca2b8f252..000000000 --- a/apps/assets/templates/assets/_user_asset_detail_modal.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% load static %} - -{% block modal_id %}user_asset_detail_modal{% endblock %} - -{% block modal_title %}{% trans "Asset detail" %}{% endblock %} - -{% block modal_body %} -
- - - -
-
-{% 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 deleted file mode 100644 index a42c6032f..000000000 --- a/apps/assets/templates/assets/admin_user_assets.html +++ /dev/null @@ -1,93 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Asset list of ' %} {{ admin_user.name }} -
- - - - - - - - - - -
-
-
- {% include 'assets/_asset_user_list.html' %} -
-
-
-
-
-
- {% trans 'Quick update' %} -
-
- - - - - - - -
{% trans 'Test connective' %}: - - - -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/admin_user_create_update.html b/apps/assets/templates/assets/admin_user_create_update.html deleted file mode 100644 index 213f038d1..000000000 --- a/apps/assets/templates/assets/admin_user_create_update.html +++ /dev/null @@ -1,86 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} - -{% block content %} -
-
-
-
-
-
{{ action }}
- -
-
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} -
- {% csrf_token %} - {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.username layout="horizontal" %} - {% bootstrap_field form.password layout="horizontal" %} - {% bootstrap_field form.private_key layout="horizontal" %} - {% bootstrap_field form.comment layout="horizontal" %} - -
-
- - -
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/admin_user_detail.html b/apps/assets/templates/assets/admin_user_detail.html deleted file mode 100644 index 198b1e765..000000000 --- a/apps/assets/templates/assets/admin_user_detail.html +++ /dev/null @@ -1,166 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ admin_user.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ admin_user.name }}
{% trans 'Username' %}:{{ admin_user.username }}
{% trans 'Date created' %}:{{ admin_user.date_created }}
{% trans 'Created by' %}:{{ admin_user.created_by }}
{% trans 'Comment' %}:{{ admin_user.comment }}
-
-
-
-
-
-
- {% trans 'Replace node assets admin user with this' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
-
-
-
-
-
-
- - -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html deleted file mode 100644 index aaee06d69..000000000 --- a/apps/assets/templates/assets/admin_user_list.html +++ /dev/null @@ -1,77 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block help_message %} - {% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%} - {% trans 'JumpServer users of the system using the user to `push system user`, `get assets hardware information`, etc. '%} -{% endblock %} -{% block table_search %} - {% include '_csv_import_export.html' %} -{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Username' %}{% trans 'Asset' %}{% trans 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% 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 deleted file mode 100644 index 4ad5a2c43..000000000 --- a/apps/assets/templates/assets/asset_asset_user_list.html +++ /dev/null @@ -1,97 +0,0 @@ -{% extends 'base.html' %} -{% load common_tags %} -{% load static %} -{% load i18n %} -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Asset users of' %} {{ asset.hostname }} -
- - - - - - - - - - -
-
-
- {% include 'assets/_asset_user_list.html' %} -
-
-
-
-
-
- {% trans 'Quick modify' %} -
-
- - - {% if asset.is_support_ansible %} - - - - - {% endif %} - -
{% trans 'Test connective' %}: - - - -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/asset_bulk_update.html b/apps/assets/templates/assets/asset_bulk_update.html deleted file mode 100644 index 207d5eb52..000000000 --- a/apps/assets/templates/assets/asset_bulk_update.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends '_base_create_update.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} - -{% block form %} -
-

{% trans 'Select properties that need to be modified' %}

-
- {% trans 'Select all' %} - {% for field in form %} - {% if field.name != 'assets' %} - {{ field.label }} - {% endif %} - {% endfor %} -
-
-
- {% csrf_token %} - {% bootstrap_form form layout="horizontal" %} -
-
- - -
-
-
-{% include 'assets/_asset_list_modal.html' %} -{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/asset_create.html b/apps/assets/templates/assets/asset_create.html deleted file mode 100644 index 0f50b2b86..000000000 --- a/apps/assets/templates/assets/asset_create.html +++ /dev/null @@ -1,249 +0,0 @@ -{% extends '_base_create_update.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load asset_tags %} -{% load common_tags %} - -{% block form %} -
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% csrf_token %} -

{% trans 'Basic' %}

- {% bootstrap_field form.hostname layout="horizontal" %} - {% bootstrap_field form.ip layout="horizontal" %} - {% bootstrap_field form.platform layout="horizontal" %} - {% bootstrap_field form.public_ip layout="horizontal" %} - {% bootstrap_field form.domain layout="horizontal" %} -
- -

{% trans 'Protocols' %}

-
- {% for fm in formset.forms %} -
-
{{ fm.name }}
-
{{ fm.port }}
-
- - -
-
- {% endfor %} -
-
-

{% trans 'Auth' %}

- {% bootstrap_field form.admin_user layout="horizontal" %} - -
-

{% trans 'Node' %}

- {% bootstrap_field form.nodes layout="horizontal" %} - -
-

{% trans 'Labels' %}

-
- -
- - {% if form.errors.labels %} - {% for e in form.errors.labels %} -
{{ e }}
- {% endfor %} - {% endif %} -
-
- {% block extra %} - {% endblock %} - -
-

{% trans 'Other' %}

- {% bootstrap_field form.comment layout="horizontal" %} - {% bootstrap_field form.is_active layout="horizontal" %} - -
-
-
- - -
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/asset_detail.html b/apps/assets/templates/assets/asset_detail.html deleted file mode 100644 index 53717c643..000000000 --- a/apps/assets/templates/assets/asset_detail.html +++ /dev/null @@ -1,362 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ asset.hostname }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Hostname' %}:{{ asset.hostname }}
{% trans 'IP' %}:{{ asset.ip }}
{% trans 'Public IP' %}:{{ asset.public_ip|default:"" }}
{% trans 'Protocol' %}{{ asset.protocols }}
{% trans 'Admin user' %}:{{ asset.admin_user }}
{% trans 'Domain' %}:{{ asset.domain|default:"" }}
{% trans 'Vendor' %}:{{ asset.vendor|default:"" }}
{% trans 'Model' %}:{{ asset.model|default:"" }}
{% trans 'CPU' %}:{{ asset.cpu_info }}
{% trans 'Memory' %}:{{ asset.memory|default:"" }}
{% trans 'Disk' %}:{{ asset.disk_total|default:"" }}
{% trans 'Platform' %}:{{ asset.platform|default:"" }}
{% trans 'OS' %}:{{ asset.os|default:"" }} {{ asset.os_version|default:"" }} {{ asset.os_arch|default:"" }}
{% trans 'Is active' %}:{{ asset.is_active|yesno:"Yes,No" }}
{% trans 'Serial number' %}:{{ asset.sn|default:"" }}
{% trans 'Asset number' %}:{{ asset.number|default:"" }}
{% trans 'Created by' %}:{{ asset.created_by }}
{% trans 'Date joined' %}:{{ asset.date_joined|date:"Y-m-j H:i:s" }}
{% trans 'Comment' %}:{{ asset.comment }}
-
-
-
- {% if user.is_superuser or user.is_org_admin %} -
-
-
- {% trans 'Quick modify' %} -
-
- - - - - - - {% if asset.is_support_ansible %} - - - - - - - - - {% endif %} - -
{% trans 'Active' %}: - -
-
- - -
-
-
-
{% trans 'Refresh hardware' %}: - - - -
{% trans 'Test connective' %}: - - - -
-
-
- -
-
- {% trans 'Nodes' %} -
-
- - - - - - - - - - - - {% for node in asset.nodes.all %} - - - - - {% endfor %} - -
- -
- -
{{ node.full_value }} - -
-
-
- -
-
- {% trans 'Labels' %} -
-
- -
-
- {% endif %} -
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html deleted file mode 100644 index 2eeda8f69..000000000 --- a/apps/assets/templates/assets/asset_list.html +++ /dev/null @@ -1,416 +0,0 @@ -{% extends '_base_asset_tree_list.html' %} -{% load static %} -{% load i18n %} - -{% block help_message %} - {% trans 'The left side is the asset tree, right click to create, delete, and change the tree node, authorization asset is also organized as a node, and the right side is the asset under that node' %} -{% endblock %} - -{% block table_container %} - - {% include '_csv_import_export.html' %} -
- - -
- - - - - - - - - - - - - -
{% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Hardware' %}{% trans 'Reachable' %}{% trans 'Action' %}
-
-
- -
- -
-
-
-{% include 'assets/_asset_list_modal.html' %} -{% include 'assets/_node_detail_modal.html' %} -{% endblock %} - -{% block custom_foot_js %} - - -{% endblock %} diff --git a/apps/assets/templates/assets/asset_update.html b/apps/assets/templates/assets/asset_update.html deleted file mode 100644 index 4c23f7095..000000000 --- a/apps/assets/templates/assets/asset_update.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends 'assets/asset_create.html' %} -{% load bootstrap3 %} -{% load i18n %} - -{% block extra %} -
-

{% trans 'Configuration' %}

- {% bootstrap_field form.number layout="horizontal" %} -{% endblock %} - -{% block formUrl %} - var the_url = '{% url 'api-assets:asset-detail' pk=object.id %}'; - var redirect_to = '{% url "assets:asset-list" %}'; - var method = 'PUT'; -{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/cmd_filter_create_update.html b/apps/assets/templates/assets/cmd_filter_create_update.html deleted file mode 100644 index 678e1e3eb..000000000 --- a/apps/assets/templates/assets/cmd_filter_create_update.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends '_base_create_update.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} - -{% block form %} -
- {% csrf_token %} - {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.comment layout="horizontal" %} - -
-
-
- - -
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/cmd_filter_detail.html b/apps/assets/templates/assets/cmd_filter_detail.html deleted file mode 100644 index 24e192253..000000000 --- a/apps/assets/templates/assets/cmd_filter_detail.html +++ /dev/null @@ -1,173 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ object.name }}
{% trans 'Comment' %}:{{ object.comment }}
{% trans 'Date created' %}:{{ object.date_created }}
{% trans 'Date updated' %}:{{ object.date_updated }}
{% trans 'Created by' %}:{{ object.created_by }}
-
-
-
- -
-
-
- {% trans 'System users' %} -
-
- - - - - - - - - - - {% for system_user in object.system_users.all %} - - - - - {% endfor %} - -
- -
- -
{{ system_user }} - -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/cmd_filter_list.html b/apps/assets/templates/assets/cmd_filter_list.html deleted file mode 100644 index 1d98d5500..000000000 --- a/apps/assets/templates/assets/cmd_filter_list.html +++ /dev/null @@ -1,89 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %}{% endblock %} -{% block help_message %} - {% trans 'System user bound some command filter, each command filter has some rules,'%} - {% trans 'When user login asset with this system user, then run a command,' %} - {% trans 'The command will be filter by rules, higher priority rule run first,' %} - {% trans 'When a rule matched, if rule action is allow, then allow command execute,' %} - {% trans 'else if action is deny, then command with be deny,' %} - {% trans 'else match next rule, if none matched, allowed' %} -{% endblock %} -{% block table_container %} - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Rules' %}{% trans 'System users' %}{% trans 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - - - diff --git a/apps/assets/templates/assets/cmd_filter_rule_create_update.html b/apps/assets/templates/assets/cmd_filter_rule_create_update.html deleted file mode 100644 index 21279b410..000000000 --- a/apps/assets/templates/assets/cmd_filter_rule_create_update.html +++ /dev/null @@ -1,90 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} - -{% block content %} -
-
-
-
-
-
{{ action }}
- -
-
-
- {% csrf_token %} - {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% bootstrap_form form layout="horizontal" %} -
-
- - -
-
-
-
-
-
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/cmd_filter_rule_list.html b/apps/assets/templates/assets/cmd_filter_rule_list.html deleted file mode 100644 index 6076cb2ac..000000000 --- a/apps/assets/templates/assets/cmd_filter_rule_list.html +++ /dev/null @@ -1,110 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
-
-
- {% trans 'Command filter rule list' %} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - -
- - {% trans 'Type' %}{% trans 'Content' %}{% trans 'Priority' %}{% trans 'Strategy' %}{% trans 'Comment' %}{% trans 'Action' %}
-
-
-
-
-
-
-
-
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/delete_confirm.html b/apps/assets/templates/assets/delete_confirm.html deleted file mode 100644 index 94f017ca2..000000000 --- a/apps/assets/templates/assets/delete_confirm.html +++ /dev/null @@ -1,15 +0,0 @@ -{% load i18n %} - - - - - {% trans 'Confirm delete' %} - - -
- {% csrf_token %} -

{% trans 'Are you sure delete' %} {{ object.name }} ?

- -
- - \ No newline at end of file diff --git a/apps/assets/templates/assets/domain_create_update.html b/apps/assets/templates/assets/domain_create_update.html deleted file mode 100644 index 39939c8ca..000000000 --- a/apps/assets/templates/assets/domain_create_update.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends '_base_create_update.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} - -{% block form %} -
- {% csrf_token %} - {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.assets layout="horizontal" %} - {% bootstrap_field form.comment layout="horizontal" %} - -
-
-
- - -
-
-
-{% include 'assets/_asset_list_modal.html' %} -{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/domain_detail.html b/apps/assets/templates/assets/domain_detail.html deleted file mode 100644 index 119b58080..000000000 --- a/apps/assets/templates/assets/domain_detail.html +++ /dev/null @@ -1,132 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ object.name }}
{% trans 'Asset' %}:{{ object.assets.count }}
{% trans 'Gateway' %}:{{ object.gateway_set.count }}
{% trans 'Date created' %}:{{ object.date_created }}
{% trans 'Comment' %}:{{ object.comment }}
-
-
-
-
-
-
-
-
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/domain_gateway_list.html b/apps/assets/templates/assets/domain_gateway_list.html deleted file mode 100644 index 8917ef810..000000000 --- a/apps/assets/templates/assets/domain_gateway_list.html +++ /dev/null @@ -1,141 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
-
-
- {% trans 'Gateway list' %} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'IP' %}{% trans 'Port' %}{% trans 'Protocol' %}{% trans 'Username' %}{% trans 'Comment' %}{% trans 'Action' %}
-
-
-
-
-
-
-
-
-{% include 'assets/_gateway_test_modal.html' %} -{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/domain_list.html b/apps/assets/templates/assets/domain_list.html deleted file mode 100644 index 623f1bea2..000000000 --- a/apps/assets/templates/assets/domain_list.html +++ /dev/null @@ -1,79 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %}{% endblock %} - -{% block help_message %} - {% trans 'The domain function is added to address the fact that some environments (such as the hybrid cloud) cannot be connected directly by jumping on the gateway server.' %} -
- {% trans 'JMS => Domain gateway => Target assets' %} -{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Asset' %}{% trans 'Gateway' %}{% trans 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/gateway_create_update.html b/apps/assets/templates/assets/gateway_create_update.html deleted file mode 100644 index 43dc07b1b..000000000 --- a/apps/assets/templates/assets/gateway_create_update.html +++ /dev/null @@ -1,124 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} - -{% block content %} -
-
-
-
-
-
{{ action }}
- -
-
-
- {% csrf_token %} - {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} -

{% trans 'Basic' %}

- {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.ip layout="horizontal" %} - {% bootstrap_field form.port layout="horizontal" %} - {% bootstrap_field form.protocol layout="horizontal" %} - {% bootstrap_field form.domain layout="horizontal" %} - - {% block auth %} -

{% trans 'Auth' %}

-
- {% bootstrap_field form.username layout="horizontal" %} - {% bootstrap_field form.password layout="horizontal" %} - {% bootstrap_field form.private_key layout="horizontal" %} -
- {% endblock %} - -

{% trans 'Other' %}

- {% bootstrap_field form.is_active layout="horizontal" %} - {% bootstrap_field form.comment layout="horizontal" %} -
-
- - -
-
-
-
-
-
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/label_create_update.html b/apps/assets/templates/assets/label_create_update.html deleted file mode 100644 index 62732d6ba..000000000 --- a/apps/assets/templates/assets/label_create_update.html +++ /dev/null @@ -1,56 +0,0 @@ -{% extends '_base_create_update.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} - - - -{% block form %} -
- {% csrf_token %} - {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.value layout="horizontal" %} - {% bootstrap_field form.assets layout="horizontal" %} - -
-
-
- - -
-
-
-{% include 'assets/_asset_list_modal.html' %} -{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/label_list.html b/apps/assets/templates/assets/label_list.html deleted file mode 100644 index 104e5820b..000000000 --- a/apps/assets/templates/assets/label_list.html +++ /dev/null @@ -1,72 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %}{% endblock %} -{% block table_container %} - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Value' %}{% trans 'Asset' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - - - diff --git a/apps/assets/templates/assets/platform_create_update.html b/apps/assets/templates/assets/platform_create_update.html deleted file mode 100644 index e130e9e97..000000000 --- a/apps/assets/templates/assets/platform_create_update.html +++ /dev/null @@ -1,79 +0,0 @@ -{% extends '_base_create_update.html' %} {% load static %} {% load bootstrap3 %} -{% load i18n %} - -{% block form %} -
- {% csrf_token %} - {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.base layout="horizontal" %} -
- -
- {% bootstrap_field form.comment layout="horizontal" %} - -
-
-
- - -
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/assets/templates/assets/platform_detail.html b/apps/assets/templates/assets/platform_detail.html deleted file mode 100644 index 89d364e0f..000000000 --- a/apps/assets/templates/assets/platform_detail.html +++ /dev/null @@ -1,91 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ object.name }}
{% trans 'Base platform' %}:{{ object.base }}
{% trans 'Charset' %}:{{ object.charset }}
{% trans 'Meta' %}:{{ object.meta }}
{% trans 'Comment' %}:{{ object.comment }}
-
-
-
-
-
-
-
-
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/platform_list.html b/apps/assets/templates/assets/platform_list.html deleted file mode 100644 index cb1eef5bc..000000000 --- a/apps/assets/templates/assets/platform_list.html +++ /dev/null @@ -1,75 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %} -{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Base platform' %}{% trans 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/system_user_assets.html b/apps/assets/templates/assets/system_user_assets.html deleted file mode 100644 index d48f48201..000000000 --- a/apps/assets/templates/assets/system_user_assets.html +++ /dev/null @@ -1,349 +0,0 @@ -{% extends 'base.html' %} -{% load common_tags %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - -{% endblock %} - -{% block content %} -
-
-
-
-
- -
-
-
-
-
- {{ system_user.name }} {{ paginator.count }} -
- - - - - - - - - - -
-
-
- {% include 'assets/_asset_user_list.html' %} -
-
-
-
-
-
- {% trans 'Quick update' %} -
-
- - - - - - - {% if system_user.auto_push %} - - - - - {% endif %} - -
{% trans 'Test assets connective' %}: - - - -
{% trans 'Push system user now' %}: - - - -
-
-
-
-
- {% trans 'Assets' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
-
-
- {% trans 'Nodes' %} -
-
- - - - - - - - - - -
- -
- -
-
- - -
-
-
-
-
-
-
-
- {% include 'assets/_asset_list_modal.html' %} -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/system_user_create.html b/apps/assets/templates/assets/system_user_create.html deleted file mode 100644 index 7127de993..000000000 --- a/apps/assets/templates/assets/system_user_create.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'assets/_system_user.html' %} -{% load i18n %} -{% load static %} - -{% block auth %} - {{ block.super }} -{% endblock %} diff --git a/apps/assets/templates/assets/system_user_detail.html b/apps/assets/templates/assets/system_user_detail.html deleted file mode 100644 index 01e71fc1c..000000000 --- a/apps/assets/templates/assets/system_user_detail.html +++ /dev/null @@ -1,260 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
-
- -
-
-
-
-
- {{ system_user.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - {% if system_user.username_same_with_user %} - - {% else %} - - {% endif %} - - - - - - - - - - - - - - {% if system_user.shell %} - - - - - {% endif %} - {% if system_user.home %} - - - - - {% endif %} - {% if system_user.uid %} - - - - - {% endif %} - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ system_user.name }}
{% trans 'Username' %}:{% trans 'Username same with user' %}{{ system_user.username }}
{% trans 'Login mode' %}:{{ system_user.get_login_mode_display }}
{% trans 'Protocol' %}:{{ system_user.protocol }}
{% trans 'Sudo' %}:{{ system_user.sudo }}
{% trans 'Shell' %}:{{ system_user.shell }}
{% trans 'Home' %}:{{ system_user.home }}
{% trans 'Uid' %}:{{ system_user.uid }}
{% trans 'Date created' %}:{{ system_user.date_created }}
{% trans 'Created by' %}:{{ system_user.created_by }}
{% trans 'Comment' %}:{{ system_user.comment }}
-
-
-
- -
-
-
- {% trans 'Quick update' %} -
-
- - - - - - - -
{% trans 'Auto push' %}: - -
-
- - -
-
-
-
-
-
-
-
- {% if system_user.is_need_cmd_filter %} -
-
-
- {% trans 'Command filter' %} -
-
- - - - - - - - - - - {% for cf in object.cmd_filters.all %} - - - - - {% endfor %} - -
- -
- -
{{ cf }} - -
-
-
-
- {% endif %} -
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html deleted file mode 100644 index b3bd556ab..000000000 --- a/apps/assets/templates/assets/system_user_list.html +++ /dev/null @@ -1,90 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} - -{% block help_message %} - {% trans 'System user is JumpServer jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); '%} - {% trans 'In simple terms, users log into JumpServer using their own username, and JumpServer uses system users to log into assets. '%} - {% trans 'When system users are created, if you choose auto push JumpServer to use Ansible push system users into the asset, if the asset (Switch) does not support ansible, please manually fill in the account password.' %} -{% endblock %} - -{% block table_search %} - {% include '_csv_import_export.html' %} -{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Username' %}{% trans 'Protocol' %}{% trans 'Login mode' %}{% trans 'Asset' %}{% trans 'Comment' %}{% trans 'Action' %}
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - - - diff --git a/apps/assets/templates/assets/system_user_update.html b/apps/assets/templates/assets/system_user_update.html deleted file mode 100644 index 0a60d3e70..000000000 --- a/apps/assets/templates/assets/system_user_update.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends 'assets/_system_user.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} - -{% block auth %} - {% bootstrap_field form.password layout="horizontal" %} - {% bootstrap_field form.private_key layout="horizontal" %} -
- -
- {{ form.auto_push}} -
-
-{% endblock %} - -{% block formUrl %} - var the_url = '{% url 'api-assets:system-user-detail' pk=object.pk %}'; - var redirect_to = '{% url "assets:system-user-list" %}'; - var method = "PUT"; -{% endblock %} - diff --git a/apps/assets/templates/assets/system_user_users.html b/apps/assets/templates/assets/system_user_users.html deleted file mode 100644 index 59d5e0653..000000000 --- a/apps/assets/templates/assets/system_user_users.html +++ /dev/null @@ -1,212 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - -{% endblock %} - -{% block content %} -
-
-
-
-
- -
-
-
-
-
- {{ system_user.name }} {{ paginator.count }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - -
- - {% trans 'User' %}{% trans 'Action' %}
-
-
-
-
-
-
- {% trans 'Users' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templates/assets/user_asset_list.html b/apps/assets/templates/assets/user_asset_list.html deleted file mode 100644 index 2a8c8cf64..000000000 --- a/apps/assets/templates/assets/user_asset_list.html +++ /dev/null @@ -1,132 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - - -{% endblock %} - -{% block content %} -
-
- {% include 'users/_granted_assets.html' %} -
-
- -{% include 'assets/_user_asset_detail_modal.html' %} -{% endblock %} - - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/assets/templatetags/__init__.py b/apps/assets/templatetags/__init__.py deleted file mode 100644 index ec51c5a2b..000000000 --- a/apps/assets/templatetags/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -# diff --git a/apps/assets/templatetags/asset_tags.py b/apps/assets/templatetags/asset_tags.py deleted file mode 100644 index 15605f835..000000000 --- a/apps/assets/templatetags/asset_tags.py +++ /dev/null @@ -1,12 +0,0 @@ -from collections import defaultdict - -from django import template -register = template.Library() - - -@register.filter -def group_labels(queryset): - grouped = defaultdict(list) - for label in queryset: - grouped[label.name].append(label) - return [(name, labels) for name, labels in grouped.items()] diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py index 25b6deefd..d9832e041 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -1,66 +1,6 @@ # coding:utf-8 -from django.urls import path -from .. import views app_name = 'assets' urlpatterns = [ - # Resource asset url - path('', views.AssetListView.as_view(), name='asset-index'), - path('asset/', views.AssetListView.as_view(), name='asset-list'), - path('asset/create/', views.AssetCreateView.as_view(), name='asset-create'), - path('asset//', views.AssetDetailView.as_view(), name='asset-detail'), - path('asset//update/', views.AssetUpdateView.as_view(), name='asset-update'), - path('asset//delete/', views.AssetDeleteView.as_view(), name='asset-delete'), - path('asset/update/', views.AssetBulkUpdateView.as_view(), name='asset-bulk-update'), - # Asset user view - path('asset//asset-user/', views.AssetUserListView.as_view(), name='asset-user-list'), - - path('platform/', views.PlatformListView.as_view(), name='platform-list'), - path('platform/create/', views.PlatformCreateView.as_view(), name='platform-create'), - path('platform//', views.PlatformDetailView.as_view(), name='platform-detail'), - path('platform//update/', views.PlatformUpdateView.as_view(), name='platform-update'), - - # User asset view - path('user-asset/', views.UserAssetListView.as_view(), name='user-asset-list'), - - # Resource admin user url - path('admin-user/', views.AdminUserListView.as_view(), name='admin-user-list'), - path('admin-user/create/', views.AdminUserCreateView.as_view(), name='admin-user-create'), - path('admin-user//', views.AdminUserDetailView.as_view(), name='admin-user-detail'), - path('admin-user//update/', views.AdminUserUpdateView.as_view(), name='admin-user-update'), - path('admin-user//delete/', views.AdminUserDeleteView.as_view(), name='admin-user-delete'), - path('admin-user//assets/', views.AdminUserAssetsView.as_view(), name='admin-user-assets'), - - # Resource system user url - path('system-user/', views.SystemUserListView.as_view(), name='system-user-list'), - path('system-user/create/', views.SystemUserCreateView.as_view(), name='system-user-create'), - path('system-user//', views.SystemUserDetailView.as_view(), name='system-user-detail'), - path('system-user//update/', views.SystemUserUpdateView.as_view(), name='system-user-update'), - path('system-user//delete/', views.SystemUserDeleteView.as_view(), name='system-user-delete'), - path('system-user//asset/', views.SystemUserAssetView.as_view(), name='system-user-asset'), - path('system-user//user/', views.SystemUserUserView.as_view(), name='system-user-user'), - - path('label/', views.LabelListView.as_view(), name='label-list'), - path('label/create/', views.LabelCreateView.as_view(), name='label-create'), - path('label//update/', views.LabelUpdateView.as_view(), name='label-update'), - path('label//delete/', views.LabelDeleteView.as_view(), name='label-delete'), - - path('domain/', views.DomainListView.as_view(), name='domain-list'), - path('domain/create/', views.DomainCreateView.as_view(), name='domain-create'), - path('domain//', views.DomainDetailView.as_view(), name='domain-detail'), - path('domain//update/', views.DomainUpdateView.as_view(), name='domain-update'), - path('domain//delete/', views.DomainDeleteView.as_view(), name='domain-delete'), - path('domain//gateway/', views.DomainGatewayListView.as_view(), name='domain-gateway-list'), - - path('domain//gateway/create/', views.DomainGatewayCreateView.as_view(), name='domain-gateway-create'), - path('domain/gateway//update/', views.DomainGatewayUpdateView.as_view(), name='domain-gateway-update'), - - path('cmd-filter/', views.CommandFilterListView.as_view(), name='cmd-filter-list'), - path('cmd-filter/create/', views.CommandFilterCreateView.as_view(), name='cmd-filter-create'), - path('cmd-filter//update/', views.CommandFilterUpdateView.as_view(), name='cmd-filter-update'), - path('cmd-filter//', views.CommandFilterDetailView.as_view(), name='cmd-filter-detail'), - path('cmd-filter//rule/', views.CommandFilterRuleListView.as_view(), name='cmd-filter-rule-list'), - path('cmd-filter//rule/create/', views.CommandFilterRuleCreateView.as_view(), name='cmd-filter-rule-create'), - path('cmd-filter//rule//update/', views.CommandFilterRuleUpdateView.as_view(), name='cmd-filter-rule-update'), ] diff --git a/apps/assets/views/__init__.py b/apps/assets/views/__init__.py deleted file mode 100644 index 74055a76c..000000000 --- a/apps/assets/views/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# coding:utf-8 -from .asset import * -from .platform import * -from .system_user import * -from .admin_user import * -from .label import * -from .domain import * -from .cmd_filter import * diff --git a/apps/assets/views/admin_user.py b/apps/assets/views/admin_user.py deleted file mode 100644 index 611b03d7d..000000000 --- a/apps/assets/views/admin_user.py +++ /dev/null @@ -1,121 +0,0 @@ -# coding:utf-8 -from __future__ import absolute_import, unicode_literals -from django.utils.translation import ugettext as _ -from django.conf import settings -from django.urls import reverse_lazy -from django.views.generic import TemplateView, ListView -from django.views.generic.edit import CreateView, DeleteView, UpdateView -from django.contrib.messages.views import SuccessMessageMixin -from django.views.generic.detail import DetailView, SingleObjectMixin - -from common.const import create_success_msg, update_success_msg -from .. import forms -from ..models import AdminUser, Node -from common.permissions import PermissionsMixin, IsOrgAdmin - -__all__ = [ - 'AdminUserCreateView', 'AdminUserDetailView', - 'AdminUserDeleteView', 'AdminUserListView', - 'AdminUserUpdateView', 'AdminUserAssetsView', -] - - -class AdminUserListView(PermissionsMixin, TemplateView): - model = AdminUser - template_name = 'assets/admin_user_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Admin user list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdminUserCreateView(PermissionsMixin, - SuccessMessageMixin, - CreateView): - model = AdminUser - form_class = forms.AdminUserForm - template_name = 'assets/admin_user_create_update.html' - success_url = reverse_lazy('assets:admin-user-list') - success_message = create_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create admin user'), - "type": "create" - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdminUserUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView): - model = AdminUser - form_class = forms.AdminUserForm - template_name = 'assets/admin_user_create_update.html' - success_url = reverse_lazy('assets:admin-user-list') - success_message = update_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update admin user'), - "type": "update" - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdminUserDetailView(PermissionsMixin, DetailView): - model = AdminUser - template_name = 'assets/admin_user_detail.html' - context_object_name = 'admin_user' - object = None - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Admin user detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdminUserAssetsView(PermissionsMixin, SingleObjectMixin, ListView): - paginate_by = settings.DISPLAY_PER_PAGE - template_name = 'assets/admin_user_assets.html' - context_object_name = 'admin_user' - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=AdminUser.objects.all()) - return super().get(request, *args, **kwargs) - - def get_queryset(self): - self.queryset = self.object.assets.all() - return self.queryset - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Admin user assets'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdminUserDeleteView(PermissionsMixin, DeleteView): - model = AdminUser - template_name = 'delete_confirm.html' - success_url = reverse_lazy('assets:admin-user-list') - permission_classes = [IsOrgAdmin] - - diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py deleted file mode 100644 index 63179f4f8..000000000 --- a/apps/assets/views/asset.py +++ /dev/null @@ -1,203 +0,0 @@ -# coding:utf-8 -from __future__ import absolute_import, unicode_literals - -from django.contrib import messages -from django.utils.translation import ugettext_lazy as _ -from django.views.generic import TemplateView, ListView -from django.views.generic.edit import FormMixin -from django.views.generic.edit import DeleteView, UpdateView -from django.urls import reverse_lazy -from django.views.generic.detail import DetailView -from django.core.cache import cache -from django.shortcuts import redirect -from django.forms.formsets import formset_factory - -from common.utils import get_object_or_none, get_logger -from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser -from common.const import KEY_CACHE_RESOURCES_ID -from .. import forms -from ..models import Asset, Label, Node - - -__all__ = [ - 'AssetListView', 'AssetCreateView', 'AssetUpdateView', 'AssetUserListView', - 'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView', - 'AssetDeleteView', -] -logger = get_logger(__file__) - - -class AssetListView(PermissionsMixin, TemplateView): - template_name = 'assets/asset_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - Node.org_root() - context = { - 'app': _('Assets'), - 'action': _('Asset list'), - 'labels': Label.objects.all().order_by('name'), - 'nodes': Node.objects.all().order_by('-key'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetUserListView(PermissionsMixin, DetailView): - model = Asset - context_object_name = 'asset' - template_name = 'assets/asset_asset_user_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Asset user list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserAssetListView(PermissionsMixin, TemplateView): - template_name = 'assets/user_asset_list.html' - permission_classes = [IsValidUser] - - def get_context_data(self, **kwargs): - context = { - 'action': _('My assets'), - 'labels': Label.objects.all().order_by('name'), - 'show_actions': True - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetCreateView(PermissionsMixin, FormMixin, TemplateView): - model = Asset - form_class = forms.AssetCreateUpdateForm - template_name = 'assets/asset_create.html' - success_url = reverse_lazy('assets:asset-list') - permission_classes = [IsOrgAdmin] - - def get_form(self, form_class=None): - form = super().get_form(form_class=form_class) - node_id = self.request.GET.get("node_id") - if node_id: - node = get_object_or_none(Node, id=node_id) - else: - node = Node.org_root() - form.add_nodes_initial(node) - return form - - def get_protocol_formset(self): - ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5) - if self.request.method == "POST": - formset = ProtocolFormset(self.request.POST) - else: - formset = ProtocolFormset() - return formset - - def get_context_data(self, **kwargs): - formset = self.get_protocol_formset() - context = { - 'app': _('Assets'), - 'action': _('Create asset'), - 'formset': formset, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetUpdateView(PermissionsMixin, UpdateView): - model = Asset - form_class = forms.AssetCreateUpdateForm - template_name = 'assets/asset_update.html' - success_url = reverse_lazy('assets:asset-list') - permission_classes = [IsOrgAdmin] - - def get_protocol_formset(self): - ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5) - if self.request.method == "POST": - formset = ProtocolFormset(self.request.POST) - else: - initial_data = self.object.protocols_as_json - formset = ProtocolFormset(initial=initial_data) - return formset - - def get_context_data(self, **kwargs): - formset = self.get_protocol_formset() - context = { - 'app': _('Assets'), - 'action': _('Update asset'), - 'formset': formset, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetBulkUpdateView(PermissionsMixin, ListView): - model = Asset - form_class = forms.AssetBulkUpdateForm - template_name = 'assets/asset_bulk_update.html' - success_url = reverse_lazy('assets:asset-list') - success_message = _("Bulk update asset success") - id_list = None - form = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - spm = request.GET.get('spm', '') - assets_id = cache.get(KEY_CACHE_RESOURCES_ID.format(spm)) - if kwargs.get('form'): - self.form = kwargs['form'] - elif assets_id: - self.form = self.form_class(initial={'assets': assets_id}) - else: - self.form = self.form_class() - return super().get(request, *args, **kwargs) - - def post(self, request, *args, **kwargs): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - messages.success(request, self.success_message) - return redirect(self.success_url) - else: - return self.get(request, form=form, *args, **kwargs) - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Bulk update asset'), - 'form': self.form, - 'assets_selected': self.id_list, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetDeleteView(PermissionsMixin, DeleteView): - model = Asset - template_name = 'delete_confirm.html' - success_url = reverse_lazy('assets:asset-list') - permission_classes = [IsOrgAdmin] - - -class AssetDetailView(PermissionsMixin, DetailView): - model = Asset - context_object_name = 'asset' - template_name = 'assets/asset_detail.html' - permission_classes = [IsValidUser] - - def get_queryset(self): - return super().get_queryset().prefetch_related( - "nodes", "labels", - ).select_related('admin_user', 'domain') - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Asset detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/assets/views/cmd_filter.py b/apps/assets/views/cmd_filter.py deleted file mode 100644 index 530f4193b..000000000 --- a/apps/assets/views/cmd_filter.py +++ /dev/null @@ -1,180 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.views.generic import TemplateView, CreateView, \ - UpdateView, DeleteView, DetailView -from django.views.generic.detail import SingleObjectMixin -from django.utils.translation import ugettext_lazy as _ -from django.urls import reverse_lazy -from django.shortcuts import get_object_or_404, reverse - -from common.permissions import PermissionsMixin, IsOrgAdmin -from common.const import create_success_msg, update_success_msg -from ..models import CommandFilter, CommandFilterRule, SystemUser -from ..forms import CommandFilterForm, CommandFilterRuleForm - - -__all__ = ( - "CommandFilterListView", "CommandFilterCreateView", - "CommandFilterUpdateView", - "CommandFilterRuleListView", "CommandFilterRuleCreateView", - "CommandFilterRuleUpdateView", "CommandFilterDetailView", -) - - -class CommandFilterListView(PermissionsMixin, TemplateView): - template_name = 'assets/cmd_filter_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Command filter list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandFilterCreateView(PermissionsMixin, CreateView): - model = CommandFilter - template_name = 'assets/cmd_filter_create_update.html' - form_class = CommandFilterForm - success_url = reverse_lazy('assets:cmd-filter-list') - success_message = create_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create command filter'), - 'type': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandFilterUpdateView(PermissionsMixin, UpdateView): - model = CommandFilter - template_name = 'assets/cmd_filter_create_update.html' - form_class = CommandFilterForm - success_url = reverse_lazy('assets:cmd-filter-list') - success_message = update_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update command filter'), - 'type': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandFilterDetailView(PermissionsMixin, DetailView): - model = CommandFilter - template_name = 'assets/cmd_filter_detail.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - system_users_remain = SystemUser.objects\ - .exclude(cmd_filters=self.object)\ - .exclude(protocol='rdp') - context = { - 'app': _('Assets'), - 'action': _('Command filter detail'), - 'system_users_remain': system_users_remain - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandFilterRuleListView(PermissionsMixin, SingleObjectMixin, TemplateView): - template_name = 'assets/cmd_filter_rule_list.html' - model = CommandFilter - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=self.model.objects.all()) - return super().get(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Command filter rule list'), - 'object': self.get_object() - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandFilterRuleCreateView(PermissionsMixin, CreateView): - template_name = 'assets/cmd_filter_rule_create_update.html' - model = CommandFilterRule - form_class = CommandFilterRuleForm - success_message = create_success_msg - cmd_filter = None - permission_classes = [IsOrgAdmin] - - def get_success_url(self): - return reverse('assets:cmd-filter-rule-list', kwargs={ - 'pk': self.cmd_filter.id - }) - - def get_form(self, form_class=None): - form = super().get_form(form_class=form_class) - form['filter'].initial = self.cmd_filter - form['filter'].field.widget.attrs['readonly'] = 1 - return form - - def dispatch(self, request, *args, **kwargs): - filter_pk = self.kwargs.get('filter_pk') - self.cmd_filter = get_object_or_404(CommandFilter, pk=filter_pk) - return super().dispatch(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create command filter rule'), - 'object': self.cmd_filter, - 'request_type': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandFilterRuleUpdateView(PermissionsMixin, UpdateView): - template_name = 'assets/cmd_filter_rule_create_update.html' - model = CommandFilterRule - form_class = CommandFilterRuleForm - success_message = create_success_msg - cmd_filter = None - permission_classes = [IsOrgAdmin] - - def get_success_url(self): - return reverse('assets:cmd-filter-rule-list', kwargs={ - 'pk': self.cmd_filter.id - }) - - def get_form(self, form_class=None): - form = super().get_form(form_class=form_class) - form['filter'].initial = self.cmd_filter - form['filter'].field.widget.attrs['readonly'] = 1 - return form - - def dispatch(self, request, *args, **kwargs): - filter_pk = self.kwargs.get('filter_pk') - self.cmd_filter = get_object_or_404(CommandFilter, pk=filter_pk) - return super().dispatch(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update command filter rule'), - 'object': self.cmd_filter, - 'rule': self.get_object(), - 'request_type': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) \ No newline at end of file diff --git a/apps/assets/views/domain.py b/apps/assets/views/domain.py deleted file mode 100644 index ad7fad1b6..000000000 --- a/apps/assets/views/domain.py +++ /dev/null @@ -1,162 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.views.generic import ( - TemplateView, CreateView, UpdateView, DeleteView, DetailView -) -from django.views.generic.detail import SingleObjectMixin -from django.utils.translation import ugettext_lazy as _ -from django.urls import reverse_lazy, reverse - -from common.permissions import PermissionsMixin, IsOrgAdmin -from common.const import create_success_msg, update_success_msg -from common.utils import get_object_or_none -from ..models import Domain, Gateway -from ..forms import DomainForm, GatewayForm - - -__all__ = ( - "DomainListView", "DomainCreateView", "DomainUpdateView", - "DomainDetailView", "DomainDeleteView", "DomainGatewayListView", - "DomainGatewayCreateView", 'DomainGatewayUpdateView', -) - - -class DomainListView(PermissionsMixin, TemplateView): - template_name = 'assets/domain_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Domain list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DomainCreateView(PermissionsMixin, CreateView): - model = Domain - template_name = 'assets/domain_create_update.html' - form_class = DomainForm - success_url = reverse_lazy('assets:domain-list') - success_message = create_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create domain'), - 'type': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DomainUpdateView(PermissionsMixin, UpdateView): - model = Domain - template_name = 'assets/domain_create_update.html' - form_class = DomainForm - success_url = reverse_lazy('assets:domain-list') - success_message = update_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update domain'), - 'type': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DomainDetailView(PermissionsMixin, DetailView): - model = Domain - template_name = 'assets/domain_detail.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Domain detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DomainDeleteView(PermissionsMixin, DeleteView): - model = Domain - template_name = 'delete_confirm.html' - success_url = reverse_lazy('assets:domain-list') - permission_classes = [IsOrgAdmin] - - -class DomainGatewayListView(PermissionsMixin, SingleObjectMixin, TemplateView): - template_name = 'assets/domain_gateway_list.html' - model = Domain - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=self.model.objects.all()) - return super().get(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Domain gateway list'), - 'object': self.get_object() - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DomainGatewayCreateView(PermissionsMixin, CreateView): - model = Gateway - template_name = 'assets/gateway_create_update.html' - form_class = GatewayForm - success_message = create_success_msg - permission_classes = [IsOrgAdmin] - - def get_success_url(self): - domain = self.object.domain - return reverse('assets:domain-gateway-list', kwargs={"pk": domain.id}) - - def get_form(self, form_class=None): - form = super().get_form(form_class=form_class) - domain_id = self.kwargs.get("pk") - domain = get_object_or_none(Domain, id=domain_id) - if domain: - form['domain'].initial = domain - return form - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create gateway'), - 'type': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DomainGatewayUpdateView(PermissionsMixin, UpdateView): - model = Gateway - template_name = 'assets/gateway_create_update.html' - form_class = GatewayForm - success_message = update_success_msg - permission_classes = [IsOrgAdmin] - - def get_success_url(self): - domain = self.object.domain - return reverse('assets:domain-gateway-list', kwargs={"pk": domain.id}) - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update gateway'), - "type": "update" - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/assets/views/label.py b/apps/assets/views/label.py deleted file mode 100644 index 522962ce3..000000000 --- a/apps/assets/views/label.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.views.generic import TemplateView, CreateView, \ - UpdateView, DeleteView, DetailView -from django.utils.translation import ugettext_lazy as _ -from django.urls import reverse_lazy - -from common.permissions import PermissionsMixin, IsOrgAdmin -from common.const import create_success_msg, update_success_msg -from ..models import Label -from ..forms import LabelForm - - -__all__ = ( - "LabelListView", "LabelCreateView", "LabelUpdateView", - "LabelDetailView", "LabelDeleteView", -) - - -class LabelListView(PermissionsMixin, TemplateView): - template_name = 'assets/label_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Label list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class LabelCreateView(PermissionsMixin, CreateView): - model = Label - template_name = 'assets/label_create_update.html' - form_class = LabelForm - success_url = reverse_lazy('assets:label-list') - success_message = create_success_msg - disable_name = ['draw', 'search', 'limit', 'offset', '_'] - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create label'), - 'type': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def form_valid(self, form): - name = form.cleaned_data.get('name') - if name in self.disable_name: - msg = _( - 'Tips: Avoid using label names reserved internally: {}' - ).format(', '.join(self.disable_name)) - form.add_error("name", msg) - return self.form_invalid(form) - return super().form_valid(form) - - -class LabelUpdateView(PermissionsMixin, UpdateView): - model = Label - template_name = 'assets/label_create_update.html' - form_class = LabelForm - success_url = reverse_lazy('assets:label-list') - success_message = update_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update label'), - 'type': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class LabelDetailView(PermissionsMixin, DetailView): - pass - - -class LabelDeleteView(PermissionsMixin, DeleteView): - model = Label - template_name = 'delete_confirm.html' - success_url = reverse_lazy('assets:label-list') - permission_classes = [IsOrgAdmin] diff --git a/apps/assets/views/platform.py b/apps/assets/views/platform.py deleted file mode 100644 index 8c74da138..000000000 --- a/apps/assets/views/platform.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -from django.views import generic -from django.utils.translation import ugettext as _ - -from common.permissions import PermissionsMixin, IsSuperUser -from ..models import Platform -from ..forms import PlatformForm, PlatformMetaForm - -__all__ = [ - 'PlatformListView', 'PlatformUpdateView', 'PlatformCreateView', - 'PlatformDetailView', -] - - -class PlatformListView(PermissionsMixin, generic.TemplateView): - template_name = 'assets/platform_list.html' - permission_classes = (IsSuperUser,) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({ - 'app': _('Assets'), - 'action': _("Platform list"), - }) - return context - - -class PlatformCreateView(PermissionsMixin, generic.CreateView): - form_class = PlatformForm - permission_classes = (IsSuperUser,) - template_name = 'assets/platform_create_update.html' - model = Platform - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - meta_form = PlatformMetaForm() - context.update({ - 'app': _('Assets'), - 'action': _("Create platform"), - 'meta_form': meta_form, - }) - return context - - -class PlatformUpdateView(generic.UpdateView): - form_class = PlatformForm - permission_classes = (IsSuperUser,) - model = Platform - template_name = 'assets/platform_create_update.html' - - def post(self, *args, **kwargs): - pass - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - meta_form = PlatformMetaForm(initial=self.object.meta) - context.update({ - 'app': _('Assets'), - 'action': _("Update platform"), - 'type': 'update', - 'meta_form': meta_form, - }) - return context - - -class PlatformDetailView(generic.DetailView): - permission_classes = (IsSuperUser,) - model = Platform - template_name = 'assets/platform_detail.html' - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({ - 'app': _('Assets'), - 'action': _("Platform detail"), - }) - return context diff --git a/apps/assets/views/system_user.py b/apps/assets/views/system_user.py deleted file mode 100644 index 5aa1e8431..000000000 --- a/apps/assets/views/system_user.py +++ /dev/null @@ -1,122 +0,0 @@ -# ~*~ coding: utf-8 ~*~ - -from django.utils.translation import ugettext as _ -from django.views.generic import TemplateView -from django.views.generic.edit import CreateView, DeleteView, UpdateView -from django.urls import reverse_lazy -from django.contrib.messages.views import SuccessMessageMixin -from django.views.generic.detail import DetailView - -from common.const import create_success_msg, update_success_msg -from ..forms import SystemUserForm -from ..models import SystemUser, Node, CommandFilter -from common.permissions import PermissionsMixin, IsOrgAdmin - - -__all__ = [ - 'SystemUserCreateView', 'SystemUserUpdateView', - 'SystemUserDetailView', 'SystemUserDeleteView', - 'SystemUserAssetView', 'SystemUserListView', - 'SystemUserUserView', -] - - -class SystemUserListView(PermissionsMixin, TemplateView): - template_name = 'assets/system_user_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('System user list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SystemUserCreateView(PermissionsMixin, SuccessMessageMixin, CreateView): - model = SystemUser - form_class = SystemUserForm - template_name = 'assets/system_user_create.html' - success_url = reverse_lazy('assets:system-user-list') - success_message = create_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Create system user'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SystemUserUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView): - model = SystemUser - form_class = SystemUserForm - template_name = 'assets/system_user_update.html' - success_url = reverse_lazy('assets:system-user-list') - success_message = update_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Assets'), - 'action': _('Update system user') - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SystemUserDetailView(PermissionsMixin, DetailView): - template_name = 'assets/system_user_detail.html' - context_object_name = 'system_user' - model = SystemUser - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - cmd_filters_remain = CommandFilter.objects.exclude(system_users=self.object) - context = { - 'app': _('Assets'), - 'action': _('System user detail'), - 'cmd_filters_remain': cmd_filters_remain, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SystemUserDeleteView(PermissionsMixin, DeleteView): - model = SystemUser - template_name = 'delete_confirm.html' - success_url = reverse_lazy('assets:system-user-list') - permission_classes = [IsOrgAdmin] - - -class SystemUserAssetView(PermissionsMixin, DetailView): - model = SystemUser - template_name = 'assets/system_user_assets.html' - context_object_name = 'system_user' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('assets'), - 'action': _('System user assets'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SystemUserUserView(PermissionsMixin, DetailView): - model = SystemUser - template_name = 'assets/system_user_users.html' - context_object_name = 'system_user' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('assets'), - 'action': _('System user users'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/audits/templates/audits/ftp_log_list.html b/apps/audits/templates/audits/ftp_log_list.html deleted file mode 100644 index e0399ca73..000000000 --- a/apps/audits/templates/audits/ftp_log_list.html +++ /dev/null @@ -1,138 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% load terminal_tags %} -{% load common_tags %} -{% block custom_head_css_js %} - - -{% endblock %} - -{% block content_left_head %} -{% endblock %} - - -{% block table_search %} -
-
-
- - - to - -
-
-
- -
-
- -
-
- -
-
- -
-
-
- -
-
-
-{% endblock %} - -{% block table_head %} -{# {% trans 'ID' %}#} - {% trans 'User' %} - {% trans 'Asset' %} - {% trans 'System user' %} - {% trans 'Remote addr' %} - {% trans 'Operate' %} - {% trans 'Filename' %} - {% trans 'Success' %} - {% trans 'Date start' %} -{# {% trans 'Action' %}#} -{% endblock %} - -{% block table_body %} - {% for object in object_list %} - -{# #} -{# {{ forloop.counter }}#} -{# #} - {{ object.user }} - {{ object.asset }} - {{ object.system_user }} - {{ object.remote_addr|default:"" }} - {{ object.operate }} - {{ object.filename }} - - {% if object.is_success %} - - {% else %} - - {% endif %} - - {{ object.date_start }} - - {% endfor %} -{% endblock %} - -{% block content_bottom_left %} -{% endblock %} - -{% block custom_foot_js %} - - -{% endblock %} - diff --git a/apps/audits/templates/audits/login_log_list.html b/apps/audits/templates/audits/login_log_list.html deleted file mode 100644 index 1ac74d311..000000000 --- a/apps/audits/templates/audits/login_log_list.html +++ /dev/null @@ -1,148 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% load common_tags %} -{% block content_left_head %} - - -{% endblock %} - - -{% block table_search %} -
-
-
- - -{# #} - to - -
-
-
- -
-
- -
-
-
- -
-
-
-{% endblock %} -{% block table_container %} - - - - - - - - - - - - - - - - - - {% for login_log in object_list %} - - - - - - - - - - - - - {% endfor %} - -
{% trans 'ID' %}{% trans 'Username' %}{% trans 'Type' %}{% trans 'UA' %}{% trans 'IP' %}{% trans 'City' %}{% trans 'MFA' %}{% trans 'Reason' %}{% trans 'Status' %}{% trans 'Date' %}
{{ forloop.counter }}{{ login_log.username }}{{ login_log.get_type_display }} - {{ login_log.user_agent | truncatechars:20 }} - {{ login_log.ip }}{{ login_log.city }}{{ login_log.get_mfa_display }}{{ login_log.reason_display }}{{ login_log.get_status_display }}{{ login_log.datetime }}
-
-
- -
- -
-
-
- -{% endblock %} - - -{% block custom_foot_js %} - - -{% endblock %} - diff --git a/apps/audits/templates/audits/operate_log_list.html b/apps/audits/templates/audits/operate_log_list.html deleted file mode 100644 index 0dd8c8ff4..000000000 --- a/apps/audits/templates/audits/operate_log_list.html +++ /dev/null @@ -1,123 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% load terminal_tags %} -{% load common_tags %} -{% block custom_head_css_js %} - - -{% endblock %} - -{% block content_left_head %} -{% endblock %} - - -{% block table_search %} -
-
-
- - - to - -
-
-
- -
-
- -
-
- -
-
-
- -
-
-
-{% endblock %} - -{% block table_head %} - {% trans 'Handlers' %} - {% trans 'Action' %} - {% trans 'Resource Type' %} - {% trans 'Resource' %} - {% trans 'Remote addr' %} - {% trans 'Datetime' %} -{% endblock %} - -{% block table_body %} - {% for object in object_list %} - -{# #} -{# {{ forloop.counter }}#} -{# #} - {{ object.user }} - {{ object.get_action_display }} - {{ object.resource_type }} - {{ object.resource }} - {{ object.remote_addr }} - {{ object.datetime }} - - {% endfor %} -{% endblock %} - -{% block content_bottom_left %} -{% endblock %} - -{% block custom_foot_js %} - - -{% endblock %} - diff --git a/apps/audits/templates/audits/password_change_log_list.html b/apps/audits/templates/audits/password_change_log_list.html deleted file mode 100644 index 506366cbf..000000000 --- a/apps/audits/templates/audits/password_change_log_list.html +++ /dev/null @@ -1,100 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% load terminal_tags %} -{% load common_tags %} -{% block custom_head_css_js %} - - -{% endblock %} - -{% block content_left_head %} -{% endblock %} - - -{% block table_search %} -
-
-
- - - to - -
-
-
- -
-
-
- -
-
-
-{% endblock %} - -{% block table_head %} - {% trans 'User' %} - {% trans 'Change by' %} - {% trans 'Remote addr' %} - {% trans 'Datetime' %} -{% endblock %} - -{% block table_body %} - {% for object in object_list %} - - {{ object.user }} - {{ object.change_by }} - {{ object.remote_addr }} - {{ object.datetime }} - - {% endfor %} -{% endblock %} - -{% block content_bottom_left %} -{% endblock %} - -{% block custom_foot_js %} - - -{% endblock %} - diff --git a/apps/audits/urls/view_urls.py b/apps/audits/urls/view_urls.py index ef400cb99..2066db771 100644 --- a/apps/audits/urls/view_urls.py +++ b/apps/audits/urls/view_urls.py @@ -2,17 +2,10 @@ from __future__ import unicode_literals from django.urls import path -from .. import views __all__ = ["urlpatterns"] app_name = "audits" urlpatterns = [ - path('login-log/', views.LoginLogListView.as_view(), name='login-log-list'), - path('ftp-log/', views.FTPLogListView.as_view(), name='ftp-log-list'), - path('operate-log/', views.OperateLogListView.as_view(), name='operate-log-list'), - path('password-change-log/', views.PasswordChangeLogList.as_view(), name='password-change-log-list'), - path('command-execution-log/', views.CommandExecutionListView.as_view(), name='command-execution-log-list'), - path('login-log/export/', views.LoginLogExportView.as_view(), name='login-log-export'), ] diff --git a/apps/audits/views.py b/apps/audits/views.py deleted file mode 100644 index 6ac0b1b99..000000000 --- a/apps/audits/views.py +++ /dev/null @@ -1,292 +0,0 @@ -import csv -import json -import uuid -import codecs - - -from django.conf import settings -from django.urls import reverse -from django.utils import timezone -from django.core.cache import cache -from django.http import HttpResponse, JsonResponse -from django.utils.decorators import method_decorator -from django.views import View -from django.views.decorators.csrf import csrf_exempt -from django.views.generic import ListView -from django.utils.translation import ugettext as _ -from django.db.models import Q - -from audits.utils import get_excel_response, write_content_to_excel -from common.mixins import DatetimeSearchMixin -from common.permissions import ( - PermissionsMixin, IsOrgAdmin, IsValidUser, IsOrgAuditor -) -from orgs.utils import current_org -from ops.views import CommandExecutionListView as UserCommandExecutionListView -from .models import FTPLog, OperateLog, PasswordChangeLog, UserLoginLog - - -def get_resource_type_list(): - from users.models import User, UserGroup - from assets.models import ( - Asset, Node, AdminUser, SystemUser, Domain, Gateway, CommandFilter, - CommandFilterRule, - ) - from orgs.models import Organization - from perms.models import AssetPermission - - models = [ - User, UserGroup, Asset, Node, AdminUser, SystemUser, Domain, - Gateway, Organization, AssetPermission, CommandFilter, CommandFilterRule - ] - return [model._meta.verbose_name for model in models] - - -class FTPLogListView(PermissionsMixin, DatetimeSearchMixin, ListView): - model = FTPLog - template_name = 'audits/ftp_log_list.html' - paginate_by = settings.DISPLAY_PER_PAGE - user = asset = system_user = filename = '' - date_from = date_to = None - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - def get_queryset(self): - self.queryset = super().get_queryset() - self.user = self.request.GET.get('user') - self.asset = self.request.GET.get('asset') - self.system_user = self.request.GET.get('system_user') - self.filename = self.request.GET.get('filename', '') - - filter_kwargs = dict() - filter_kwargs['date_start__gt'] = self.date_from - filter_kwargs['date_start__lt'] = self.date_to - if self.user: - filter_kwargs['user'] = self.user - if self.asset: - filter_kwargs['asset'] = self.asset - if self.system_user: - filter_kwargs['system_user'] = self.system_user - if self.filename: - filter_kwargs['filename__contains'] = self.filename - if filter_kwargs: - self.queryset = self.queryset.filter(**filter_kwargs).order_by('-date_start') - return self.queryset - - def get_context_data(self, **kwargs): - context = { - 'user_list': FTPLog.objects.values_list('user', flat=True).distinct(), - 'asset_list': FTPLog.objects.values_list('asset', flat=True).distinct(), - 'system_user_list': FTPLog.objects.values_list('system_user', flat=True).distinct(), - 'date_from': self.date_from, - 'date_to': self.date_to, - 'user': self.user, - 'asset': self.asset, - 'system_user': self.system_user, - 'filename': self.filename, - "app": _("Audits"), - "action": _("FTP log"), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class OperateLogListView(PermissionsMixin, DatetimeSearchMixin, ListView): - model = OperateLog - template_name = 'audits/operate_log_list.html' - paginate_by = settings.DISPLAY_PER_PAGE - user = action = resource_type = '' - date_from = date_to = None - actions_dict = dict(OperateLog.ACTION_CHOICES) - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - def get_queryset(self): - self.queryset = super().get_queryset() - self.user = self.request.GET.get('user') - self.action = self.request.GET.get('action') - self.resource_type = self.request.GET.get('resource_type') - - filter_kwargs = dict() - filter_kwargs['datetime__gt'] = self.date_from - filter_kwargs['datetime__lt'] = self.date_to - if self.user: - filter_kwargs['user'] = self.user - if self.action: - filter_kwargs['action'] = self.action - if self.resource_type: - filter_kwargs['resource_type'] = self.resource_type - if filter_kwargs: - self.queryset = self.queryset.filter(**filter_kwargs).order_by('-datetime') - return self.queryset - - def get_context_data(self, **kwargs): - context = { - 'user_list': [str(user) for user in current_org.get_org_members()], - 'actions': self.actions_dict, - 'search_action': self.action, - 'resource_type_list': get_resource_type_list(), - 'date_from': self.date_from, - 'date_to': self.date_to, - 'user': self.user, - 'resource_type': self.resource_type, - "app": _("Audits"), - "action": _("Operate log"), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class PasswordChangeLogList(PermissionsMixin, DatetimeSearchMixin, ListView): - model = PasswordChangeLog - template_name = 'audits/password_change_log_list.html' - paginate_by = settings.DISPLAY_PER_PAGE - user = '' - date_from = date_to = None - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - def get_queryset(self): - users = current_org.get_org_members() - self.queryset = super().get_queryset().filter( - user__in=[user.__str__() for user in users] - ) - self.user = self.request.GET.get('user') - - filter_kwargs = dict() - filter_kwargs['datetime__gt'] = self.date_from - filter_kwargs['datetime__lt'] = self.date_to - if self.user: - filter_kwargs['user'] = self.user - if filter_kwargs: - self.queryset = self.queryset.filter(**filter_kwargs).order_by('-datetime') - return self.queryset - - def get_context_data(self, **kwargs): - context = { - 'user_list': [str(user) for user in current_org.get_org_members()], - 'date_from': self.date_from, - 'date_to': self.date_to, - 'user': self.user, - "app": _("Audits"), - "action": _("Password change log"), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class LoginLogListView(PermissionsMixin, DatetimeSearchMixin, ListView): - template_name = 'audits/login_log_list.html' - model = UserLoginLog - paginate_by = settings.DISPLAY_PER_PAGE - user = keyword = "" - date_to = date_from = None - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - @staticmethod - def get_org_members(): - users = current_org.get_org_members().values_list('username', flat=True) - return users - - def get_queryset(self): - if current_org.is_default(): - queryset = super().get_queryset() - else: - users = self.get_org_members() - queryset = super().get_queryset().filter(username__in=users) - - self.user = self.request.GET.get('user', '') - self.keyword = self.request.GET.get("keyword", '') - - queryset = queryset.filter( - datetime__gt=self.date_from, datetime__lt=self.date_to - ) - if self.user: - queryset = queryset.filter(username=self.user) - if self.keyword: - queryset = queryset.filter( - Q(ip__contains=self.keyword) | - Q(city__contains=self.keyword) | - Q(username__contains=self.keyword) - ) - return queryset - - def get_context_data(self, **kwargs): - context = { - 'app': _('Audits'), - 'action': _('Login log'), - 'date_from': self.date_from, - 'date_to': self.date_to, - 'user': self.user, - 'keyword': self.keyword, - 'user_list': self.get_org_members(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandExecutionListView(UserCommandExecutionListView): - user_id = None - - def get_user_list(self): - users = current_org.get_org_members(exclude=('Auditor',)) - return users - - def get_queryset(self): - queryset = self._get_queryset() - self.user_id = self.request.GET.get('user') - org_users = self.get_user_list() - if self.user_id: - queryset = queryset.filter(user=self.user_id) - else: - queryset = queryset.filter(user__in=org_users) - return queryset - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({ - 'app': _('Audits'), - 'action': _('Command execution log'), - 'date_from': self.date_from, - 'date_to': self.date_to, - 'user_list': [(str(user.id), user) for user in self.get_user_list()], - 'keyword': self.keyword, - 'user_id': self.user_id, - }) - return context - - -@method_decorator(csrf_exempt, name='dispatch') -class LoginLogExportView(PermissionsMixin, View): - permission_classes = [IsValidUser] - - def get(self, request): - fields = [ - field for field in UserLoginLog._meta.fields - ] - filename = 'login-logs-{}.csv'.format( - timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S') - ) - excel_response = get_excel_response(filename) - header = [field.verbose_name for field in fields] - login_logs = cache.get(request.GET.get('spm', ''), []) - - response = write_content_to_excel( - excel_response, login_logs=login_logs, header=header, fields=fields - ) - return response - - def post(self, request): - try: - date_from = json.loads(request.body).get('date_from', []) - date_to = json.loads(request.body).get('date_to', []) - user = json.loads(request.body).get('user', []) - keyword = json.loads(request.body).get('keyword', []) - - login_logs = UserLoginLog.get_login_logs( - date_from=date_from, date_to=date_to, user=user, - keyword=keyword, - ) - except ValueError: - return HttpResponse('Json object not valid', status=400) - spm = uuid.uuid4().hex - cache.set(spm, login_logs, 300) - url = reverse('audits:login-log-export') + '?spm=%s' % spm - return JsonResponse({'redirect': url}) \ No newline at end of file diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index bfe7aed83..d5c28bc93 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -31,32 +31,11 @@ api_v2 = [ path('users/', include('users.urls.api_urls_v2', namespace='api-users-v2')), ] -disabled_view_pattern = [ - path('users/', include('users.urls.views_urls', namespace='users')), - path('assets/', include('assets.urls.views_urls', namespace='assets')), - path('perms/', include('perms.urls.views_urls', namespace='perms')), - path('terminal/', include('terminal.urls.views_urls', namespace='terminal')), - path('ops/', include('ops.urls.view_urls', namespace='ops')), - path('audits/', include('audits.urls.view_urls', namespace='audits')), - path('orgs/', include('orgs.urls.views_urls', namespace='orgs')), - path('applications/', include('applications.urls.views_urls', namespace='applications')), - path('tickets/', include('tickets.urls.views_urls', namespace='tickets')), - re_path(r'flower/(?P.*)', views.celery_flower_view, name='flower-view'), - re_path('luna/.*', views.LunaView.as_view(), name='luna-view'), - re_path('koko/.*', views.KokoView.as_view(), name='koko-view'), - re_path('ws/.*', views.WsView.as_view(), name='ws-view'), - path('i18n//', views.I18NView.as_view(), name='i18n-switch'), - path('settings/', include('settings.urls.view_urls', namespace='settings')), -] - app_view_patterns = [ path('auth/', include('authentication.urls.view_urls'), name='auth'), ] -if os.environ.get('ENABLE_OLD_VIEW'): - app_view_patterns += disabled_view_pattern - if settings.XPACK_ENABLED: app_view_patterns.append( diff --git a/apps/ops/forms.py b/apps/ops/forms.py deleted file mode 100644 index 6658980f8..000000000 --- a/apps/ops/forms.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django import forms - -from assets.models import SystemUser -from .models import CommandExecution - - -class CommandExecutionForm(forms.ModelForm): - class Meta: - model = CommandExecution - fields = ['run_as', 'command'] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - run_as_field = self.fields.get('run_as') - run_as_field.queryset = SystemUser.objects.all() diff --git a/apps/ops/templates/ops/adhoc_detail.html b/apps/ops/templates/ops/adhoc_detail.html deleted file mode 100644 index 1a28ee0e8..000000000 --- a/apps/ops/templates/ops/adhoc_detail.html +++ /dev/null @@ -1,201 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.task.name }}: {{ object.short_id }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - {% if object.run_as_admin %} - - - - - {% else %} - - - - - {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'ID' %}:{{ object.id }}
{% trans 'Hosts' %}:{{ object.hosts | length }}
{% trans 'Pattern' %}:{{ object.pattern }}
{% trans 'Options' %} - - {% for k, v in object.options.items %} - {{ k }} = {{ v }}
- {% endfor %} -
-
{% trans 'Run as' %} Admin
{% trans 'Run as' %}:{{ object.get_latest_execution.date_start }}
{% trans 'Become' %}{{ object.become_display }}
{% trans 'Created by' %}{{ object.created_by }}
{% trans 'Date created' %}:{{ object.date_created }}
{% trans 'Run times' %}:{{ object.execution.all | length }}
{% trans 'Last run' %}:{{ object.latest_execution.short_id }}
{% trans 'Time delta' %}:{{ object.latest_execution.timedelta|floatformat}} s
{% trans 'Is finished' %}:{{ object.latest_execution.is_finished|yesno:"Yes,No,Unkown" }}
{% trans 'Is success ' %}:{{ object.latest_execution.is_success|yesno:"Yes,No,Unkown" }}
{% trans 'Tasks' %}: - - {% for task in object.tasks %} - {{ forloop.counter }}. {{ task.name }} ::: {{ task.action.module }}
- {% endfor %} -
-
-
-
-
-
-
-
- {% trans 'Last run failed hosts' %} -
-
- - - {% for host, task in object.latest_execution.failed_hosts.items %} - {% if forloop.first %} - - {% else %} - - {% endif %} - - - - {% empty %} - - - - {% endfor %} - -
{{ host }}: - {% for name, result in task.items %} - {{ name }} => {{ result.msg }} - {% endfor %} -
{% trans 'No hosts' %}
-
-
- -
-
- {% trans 'Last run success hosts' %} -
-
- - - {% for host in object.latest_execution.success_hosts %} - {% if forloop.first %} - - {% else %} - - {% endif %} - - - {% empty %} - - - - {% endfor %} - -
{{ host }}
{% trans 'No hosts' %}
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/ops/templates/ops/adhoc_history.html b/apps/ops/templates/ops/adhoc_history.html deleted file mode 100644 index e9662cb45..000000000 --- a/apps/ops/templates/ops/adhoc_history.html +++ /dev/null @@ -1,146 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Executions of ' %} {{ object.task.name }}:{{ object.short_id }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - -
- - {% trans 'Date start' %}{% trans 'F/S/T' %}{% trans 'Ratio' %}{% trans 'Is finished' %}{% trans 'Is success' %}{% trans 'Time' %}{% trans 'Version' %}{% trans 'Action' %}
-
-
-
-
-
-
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/ops/templates/ops/adhoc_history_detail.html b/apps/ops/templates/ops/adhoc_history_detail.html deleted file mode 100644 index 52e5e03f0..000000000 --- a/apps/ops/templates/ops/adhoc_history_detail.html +++ /dev/null @@ -1,152 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Execution detail of' %} {{ object.task.name }}: {{ object.adhoc.short_id }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'ID' %}:{{ object.id }}
{% trans 'Task name' %}:{{ object.task.name }}
{% trans 'Version' %}:{{ object.adhoc.short_id }}
{% trans 'Date start' %}:{{ object.date_start }}
{% trans 'Time delta' %}:{{ object.timedelta|floatformat}} s
{% trans 'Is finished' %}:{{ object.is_finished|yesno:"Yes,No,Unkown" }}
{% trans 'Is success ' %}:{{ object.is_success|yesno:"Yes,No,Unkown" }}
-
-
-
-
-
-
- {% trans 'Failed assets' %} -
-
- - - {% for host, task in object.failed_hosts.items %} - {% if forloop.first %} - - {% else %} - - {% endif %} - - - - {% empty %} - - - - {% endfor %} - -
{{ host }}: - {% for name, result in task.items %} - {{ name }} => {{ result.msg }} - {% endfor %} -
{% trans 'No assets' %}
-
-
- -
-
- {% trans 'Success assets' %} -
-
- - - {% for host in object.success_hosts %} - {% if forloop.first %} - - {% else %} - - {% endif %} - - - {% empty %} - - - - {% endfor %} - -
{{ host }}
{% trans 'No assets' %}
-
-
-
-
-
-
-
-
- {% include 'users/_user_update_pk_modal.html' %} -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/ops/templates/ops/celery_task_log.html b/apps/ops/templates/ops/celery_task_log.html deleted file mode 100644 index 455fc28d6..000000000 --- a/apps/ops/templates/ops/celery_task_log.html +++ /dev/null @@ -1,91 +0,0 @@ -{% load static %} -{% load i18n %} - - {% trans 'Task log' %} - - - - - - - -
-
- - diff --git a/apps/ops/templates/ops/command_execution_create.html b/apps/ops/templates/ops/command_execution_create.html deleted file mode 100644 index 8912cbe71..000000000 --- a/apps/ops/templates/ops/command_execution_create.html +++ /dev/null @@ -1,335 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} -{% load bootstrap3 %} - -{% block custom_head_css_js %} - - - - - - - - - - - - -{% endblock %} - -{% block content %} -
-
-
-
-
-
-
- {% trans 'Loading' %} .. -
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- -
-
-
- - -
-
-
-
-
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/ops/templates/ops/command_execution_list.html b/apps/ops/templates/ops/command_execution_list.html deleted file mode 100644 index a21a1b108..000000000 --- a/apps/ops/templates/ops/command_execution_list.html +++ /dev/null @@ -1,129 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% load common_tags %} - -{% block custom_head_css_js %} - - - -{% endblock %} - -{% block content_left_head %} -{% endblock %} - -{% block table_search %} -
-
-
- - - to - -
-
- {% if user_list %} -
- -
- {% endif %} -
- -
-
-
- -
-
-
-{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - - {% for object in object_list %} - - - - - - - - - - - - {% endfor %} - -
{% trans 'Hosts' %}{% trans 'User' %}{% trans 'Command' %}{% trans 'Run as' %}{% trans 'Output' %}{% trans 'Finished' %}{% trans 'Success' %}{% trans 'Date start' %}
{{ forloop.counter }}{{ object.user.name }}{{ object.command| truncatechars:16 }}{{ object.run_as.username }}查看{{ object.is_finished | state_show | safe }}{{ object.is_success | state_show | safe }}{{ object.date_start }}
-{% endblock %} - -{% block custom_foot_js %} - - - -{% endblock %} - diff --git a/apps/ops/templates/ops/task_adhoc.html b/apps/ops/templates/ops/task_adhoc.html deleted file mode 100644 index bddd2388e..000000000 --- a/apps/ops/templates/ops/task_adhoc.html +++ /dev/null @@ -1,138 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Versions of ' %} {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - -
- - {% trans 'Version' %}{% trans 'Hosts' %}{% trans 'Pattern' %}{% trans 'Run as' %}{% trans 'Become' %}{% trans 'Datetime' %}{% trans 'Action' %}
-
-
-
-
-
-
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/ops/templates/ops/task_detail.html b/apps/ops/templates/ops/task_detail.html deleted file mode 100644 index 39696b3d9..000000000 --- a/apps/ops/templates/ops/task_detail.html +++ /dev/null @@ -1,192 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'ID' %}:{{ object.id }}
{% trans 'Name' %}:{{ object.name }}
{% trans 'Date created' %}:{{ object.date_created }}
{% trans 'Total versions' %}{{ object.adhoc.all |length }}
{% trans 'Latest version' %}{{ object.latest_adhoc.short_id }}
{% trans 'Last run' %}:{{ object.latest_execution.date_start }}
{% trans 'Time delta' %}:{{ object.latest_execution.timedelta|floatformat}} s
{% trans 'Is finished' %}: - {% if object.latest_execution.is_finished %} - {% trans 'Yes' %} - {% else %} - {% trans 'No' %} - {% endif %} -
{% trans 'Is success ' %}: - {% if object.latest_execution.is_success %} - {% trans 'Yes' %} - {% else %} - {% trans 'No' %} - {% endif %} -
{% trans 'Contents' %}: - - {% for task in object.latest_adhoc.tasks %} - {{ forloop.counter }}. {{ task.name }} ::: {{ task.action.module }}
- {% endfor %} -
-
-
-
-
-
-
-
- {% trans 'Last run failed hosts' %} -
-
- - - {% for host, task in object.latest_execution.failed_hosts.items %} - {% if forloop.first %} - - {% else %} - - {% endif %} - - - - {% empty %} - - - - {% endfor %} - -
{{ host }}: - {% for name, result in task.items %} - {{ name }} => {{ result.msg }} - {% endfor %} -
{% trans 'No hosts' %}
-
-
- -
-
- {% trans 'Last run success hosts' %} -
-
- - - {% for host in object.latest_execution.success_hosts %} - {% if forloop.first %} - - {% else %} - - {% endif %} - - - {% empty %} - - - - {% endfor %} - -
{{ host }}
{% trans 'No hosts' %}
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/ops/templates/ops/task_history.html b/apps/ops/templates/ops/task_history.html deleted file mode 100644 index 06dc3416c..000000000 --- a/apps/ops/templates/ops/task_history.html +++ /dev/null @@ -1,160 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Executions of ' %} {{ object.name }}:{{ object.short_id }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - -
- - {% trans 'Date start' %}{% trans 'F/S/T' %}{% trans 'Ratio' %}{% trans 'Is finished' %}{% trans 'Is success' %}{% trans 'Time' %}{% trans 'Version' %}{% trans 'Action' %}
-
-
-
-
-
-
-
-
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/ops/templates/ops/task_list.html b/apps/ops/templates/ops/task_list.html deleted file mode 100644 index a004138df..000000000 --- a/apps/ops/templates/ops/task_list.html +++ /dev/null @@ -1,119 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %}{% endblock %} -{% block table_container %} - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Run times' %}{% trans 'Hosts' %}{% trans 'Success' %}{% trans 'Date' %}{% trans 'Time' %}{% trans 'Action' %}
-{% endblock %} - -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/ops/urls/view_urls.py b/apps/ops/urls/view_urls.py index f17fb8662..fb52a3e07 100644 --- a/apps/ops/urls/view_urls.py +++ b/apps/ops/urls/view_urls.py @@ -1,24 +1,9 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals -from django.urls import path - -from .. import views __all__ = ["urlpatterns"] app_name = "ops" urlpatterns = [ - # Resource Task url - path('task/', views.TaskListView.as_view(), name='task-list'), - path('task//', views.TaskDetailView.as_view(), name='task-detail'), - path('task//adhoc/', views.TaskAdhocView.as_view(), name='task-adhoc'), - path('task//executions/', views.TaskExecutionView.as_view(), name='task-execution'), - path('adhoc//', views.AdHocDetailView.as_view(), name='adhoc-detail'), - path('adhoc//executions/', views.AdHocExecutionView.as_view(), name='adhoc-execution'), - path('adhoc/executions//', views.AdHocExecutionDetailView.as_view(), name='adhoc-execution-detail'), - path('celery/task//log/', views.CeleryTaskLogView.as_view(), name='celery-task-log'), - - path('command-executions/', views.CommandExecutionListView.as_view(), name='command-execution-list'), - path('command-executions/create/', views.CommandExecutionCreateView.as_view(), name='command-execution-create'), ] diff --git a/apps/ops/views/__init__.py b/apps/ops/views/__init__.py deleted file mode 100644 index 58bb835a6..000000000 --- a/apps/ops/views/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .adhoc import * -from .celery import * -from .command import * \ No newline at end of file diff --git a/apps/ops/views/adhoc.py b/apps/ops/views/adhoc.py deleted file mode 100644 index e21c7b575..000000000 --- a/apps/ops/views/adhoc.py +++ /dev/null @@ -1,127 +0,0 @@ -# ~*~ coding: utf-8 ~*~ - -from django.utils.translation import ugettext as _ -from django.conf import settings -from django.views.generic import ListView, DetailView, TemplateView - -from common.mixins import DatetimeSearchMixin -from common.permissions import PermissionsMixin, IsOrgAdmin -from orgs.utils import current_org -from ..models import Task, AdHoc, AdHocExecution - - -__all__ = [ - 'TaskListView', 'TaskDetailView', 'TaskExecutionView', - 'TaskAdhocView', 'AdHocDetailView', 'AdHocExecutionDetailView', - 'AdHocExecutionView' -] - - -class TaskListView(PermissionsMixin, TemplateView): - paginate_by = settings.DISPLAY_PER_PAGE - model = Task - ordering = ('-date_created',) - context_object_name = 'task_list' - template_name = 'ops/task_list.html' - keyword = '' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Task list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class TaskDetailView(PermissionsMixin, DetailView): - model = Task - template_name = 'ops/task_detail.html' - permission_classes = [IsOrgAdmin] - - def get_queryset(self): - queryset = super().get_queryset() - return queryset - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Task detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class TaskAdhocView(PermissionsMixin, DetailView): - model = Task - template_name = 'ops/task_adhoc.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Task versions'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class TaskExecutionView(PermissionsMixin, DetailView): - model = Task - template_name = 'ops/task_history.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Task execution list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdHocDetailView(PermissionsMixin, DetailView): - model = AdHoc - template_name = 'ops/adhoc_detail.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Task detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdHocExecutionView(PermissionsMixin, DetailView): - model = AdHoc - template_name = 'ops/adhoc_history.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Version run execution'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AdHocExecutionDetailView(PermissionsMixin, DetailView): - model = AdHocExecution - template_name = 'ops/adhoc_history_detail.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Execution detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - - - diff --git a/apps/ops/views/celery.py b/apps/ops/views/celery.py deleted file mode 100644 index 9ae2d9755..000000000 --- a/apps/ops/views/celery.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django.views.generic import TemplateView -from django.conf import settings - -from common.permissions import PermissionsMixin, IsOrgAdmin, IsOrgAuditor - - -__all__ = ['CeleryTaskLogView'] - - -class CeleryTaskLogView(PermissionsMixin, TemplateView): - template_name = 'ops/celery_task_log.html' - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({ - 'task_id': self.kwargs.get('pk'), - 'ws_port': settings.WS_LISTEN_PORT - }) - return context diff --git a/apps/ops/views/command.py b/apps/ops/views/command.py deleted file mode 100644 index 4dfc3d2e7..000000000 --- a/apps/ops/views/command.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.utils.translation import ugettext as _ -from django.conf import settings -from django.views.generic import ListView, TemplateView - -from common.permissions import ( - PermissionsMixin, IsOrgAdmin, IsValidUser, IsOrgAuditor -) -from common.mixins import DatetimeSearchMixin -from orgs.utils import tmp_to_root_org -from ..models import CommandExecution -from ..forms import CommandExecutionForm - - -__all__ = [ - 'CommandExecutionListView', 'CommandExecutionCreateView' -] - - -class CommandExecutionListView(PermissionsMixin, DatetimeSearchMixin, ListView): - template_name = 'ops/command_execution_list.html' - model = CommandExecution - paginate_by = settings.DISPLAY_PER_PAGE - ordering = ('-date_created',) - context_object_name = 'task_list' - keyword = '' - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - def _get_queryset(self): - self.keyword = self.request.GET.get('keyword', '') - queryset = super().get_queryset() - if self.date_from: - queryset = queryset.filter(date_start__gte=self.date_from) - if self.date_to: - queryset = queryset.filter(date_start__lte=self.date_to) - if self.keyword: - queryset = queryset.filter(command__icontains=self.keyword) - return queryset - - def get_queryset(self): - queryset = self._get_queryset().filter(user=self.request.user) - return queryset - - def get_context_data(self, **kwargs): - context = { - 'app': _('Ops'), - 'action': _('Command execution list'), - 'date_from': self.date_from, - 'date_to': self.date_to, - 'keyword': self.keyword, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandExecutionCreateView(PermissionsMixin, TemplateView): - template_name = 'ops/command_execution_create.html' - form_class = CommandExecutionForm - permission_classes = [IsValidUser] - - def get_permissions(self): - if not settings.SECURITY_COMMAND_EXECUTION: - return [IsOrgAdmin] - return super().get_permissions() - - def get_user_system_users(self): - from perms.utils import AssetPermissionUtil - user = self.request.user - with tmp_to_root_org(): - util = AssetPermissionUtil(user) - system_users = util.get_system_users() - return system_users - - def get_context_data(self, **kwargs): - system_users = self.get_user_system_users() - context = { - 'app': _('Ops'), - 'action': _('Command execution'), - 'form': self.get_form(), - 'system_users': system_users, - 'ws_port': settings.WS_LISTEN_PORT - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def get_form(self): - return self.form_class() diff --git a/apps/orgs/urls/views_urls.py b/apps/orgs/urls/views_urls.py index 225457827..581eed2b0 100644 --- a/apps/orgs/urls/views_urls.py +++ b/apps/orgs/urls/views_urls.py @@ -1,14 +1,7 @@ # -*- coding: utf-8 -*- # - -from django.urls import path - -from .. import views - app_name = 'orgs' urlpatterns = [ - path('/switch/', views.SwitchOrgView.as_view(), name='org-switch'), - path('switch-a-org/', views.SwitchToAOrgView.as_view(), name='switch-a-org') ] diff --git a/apps/orgs/views.py b/apps/orgs/views.py deleted file mode 100644 index 0cbcd00e0..000000000 --- a/apps/orgs/views.py +++ /dev/null @@ -1,53 +0,0 @@ -from django.shortcuts import redirect, reverse -from django.conf import settings -from django.http import HttpResponseForbidden - -from django.views.generic import DetailView, View - -from .models import Organization -from common.utils import UUID_PATTERN - - -class SwitchOrgView(DetailView): - model = Organization - object = None - - def get(self, request, *args, **kwargs): - pk = kwargs.get('pk') - self.object = Organization.get_instance(pk) - oid = str(self.object.id) - request.session['oid'] = oid - org_change_to_url = settings.ORG_CHANGE_TO_URL - if org_change_to_url: - return redirect(org_change_to_url) - host = request.get_host() - referer = request.META.get('HTTP_REFERER', '') - if referer.find(host) == -1: - return redirect(reverse('index')) - if UUID_PATTERN.search(referer): - return redirect(reverse('index')) - # 组织管理员切换到组织审计员时(403) - if not self.object.get_org_admins().filter(id=request.user.id): - return redirect(reverse('index')) - return redirect(referer) - - -class SwitchToAOrgView(View): - def get(self, request, *args, **kwargs): - if request.user.is_common_user: - return HttpResponseForbidden() - admin_orgs = request.user.admin_orgs - audit_orgs = request.user.audit_orgs - default_org = Organization.default() - if admin_orgs: - if default_org in admin_orgs: - redirect_org = default_org - else: - redirect_org = admin_orgs[0] - return redirect(reverse('orgs:org-switch', kwargs={'pk': redirect_org.id})) - if audit_orgs: - if default_org in audit_orgs: - redirect_org = default_org - else: - redirect_org = audit_orgs[0] - return redirect(reverse('orgs:org-switch', kwargs={'pk': redirect_org.id})) diff --git a/apps/perms/templates/perms/asset_permission_asset.html b/apps/perms/templates/perms/asset_permission_asset.html deleted file mode 100644 index b6079cdaa..000000000 --- a/apps/perms/templates/perms/asset_permission_asset.html +++ /dev/null @@ -1,282 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Asset list of ' %} {{ asset_permission.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - -
- - {% trans 'Asset' %}{% trans 'Action' %}
-
-
-
-
-
-
- {% trans 'Add asset to this permission' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
- -
-
- {% trans 'Add node to this permission' %} -
-
- - - - - - - - - - - - {% for node in asset_permission.nodes.all %} - - - - - {% endfor %} - -
- -
- -
{{ node.full_value }} - -
-
-
-
-
- {% trans 'System user' %} -
-
- - - - - - - - - - - - {% for system_user in object.system_users.all %} - - - - - {% endfor %} - -
- -
- -
{{ system_user|truncatechars:21}} - -
-
-
-
-
-
-
-
-
-{% include 'assets/_asset_list_modal.html' %} -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/asset_permission_create_update.html b/apps/perms/templates/perms/asset_permission_create_update.html deleted file mode 100644 index 937e7273e..000000000 --- a/apps/perms/templates/perms/asset_permission_create_update.html +++ /dev/null @@ -1,220 +0,0 @@ -{% 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 'Asset' %}

- {% bootstrap_field form.assets layout="horizontal" %} - {% bootstrap_field form.nodes layout="horizontal" %} - {% bootstrap_field form.system_users layout="horizontal" %} - -
-

{% trans 'Action' %}

-
- -
-
-
    -
  • -
    {{ form.actions.0}}
    -
      -
    • -
      {{ form.actions.1}}
      -
    • - -
    • -
      {{ form.actions.4}}
      -
        -
      • -
        {{ form.actions.2}}
        -
      • -
      • -
        {{ form.actions.3}}
        -
      • -
      -
    • -
    - -
  • -
-
{{ form.actions.help_text }}
-
-
-
- -
-

{% 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" %} - -
-
- - -
-
- -
-
-
-
-
-
- {% include 'assets/_asset_list_modal.html' %} -{% endblock %} -{% block custom_foot_js %} - - - - - - -{% endblock %} diff --git a/apps/perms/templates/perms/asset_permission_detail.html b/apps/perms/templates/perms/asset_permission_detail.html deleted file mode 100644 index 6930cfc5a..000000000 --- a/apps/perms/templates/perms/asset_permission_detail.html +++ /dev/null @@ -1,178 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ object.name }}
{% trans 'User count' %}:{{ object.users.count }}
{% trans 'User group count' %}:{{ object.user_groups.count }}
{% trans 'Asset count' %}:{{ object.assets.count }}
{% trans 'Node count' %}:{{ object.nodes.count }}
{% trans 'System user count' %}:{{ object.system_users.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 %} diff --git a/apps/perms/templates/perms/asset_permission_list.html b/apps/perms/templates/perms/asset_permission_list.html deleted file mode 100644 index 1f44f7de1..000000000 --- a/apps/perms/templates/perms/asset_permission_list.html +++ /dev/null @@ -1,260 +0,0 @@ -{% extends '_base_asset_tree_list.html' %} -{% load static %} -{% load i18n %} - -{% block custom_head_css_js %} - - - - - - -{% endblock %} - -{% block table_container %} -
- - - -
- - - - - - - - - - - - - - - - -
{% trans 'Name' %}{% trans 'User' %}{% trans 'User group' %}{% trans 'Asset' %}{% trans 'Node'%}{% trans 'System user' %}{% trans 'Validity' %}{% trans 'Action' %}
-{% include '_filter_dropdown.html' %} -{% endblock %} - -{% block custom_foot_js %} - - - -{% endblock %} diff --git a/apps/perms/templates/perms/asset_permission_user.html b/apps/perms/templates/perms/asset_permission_user.html deleted file mode 100644 index 088d80c75..000000000 --- a/apps/perms/templates/perms/asset_permission_user.html +++ /dev/null @@ -1,251 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'User list of ' %} {{ asset_permission.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Action' %}
-
-
-
-
-
-
- {% trans 'Add user to asset permission' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
- -
-
- {% trans 'Add user group to asset permission' %} -
-
- - - - - - - - - - - - {% for user_group in asset_permission.user_groups.all %} - - - - - {% endfor %} - -
- -
- -
{{ user_group }} - -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/database_app_permission_create_update.html b/apps/perms/templates/perms/database_app_permission_create_update.html deleted file mode 100644 index a779e5826..000000000 --- a/apps/perms/templates/perms/database_app_permission_create_update.html +++ /dev/null @@ -1,143 +0,0 @@ -{% 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 'DatabaseApp' %}

- {% bootstrap_field form.database_apps layout="horizontal" %} - {% bootstrap_field form.system_users 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 %} diff --git a/apps/perms/templates/perms/database_app_permission_database_app.html b/apps/perms/templates/perms/database_app_permission_database_app.html deleted file mode 100644 index 0a23618d0..000000000 --- a/apps/perms/templates/perms/database_app_permission_database_app.html +++ /dev/null @@ -1,237 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'DatabaseApp list of ' %} {{ database_app_permission.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - -
- - {% trans 'DatabaseApp' %}{% trans 'Action' %}
-
-
-
-
-
-
- {% trans 'Add DatabaseApp to this permission' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
- -
-
- {% trans 'System user' %} -
-
- - - - - - - - - - - - {% for system_user in object.system_users.all %} - - - - - {% endfor %} - -
- -
- -
{{ system_user|truncatechars:21}} - -
-
-
- -
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/database_app_permission_detail.html b/apps/perms/templates/perms/database_app_permission_detail.html deleted file mode 100644 index feae4db24..000000000 --- a/apps/perms/templates/perms/database_app_permission_detail.html +++ /dev/null @@ -1,157 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {{ object.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'Name' %}:{{ object.name }}
{% trans 'User count' %}:{{ object.users.count }}
{% trans 'User group count' %}:{{ object.user_groups.count }}
{% trans 'DatabaseApp count' %}:{{ object.database_apps.count }}
{% trans 'System user count' %}:{{ object.system_users.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 %} diff --git a/apps/perms/templates/perms/database_app_permission_list.html b/apps/perms/templates/perms/database_app_permission_list.html deleted file mode 100644 index b85454cb8..000000000 --- a/apps/perms/templates/perms/database_app_permission_list.html +++ /dev/null @@ -1,99 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %}{% endblock %} -{% block table_container %} - - - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'User' %}{% trans 'User group' %}{% trans 'DatabaseApp' %}{% trans 'System user' %}{% trans 'Validity' %}{% trans 'Action' %}
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/database_app_permission_user.html b/apps/perms/templates/perms/database_app_permission_user.html deleted file mode 100644 index 8b109fa1a..000000000 --- a/apps/perms/templates/perms/database_app_permission_user.html +++ /dev/null @@ -1,251 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'User list of ' %} {{ database_app_permission.name }} -
- - - - - - - - - - -
-
-
- - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Action' %}
-
-
-
-
-
-
- {% trans 'Add user to permission' %} -
-
- - - - - - - - - - - -
- -
- -
-
-
- -
-
- {% trans 'Add user group to permission' %} -
-
- - - - - - - - - - - - {% for user_group in database_app_permission.user_groups.all %} - - - - - {% endfor %} - -
- -
- -
{{ user_group }} - -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/delete_confirm.html b/apps/perms/templates/perms/delete_confirm.html deleted file mode 100644 index 777d1dbf9..000000000 --- a/apps/perms/templates/perms/delete_confirm.html +++ /dev/null @@ -1,15 +0,0 @@ -{% load i18n %} - - - - - {% trans 'Confirm delete' %} - - -
- {% csrf_token %} -

Are you sure you want to delete "{{ object.name }}"?

- -
- - \ No newline at end of file diff --git a/apps/perms/templates/perms/remote_app_permission_create_update.html b/apps/perms/templates/perms/remote_app_permission_create_update.html deleted file mode 100644 index f6e0cda7d..000000000 --- a/apps/perms/templates/perms/remote_app_permission_create_update.html +++ /dev/null @@ -1,143 +0,0 @@ -{% 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" %} - {% bootstrap_field form.system_users 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 %} diff --git a/apps/perms/templates/perms/remote_app_permission_detail.html b/apps/perms/templates/perms/remote_app_permission_detail.html deleted file mode 100644 index aedef832e..000000000 --- a/apps/perms/templates/perms/remote_app_permission_detail.html +++ /dev/null @@ -1,242 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% 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' %} : -
-
- - -
-
-
-
-
-
-
- {% trans 'System user' %} -
-
- - - - - - - - - - - - {% for system_user in object.system_users.all %} - - - - - {% endfor %} - -
- -
- -
{{ system_user }} - -
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/perms/templates/perms/remote_app_permission_list.html b/apps/perms/templates/perms/remote_app_permission_list.html deleted file mode 100644 index f6071430a..000000000 --- a/apps/perms/templates/perms/remote_app_permission_list.html +++ /dev/null @@ -1,99 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block table_search %}{% endblock %} -{% block table_container %} - - - - - - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'User' %}{% trans 'User group' %}{% trans 'RemoteApp' %}{% trans 'System user' %}{% 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 deleted file mode 100644 index 991489033..000000000 --- a/apps/perms/templates/perms/remote_app_permission_remote_app.html +++ /dev/null @@ -1,160 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% 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 %} diff --git a/apps/perms/templates/perms/remote_app_permission_user.html b/apps/perms/templates/perms/remote_app_permission_user.html deleted file mode 100644 index 9fa623585..000000000 --- a/apps/perms/templates/perms/remote_app_permission_user.html +++ /dev/null @@ -1,256 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% 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 %} diff --git a/apps/perms/templatetags/perms/example_tags.py b/apps/perms/templatetags/perms/example_tags.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/perms/urls/views_urls.py b/apps/perms/urls/views_urls.py index f4deba8c8..89d8c241a 100644 --- a/apps/perms/urls/views_urls.py +++ b/apps/perms/urls/views_urls.py @@ -1,34 +1,5 @@ # coding:utf-8 - -from django.conf.urls import url -from django.urls import path -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'), - path('asset-permission//', views.AssetPermissionDetailView.as_view(),name='asset-permission-detail'), - 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'), - - # database-app-permission - path('database-app-permission/', views.DatabaseAppPermissionListView.as_view(), name='database-app-permission-list'), - path('database-app-permission/create/', views.DatabaseAppPermissionCreateView.as_view(), name='database-app-permission-create'), - path('database-app-permission//update/', views.DatabaseAppPermissionUpdateView.as_view(), name='database-app-permission-update'), - path('database-app-permission//', views.DatabaseAppPermissionDetailView.as_view(), name='database-app-permission-detail'), - path('database-app-permission//user/', views.DatabaseAppPermissionUserView.as_view(), name='database-app-permission-user-list'), - path('database-app-permission//database-app/', views.DatabaseAppPermissionDatabaseAppView.as_view(), name='database-app-permission-database-app-list'), ] diff --git a/apps/perms/views/__init__.py b/apps/perms/views/__init__.py deleted file mode 100644 index c6581b858..000000000 --- a/apps/perms/views/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# coding: utf-8 -# - -from .asset_permission import * -from .remote_app_permission import * -from .database_app_permission import * diff --git a/apps/perms/views/asset_permission.py b/apps/perms/views/asset_permission.py deleted file mode 100644 index 71b4d5604..000000000 --- a/apps/perms/views/asset_permission.py +++ /dev/null @@ -1,175 +0,0 @@ -# ~*~ coding: utf-8 ~*~ - -from __future__ import unicode_literals, absolute_import - -from django.utils.translation import ugettext as _ -from django.views.generic import ListView, CreateView, UpdateView, DetailView, TemplateView -from django.views.generic.edit import DeleteView, SingleObjectMixin -from django.urls import reverse_lazy -from django.conf import settings - -from common.permissions import PermissionsMixin, IsOrgAdmin -from orgs.utils import current_org -from perms.hands import Node, Asset, SystemUser, UserGroup -from perms.models import AssetPermission -from perms.forms import AssetPermissionForm - - -__all__ = [ - 'AssetPermissionListView', 'AssetPermissionCreateView', - 'AssetPermissionUpdateView', 'AssetPermissionDetailView', - 'AssetPermissionDeleteView', 'AssetPermissionUserView', - 'AssetPermissionAssetView', - -] - - -class AssetPermissionListView(PermissionsMixin, TemplateView): - template_name = 'perms/asset_permission_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Asset permission list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetPermissionCreateView(PermissionsMixin, CreateView): - model = AssetPermission - form_class = AssetPermissionForm - template_name = 'perms/asset_permission_create_update.html' - success_url = reverse_lazy('perms:asset-permission-list') - permission_classes = [IsOrgAdmin] - - def get_form(self, form_class=None): - form = super().get_form(form_class=form_class) - nodes_id = self.request.GET.get("nodes") - assets_id = self.request.GET.get("assets") - - if nodes_id: - nodes_id = nodes_id.split(",") - nodes = Node.objects.filter(id__in=nodes_id)\ - .exclude(id=Node.org_root().id) - form.set_nodes_initial(nodes) - if assets_id: - assets_id = assets_id.split(",") - assets = Asset.objects.filter(id__in=assets_id) - form.set_assets_initial(assets) - return form - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Create asset permission'), - 'api_action': "create", - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetPermissionUpdateView(PermissionsMixin, UpdateView): - model = AssetPermission - form_class = AssetPermissionForm - template_name = 'perms/asset_permission_create_update.html' - success_url = reverse_lazy("perms:asset-permission-list") - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Update asset permission'), - 'api_action': "update", - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetPermissionDetailView(PermissionsMixin, DetailView): - model = AssetPermission - form_class = AssetPermissionForm - template_name = 'perms/asset_permission_detail.html' - success_url = reverse_lazy("perms:asset-permission-list") - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Asset permission detail'), - - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetPermissionDeleteView(PermissionsMixin, DeleteView): - model = AssetPermission - template_name = 'delete_confirm.html' - success_url = reverse_lazy('perms:asset-permission-list') - permission_classes = [IsOrgAdmin] - - -class AssetPermissionUserView(PermissionsMixin, - SingleObjectMixin, - ListView): - template_name = 'perms/asset_permission_user.html' - context_object_name = 'asset_permission' - paginate_by = settings.DISPLAY_PER_PAGE - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=AssetPermission.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): - users = [str(i) for i in self.object.users.all().values_list('id', flat=True)] - user_groups_remain = UserGroup.objects.exclude( - assetpermission=self.object) - context = { - 'app': _('Perms'), - 'action': _('Asset permission user list'), - 'users': users, - 'user_groups_remain': user_groups_remain, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class AssetPermissionAssetView(PermissionsMixin, - SingleObjectMixin, - ListView): - template_name = 'perms/asset_permission_asset.html' - context_object_name = 'asset_permission' - paginate_by = settings.DISPLAY_PER_PAGE - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=AssetPermission.objects.all()) - return super().get(request, *args, **kwargs) - - def get_queryset(self): - queryset = list(self.object.get_all_assets()) - return queryset - - def get_context_data(self, **kwargs): - assets = self.object.assets.all().values_list('id', flat=True) - assets = [str(i) for i in assets] - system_users_remain = SystemUser.objects\ - .exclude(granted_by_permissions=self.object)\ - .exclude(protocol=SystemUser.PROTOCOL_MYSQL) - context = { - 'app': _('Perms'), - 'assets': assets, - 'action': _('Asset permission asset list'), - 'system_users_remain': system_users_remain, - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/perms/views/database_app_permission.py b/apps/perms/views/database_app_permission.py deleted file mode 100644 index 50627defe..000000000 --- a/apps/perms/views/database_app_permission.py +++ /dev/null @@ -1,152 +0,0 @@ -# coding: utf-8 -# - -from django.utils.translation import ugettext as _ - -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 PermissionsMixin, IsOrgAdmin -from users.models import UserGroup -from applications.models import DatabaseApp -from assets.models import SystemUser - -from .. import models, forms - - -__all__ = [ - 'DatabaseAppPermissionListView', 'DatabaseAppPermissionCreateView', - 'DatabaseAppPermissionUpdateView', 'DatabaseAppPermissionDetailView', - 'DatabaseAppPermissionUserView', 'DatabaseAppPermissionDatabaseAppView', -] - - -class DatabaseAppPermissionListView(PermissionsMixin, TemplateView): - template_name = 'perms/database_app_permission_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('DatabaseApp permission list') - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppPermissionCreateView(PermissionsMixin, CreateView): - template_name = 'perms/database_app_permission_create_update.html' - model = models.DatabaseAppPermission - form_class = forms.DatabaseAppPermissionCreateUpdateForm - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Create DatabaseApp permission'), - 'api_action': 'create', - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppPermissionUpdateView(PermissionsMixin, UpdateView): - template_name = 'perms/database_app_permission_create_update.html' - model = models.DatabaseAppPermission - form_class = forms.DatabaseAppPermissionCreateUpdateForm - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Update DatabaseApp permission'), - 'api_action': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppPermissionDetailView(PermissionsMixin, DetailView): - template_name = 'perms/database_app_permission_detail.html' - model = models.DatabaseAppPermission - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('DatabaseApp permission detail') - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppPermissionUserView(PermissionsMixin, - SingleObjectMixin, - ListView): - template_name = 'perms/database_app_permission_user.html' - context_object_name = 'database_app_permission' - paginate_by = settings.DISPLAY_PER_PAGE - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=models.DatabaseAppPermission.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): - users = [str(i) for i in self.object.users.all().values_list('id', flat=True)] - user_groups_remain = UserGroup.objects.exclude( - databaseapppermission=self.object) - context = { - 'app': _('Perms'), - 'action': _('DatabaseApp permission user list'), - 'users': users, - 'user_groups_remain': user_groups_remain, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class DatabaseAppPermissionDatabaseAppView(PermissionsMixin, - SingleObjectMixin, - ListView): - template_name = 'perms/database_app_permission_database_app.html' - context_object_name = 'database_app_permission' - paginate_by = settings.DISPLAY_PER_PAGE - object = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - self.object = self.get_object( - queryset=models.DatabaseAppPermission.objects.all() - ) - return super().get(request, *args, **kwargs) - - def get_queryset(self): - queryset = list(self.object.get_all_database_apps()) - return queryset - - def get_context_data(self, **kwargs): - database_apps = self.object.get_all_database_apps().values_list('id', flat=True) - database_apps = [str(i) for i in database_apps] - system_users_remain = SystemUser.objects\ - .exclude(granted_by_database_app_permissions=self.object)\ - .filter(protocol=SystemUser.PROTOCOL_MYSQL) - context = { - 'app': _('Perms'), - 'database_apps': database_apps, - 'database_apps_remain': DatabaseApp.objects.exclude( - granted_by_permissions=self.object - ), - 'system_users_remain': system_users_remain, - 'action': _('DatabaseApp permission DatabaseApp list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/perms/views/remote_app_permission.py b/apps/perms/views/remote_app_permission.py deleted file mode 100644 index 875600c68..000000000 --- a/apps/perms/views/remote_app_permission.py +++ /dev/null @@ -1,155 +0,0 @@ -# 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 PermissionsMixin, IsOrgAdmin -from orgs.utils import current_org - -from ..hands import RemoteApp, UserGroup, SystemUser -from ..models import RemoteAppPermission -from ..forms import RemoteAppPermissionCreateUpdateForm - - -__all__ = [ - 'RemoteAppPermissionListView', 'RemoteAppPermissionCreateView', - 'RemoteAppPermissionUpdateView', 'RemoteAppPermissionDetailView', - 'RemoteAppPermissionUserView', 'RemoteAppPermissionRemoteAppView' -] - - -class RemoteAppPermissionListView(PermissionsMixin, TemplateView): - template_name = 'perms/remote_app_permission_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('RemoteApp permission list'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class RemoteAppPermissionCreateView(PermissionsMixin, CreateView): - template_name = 'perms/remote_app_permission_create_update.html' - model = RemoteAppPermission - form_class = RemoteAppPermissionCreateUpdateForm - success_url = reverse_lazy('perms:remote-app-permission-list') - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Create RemoteApp permission'), - 'api_action': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class RemoteAppPermissionUpdateView(PermissionsMixin, UpdateView): - template_name = 'perms/remote_app_permission_create_update.html' - model = RemoteAppPermission - form_class = RemoteAppPermissionCreateUpdateForm - success_url = reverse_lazy('perms:remote-app-permission-list') - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Perms'), - 'action': _('Update RemoteApp permission'), - 'api_action': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class RemoteAppPermissionDetailView(PermissionsMixin, DetailView): - template_name = 'perms/remote_app_permission_detail.html' - model = RemoteAppPermission - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - system_users_remain = SystemUser.objects\ - .exclude(granted_by_remote_app_permissions=self.object)\ - .filter(protocol=SystemUser.PROTOCOL_RDP) - context = { - 'app': _('Perms'), - 'action': _('RemoteApp permission detail'), - 'system_users_remain': system_users_remain, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class RemoteAppPermissionUserView(PermissionsMixin, - SingleObjectMixin, - ListView): - template_name = 'perms/remote_app_permission_user.html' - context_object_name = 'remote_app_permission' - paginate_by = settings.DISPLAY_PER_PAGE - object = None - permission_classes = [IsOrgAdmin] - - 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): - user_remain = current_org.get_org_members(exclude=('Auditor',))\ - .exclude(remoteapppermission=self.object) - user_groups_remain = UserGroup.objects\ - .exclude(remoteapppermission=self.object) - context = { - 'app': _('Perms'), - 'action': _('RemoteApp permission user list'), - 'users_remain': user_remain, - 'user_groups_remain': user_groups_remain, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class RemoteAppPermissionRemoteAppView(PermissionsMixin, - 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 - permission_classes = [IsOrgAdmin] - - 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/settings/forms/__init__.py b/apps/settings/forms/__init__.py deleted file mode 100644 index 4c5a69e8c..000000000 --- a/apps/settings/forms/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# coding: utf-8 -# - -from .base import * -from .basic import * -from .email import * -from .ldap import * -from .security import * -from .terminal import * diff --git a/apps/settings/forms/base.py b/apps/settings/forms/base.py deleted file mode 100644 index b7b32dea6..000000000 --- a/apps/settings/forms/base.py +++ /dev/null @@ -1,57 +0,0 @@ -# coding: utf-8 -# - -import json -from django import forms -from django.db import transaction -from django.conf import settings - -from ..models import Setting -from common.fields import FormEncryptMixin - -__all__ = ['BaseForm'] - - -class BaseForm(forms.Form): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - for name, field in self.fields.items(): - value = getattr(settings, name, None) - if value is None: # and django_value is None: - continue - - if value is not None: - if isinstance(value, dict): - value = json.dumps(value) - initial_value = value - else: - initial_value = '' - field.initial = initial_value - - def save(self, category="default"): - if not self.is_bound: - raise ValueError("Form is not bound") - - # db_settings = Setting.objects.all() - if not self.is_valid(): - raise ValueError(self.errors) - - with transaction.atomic(): - for name, value in self.cleaned_data.items(): - field = self.fields[name] - if isinstance(field.widget, forms.PasswordInput) and not value: - continue - # if value == getattr(settings, name): - # continue - - encrypted = True if isinstance(field, FormEncryptMixin) else False - try: - setting = Setting.objects.get(name=name) - except Setting.DoesNotExist: - setting = Setting() - setting.name = name - setting.category = category - setting.encrypted = encrypted - setting.cleaned_value = value - setting.save() - diff --git a/apps/settings/forms/basic.py b/apps/settings/forms/basic.py deleted file mode 100644 index dd93c9537..000000000 --- a/apps/settings/forms/basic.py +++ /dev/null @@ -1,24 +0,0 @@ -# coding: utf-8 -# - -from django import forms -from django.utils.translation import ugettext_lazy as _ -from .base import BaseForm - -__all__ = ['BasicSettingForm'] - - -class BasicSettingForm(BaseForm): - SITE_URL = forms.URLField( - label=_("Current SITE URL"), - help_text="eg: http://jumpserver.abc.com:8080" - ) - USER_GUIDE_URL = forms.URLField( - label=_("User Guide URL"), required=False, - help_text=_("User first login update profile done redirect to it") - ) - EMAIL_SUBJECT_PREFIX = forms.CharField( - max_length=1024, label=_("Email Subject Prefix"), - help_text=_("Tips: Some word will be intercept by mail provider") - ) - diff --git a/apps/settings/forms/email.py b/apps/settings/forms/email.py deleted file mode 100644 index 6fa61148a..000000000 --- a/apps/settings/forms/email.py +++ /dev/null @@ -1,65 +0,0 @@ -# coding: utf-8 -# - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from common.fields import FormEncryptCharField -from .base import BaseForm - -__all__ = ['EmailSettingForm', 'EmailContentSettingForm'] - - -class EmailSettingForm(BaseForm): - EMAIL_HOST = forms.CharField( - max_length=1024, label=_("SMTP host"), initial='smtp.jumpserver.org' - ) - EMAIL_PORT = forms.CharField(max_length=5, label=_("SMTP port"), initial=25) - EMAIL_HOST_USER = forms.CharField( - max_length=128, label=_("SMTP user"), initial='noreply@jumpserver.org' - ) - EMAIL_HOST_PASSWORD = FormEncryptCharField( - max_length=1024, label=_("SMTP password"), widget=forms.PasswordInput, - required=False, - help_text=_("Tips: Some provider use token except password") - ) - EMAIL_FROM = forms.CharField( - max_length=128, label=_("Send user"), initial='', required=False, - help_text=_( - "Tips: Send mail account, default SMTP account as the send account" - ) - ) - EMAIL_RECIPIENT = forms.CharField( - max_length=128, label=_("Test recipient"), initial='', required=False, - help_text=_("Tips: Used only as a test mail recipient") - ) - EMAIL_USE_SSL = forms.BooleanField( - label=_("Use SSL"), initial=False, required=False, - help_text=_("If SMTP port is 465, may be select") - ) - EMAIL_USE_TLS = forms.BooleanField( - label=_("Use TLS"), initial=False, required=False, - help_text=_("If SMTP port is 587, may be select") - ) - - -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/forms/ldap.py b/apps/settings/forms/ldap.py deleted file mode 100644 index c44d1c3e4..000000000 --- a/apps/settings/forms/ldap.py +++ /dev/null @@ -1,46 +0,0 @@ -# coding: utf-8 -# - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from common.fields import FormDictField, FormEncryptCharField -from .base import BaseForm - - -__all__ = ['LDAPSettingForm'] - - -class LDAPSettingForm(BaseForm): - AUTH_LDAP_SERVER_URI = forms.CharField( - label=_("LDAP server"), - ) - AUTH_LDAP_BIND_DN = forms.CharField( - required=False, label=_("Bind DN"), - ) - AUTH_LDAP_BIND_PASSWORD = FormEncryptCharField( - label=_("Password"), - widget=forms.PasswordInput, required=False - ) - AUTH_LDAP_SEARCH_OU = forms.CharField( - label=_("User OU"), - help_text=_("Use | split User OUs"), - required=False, - ) - AUTH_LDAP_SEARCH_FILTER = forms.CharField( - label=_("User search filter"), - help_text=_("Choice may be (cn|uid|sAMAccountName)=%(user)s)") - ) - AUTH_LDAP_USER_ATTR_MAP = FormDictField( - label=_("User attr map"), - help_text=_( - "User attr map present how to map LDAP user attr to jumpserver, " - "username,name,email is jumpserver attr" - ), - ) - # AUTH_LDAP_GROUP_SEARCH_OU = CONFIG.AUTH_LDAP_GROUP_SEARCH_OU - # AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER - # AUTH_LDAP_START_TLS = forms.BooleanField( - # label=_("Use SSL"), required=False - # ) - AUTH_LDAP = forms.BooleanField(label=_("Enable LDAP auth"), required=False) diff --git a/apps/settings/forms/security.py b/apps/settings/forms/security.py deleted file mode 100644 index 6d30d8b73..000000000 --- a/apps/settings/forms/security.py +++ /dev/null @@ -1,93 +0,0 @@ -# coding: utf-8 -# - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from .base import BaseForm - - -__all__ = ['SecuritySettingForm'] - - -class SecuritySettingForm(BaseForm): - # MFA global setting - SECURITY_MFA_AUTH = forms.BooleanField( - required=False, label=_("MFA"), - help_text=_( - 'After opening, all user login must use MFA' - '(valid for all users, including administrators)' - ) - ) - # Execute commands for user - SECURITY_COMMAND_EXECUTION = forms.BooleanField( - required=False, label=_("Batch execute commands"), - help_text=_("Allow user batch execute commands") - ) - SECURITY_SERVICE_ACCOUNT_REGISTRATION = forms.BooleanField( - required=False, label=_("Service account registration"), - help_text=_("Allow using bootstrap token register service account, " - "when terminal setup, can disable it") - ) - # limit login count - SECURITY_LOGIN_LIMIT_COUNT = forms.IntegerField( - 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, 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." - ) - ) - # ssh max idle time - SECURITY_MAX_IDLE_TIME = forms.IntegerField( - min_value=1, max_value=99999, required=False, - label=_("Connection max idle time"), - help_text=_( - 'If idle time more than it, disconnect connection ' - 'Unit: minute' - ), - ) - # password expiration time - SECURITY_PASSWORD_EXPIRATION_TIME = forms.IntegerField( - 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, " - "the user password will expire failure;" - "The password expiration reminder mail will be automatic sent to the user " - "by system within 5 days (daily) before the password expires" - ) - ) - # min length - SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField( - min_value=6, max_value=30, label=_("Password minimum length"), - ) - # upper case - SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField( - required=False, label=_("Must contain capital letters"), - help_text=_( - 'After opening, the user password changes ' - 'and resets must contain uppercase letters') - ) - # lower case - SECURITY_PASSWORD_LOWER_CASE = forms.BooleanField( - required=False, label=_("Must contain lowercase letters"), - help_text=_('After opening, the user password changes ' - 'and resets must contain lowercase letters') - ) - # number - SECURITY_PASSWORD_NUMBER = forms.BooleanField( - required=False, label=_("Must contain numeric characters"), - help_text=_('After opening, the user password changes ' - 'and resets must contain numeric characters') - ) - # special char - SECURITY_PASSWORD_SPECIAL_CHAR = forms.BooleanField( - required=False, label=_("Must contain special characters"), - help_text=_('After opening, the user password changes ' - 'and resets must contain special characters') - ) diff --git a/apps/settings/forms/terminal.py b/apps/settings/forms/terminal.py deleted file mode 100644 index d879e88d2..000000000 --- a/apps/settings/forms/terminal.py +++ /dev/null @@ -1,50 +0,0 @@ -# coding: utf-8 -# - - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from .base import BaseForm - -__all__ = ['TerminalSettingForm'] - - -class TerminalSettingForm(BaseForm): - SORT_BY_CHOICES = ( - ('hostname', _('Hostname')), - ('ip', _('IP')), - ) - PAGE_SIZE_CHOICES = ( - ('all', _('All')), - ('auto', _('Auto')), - (10, 10), - (15, 15), - (25, 25), - (50, 50), - ) - TERMINAL_PASSWORD_AUTH = forms.BooleanField( - required=False, label=_("Password auth") - ) - TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField( - required=False, label=_("Public key auth") - ) - TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField( - min_value=5, max_value=99999, label=_("Heartbeat interval"), - help_text=_("Units: seconds") - ) - TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField( - choices=SORT_BY_CHOICES, label=_("List sort by") - ) - TERMINAL_ASSET_LIST_PAGE_SIZE = forms.ChoiceField( - choices=PAGE_SIZE_CHOICES, label=_("List page size"), - ) - TERMINAL_SESSION_KEEP_DURATION = forms.IntegerField( - 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") - ) - TERMINAL_TELNET_REGEX = forms.CharField( - required=False, label=_("Telnet login regex"), - help_text=_("ex: Last\s*login|success|成功") - ) diff --git a/apps/settings/templates/settings/_ldap_list_users_modal.html b/apps/settings/templates/settings/_ldap_list_users_modal.html deleted file mode 100644 index 8589b0066..000000000 --- a/apps/settings/templates/settings/_ldap_list_users_modal.html +++ /dev/null @@ -1,176 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% load static %} - -{% block modal_class %}modal-lg{% endblock %} -{% block modal_id %}ldap_list_users_modal{% endblock %} -{% block modal_title%}{% trans "LDAP user list" %}{% endblock %} - -{% block modal_help_message%}
{% trans 'Please submit the LDAP configuration before import' %}
{% endblock %} - -{% block modal_body %} - - - - - -
-
-
-
- - - - - - - - - - - - - -
{% trans 'Username' %}{% trans 'Name' %}{% trans 'Email' %}{% trans 'Existing' %}
-
-
{% trans 'Loading' %}...
-
-
-
-
-
- - -{% endblock %} - -{% block modal_button %} - - -{% endblock %} - - - diff --git a/apps/settings/templates/settings/_ldap_test_user_login_modal.html b/apps/settings/templates/settings/_ldap_test_user_login_modal.html deleted file mode 100644 index 3359d2468..000000000 --- a/apps/settings/templates/settings/_ldap_test_user_login_modal.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% block modal_id %}test_user_login_modal{% endblock %} -{% block modal_title%}{% trans "Test LDAP user login" %}{% endblock %} -{% block modal_comment %}{% trans "Save the configuration before testing the login" %}{% endblock %} -{% block modal_body %} -
- {% csrf_token %} -
- -
- -
-
-
- -
- -
-
-
- -{% endblock %} -{% block modal_confirm_id %}btn_test_user_login_modal_confirm{% endblock %} diff --git a/apps/settings/templates/settings/_setting_tabs.html b/apps/settings/templates/settings/_setting_tabs.html deleted file mode 100644 index b012a6669..000000000 --- a/apps/settings/templates/settings/_setting_tabs.html +++ /dev/null @@ -1,37 +0,0 @@ -{% load i18n %} - - - diff --git a/apps/settings/templates/settings/basic_setting.html b/apps/settings/templates/settings/basic_setting.html deleted file mode 100644 index ac8cabb8b..000000000 --- a/apps/settings/templates/settings/basic_setting.html +++ /dev/null @@ -1,64 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load common_tags %} - -{% block content %} -
-
-
-
-
- {% include 'settings/_setting_tabs.html' %} -
-
-
-
-
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% csrf_token %} - {% for field in form %} - {% if not field.field|is_bool_field %} - {% bootstrap_field field layout="horizontal" %} - {% else %} -
- -
-
- {{ field }} -
-
- {{ field.help_text }} -
-
-
- {% endif %} - {% endfor %} -
-
-
- - -
-
-
-
-
-
-
-
-
-
- -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/settings/templates/settings/email_content_setting.html b/apps/settings/templates/settings/email_content_setting.html deleted file mode 100644 index c2c5b2720..000000000 --- a/apps/settings/templates/settings/email_content_setting.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load common_tags %} - -{% block content %} -
-
-
-
-
- {% include 'settings/_setting_tabs.html' %} -
-
-
-
-
- {% 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 deleted file mode 100644 index d62a921cd..000000000 --- a/apps/settings/templates/settings/email_setting.html +++ /dev/null @@ -1,91 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load common_tags %} - -{% block content %} -
-
-
-
-
- {% include 'settings/_setting_tabs.html' %} -
-
-
-
-
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% csrf_token %} - {% for field in form %} - {% if not field.field|is_bool_field %} - {% bootstrap_field field layout="horizontal" %} - {% else %} -
- -
-
- {{ field }} -
-
- {{ field.help_text }} -
-
-
- {% endif %} - {% endfor %} -
-
-
- - - -
-
-
-
-
-
-
-
-
-
- -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/settings/templates/settings/ldap_setting.html b/apps/settings/templates/settings/ldap_setting.html deleted file mode 100644 index 42902391b..000000000 --- a/apps/settings/templates/settings/ldap_setting.html +++ /dev/null @@ -1,99 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load common_tags %} - -{% block content %} -
-
-
-
-
- {% include 'settings/_setting_tabs.html' %} -
-
-
-
-
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% csrf_token %} - {% for field in form %} - {% if not field.field|is_bool_field %} - {% bootstrap_field field layout="horizontal" %} - {% else %} -
- -
-
- {{ field }} -
-
- {{ field.help_text }} -
-
-
- {% endif %} - {% endfor %} -
-
-
- - - - - -
-
-
-
-
-
-
-
-
-
- - {% include 'settings/_ldap_list_users_modal.html' %} - {% include 'settings/_ldap_test_user_login_modal.html' %} -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/settings/templates/settings/security_setting.html b/apps/settings/templates/settings/security_setting.html deleted file mode 100644 index 663632a72..000000000 --- a/apps/settings/templates/settings/security_setting.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load common_tags %} - -{% block content %} -
-
-
-
-
- {% include 'settings/_setting_tabs.html' %} -
-
-
-
-
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% csrf_token %} - -

{% trans "Security setting" %}

- {% for field in form %} - {% if forloop.counter == 8 %} -
-

{% trans "Password check rule" %}

- {% endif %} - - {% if not field.field|is_bool_field %} - {% bootstrap_field field layout="horizontal" %} - {% else %} -
- -
-
- {{ field }} -
-
- {{ field.help_text }} -
-
-
- {% endif %} - {% endfor %} - -
- -
-
- - -
-
-
-
-
-
-
-
-
-
- -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/settings/templates/settings/terminal_setting.html b/apps/settings/templates/settings/terminal_setting.html deleted file mode 100644 index a0b35aabb..000000000 --- a/apps/settings/templates/settings/terminal_setting.html +++ /dev/null @@ -1,72 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} -{% load common_tags %} -{% block help_message %} - {% trans "Command and Replay storage configuration migrated to" %} - {% trans "Sessions -> Terminal -> Storage configuration" %} - {% trans 'Here' %} -{% endblock %} - -{% block content %} -
-
-
-
-
- {% include 'settings/_setting_tabs.html' %} -
-
-
-
-
- {% if form.non_field_errors %} -
- {{ form.non_field_errors }} -
- {% endif %} - {% csrf_token %} - -

{% trans "Basic setting" %}

- {% for field in form %} - {% if not field.field|is_bool_field %} - {% bootstrap_field field layout="horizontal" %} - {% else %} -
- -
-
- {{ field }} -
-
- {{ field.help_text }} -
-
-
- {% endif %} - {% endfor %} -
-
-
- - -
-
-
-
-
-
-
-
-
-
-{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/settings/urls/view_urls.py b/apps/settings/urls/view_urls.py deleted file mode 100644 index 6a1c5baaf..000000000 --- a/apps/settings/urls/view_urls.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import absolute_import - -from django.conf.urls import url - -from .. import views - -app_name = 'common' - -urlpatterns = [ - url(r'^$', views.BasicSettingView.as_view(), name='basic-setting'), - url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'), - url(r'^email-content/$', views.EmailContentSettingView.as_view(), name='email-content-setting'), - url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'), - url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'), - url(r'^security/$', views.SecuritySettingView.as_view(), name='security-setting'), -] diff --git a/apps/settings/views.py b/apps/settings/views.py deleted file mode 100644 index 6ebbeef97..000000000 --- a/apps/settings/views.py +++ /dev/null @@ -1,178 +0,0 @@ -from django.views.generic import TemplateView -from django.shortcuts import render, redirect -from django.contrib import messages -from django.utils.translation import ugettext as _ - -from common.permissions import PermissionsMixin, IsSuperUser -from .utils import LDAPSyncUtil -from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ - TerminalSettingForm, SecuritySettingForm, EmailContentSettingForm - - -class BasicSettingView(PermissionsMixin, TemplateView): - form_class = BasicSettingForm - template_name = "settings/basic_setting.html" - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Settings'), - 'action': _('Basic setting'), - 'form': self.form_class(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def post(self, request): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - msg = _("Update setting successfully") - messages.success(request, msg) - return redirect('settings:basic-setting') - else: - context = self.get_context_data() - context.update({"form": form}) - return render(request, self.template_name, context) - - -class EmailSettingView(PermissionsMixin, TemplateView): - form_class = EmailSettingForm - template_name = "settings/email_setting.html" - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Settings'), - 'action': _('Email setting'), - 'form': self.form_class(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def post(self, request): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - msg = _("Update setting successfully") - messages.success(request, msg) - return redirect('settings:email-setting') - else: - context = self.get_context_data() - context.update({"form": form}) - return render(request, self.template_name, context) - - -class LDAPSettingView(PermissionsMixin, TemplateView): - form_class = LDAPSettingForm - template_name = "settings/ldap_setting.html" - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Settings'), - 'action': _('LDAP setting'), - 'form': self.form_class(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def post(self, request): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - msg = _("Update setting successfully") - messages.success(request, msg) - LDAPSyncUtil().clear_cache() - return redirect('settings:ldap-setting') - else: - context = self.get_context_data() - context.update({"form": form}) - return render(request, self.template_name, context) - - -class TerminalSettingView(PermissionsMixin, TemplateView): - form_class = TerminalSettingForm - template_name = "settings/terminal_setting.html" - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - from terminal.models import CommandStorage, ReplayStorage - command_storage = CommandStorage.objects.all() - replay_storage = ReplayStorage.objects.all() - - context = { - 'app': _('Settings'), - 'action': _('Terminal setting'), - 'form': self.form_class(), - 'replay_storage': replay_storage, - 'command_storage': command_storage - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def post(self, request): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - msg = _("Update setting successfully") - messages.success(request, msg) - return redirect('settings:terminal-setting') - else: - context = self.get_context_data() - context.update({"form": form}) - return render(request, self.template_name, context) - - -class SecuritySettingView(PermissionsMixin, TemplateView): - form_class = SecuritySettingForm - template_name = "settings/security_setting.html" - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Settings'), - 'action': _('Security setting'), - 'form': self.form_class(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def post(self, request): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - msg = _("Update setting successfully") - messages.success(request, msg) - return redirect('settings:security-setting') - else: - context = self.get_context_data() - context.update({"form": form}) - return render(request, self.template_name, context) - - -class EmailContentSettingView(PermissionsMixin, TemplateView): - template_name = "settings/email_content_setting.html" - form_class = EmailContentSettingForm - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Settings'), - 'action': _('Email content setting'), - 'form': self.form_class(), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def post(self, request): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - msg = _("Update setting successfully") - messages.success(request, msg) - return redirect('settings:email-content-setting') - else: - context = self.get_context_data() - context.update({"form": form}) - return render(request, self.template_name, context) diff --git a/apps/terminal/forms/__init__.py b/apps/terminal/forms/__init__.py deleted file mode 100644 index 23c06a94c..000000000 --- a/apps/terminal/forms/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# coding: utf-8 -# - -from .terminal import * -from .storage import * diff --git a/apps/terminal/forms/storage.py b/apps/terminal/forms/storage.py deleted file mode 100644 index 3b586560c..000000000 --- a/apps/terminal/forms/storage.py +++ /dev/null @@ -1,165 +0,0 @@ -# coding: utf-8 -# - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from terminal.models import ReplayStorage, CommandStorage - - -__all__ = [ - 'ReplayStorageAzureForm', 'ReplayStorageOSSForm', 'ReplayStorageS3Form', - 'ReplayStorageCephForm', 'ReplayStorageSwiftForm', - 'CommandStorageTypeESForm', -] - - -class BaseStorageForm(forms.Form): - - def __init__(self, *args, **kwargs): - super(BaseStorageForm, self).__init__(*args, **kwargs) - self.fields['type'].widget.attrs['disabled'] = True - self.fields.move_to_end('comment') - - -class BaseReplayStorageForm(BaseStorageForm, forms.ModelForm): - - class Meta: - model = ReplayStorage - fields = ['name', 'type', 'comment'] - - -class BaseCommandStorageForm(BaseStorageForm, forms.ModelForm): - - class Meta: - model = CommandStorage - fields = ['name', 'type', 'comment'] - - -class ReplayStorageAzureForm(BaseReplayStorageForm): - azure_container_name = forms.CharField( - max_length=128, label=_('Container name'), required=False - ) - azure_account_name = forms.CharField( - max_length=128, label=_('Account name'), required=False - ) - azure_account_key = forms.CharField( - max_length=128, label=_('Account key'), required=False, - widget=forms.PasswordInput - ) - azure_endpoint_suffix = forms.ChoiceField( - choices=( - ('core.chinacloudapi.cn', 'core.chinacloudapi.cn'), - ('core.windows.net', 'core.windows.net') - ), - label=_('Endpoint suffix'), required=False, - ) - - -class ReplayStorageOSSForm(BaseReplayStorageForm): - oss_bucket = forms.CharField( - max_length=128, label=_('Bucket'), required=False - ) - oss_access_key = forms.CharField( - max_length=128, label=_('Access key'), required=False, - widget=forms.PasswordInput - ) - oss_secret_key = forms.CharField( - max_length=128, label=_('Secret key'), required=False, - widget=forms.PasswordInput - ) - oss_endpoint = forms.CharField( - max_length=128, label=_('Endpoint'), required=False, - help_text=_( - """ - OSS: http://{REGION_NAME}.aliyuncs.com
- Example: http://oss-cn-hangzhou.aliyuncs.com - """ - ) - ) - - -class ReplayStorageS3Form(BaseReplayStorageForm): - s3_bucket = forms.CharField( - max_length=128, label=_('Bucket'), required=False - ) - s3_access_key = forms.CharField( - max_length=128, label=_('Access key'), required=False, - widget=forms.PasswordInput - ) - s3_secret_key = forms.CharField( - max_length=128, label=_('Secret key'), required=False, - widget=forms.PasswordInput - ) - s3_endpoint = forms.CharField( - max_length=128, label=_('Endpoint'), required=False, - help_text=_( - """ - S3: http://s3.{REGION_NAME}.amazonaws.com
- S3(China): http://s3.{REGION_NAME}.amazonaws.com.cn
- Example: http://s3.cn-north-1.amazonaws.com.cn - """ - ) - ) - - -class ReplayStorageCephForm(BaseReplayStorageForm): - ceph_bucket = forms.CharField( - max_length=128, label=_('Bucket'), required=False - ) - ceph_access_key = forms.CharField( - max_length=128, label=_('Access key'), required=False, - widget=forms.PasswordInput - ) - ceph_secret_key = forms.CharField( - max_length=128, label=_('Secret key'), required=False, - widget=forms.PasswordInput - ) - ceph_endpoint = forms.CharField( - max_length=128, label=_('Endpoint'), required=False, - ) - - -class ReplayStorageSwiftForm(BaseReplayStorageForm): - swift_bucket = forms.CharField( - max_length=128, label=_('Bucket'), required=False - ) - swift_access_key = forms.CharField( - max_length=128, label=_('Access key'), required=False, - widget=forms.PasswordInput - ) - swift_secret_key = forms.CharField( - max_length=128, label=_('Secret key'), required=False, - widget=forms.PasswordInput - ) - swift_region = forms.CharField( - max_length=128, label=_('Region'), required=False, - ) - swift_endpoint = forms.CharField( - max_length=128, label=_('Endpoint'), required=False, - ) - swift_protocol = forms.ChoiceField( - choices=( - ('HTTP', 'http'), - ('HTTPS', 'https') - ), initial='http', label=_('Protocol'), required=True, - ) - - -class CommandStorageTypeESForm(BaseCommandStorageForm): - es_hosts = forms.CharField( - max_length=128, label=_('Hosts'), required=True, - help_text=_( - """ - Tips: If there are multiple hosts, separate them with a comma (,) -
- eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com - """ - ) - ) - es_index = forms.CharField( - max_length=128, label=_('Index'), required=True - ) - es_doc_type = forms.CharField( - max_length=128, label=_('Doc type'), required=True - ) diff --git a/apps/terminal/forms/terminal.py b/apps/terminal/forms/terminal.py deleted file mode 100644 index 6051532d9..000000000 --- a/apps/terminal/forms/terminal.py +++ /dev/null @@ -1,39 +0,0 @@ -# coding: utf-8 -# - -__all__ = ['TerminalForm'] - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from ..models import Terminal, ReplayStorage, CommandStorage - - -def get_all_command_storage(): - for c in CommandStorage.objects.all(): - yield (c.name, c.name) - - -def get_all_replay_storage(): - for r in ReplayStorage.objects.all(): - yield (r.name, r.name) - - -class TerminalForm(forms.ModelForm): - command_storage = forms.ChoiceField( - choices=get_all_command_storage, - label=_("Command storage"), - help_text=_("Command can store in server db or ES, default to server, more see docs"), - ) - replay_storage = forms.ChoiceField( - choices=get_all_replay_storage, - label=_("Replay storage"), - help_text=_("Replay file can store in server disk, AWS S3, Aliyun OSS, default to server, more see docs"), - ) - - class Meta: - model = Terminal - fields = [ - 'name', 'remote_addr', 'comment', - 'command_storage', 'replay_storage', - ] diff --git a/apps/terminal/templates/terminal/base_storage_create_update.html b/apps/terminal/templates/terminal/base_storage_create_update.html deleted file mode 100644 index e432ecfda..000000000 --- a/apps/terminal/templates/terminal/base_storage_create_update.html +++ /dev/null @@ -1,59 +0,0 @@ -{% extends '_base_create_update.html' %} -{% load static %} -{% load bootstrap3 %} -{% load i18n %} - -{% block form %} -
- {% bootstrap_form form layout="horizontal"%} -
-
-
- - -
-
- -
-{% endblock %} - -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/terminal/templates/terminal/base_storage_list.html b/apps/terminal/templates/terminal/base_storage_list.html deleted file mode 100644 index d38fd88cc..000000000 --- a/apps/terminal/templates/terminal/base_storage_list.html +++ /dev/null @@ -1,129 +0,0 @@ -{% extends 'base.html' %} -{% load i18n static %} - -{% block content %} -
-
-
- -
-
-
-
- - -
- - - - - - - - - - - - -
- - {% trans 'Name' %}{% trans 'Type' %}{% trans 'Comment' %}{% trans 'Action' %}
-
-
-
-
-
-
-{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/terminal/templates/terminal/command_list.html b/apps/terminal/templates/terminal/command_list.html deleted file mode 100644 index 5fc7e36cf..000000000 --- a/apps/terminal/templates/terminal/command_list.html +++ /dev/null @@ -1,237 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% load common_tags %} -{% block custom_head_css_js %} - - -{% endblock %} - -{% block table_pagination %} -{% endblock %} - -{% block table_search %} -{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - - - -
{% trans 'Command' %}{% trans 'Risk level' %}{% trans 'User' %}{% trans 'Asset' %}{% trans 'System user'%}{% trans 'Session' %}{% trans 'Datetime' %}
- -
-
- -
- -
-
-
- -
-
-
- - - to - -
-
-
-{% include '_filter_dropdown.html' %} -{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - - - -{% endblock %} - - diff --git a/apps/terminal/templates/terminal/command_report.html b/apps/terminal/templates/terminal/command_report.html deleted file mode 100644 index 3542c1423..000000000 --- a/apps/terminal/templates/terminal/command_report.html +++ /dev/null @@ -1,103 +0,0 @@ -{% load common_tags %} -{% load static %} - - - - - Command Report - - - - -
-
-

Command Report

-
-

total: {{ total_count }}

-

date: {{ now | ts_to_date }}

-
- -
- -
- {% for command in queryset %} -
-

- [{{ command.user}} {{ command.system_user }}@{{ command.asset }} {{ command.timestamp | ts_to_date }}] - {{ forloop.counter }} -

- -

$ {{ command.input }}

- -
{{ command.output }}
-
- -
- {% endfor %} -
-
-
- - \ No newline at end of file diff --git a/apps/terminal/templates/terminal/command_storage_create_update.html b/apps/terminal/templates/terminal/command_storage_create_update.html deleted file mode 100644 index 8abb976ae..000000000 --- a/apps/terminal/templates/terminal/command_storage_create_update.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'terminal/base_storage_create_update.html' %} -{% load i18n static %} - -{% block custom_foot_js %} -{{ block.super }} - - -{% endblock %} diff --git a/apps/terminal/templates/terminal/command_storage_list.html b/apps/terminal/templates/terminal/command_storage_list.html deleted file mode 100644 index 724425cca..000000000 --- a/apps/terminal/templates/terminal/command_storage_list.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends 'terminal/base_storage_list.html' %} -{% load i18n static %} - -{% block create_storage_url %}{% url "terminal:command-storage-create" %}{% endblock%} -{% block create_storage_info %}{% trans 'Create command storage' %}{% endblock %} -{% block custom_foot_js %} -{{ block.super }} - -{% endblock %} - diff --git a/apps/terminal/templates/terminal/replay_storage_create_update.html b/apps/terminal/templates/terminal/replay_storage_create_update.html deleted file mode 100644 index 878301d93..000000000 --- a/apps/terminal/templates/terminal/replay_storage_create_update.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends 'terminal/base_storage_create_update.html' %} -{% load i18n static %} - -{% block custom_foot_js %} -{{ block.super }} - - -{% endblock %} diff --git a/apps/terminal/templates/terminal/replay_storage_list.html b/apps/terminal/templates/terminal/replay_storage_list.html deleted file mode 100644 index 786f4c844..000000000 --- a/apps/terminal/templates/terminal/replay_storage_list.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'terminal/base_storage_list.html' %} -{% load i18n static %} - -{% block create_storage_url %}{% url "terminal:replay-storage-create" %}{% endblock%} -{% block create_storage_info %}{% trans 'Create replay storage' %}{% endblock %} -{% block custom_foot_js %} -{{ block.super }} - -{% endblock %} diff --git a/apps/terminal/templates/terminal/session_commands.html b/apps/terminal/templates/terminal/session_commands.html deleted file mode 100644 index 3aeb2989f..000000000 --- a/apps/terminal/templates/terminal/session_commands.html +++ /dev/null @@ -1,96 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} -{% load common_tags %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
-
-
-
- -
-
-
-
- {% trans 'Command list' %} -
- - - - - - - - - - -
-
-
- - - - - - - - - - - {% for command in object_list %} - - - - - - - {% empty %} - - - - {% endfor %} - - - - - - -
ID{% trans 'Command' %}{% trans 'Datetime' %}
{{ forloop.counter }}{{ command.input | truncatechars:40 }}
-$ {{ command.input }}
-
-{{ command.output }}
-                                                
{{ command.timestamp|ts_to_date}}
{% trans "There is no command about this session" %}
-
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -{% endblock %} -{% block custom_foot_js %} - - -{% endblock %} diff --git a/apps/terminal/templates/terminal/session_detail.html b/apps/terminal/templates/terminal/session_detail.html deleted file mode 100644 index 1863ad75f..000000000 --- a/apps/terminal/templates/terminal/session_detail.html +++ /dev/null @@ -1,211 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} -{% load common_tags %} - -{% block custom_head_css_js %} - - -{% endblock %} -{% block content %} -
    -
    -
    -
    - -
    -
    -
    -
    - {{ object.id }} -
    - - - - - - - - - - -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    {% trans 'User' %}:{{ object.user }}
    {% trans 'Asset' %}:{{ object.asset }}
    {% trans 'System user' %}:{{ object.system_user }}
    {% trans 'Protocol' %}:{{ object.protocol }}
    {% trans 'Login from' %}:{{ object.login_from_display }}
    {% trans 'Remote addr' %}:{{ object.remote_addr }}
    {% trans 'Date start' %}:{{ object.date_start }}
    {% trans 'Date end' %}:{{ object.date_end }}
    -
    -
    -
    -
    -
    -
    - {% trans 'Quick modify' %} -
    -
    - - - {% if object.is_finished %} - - - - - - - - - {% else %} - - - - - - - - - - - - - {% endif %} - -
    {% trans 'Replay session' %}: - - - -
    {% trans 'Download replay' %}: - - - -
    {% trans 'Terminate session' %}: - - - -
    -
    -
    -
    -
    -
    -
    -
    -
    - -{% endblock %} -{% block custom_foot_js %} - - - - -{% endblock %} diff --git a/apps/terminal/templates/terminal/session_list.html b/apps/terminal/templates/terminal/session_list.html deleted file mode 100644 index c7e7dce6c..000000000 --- a/apps/terminal/templates/terminal/session_list.html +++ /dev/null @@ -1,322 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n %} -{% load static %} -{% load terminal_tags %} -{% load common_tags %} -{% block custom_head_css_js %} - -{% endblock %} - -{% block content_left_head %} -{% endblock %} - -{% block table_pagination %} -{% endblock %} - -{% block table_search %} -{% endblock %} - -{% block table_container %} - - - - - - - - - - - - - - - - - - - -
    {% trans 'ID' %}{% trans 'User' %}{% trans 'Asset' %}{% trans 'System user' %}{% trans 'Remote addr' %}{% trans 'Protocol' %}{% trans 'Login from' %}{% trans 'Command' %}{% trans 'Date start' %}{% trans 'Duration' %}{% trans 'Action' %}
    - -
    - {% if type == "online" and request.user.can_admin_current_org %} -
    - -
    - -
    -
    - {% endif %} -
    - -
    -
    -
    - - - to - -
    -
    -
    - - -{% endblock %} - - -{% block custom_foot_js %} - - - -{% endblock %} - diff --git a/apps/terminal/templates/terminal/terminal_detail.html b/apps/terminal/templates/terminal/terminal_detail.html deleted file mode 100644 index 14aff902c..000000000 --- a/apps/terminal/templates/terminal/terminal_detail.html +++ /dev/null @@ -1,77 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
    -
    -
    -
    - -
    -
    -
    -
    - {{ terminal.name }} -
    - - - - - - - - - - -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    {% trans 'Name' %}:{{ terminal.name }}
    {% trans 'Remote addr' %}:{{ terminal.remote_addr }}
    {% trans 'SSH port' %}:{{ terminal.ssh_port }}
    {% trans 'Http port' %}:{{ terminal.http_port }}
    {% trans 'Date created' %}:{{ terminal.date_created }}
    {% trans 'Comment' %}:{{ asset.comment }}
    -
    -
    -
    -
    -
    -
    -
    -
    -{% endblock %} - diff --git a/apps/terminal/templates/terminal/terminal_list.html b/apps/terminal/templates/terminal/terminal_list.html deleted file mode 100644 index f77949bfa..000000000 --- a/apps/terminal/templates/terminal/terminal_list.html +++ /dev/null @@ -1,144 +0,0 @@ -{% extends '_base_list.html' %} -{% load i18n static %} -{% block custom_head_css_js %} -{{ block.super }} - -{% endblock %} - -{% block table_search %}{% endblock %} - -{% block table_container %} - - - - - - - -{# #} -{# #} - - - - - - - - -
    -
    - -
    -
    {% trans 'Name' %}{% trans 'Addr' %}{% trans 'SSH port' %}{% trans 'Http port' %}{% trans 'Session' %}{% trans 'Active' %}{% trans 'Alive' %}{% trans 'Action' %}
    -{% include 'terminal/terminal_modal_accept.html' %} -{% endblock %} -{% block custom_foot_js %} - - -{% endblock %} diff --git a/apps/terminal/templates/terminal/terminal_modal_accept.html b/apps/terminal/templates/terminal/terminal_modal_accept.html deleted file mode 100644 index ae81ff95e..000000000 --- a/apps/terminal/templates/terminal/terminal_modal_accept.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends '_modal.html' %} -{% load i18n %} -{% block modal_id %}modal_terminal_accept{% endblock %} -{% block modal_class %}modal-lg{% endblock %} -{% block modal_title%}{% trans "Accept terminal registration" %}{% endblock %} -{% block modal_body %} -{% load bootstrap3 %} -
    - {% csrf_token %} - - {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.remote_addr layout="horizontal" %} -{# {% bootstrap_field form.ssh_port layout="horizontal" %}#} -{# {% bootstrap_field form.http_port layout="horizontal" %}#} - {% bootstrap_field form.command_storage layout="horizontal" %} - {% bootstrap_field form.replay_storage layout="horizontal" %} - {% bootstrap_field form.comment layout="horizontal" %} -
    - -{% endblock %} -{% block modal_confirm_id %}btn-confirm{% endblock %} \ No newline at end of file diff --git a/apps/terminal/templates/terminal/terminal_modal_test.html b/apps/terminal/templates/terminal/terminal_modal_test.html deleted file mode 100644 index 862e39f62..000000000 --- a/apps/terminal/templates/terminal/terminal_modal_test.html +++ /dev/null @@ -1,5 +0,0 @@ -
    - {% csrf_token %} - {{ form }} - -
    \ No newline at end of file diff --git a/apps/terminal/templates/terminal/terminal_update.html b/apps/terminal/templates/terminal/terminal_update.html deleted file mode 100644 index e140cd678..000000000 --- a/apps/terminal/templates/terminal/terminal_update.html +++ /dev/null @@ -1,70 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load bootstrap3 %} -{% block custom_head_css_js %} - -{% endblock %} - -{% block content %} -
    -
    -
    -
    -
    -
    {{ action }}
    - -
    -
    -
    - {% csrf_token %} -

    {% trans 'Info' %}

    - {% bootstrap_field form.name layout="horizontal" %} - {% bootstrap_field form.remote_addr layout="horizontal" %} - {% bootstrap_field form.command_storage layout="horizontal" %} - {% bootstrap_field form.replay_storage layout="horizontal" %} - -
    -

    {% trans 'Other' %}

    - {% bootstrap_field form.comment layout="horizontal" %} -
    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    -{% endblock %} -{% block custom_foot_js %} - - -{% endblock %} diff --git a/apps/terminal/templatetags/__init__.py b/apps/terminal/templatetags/__init__.py deleted file mode 100644 index ec51c5a2b..000000000 --- a/apps/terminal/templatetags/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -# diff --git a/apps/terminal/templatetags/terminal_tags.py b/apps/terminal/templatetags/terminal_tags.py deleted file mode 100644 index c0844eb31..000000000 --- a/apps/terminal/templatetags/terminal_tags.py +++ /dev/null @@ -1,14 +0,0 @@ -# ~*~ coding: utf-8 ~*~ - -from django import template - -from ..backends import get_multi_command_storage - -register = template.Library() - - -@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/urls/views_urls.py b/apps/terminal/urls/views_urls.py deleted file mode 100644 index 048a79d71..000000000 --- a/apps/terminal/urls/views_urls.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# - -from django.urls import path - -from .. import views - -app_name = 'terminal' - -urlpatterns = [ - # Terminal view - path('terminal/', views.TerminalListView.as_view(), name='terminal-list'), - path('terminal//', views.TerminalDetailView.as_view(), name='terminal-detail'), - path('terminal//connect/', views.TerminalConnectView.as_view(), name='terminal-connect'), - path('terminal//update/', views.TerminalUpdateView.as_view(), name='terminal-update'), - path('/accept/', views.TerminalAcceptView.as_view(), name='terminal-accept'), - path('web-terminal/', views.WebTerminalView.as_view(), name='web-terminal'), - path('web-sftp/', views.WebSFTPView.as_view(), name='web-sftp'), - - # Session view - path('session-online/', views.SessionOnlineListView.as_view(), name='session-online-list'), - path('session-offline/', views.SessionOfflineListView.as_view(), name='session-offline-list'), - path('session//', views.SessionDetailView.as_view(), name='session-detail'), - path('session//commands/', views.SessionCommandsView.as_view(), name='session-commands'), - path('session//replay/download/', views.SessionReplayDownloadView.as_view(), name='session-replay-download'), - - # Command view - path('command/', views.CommandListView.as_view(), name='command-list'), - - # replay-storage - path('terminal/replay-storage/', views.ReplayStorageListView.as_view(), name='replay-storage-list'), - path('terminal/replay-storage/create/', views.ReplayStorageCreateView.as_view(), name='replay-storage-create'), - path('terminal/replay-storage//update/', views.ReplayStorageUpdateView.as_view(), name='replay-storage-update'), - - # command-storage - path('terminal/command-storage/', views.CommandStorageListView.as_view(), name='command-storage-list'), - path('terminal/command-storage/create/', views.CommandStorageCreateView.as_view(), name='command-storage-create'), - path('terminal/command-storage//update/', views.CommandStorageUpdateView.as_view(), name='command-storage-update'), - -] diff --git a/apps/terminal/views/__init__.py b/apps/terminal/views/__init__.py deleted file mode 100644 index a63c3cf9a..000000000 --- a/apps/terminal/views/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -# -from .terminal import * -from .session import * -from .command import * -from .storage import * - -# from .replay_storage import * -# from .command_storage import * diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py deleted file mode 100644 index 3ac31eed5..000000000 --- a/apps/terminal/views/command.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# - -from django.views.generic import TemplateView -from django.utils.translation import ugettext as _ -from django.utils import timezone - -from common.permissions import PermissionsMixin, IsOrgAdmin, IsOrgAuditor - -__all__ = ['CommandListView'] - - -class CommandListView(PermissionsMixin, TemplateView): - template_name = "terminal/command_list.html" - permission_classes = [IsOrgAdmin | IsOrgAuditor] - default_days_ago = 5 - - def get_context_data(self, **kwargs): - now = timezone.now() - context = { - 'app': _('Sessions'), - 'action': _('Command list'), - 'date_from': now - timezone.timedelta(days=self.default_days_ago), - 'date_to': now, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - diff --git a/apps/terminal/views/session.py b/apps/terminal/views/session.py deleted file mode 100644 index a7e56cd57..000000000 --- a/apps/terminal/views/session.py +++ /dev/null @@ -1,142 +0,0 @@ -# -*- coding: utf-8 -*- -# -import os -import tarfile - -from django.views.generic import ListView, TemplateView, DetailView -from django.views.generic.edit import SingleObjectMixin -from django.utils.translation import ugettext as _ -from django.utils import timezone -from django.utils.encoding import escape_uri_path -from django.http import FileResponse, HttpResponse -from django.core.files.storage import default_storage - -from common.permissions import PermissionsMixin, IsOrgAdmin, IsOrgAuditor -from common.utils import model_to_json -from ..models import Session -from ..backends import get_multi_command_storage -from .. import utils - - -__all__ = [ - 'SessionOnlineListView', 'SessionOfflineListView', - 'SessionDetailView', 'SessionReplayDownloadView', - 'SessionCommandsView', -] - - -class SessionListView(PermissionsMixin, TemplateView): - model = Session - template_name = 'terminal/session_list.html' - date_from = date_to = None - permission_classes = [IsOrgAdmin | IsOrgAuditor] - default_days_ago = 5 - - def get_context_data(self, **kwargs): - now = timezone.now() - context = { - 'date_from': now - timezone.timedelta(days=self.default_days_ago), - 'date_to': now, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SessionOnlineListView(SessionListView): - def get_context_data(self, **kwargs): - context = { - 'app': _('Sessions'), - 'action': _('Session online list'), - 'type': 'online', - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SessionOfflineListView(SessionListView): - def get_context_data(self, **kwargs): - context = { - 'app': _('Sessions'), - 'action': _('Session offline'), - 'type': 'offline', - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SessionDetailView(PermissionsMixin, DetailView): - template_name = 'terminal/session_detail.html' - model = Session - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Sessions'), - 'action': _('Session detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SessionCommandsView(SingleObjectMixin, PermissionsMixin, ListView): - template_name = 'terminal/session_commands.html' - model = Session - object = None - permission_classes = [IsOrgAdmin | IsOrgAuditor] - - def get(self, request, *args, **kwargs): - self.object = self.get_object(queryset=self.model.objects.all()) - 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): - context = { - 'app': _('Sessions'), - 'action': _('Session detail'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class SessionReplayDownloadView(PermissionsMixin, DetailView): - permission_classes = [IsOrgAdmin | IsOrgAuditor] - model = Session - - @staticmethod - def prepare_offline_file(session, local_path): - replay_path = default_storage.path(local_path) - current_dir = os.getcwd() - dir_path = os.path.dirname(replay_path) - replay_filename = os.path.basename(replay_path) - meta_filename = '{}.json'.format(session.id) - offline_filename = '{}.tar'.format(session.id) - os.chdir(dir_path) - - with open(meta_filename, 'wt') as f: - f.write(model_to_json(session)) - - with tarfile.open(offline_filename, 'w') as f: - f.add(replay_filename) - f.add(meta_filename) - file = open(offline_filename, 'rb') - os.chdir(current_dir) - return file - - def get(self, request, *args, **kwargs): - session = self.get_object() - local_path, url = utils.get_session_replay_url(session) - if local_path is None: - error = url - return HttpResponse(error) - file = self.prepare_offline_file(session, local_path) - response = FileResponse(file) - response['Content-Type'] = 'application/octet-stream' - # 这里要注意哦,网上查到的方法都是response['Content-Disposition']='attachment;filename="filename.py"', - # 但是如果文件名是英文名没问题,如果文件名包含中文,下载下来的文件名会被改为url中的path。 - filename = escape_uri_path('{}.tar'.format(session.id)) - disposition = "attachment; filename*=UTF-8''{}".format(filename) - response["Content-Disposition"] = disposition - return response diff --git a/apps/terminal/views/storage.py b/apps/terminal/views/storage.py deleted file mode 100644 index 778cdd489..000000000 --- a/apps/terminal/views/storage.py +++ /dev/null @@ -1,181 +0,0 @@ -# coding: utf-8 -# - -from django.http import Http404 -from django.views.generic import TemplateView -from django.views.generic.edit import CreateView, UpdateView -from django.utils.translation import ugettext as _ - -from common.permissions import PermissionsMixin, IsSuperUser -from terminal.models import ReplayStorage, CommandStorage -from .. import forms, const - - -__all__ = [ - 'ReplayStorageListView', 'ReplayStorageCreateView', - 'ReplayStorageUpdateView', 'CommandStorageListView', - 'CommandStorageCreateView', 'CommandStorageUpdateView' -] - - -class ReplayStorageListView(PermissionsMixin, TemplateView): - template_name = 'terminal/replay_storage_list.html' - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Terminal'), - 'action': _('Replay storage list'), - 'is_replay': True, - 'type_choices': const.REPLAY_STORAGE_TYPE_CHOICES_EXTENDS, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandStorageListView(PermissionsMixin, TemplateView): - template_name = 'terminal/command_storage_list.html' - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Terminal'), - 'action': _('Command storage list'), - 'type_choices': const.COMMAND_STORAGE_TYPE_CHOICES_EXTENDS, - 'is_command': True, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class BaseStorageCreateUpdateViewMixin: - permission_classes = [IsSuperUser] - default_type = None - form_class = None - form_class_choices = {} - - def get_initial(self): - return {'type': self.get_type()} - - def get_type(self): - return self.default_type - - def get_form_class(self): - tp = self.get_type() - form_class = self.form_class_choices.get(tp) - if not form_class: - raise Http404() - return form_class - - -class ReplayStorageCreateUpdateViewMixin(BaseStorageCreateUpdateViewMixin): - model = ReplayStorage - default_type = const.REPLAY_STORAGE_TYPE_S3 - form_class = forms.ReplayStorageS3Form - form_class_choices = { - const.REPLAY_STORAGE_TYPE_S3: forms.ReplayStorageS3Form, - const.REPLAY_STORAGE_TYPE_CEPH: forms.ReplayStorageCephForm, - const.REPLAY_STORAGE_TYPE_SWIFT: forms.ReplayStorageSwiftForm, - const.REPLAY_STORAGE_TYPE_OSS: forms.ReplayStorageOSSForm, - const.REPLAY_STORAGE_TYPE_AZURE: forms.ReplayStorageAzureForm - } - - -class ReplayStorageCreateView(ReplayStorageCreateUpdateViewMixin, - PermissionsMixin, CreateView): - template_name = 'terminal/replay_storage_create_update.html' - - def get_type(self): - tp = self.request.GET.get("type") - if tp: - return tp.lower() - return super().get_type() - - def get_context_data(self, **kwargs): - context = { - 'app': _('Terminal'), - 'action': _('Create replay storage'), - 'api_action': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class ReplayStorageUpdateView(ReplayStorageCreateUpdateViewMixin, - PermissionsMixin, UpdateView): - template_name = 'terminal/replay_storage_create_update.html' - - def get_initial(self): - initial_data = super().get_initial() - for k, v in self.object.meta.items(): - _k = "{}_{}".format(self.object.type, k.lower()) - initial_data[_k] = v - return initial_data - - def get_type(self): - return self.object.type - - def get_context_data(self, **kwargs): - context = { - 'app': _('Terminal'), - 'action': _('Update replay storage'), - 'api_action': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandStorageCreateUpdateViewMixin(BaseStorageCreateUpdateViewMixin): - model = CommandStorage - default_type = const.COMMAND_STORAGE_TYPE_ES - form_class = forms.CommandStorageTypeESForm - form_class_choices = { - const.COMMAND_STORAGE_TYPE_ES: forms.CommandStorageTypeESForm - } - - -class CommandStorageCreateView(CommandStorageCreateUpdateViewMixin, - PermissionsMixin, CreateView): - template_name = 'terminal/command_storage_create_update.html' - - def get_type(self): - tp = self.request.GET.get("type") - if tp: - return tp.lower() - return super().get_type() - - def get_context_data(self, **kwargs): - context = { - 'app': _('Terminal'), - 'action': _('Create command storage'), - 'api_action': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class CommandStorageUpdateView(CommandStorageCreateUpdateViewMixin, - PermissionsMixin, UpdateView): - template_name = 'terminal/command_storage_create_update.html' - - def get_initial(self): - initial_data = super().get_initial() - for k, v in self.object.meta.items(): - _k = "{}_{}".format(self.object.type, k.lower()) - if k == 'HOSTS': - v = ','.join(v) - initial_data[_k] = v - return initial_data - - def get_type(self): - return self.object.type - - def get_context_data(self, **kwargs): - context = { - 'app': _('Terminal'), - 'action': _('Update command storage'), - 'api_action': 'update' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - diff --git a/apps/terminal/views/terminal.py b/apps/terminal/views/terminal.py deleted file mode 100644 index 56e3a4dc9..000000000 --- a/apps/terminal/views/terminal.py +++ /dev/null @@ -1,138 +0,0 @@ -# ~*~ coding: utf-8 ~*~ -# -import time -from django.views.generic import ListView, UpdateView, DeleteView, \ - DetailView, View -from django.utils.translation import ugettext as _ -from django.shortcuts import redirect -from django.urls import reverse_lazy, reverse - -from common.mixins import JSONResponseMixin -from ..models import Terminal -from ..forms import TerminalForm -from common.permissions import PermissionsMixin, IsSuperUser - - -__all__ = [ - "TerminalListView", "TerminalUpdateView", "TerminalDetailView", - "TerminalDeleteView", "TerminalConnectView", "TerminalAcceptView", - "WebTerminalView", 'WebSFTPView', -] - - -class TerminalListView(PermissionsMixin, ListView): - model = Terminal - template_name = 'terminal/terminal_list.html' - form_class = TerminalForm - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = super(TerminalListView, self).get_context_data(**kwargs) - context.update({ - 'app': _('Sessions'), - 'action': _('Terminal list'), - 'form': self.form_class() - }) - return context - - -class TerminalUpdateView(PermissionsMixin, UpdateView): - model = Terminal - form_class = TerminalForm - template_name = 'terminal/terminal_update.html' - success_url = reverse_lazy('terminal:terminal-list') - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = super(TerminalUpdateView, self).get_context_data(**kwargs) - context.update({'app': _('Sessions'), 'action': _('Update terminal')}) - return context - - -class TerminalDetailView(PermissionsMixin, DetailView): - model = Terminal - template_name = 'terminal/terminal_detail.html' - context_object_name = 'terminal' - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - context = super(TerminalDetailView, self).get_context_data(**kwargs) - context.update({ - 'app': _('Sessions'), - 'action': _('Terminal detail') - }) - return context - - -class TerminalDeleteView(PermissionsMixin, DeleteView): - model = Terminal - template_name = 'delete_confirm.html' - success_url = reverse_lazy('terminal:terminal-list') - permission_classes = [IsSuperUser] - - -class TerminalAcceptView(PermissionsMixin, JSONResponseMixin, UpdateView): - model = Terminal - form_class = TerminalForm - template_name = 'terminal/terminal_modal_accept.html' - permission_classes = [IsSuperUser] - - def form_valid(self, form): - terminal = form.save() - terminal.create_app_user() - terminal.is_accepted = True - terminal.is_active = True - terminal.save() - data = { - 'success': True, - 'msg': 'success' - } - return self.render_json_response(data) - - def form_invalid(self, form): - data = { - 'success': False, - 'msg': str(form.errors), - } - return self.render_json_response(data) - - -class TerminalConnectView(PermissionsMixin, DetailView): - """ - Abandon - """ - template_name = 'flash_message_standalone.html' - model = Terminal - permission_classes = [IsSuperUser] - - def get_context_data(self, **kwargs): - if self.object.type == 'Web': - context = { - 'title': _('Redirect to web terminal'), - 'messages': _('Redirect to web terminal') + self.object.url, - 'auto_redirect': True, - 'interval': 3, - 'redirect_url': self.object.url - } - else: - context = { - 'title': _('Connect ssh terminal'), - 'messages': _('You should use your ssh client tools ' - 'connect terminal: {}

    ' - '{}'.format(self.object.name, self.object.url)), - 'redirect_url': reverse('terminal:terminal-list') - } - - kwargs.update(context) - return super(TerminalConnectView, self).get_context_data(**kwargs) - - -class WebTerminalView(View): - def get(self, request, *args, **kwargs): - redirect_url = '/luna/?_={}&'.format(int(time.time())) - return redirect(redirect_url + request.GET.urlencode()) - - -class WebSFTPView(View): - def get(self, request, *args, **kwargs): - return redirect('/koko/elfinder/sftp/?' + request.GET.urlencode()) diff --git a/apps/tickets/templates/tickets/ticket_detail.html b/apps/tickets/templates/tickets/ticket_detail.html deleted file mode 100644 index bcbe83045..000000000 --- a/apps/tickets/templates/tickets/ticket_detail.html +++ /dev/null @@ -1,181 +0,0 @@ -{% extends 'base.html' %} -{% load static %} -{% load i18n %} - -{% block content %} -
    -
    -
    -
    -
    -
    - {{ object.title }} -
    - -
    -
    -
    -
    -
    -
    -
    -
    {% trans 'User' %}:
    {{ object.user_display }}
    -
    {% trans 'Type' %}:
    {{ object.get_type_display | default_if_none:"" }}
    -
    {% trans 'Status' %}:
    -
    - {% if object.status == "open" %} - - {{ object.get_status_display }} - - {% elif object.status == "closed" %} - - {{ object.get_status_display }} - - {% endif %} -
    -
    -
    -
    -
    -
    {% trans 'Assignees' %}:
    {{ object.assignees_display }}
    -
    {% trans 'Assignee' %}:
    {{ object.assignee_display | default_if_none:"" }}
    -
    {% trans 'Date created' %}:
    {{ object.date_created }}
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - image - -
    - {{ object.user_display }} {{ object.date_created|timesince}} {% trans 'ago' %} -
    - {{ object.date_created }} -
    - {{ object.body_as_html | safe }} -
    -
    -
    - {% for comment in object.comments.all %} -
    - - image - -
    - {{ comment.user_display }} {{ comment.date_created|timesince}} {% trans 'ago' %} -
    - {{ comment.date_created }} -
    - {{ comment.body }} -
    -
    -
    - {% endfor %} -
    -
    - - image - -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -{% endblock %} -{% block custom_foot_js %} - -{% endblock %} diff --git a/apps/tickets/templates/tickets/ticket_list.html b/apps/tickets/templates/tickets/ticket_list.html deleted file mode 100644 index dfbc973e7..000000000 --- a/apps/tickets/templates/tickets/ticket_list.html +++ /dev/null @@ -1,117 +0,0 @@ -{% extends 'base.html' %} -{% load i18n static %} - -{% block content %} -
    -
    -
    - -
    -
    -
    - {% if False %} -
    -
    - - -
    -
    - {% endif %} - - - - - - - - - - - - {% include '_filter_dropdown.html' %} - - -
    - - {% trans 'Title' %}{% trans 'User' %}{% trans 'Type' %}{% trans 'Status' %}{% trans 'Datetime' %}
    -
    -
    -
    -
    -
    -
    -{% endblock %} -{% block content_bottom_left %}{% endblock %} -{% block custom_foot_js %} - -{% endblock %} - diff --git a/apps/tickets/urls/views_urls.py b/apps/tickets/urls/views_urls.py deleted file mode 100644 index 46e15437e..000000000 --- a/apps/tickets/urls/views_urls.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django.urls import path -from .. import views - -app_name = 'tickets' - -urlpatterns = [ - path('tickets/', views.TicketListView.as_view(), name='ticket-list'), - path('tickets//', views.TicketDetailView.as_view(), name='ticket-detail'), -] diff --git a/apps/tickets/views.py b/apps/tickets/views.py deleted file mode 100644 index 93b4aca2f..000000000 --- a/apps/tickets/views.py +++ /dev/null @@ -1,41 +0,0 @@ -from django.views.generic import TemplateView, DetailView -from django.utils.translation import ugettext as _ - -from common.permissions import PermissionsMixin, IsValidUser -from .models import Ticket -from . import mixins - - -class TicketListView(PermissionsMixin, TemplateView): - template_name = 'tickets/ticket_list.html' - permission_classes = (IsValidUser,) - - def get_context_data(self, **kwargs): - assign = self.request.GET.get('assign', '0') == '1' - context = super().get_context_data(**kwargs) - assigned_open_count = Ticket.get_assigned_tickets(self.request.user)\ - .filter(status=Ticket.STATUS_OPEN).count() - context.update({ - 'app': _("Tickets"), - 'action': _("Ticket list"), - 'assign': assign, - 'assigned_open_count': assigned_open_count - }) - return context - - -class TicketDetailView(PermissionsMixin, mixins.TicketMixin, DetailView): - template_name = 'tickets/ticket_detail.html' - permission_classes = (IsValidUser,) - queryset = Ticket.objects.all() - - def get_context_data(self, **kwargs): - ticket = self.get_object() - has_action_perm = ticket.is_assignee(self.request.user) - context = super().get_context_data(**kwargs) - context.update({ - 'app': _("Tickets"), - 'action': _("Ticket detail"), - 'has_action_perm': has_action_perm, - }) - return context diff --git a/apps/users/urls/views_urls.py b/apps/users/urls/views_urls.py deleted file mode 100644 index 23f209d5c..000000000 --- a/apps/users/urls/views_urls.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import absolute_import - -from django.urls import path - -from .. import views - -app_name = 'users' - -urlpatterns = [ - # Login view - path('login/', views.UserLoginView.as_view(), name='login'), - path('password/forgot/', views.UserForgotPasswordView.as_view(), name='forgot-password'), - path('password/forgot/sendmail-success/', views.UserForgotPasswordSendmailSuccessView.as_view(), name='forgot-password-sendmail-success'), - path('password/reset/', views.UserResetPasswordView.as_view(), name='reset-password'), - path('password/reset/success/', views.UserResetPasswordSuccessView.as_view(), name='reset-password-success'), - path('password/verify/', views.UserVerifyPasswordView.as_view(), name='user-verify-password'), - - # Profile - path('profile/', views.UserProfileView.as_view(), name='user-profile'), - path('profile/update/', views.UserProfileUpdateView.as_view(), name='user-profile-update'), - path('profile/password/update/', views.UserPasswordUpdateView.as_view(), name='user-password-update'), - path('profile/pubkey/update/', views.UserPublicKeyUpdateView.as_view(), name='user-pubkey-update'), - path('profile/pubkey/generate/', views.UserPublicKeyGenerateView.as_view(), name='user-pubkey-generate'), - - path('profile/otp/enable/start/', views.UserOtpEnableStartView.as_view(), name='user-otp-enable-start'), - path('profile/otp/enable/install-app/', views.UserOtpEnableInstallAppView.as_view(), name='user-otp-enable-install-app'), - path('profile/otp/enable/bind/', views.UserOtpEnableBindView.as_view(), name='user-otp-enable-bind'), - path('profile/otp/disable/authentication/', views.UserDisableMFAView.as_view(), name='user-otp-disable-authentication'), - path('profile/otp/update/', views.UserOtpUpdateView.as_view(), name='user-otp-update'), - path('profile/otp/settings-success/', views.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'), - - # User view - path('user/', views.UserListView.as_view(), name='user-list'), - path('first-login/', views.UserFirstLoginView.as_view(), name='user-first-login'), - path('user/create/', views.UserCreateView.as_view(), name='user-create'), - path('user//update/', views.UserUpdateView.as_view(), name='user-update'), - path('user/update/', views.UserBulkUpdateView.as_view(), name='user-bulk-update'), - path('user//', views.UserDetailView.as_view(), name='user-detail'), - path('user//assets/', views.UserGrantedAssetView.as_view(), name='user-granted-asset'), - path('user//asset-permissions/', views.UserAssetPermissionListView.as_view(), name='user-asset-permission'), - path('user//remote-apps/', views.UserGrantedRemoteAppView.as_view(), name='user-granted-remote-app'), - path('user//remote-app-permissions/', views.UserRemoteAppPermissionListView.as_view(), name='user-remote-app-permission'), - path('user//database-apps/', views.UserGrantedDatabasesAppView.as_view(), name='user-granted-database-app'), - path('user//database-app-permissions/', views.UserDatabaseAppPermissionListView.as_view(), name='user-database-app-permission'), - path('user//login-history/', views.UserDetailView.as_view(), name='user-login-history'), - - # User group view - path('user-group/', views.UserGroupListView.as_view(), name='user-group-list'), - path('user-group//', views.UserGroupDetailView.as_view(), name='user-group-detail'), - path('user-group/create/', views.UserGroupCreateView.as_view(), name='user-group-create'), - path('user-group//update/', views.UserGroupUpdateView.as_view(), name='user-group-update'), - path('user-group//assets/', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'), - -] diff --git a/apps/users/views/__init__.py b/apps/users/views/__init__.py index 17d4f4110..bd184529d 100644 --- a/apps/users/views/__init__.py +++ b/apps/users/views/__init__.py @@ -1,6 +1,3 @@ # ~*~ coding: utf-8 ~*~ -from .login import * -from .user import * from .profile import * -from .group import * diff --git a/apps/users/views/group.py b/apps/users/views/group.py deleted file mode 100644 index ed9976eab..000000000 --- a/apps/users/views/group.py +++ /dev/null @@ -1,103 +0,0 @@ -# ~*~ coding: utf-8 ~*~ -from __future__ import unicode_literals -from django.utils.translation import ugettext as _ -from django.urls import reverse_lazy -from django.views.generic.base 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 common.utils import get_logger -from common.const import create_success_msg, update_success_msg -from common.permissions import PermissionsMixin, IsOrgAdmin -from orgs.utils import current_org -from ..models import User, UserGroup -from .. import forms - -__all__ = ['UserGroupListView', 'UserGroupCreateView', 'UserGroupDetailView', - 'UserGroupUpdateView', 'UserGroupGrantedAssetView'] -logger = get_logger(__name__) - - -class UserGroupListView(PermissionsMixin, TemplateView): - template_name = 'users/user_group_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Users'), - 'action': _('User group list') - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserGroupCreateView(PermissionsMixin, SuccessMessageMixin, CreateView): - model = UserGroup - form_class = forms.UserGroupForm - template_name = 'users/user_group_create_update.html' - success_url = reverse_lazy('users:user-group-list') - success_message = create_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Users'), - 'action': _('Create user group'), - 'type': 'create' - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserGroupUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView): - model = UserGroup - form_class = forms.UserGroupForm - template_name = 'users/user_group_create_update.html' - success_url = reverse_lazy('users:user-group-list') - success_message = update_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Users'), - 'action': _('Update user group'), - 'type': 'update' - - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserGroupDetailView(PermissionsMixin, DetailView): - model = UserGroup - context_object_name = 'user_group' - template_name = 'users/user_group_detail.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - users = current_org.get_org_members(exclude=('Auditor',)).exclude( - groups=self.object) - context = { - 'app': _('Users'), - 'action': _('User group detail'), - 'users': users, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserGroupGrantedAssetView(PermissionsMixin, DetailView): - model = UserGroup - template_name = 'users/user_group_granted_asset.html' - context_object_name = 'user_group' - object = None - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Users'), - 'action': _('User group granted asset'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) diff --git a/apps/users/views/profile/__init__.py b/apps/users/views/profile/__init__.py index 1bc58d06f..99094f092 100644 --- a/apps/users/views/profile/__init__.py +++ b/apps/users/views/profile/__init__.py @@ -5,3 +5,4 @@ from .password import * from .pubkey import * from .mfa import * from .otp import * +from .reset import * diff --git a/apps/users/views/login.py b/apps/users/views/profile/reset.py similarity index 98% rename from apps/users/views/login.py rename to apps/users/views/profile/reset.py index d7db52d51..f1da16a4d 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/profile/reset.py @@ -14,11 +14,11 @@ from django.views.generic import FormView from common.utils import get_object_or_none from common.permissions import PermissionsMixin, IsValidUser -from ..models import User -from ..utils import ( +from ...models import User +from ...utils import ( send_reset_password_mail, get_password_check_rules, check_password_rules ) -from .. import forms +from ... import forms __all__ = [ diff --git a/apps/users/views/user.py b/apps/users/views/user.py deleted file mode 100644 index 8e6650240..000000000 --- a/apps/users/views/user.py +++ /dev/null @@ -1,280 +0,0 @@ -# ~*~ coding: utf-8 ~*~ - -from __future__ import unicode_literals - - -from django.contrib import messages -from django.contrib.messages.views import SuccessMessageMixin -from django.core.cache import cache -from django.shortcuts import redirect -from django.urls import reverse_lazy -from django.utils.translation import ugettext as _ -from django.views.generic.base import TemplateView -from django.views.generic.edit import ( - CreateView, UpdateView -) -from django.views.generic.detail import DetailView - -from common.const import ( - create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID -) -from common.utils import get_logger -from common.permissions import ( - PermissionsMixin, IsOrgAdmin, - CanUpdateDeleteUser, -) -from orgs.utils import current_org -from .. import forms -from ..models import User, UserGroup -from ..utils import get_password_check_rules, is_need_unblock -from ..signals import post_user_create - -__all__ = [ - 'UserListView', 'UserCreateView', 'UserDetailView', - 'UserUpdateView', 'UserBulkUpdateView', - 'UserGrantedAssetView', 'UserAssetPermissionListView', - 'UserGrantedRemoteAppView', 'UserRemoteAppPermissionListView', - 'UserGrantedDatabasesAppView', 'UserDatabaseAppPermissionListView', -] - -logger = get_logger(__name__) - - -class UserListView(PermissionsMixin, TemplateView): - template_name = 'users/user_list.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({ - 'app': _('Users'), - 'action': _('User list'), - }) - return context - - -class UserCreateView(PermissionsMixin, SuccessMessageMixin, CreateView): - model = User - form_class = forms.UserCreateForm - template_name = 'users/user_create.html' - success_url = reverse_lazy('users:user-list') - success_message = create_success_msg - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - 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) - user.created_by = self.request.user.username or 'System' - user.save() - if current_org and current_org.is_real(): - user.related_user_orgs.add(current_org.id) - post_user_create.send(self.__class__, user=user) - return super().form_valid(form) - - def get_form_kwargs(self): - kwargs = super(UserCreateView, self).get_form_kwargs() - data = {'request': self.request} - kwargs.update(data) - return kwargs - - -class UserUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView): - model = User - form_class = forms.UserUpdateForm - template_name = 'users/user_update.html' - context_object_name = 'user_object' - success_url = reverse_lazy('users:user-list') - success_message = update_success_msg - permission_classes = [IsOrgAdmin] - - def _deny_permission(self): - obj = self.get_object() - return not self.request.user.is_superuser and obj.is_superuser - - def get(self, request, *args, **kwargs): - if self._deny_permission(): - return redirect(self.success_url) - return super().get(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - check_rules = get_password_check_rules() - context = { - 'app': _('Users'), - 'action': _('Update user'), - 'password_check_rules': check_rules, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def get_form_kwargs(self): - kwargs = super(UserUpdateView, self).get_form_kwargs() - data = {'request': self.request} - kwargs.update(data) - return kwargs - - -class UserBulkUpdateView(PermissionsMixin, TemplateView): - model = User - form_class = forms.UserBulkUpdateForm - template_name = 'users/user_bulk_update.html' - success_url = reverse_lazy('users:user-list') - success_message = _("Bulk update user success") - form = None - id_list = None - permission_classes = [IsOrgAdmin] - - def get(self, request, *args, **kwargs): - 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': users_id}) - else: - self.form = self.form_class() - return super().get(request, *args, **kwargs) - - def post(self, request, *args, **kwargs): - form = self.form_class(request.POST) - if form.is_valid(): - form.save() - messages.success(request, self.success_message) - return redirect(self.success_url) - else: - return self.get(request, form=form, *args, **kwargs) - - def get_context_data(self, **kwargs): - context = { - 'app': 'Assets', - 'action': _('Bulk update user'), - 'form': self.form, - 'users_selected': self.id_list, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserDetailView(PermissionsMixin, DetailView): - model = User - template_name = 'users/user_detail.html' - context_object_name = "object" - key_prefix_block = "_LOGIN_BLOCK_{}" - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - user = self.get_object() - key_block = self.key_prefix_block.format(user.username) - groups = UserGroup.objects.exclude(id__in=self.object.groups.all()) - context = { - 'app': _('Users'), - 'action': _('User detail'), - 'groups': groups, - 'unblock': is_need_unblock(key_block), - 'can_update': CanUpdateDeleteUser.has_update_object_permission( - self.request, self, user - ), - 'can_delete': CanUpdateDeleteUser.has_delete_object_permission( - self.request, self, user - ), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - def get_queryset(self): - queryset = super().get_queryset() - org_users = current_org.get_org_members().values_list('id', flat=True) - queryset = queryset.filter(id__in=org_users) - return queryset - - -class UserGrantedAssetView(PermissionsMixin, DetailView): - model = User - template_name = 'users/user_granted_asset.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Users'), - 'action': _('User granted assets'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserAssetPermissionListView(PermissionsMixin, DetailView): - model = User - template_name = 'users/user_asset_permission.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Users'), - 'action': _('Asset permission'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserGrantedRemoteAppView(PermissionsMixin, DetailView): - model = User - template_name = 'users/user_granted_remote_app.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Users'), - 'action': _('User granted RemoteApp'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserRemoteAppPermissionListView(PermissionsMixin, DetailView): - model = User - template_name = 'users/user_remote_app_permission.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Users'), - 'action': _('RemoteApp permission'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserGrantedDatabasesAppView(PermissionsMixin, DetailView): - model = User - template_name = 'users/user_granted_database_app.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Users'), - 'action': _('User granted DatabaseApp'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserDatabaseAppPermissionListView(PermissionsMixin, DetailView): - model = User - template_name = 'users/user_database_app_permission.html' - permission_classes = [IsOrgAdmin] - - def get_context_data(self, **kwargs): - context = { - 'app': _('Users'), - 'action': _('DatabaseApp permission'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) From 8d58d585197b855301b5ea7353d3a6c30bdd4d0f Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 3 Jun 2020 11:58:16 +0800 Subject: [PATCH 075/146] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9profile=20vie?= =?UTF-8?q?w?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/views/profile/__init__.py | 1 - apps/users/views/profile/base.py | 50 ---------------------------- 2 files changed, 51 deletions(-) delete mode 100644 apps/users/views/profile/base.py diff --git a/apps/users/views/profile/__init__.py b/apps/users/views/profile/__init__.py index 99094f092..b2d0e3491 100644 --- a/apps/users/views/profile/__init__.py +++ b/apps/users/views/profile/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # -from .base import * from .password import * from .pubkey import * from .mfa import * diff --git a/apps/users/views/profile/base.py b/apps/users/views/profile/base.py deleted file mode 100644 index 2044a0c94..000000000 --- a/apps/users/views/profile/base.py +++ /dev/null @@ -1,50 +0,0 @@ -# ~*~ coding: utf-8 ~*~ -from django.conf import settings -from django.urls import reverse_lazy -from django.utils.translation import ugettext as _ -from django.views.generic.base import TemplateView -from django.views.generic.edit import UpdateView - -from common.utils import get_logger -from common.permissions import ( - PermissionsMixin, IsValidUser, -) -from ... import forms -from ...models import User - - -__all__ = ['UserProfileView', 'UserProfileUpdateView'] -logger = get_logger(__name__) - - -class UserProfileView(PermissionsMixin, TemplateView): - template_name = 'users/user_profile.html' - permission_classes = [IsValidUser] - - def get_context_data(self, **kwargs): - mfa_setting = settings.SECURITY_MFA_AUTH - context = { - 'action': _('Profile'), - 'mfa_setting': mfa_setting if mfa_setting is not None else False, - } - kwargs.update(context) - return super().get_context_data(**kwargs) - - -class UserProfileUpdateView(PermissionsMixin, UpdateView): - template_name = 'users/user_profile_update.html' - model = User - permission_classes = [IsValidUser] - form_class = forms.UserProfileForm - success_url = reverse_lazy('users:user-profile') - - def get_object(self, queryset=None): - return self.request.user - - def get_context_data(self, **kwargs): - context = { - 'app': _('User'), - 'action': _('Profile setting'), - } - kwargs.update(context) - return super().get_context_data(**kwargs) From 68ccaf0cb39484ce64613fda56cde49c2656fe95 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 3 Jun 2020 12:26:53 +0800 Subject: [PATCH 076/146] =?UTF-8?q?feat:=20=E5=8E=BB=E6=8E=89=E7=AC=AC?= =?UTF-8?q?=E4=B8=80=E6=AC=A1=E7=99=BB=E5=BD=95=E7=9A=84=E9=82=A3=E4=B8=AA?= =?UTF-8?q?=E5=AF=BC=E8=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/urls/view_urls.py | 1 + apps/templates/_message.html | 2 +- apps/templates/_user_profile.html | 2 +- apps/users/templates/users/first_login.html | 141 +------------------- apps/users/utils.py | 2 +- apps/users/views/profile/reset.py | 58 +------- 6 files changed, 8 insertions(+), 198 deletions(-) diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py index 6972ae9cd..4bd785a08 100644 --- a/apps/authentication/urls/view_urls.py +++ b/apps/authentication/urls/view_urls.py @@ -33,6 +33,7 @@ urlpatterns = [ name='user-otp-disable-authentication'), path('profile/otp/update/', users_view.UserOtpUpdateView.as_view(), name='user-otp-update'), path('profile/otp/settings-success/', users_view.UserOtpSettingsSuccessView.as_view(), name='user-otp-settings-success'), + path('first-login/', users_view.UserFirstLoginView.as_view(), name='user-first-login'), # openid path('cas/', include(('authentication.backends.cas.urls', 'authentication'), namespace='cas')), diff --git a/apps/templates/_message.html b/apps/templates/_message.html index 19e559d36..39eacd17a 100644 --- a/apps/templates/_message.html +++ b/apps/templates/_message.html @@ -39,7 +39,7 @@ {% block first_login_message %} {% if request.user.is_authenticated and request.user.is_first_login %}
    - {% url 'users:user-first-login' as first_login_url %} + {% url 'authentication:user-first-login' as first_login_url %} {% blocktrans %} Your information was incomplete. Please click this link to complete your information. {% endblocktrans %} diff --git a/apps/templates/_user_profile.html b/apps/templates/_user_profile.html index dcab1c1a3..3e72f5b8e 100644 --- a/apps/templates/_user_profile.html +++ b/apps/templates/_user_profile.html @@ -23,7 +23,7 @@ {% for org in ADMIN_OR_AUDIT_ORGS %}
  • - + {{ org.name }} {% if org.id == CURRENT_ORG.id %} diff --git a/apps/users/templates/users/first_login.html b/apps/users/templates/users/first_login.html index 417d20a3d..bffa2fc0e 100644 --- a/apps/users/templates/users/first_login.html +++ b/apps/users/templates/users/first_login.html @@ -1,145 +1,10 @@ -{% extends 'base.html' %} +{% extends '_base_only_content.html' %} {% load static %} {% load i18n %} {% load bootstrap3 %} - -{% block custom_head_css_js %} -{{ wizard.form.media }} - -{% endblock %} -{% block first_login_message %}{% endblock %} +{% block title %} {% trans 'First Login' %} {% endblock %} {% block content %} -
    -
    -
    -
    - -
    -
    - -
    -
    -
    - {% csrf_token %} - {{ wizard.management_form }} - {% if form.finish_description %} - {{ form.finish_description }} -
    - - - - {% endif %} - - {% if wizard.steps.current == '1' and not request.user.can_update_ssh_key %} - {% trans 'User auth from {}, ssh key login is not supported' %} - {% else %} - {% bootstrap_form wizard.form %} - {% endif %} - - {% if form.mfa_description %} - {{ form.mfa_description }} - {% endif %} - - {% if form.pubkey_description and request.user.can_update_ssh_key %} - {{ form.pubkey_description }} - {% endif %} -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -{% endblock %} - -{% block custom_foot_js %} - + 使用UI重构这个页面 {% endblock %} diff --git a/apps/users/utils.py b/apps/users/utils.py index 1932e3c0e..673215146 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -206,7 +206,7 @@ def get_user_or_pre_auth_user(request): def redirect_user_first_login_or_index(request, redirect_field_name): if request.user.is_first_login: - return reverse('users:user-first-login') + return reverse('authentication:user-first-login') url_in_post = request.POST.get(redirect_field_name) if url_in_post: return url_in_post diff --git a/apps/users/views/profile/reset.py b/apps/users/views/profile/reset.py index f1da16a4d..32b44f861 100644 --- a/apps/users/views/profile/reset.py +++ b/apps/users/views/profile/reset.py @@ -129,62 +129,6 @@ class UserResetPasswordView(FormView): return redirect('authentication:reset-password-success') -class UserFirstLoginView(PermissionsMixin, SessionWizardView): +class UserFirstLoginView(PermissionsMixin, TemplateView): template_name = 'users/first_login.html' permission_classes = [IsValidUser] - form_list = [ - forms.UserProfileForm, - forms.UserPublicKeyForm, - forms.UserMFAForm, - forms.UserFirstLoginFinishForm - ] - file_storage = default_storage - - def dispatch(self, request, *args, **kwargs): - if request.user.is_authenticated and not request.user.is_first_login: - return redirect(reverse('index')) - return super().dispatch(request, *args, **kwargs) - - def done(self, form_list, **kwargs): - user = self.request.user - for form in form_list: - for field in form: - if field.value(): - setattr(user, field.name, field.value()) - user.is_first_login = False - user.save() - context = { - 'user_guide_url': settings.USER_GUIDE_URL - } - return render(self.request, 'users/first_login_done.html', context) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({'app': _('Users'), 'action': _('First login')}) - return context - - def get_form_initial(self, step): - user = self.request.user - if step == '0': - return { - 'username': user.username or '', - 'name': user.name or user.username, - 'email': user.email or '', - 'wechat': user.wechat or '', - 'phone': user.phone or '' - } - return super().get_form_initial(step) - - def get_form(self, step=None, data=None, files=None): - form = super().get_form(step, data, files) - form.instance = self.request.user - - if isinstance(form, forms.UserMFAForm): - choices = form.fields["mfa_level"].choices - if self.request.user.mfa_force_enabled: - choices = [(k, v) for k, v in choices if k == 2] - else: - choices = [(k, v) for k, v in choices if k in [0, 1]] - form.fields["mfa_level"].choices = choices - form.fields["mfa_level"].initial = self.request.user.mfa_level - return form From 999286a08996676d94936adefb67d8e1324afff5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 3 Jun 2020 12:32:52 +0800 Subject: [PATCH 077/146] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=85=AC=E9=92=A5=E9=94=99=E8=AF=AF=E5=BC=95=E8=B5=B7?= =?UTF-8?q?=E7=9A=84profile=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/models/user.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 473f62197..431a9789b 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -89,7 +89,10 @@ class AuthMixin: def get_public_key_hash_md5(self): if not callable(self.public_key_obj.hash_md5): return '' - return self.public_key_obj.hash_md5() + try: + return self.public_key_obj.hash_md5() + except: + return '' def reset_password(self, new_password): self.set_password(new_password) From 5bacab747544a38802eaf5e89875e93dd4883bbc Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 3 Jun 2020 12:44:45 +0800 Subject: [PATCH 078/146] =?UTF-8?q?fix:=20=E9=87=8D=E7=BD=AE=E5=AF=86?= =?UTF-8?q?=E9=92=A5=E5=88=B0auth=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/urls/view_urls.py | 1 + apps/users/utils.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py index 4bd785a08..12e1fea84 100644 --- a/apps/authentication/urls/view_urls.py +++ b/apps/authentication/urls/view_urls.py @@ -25,6 +25,7 @@ urlpatterns = [ path('password/verify/', users_view.UserVerifyPasswordView.as_view(), name='user-verify-password'), # Profile + path('profile/pubkey/generate/', users_view.UserPublicKeyGenerateView.as_view(), name='user-pubkey-generate'), path('profile/otp/enable/start/', users_view.UserOtpEnableStartView.as_view(), name='user-otp-enable-start'), path('profile/otp/enable/install-app/', users_view.UserOtpEnableInstallAppView.as_view(), name='user-otp-enable-install-app'), diff --git a/apps/users/utils.py b/apps/users/utils.py index 673215146..eb53c6607 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -205,8 +205,8 @@ def get_user_or_pre_auth_user(request): def redirect_user_first_login_or_index(request, redirect_field_name): - if request.user.is_first_login: - return reverse('authentication:user-first-login') + # if request.user.is_first_login: + # return reverse('authentication:user-first-login') url_in_post = request.POST.get(redirect_field_name) if url_in_post: return url_in_post From 229c782157d0533a7c8a8c0f8d288d358b32f867 Mon Sep 17 00:00:00 2001 From: Eric_Lee Date: Wed, 3 Jun 2020 14:06:44 +0800 Subject: [PATCH 079/146] change PAGE_SIZE_CHOICE to string value (#4069) --- apps/settings/serializers/settings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index c5baa67da..c25bd0196 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -55,10 +55,10 @@ class TerminalSettingSerializer(serializers.Serializer): PAGE_SIZE_CHOICES = ( ('all', _('All')), ('auto', _('Auto')), - (10, 10), - (15, 15), - (25, 25), - (50, 50), + ('10', '10'), + ('15', '15'), + ('25', '25'), + ('50', '50'), ) TERMINAL_PASSWORD_AUTH = serializers.BooleanField(required=False) TERMINAL_PUBLIC_KEY_AUTH = serializers.BooleanField(required=False) From 431ba36a26871c56a0d18ba072018754112fe97b Mon Sep 17 00:00:00 2001 From: xinwen Date: Wed, 3 Jun 2020 15:05:55 +0800 Subject: [PATCH 080/146] =?UTF-8?q?[Fix]=20=E4=BC=9A=E8=AF=9D=E7=AE=A1?= =?UTF-8?q?=E7=90=86/=E5=91=BD=E4=BB=A4=E8=AE=B0=E5=BD=95=20=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E8=BF=87=E6=BB=A4=20bug=20(#4070)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/command.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/terminal/api/command.py b/apps/terminal/api/command.py index eb0955648..12141bef1 100644 --- a/apps/terminal/api/command.py +++ b/apps/terminal/api/command.py @@ -5,6 +5,7 @@ from django.utils import timezone from django.shortcuts import HttpResponse from rest_framework import viewsets from rest_framework import generics +from rest_framework.fields import DateTimeField from rest_framework.response import Response from django.template import loader @@ -71,12 +72,22 @@ class CommandQueryMixin: def get_date_range(self): now = timezone.now() days_ago = now - timezone.timedelta(days=self.default_days_ago) - default_start_st = days_ago.timestamp() - default_end_st = now.timestamp() + date_from_st = days_ago.timestamp() + date_to_st = now.timestamp() + query_params = self.request.query_params - date_from_st = query_params.get("date_from") or default_start_st - date_to_st = query_params.get("date_to") or default_end_st - return float(date_from_st), float(date_to_st) + date_from_q = query_params.get("date_from") + date_to_q = query_params.get("date_to") + + dt_parser = DateTimeField().to_internal_value + + if date_from_q: + date_from_st = dt_parser(date_from_q).timestamp() + + if date_to_q: + date_to_st = dt_parser(date_to_q).timestamp() + + return date_from_st, date_to_st class CommandViewSet(CommandQueryMixin, viewsets.ModelViewSet): From 27d906a87773bac5c4ecb7fc2c9a3f7290155cf2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 3 Jun 2020 16:26:10 +0800 Subject: [PATCH 081/146] =?UTF-8?q?feat:=20=E5=8E=BB=E6=8E=89js=20i18n=20c?= =?UTF-8?q?atalog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/authentication/login_wait_confirm.html | 1 - apps/jumpserver/urls.py | 9 +-------- apps/static/js/jumpserver.js | 4 ++++ apps/templates/_base_only_content.html | 1 - apps/templates/_base_only_msg_content.html | 1 - apps/templates/_foot_js.html | 1 - apps/templates/_without_nav_base.html | 1 - apps/terminal/api/command.py | 1 - apps/users/templates/users/user_password_update.html | 1 - 9 files changed, 5 insertions(+), 15 deletions(-) diff --git a/apps/authentication/templates/authentication/login_wait_confirm.html b/apps/authentication/templates/authentication/login_wait_confirm.html index 8a67a501e..7ee7632f0 100644 --- a/apps/authentication/templates/authentication/login_wait_confirm.html +++ b/apps/authentication/templates/authentication/login_wait_confirm.html @@ -10,7 +10,6 @@ {{ title }} {% include '_head_css_js.html' %} - diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index d5c28bc93..331930c3a 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -1,12 +1,9 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals -import os from django.urls import path, include, re_path from django.conf import settings from django.conf.urls.static import static -from django.conf.urls.i18n import i18n_patterns -from django.views.i18n import JavaScriptCatalog from . import views, api @@ -45,10 +42,6 @@ if settings.XPACK_ENABLED: path('xpack/', include('xpack.urls.api_urls', namespace='api-xpack')) ) -js_i18n_patterns = i18n_patterns( - path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'), -) - apps = [ 'users', 'assets', 'perms', 'terminal', 'ops', 'audits', 'orgs', 'auth', @@ -71,7 +64,7 @@ urlpatterns = [ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) -urlpatterns += js_i18n_patterns +# urlpatterns += js_i18n_patterns handler404 = 'jumpserver.views.handler404' handler500 = 'jumpserver.views.handler500' diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index b930154ff..787235568 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -3,6 +3,10 @@ //此函数用于checkbox的全选和反选 var checked = false; +function gettext(s) { + return s +} + function check_all(form) { var checkboxes = document.getElementById(form); if (checked === false) { diff --git a/apps/templates/_base_only_content.html b/apps/templates/_base_only_content.html index 1e6a16c84..d773e6a78 100644 --- a/apps/templates/_base_only_content.html +++ b/apps/templates/_base_only_content.html @@ -11,7 +11,6 @@ {% include '_head_css_js.html' %} - + +
    +
    + + \ No newline at end of file diff --git a/apps/ops/urls/view_urls.py b/apps/ops/urls/view_urls.py index fb52a3e07..21bc4cc41 100644 --- a/apps/ops/urls/view_urls.py +++ b/apps/ops/urls/view_urls.py @@ -1,9 +1,14 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals +from django.urls import path + +from .. import views __all__ = ["urlpatterns"] app_name = "ops" urlpatterns = [ -] + # Resource Task url + path('celery/task//log/', views.CeleryTaskLogView.as_view(), name='celery-task-log'), +] \ No newline at end of file diff --git a/apps/ops/views.py b/apps/ops/views.py new file mode 100644 index 000000000..9ae2d9755 --- /dev/null +++ b/apps/ops/views.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# +from django.views.generic import TemplateView +from django.conf import settings + +from common.permissions import PermissionsMixin, IsOrgAdmin, IsOrgAuditor + + +__all__ = ['CeleryTaskLogView'] + + +class CeleryTaskLogView(PermissionsMixin, TemplateView): + template_name = 'ops/celery_task_log.html' + permission_classes = [IsOrgAdmin | IsOrgAuditor] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'task_id': self.kwargs.get('pk'), + 'ws_port': settings.WS_LISTEN_PORT + }) + return context From e6cd1260455456d0ca247eca8fd7db0229e6ff3c Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 4 Jun 2020 14:55:33 +0800 Subject: [PATCH 086/146] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9api=E7=9A=84?= =?UTF-8?q?=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/api.py | 4 ++-- apps/terminal/api/session.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index 716e625b0..9558a92df 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -10,7 +10,7 @@ from users.models import User from assets.models import Asset from terminal.models import Session from orgs.utils import current_org -from common.permissions import IsOrgAdmin +from common.permissions import IsOrgAdmin, IsOrgAuditor from common.utils import lazyproperty __all__ = ['IndexApi'] @@ -224,7 +224,7 @@ class TotalCountMixin: class IndexApi(TotalCountMixin, DatesLoginMetricMixin, APIView): - permission_classes = (IsOrgAdmin,) + permission_classes = (IsOrgAdmin | IsOrgAuditor,) http_method_names = ['get'] def get(self, request, *args, **kwargs): diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index 8cbf4daa8..a8ed747c2 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -59,7 +59,7 @@ class SessionViewSet(OrgBulkModelViewSet): return super().perform_create(serializer) def get_permissions(self): - if self.request.method.lower() in ['get']: + if self.request.method.lower() in ['get', 'options']: self.permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor, ) return super().get_permissions() From dbcf785e428904fe6cdc8222adfef44b995feeed Mon Sep 17 00:00:00 2001 From: Eric_Lee Date: Thu, 4 Jun 2020 15:10:02 +0800 Subject: [PATCH 087/146] add flower view (#4078) --- apps/jumpserver/urls.py | 3 ++- jms | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index b8d434dc6..a231c640d 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -31,7 +31,8 @@ api_v2 = [ app_view_patterns = [ path('auth/', include('authentication.urls.view_urls'), name='auth'), - path('ops/', include('ops.urls.view_urls'), name='ops') + path('ops/', include('ops.urls.view_urls'), name='ops'), + re_path(r'flower/(?P.*)', views.celery_flower_view, name='flower-view'), ] diff --git a/jms b/jms index d61e53da1..48110133e 100755 --- a/jms +++ b/jms @@ -251,7 +251,7 @@ def get_start_flower_kwargs(): 'celery', 'flower', '-A', 'ops', '-l', 'INFO', - '--url_prefix=flower', + '--url_prefix=/core/flower', '--auto_refresh=False', '--max_tasks=1000', '--tasks_columns=uuid,name,args,state,received,started,runtime,worker' From 55ae8bb5e6cc69ad0278c032425102be3f93c06b Mon Sep 17 00:00:00 2001 From: xinwen Date: Thu, 4 Jun 2020 16:47:43 +0800 Subject: [PATCH 088/146] =?UTF-8?q?[Fix]=20=E7=B3=BB=E7=BB=9F=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE/=E9=82=AE=E4=BB=B6=E8=AE=BE=E7=BD=AE/=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E9=93=BE=E6=8E=A5=E5=A4=B1=E8=B4=A5=20(#4081)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/settings/api.py b/apps/settings/api.py index fb3740c1f..6e7fd7248 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -55,7 +55,7 @@ class MailTestingAPI(APIView): email_recipient = email_recipient or email_from connection = get_connection( host=email_host, port=email_port, - uesrname=email_host_user, password=email_host_password, + username=email_host_user, password=email_host_password, use_tls=email_use_tls, use_ssl=email_use_ssl, ) send_mail( From 403b6fc56372b946441ae5980e625c39d5f93247 Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 4 Jun 2020 17:02:43 +0800 Subject: [PATCH 089/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=9B=B4=E6=96=B0Public=5Fkey=E7=9A=84=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/api/profile.py | 5 ----- apps/users/models/user.py | 7 ++++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/users/api/profile.py b/apps/users/api/profile.py index 321ef54e1..e9631b5b7 100644 --- a/apps/users/api/profile.py +++ b/apps/users/api/profile.py @@ -83,8 +83,3 @@ class UserPublicKeyApi(generics.RetrieveUpdateAPIView): def get_object(self): return self.request.user - - def perform_update(self, serializer): - user = self.get_object() - user.public_key = serializer.validated_data['public_key'] - user.save() diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 431a9789b..3fb058396 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -48,8 +48,9 @@ class AuthMixin: super().set_password(raw_password) def set_public_key(self, public_key): - self.public_key = public_key - self.save() + if self.can_update_ssh_key(): + self.public_key = public_key + self.save() def can_update_password(self): return self.is_local @@ -58,7 +59,7 @@ class AuthMixin: return self.can_use_ssh_key_login() def can_use_ssh_key_login(self): - return settings.TERMINAL_PUBLIC_KEY_AUTH + return self.is_local and settings.TERMINAL_PUBLIC_KEY_AUTH def is_public_key_valid(self): """ From afc7f3bb9c17a2b25912934ab07abb6e6ee7324b Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 4 Jun 2020 17:05:36 +0800 Subject: [PATCH 090/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7public=5Fkey=E7=94=9F=E6=88=90=E5=AF=86=E9=92=A5?= =?UTF-8?q?=E7=9A=84view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/views/profile/pubkey.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/users/views/profile/pubkey.py b/apps/users/views/profile/pubkey.py index 52c149084..4010fd996 100644 --- a/apps/users/views/profile/pubkey.py +++ b/apps/users/views/profile/pubkey.py @@ -46,8 +46,7 @@ class UserPublicKeyGenerateView(PermissionsMixin, View): def get(self, request, *args, **kwargs): username = request.user.username private, public = ssh_key_gen(username=username, hostname='jumpserver') - request.user.public_key = public - request.user.save() + request.user.set_public_key(public) response = HttpResponse(private, content_type='text/plain') filename = "{0}-jumpserver.pem".format(username) response['Content-Disposition'] = 'attachment; filename={}'.format(filename) From 5730e60089e0d776e04663050d1273cb1563e020 Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 4 Jun 2020 17:24:31 +0800 Subject: [PATCH 091/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7profile=E5=BA=8F=E5=88=97=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/serializers/user.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index fceeed5f3..019524cae 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -234,10 +234,6 @@ class UserProfileSerializer(UserSerializer): fields.remove('password') extra_kwargs.pop('password', None) - if 'public_key' in fields: - fields.remove('public_key') - extra_kwargs.pop('public_key', None) - @staticmethod def get_guide_url(obj): return settings.USER_GUIDE_URL @@ -247,6 +243,13 @@ class UserProfileSerializer(UserSerializer): return 2 return mfa_level + def validate_public_key(self, public_key): + if self.instance and self.instance.can_update_ssh_key(): + if not validate_ssh_public_key(public_key): + raise serializers.ValidationError(_('Not a valid ssh public key')) + return public_key + return None + class UserUpdatePasswordSerializer(serializers.ModelSerializer): old_password = serializers.CharField(required=True, max_length=128, write_only=True) From 9c6f118dbd8439e1729f8d000500127d29bc06a1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 4 Jun 2020 20:00:39 +0800 Subject: [PATCH 092/146] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E5=91=BD=E4=BB=A4=E7=9A=84relation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/api.py | 32 +++++++++++++++++++++++++++----- apps/audits/serializers.py | 32 +++++++++++++++++++++++++++++--- apps/audits/urls/api_urls.py | 2 ++ apps/common/api.py | 2 +- apps/ops/models/command.py | 10 +++++++++- apps/orgs/mixins/api.py | 4 ++-- 6 files changed, 70 insertions(+), 12 deletions(-) diff --git a/apps/audits/api.py b/apps/audits/api.py index ecfc7f1d1..c6c648f5c 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- # -from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import ListModelMixin +from django.db.models import F, Value +from django.db.models.functions import Concat -from common.mixins.api import CommonApiMixin from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsOrgAdmin from common.drf.filters import DatetimeRangeFilter, current_user_filter from common.api import CommonGenericViewSet -from orgs.mixins.api import OrgGenericViewSet +from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet, OrgRelationMixin from orgs.utils import current_org from ops.models import CommandExecution from .models import FTPLog, UserLoginLog, OperateLog, PasswordChangeLog from .serializers import FTPLogSerializer, UserLoginLogSerializer, CommandExecutionSerializer -from .serializers import OperateLogSerializer, PasswordChangeLogSerializer +from .serializers import OperateLogSerializer, PasswordChangeLogSerializer, CommandExecutionHostsRelationSerializer from .filters import CurrentOrgMembersFilter @@ -88,9 +88,31 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): model = CommandExecution serializer_class = CommandExecutionSerializer permission_classes = [IsOrgAdmin | IsOrgAuditor] - extra_filter_backends = [DatetimeRangeFilter, current_user_filter(), CurrentOrgMembersFilter] + extra_filter_backends = [DatetimeRangeFilter, CurrentOrgMembersFilter] date_range_filter_fields = [ ('date_start', ('date_from', 'date_to')) ] + filter_fields = ['user__name', 'command', 'run_as__name'] search_fields = ['command'] ordering = ['-date_created'] + + +class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet): + serializer_class = CommandExecutionHostsRelationSerializer + m2m_field = CommandExecution.hosts.field + permission_classes = (IsOrgAdmin,) + filter_fields = [ + 'id', 'asset', 'commandexecution' + ] + search_fields = ('asset__hostname', ) + http_method_names = ['options', 'get'] + + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.annotate( + asset_display=Concat( + F('asset__hostname'), Value('('), + F('asset__ip'), Value(')') + ) + ) + return queryset diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index dbb072d0b..d4213f243 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -2,7 +2,10 @@ # from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from django.db.models import F +from common.mixins import BulkSerializerMixin +from common.serializers import AdaptedBulkListSerializer from terminal.models import Session from ops.models import CommandExecution from . import models @@ -55,12 +58,16 @@ class SessionAuditSerializer(serializers.ModelSerializer): class CommandExecutionSerializer(serializers.ModelSerializer): + is_success = serializers.BooleanField(read_only=True, label=_('Is success')) + class Meta: model = CommandExecution - fields = ( - 'id', 'hosts', 'run_as', 'command', 'user', 'is_finished', + fields_mini = ['id'] + fields_small = fields_mini + [ + 'run_as', 'command', 'user', 'is_finished', 'date_start', 'result', 'is_success' - ) + ] + fields = fields_small + ['hosts', 'run_as_display', 'user_display'] extra_kwargs = { 'result': {'label': _('Result')}, # model 上的方法,只能在这修改 'is_success': {'label': _('Is success')}, @@ -68,3 +75,22 @@ class CommandExecutionSerializer(serializers.ModelSerializer): 'run_as': {'label': _('Run as')}, 'user': {'label': _('User')}, } + + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.annotate(user_display=F('user__name'))\ + .annotate(run_as_display=F('run_as__name')) + return queryset + + +class CommandExecutionHostsRelationSerializer(BulkSerializerMixin, serializers.ModelSerializer): + asset_display = serializers.ReadOnlyField() + commandexecution_display = serializers.ReadOnlyField() + + class Meta: + list_serializer_class = AdaptedBulkListSerializer + model = CommandExecution.hosts.through + fields = [ + 'id', 'asset', 'asset_display', 'commandexecution', 'commandexecution_display' + ] diff --git a/apps/audits/urls/api_urls.py b/apps/audits/urls/api_urls.py index be622a357..8cc822706 100644 --- a/apps/audits/urls/api_urls.py +++ b/apps/audits/urls/api_urls.py @@ -16,6 +16,8 @@ router.register(r'login-logs', api.UserLoginLogViewSet, 'login-log') router.register(r'operate-logs', api.OperateLogViewSet, 'operate-log') router.register(r'password-change-logs', api.PasswordChangeLogViewSet, 'password-change-log') router.register(r'command-execution-logs', api.CommandExecutionViewSet, 'command-execution-log') +router.register(r'command-executions-hosts-relations', api.CommandExecutionHostRelationViewSet, 'command-executions-hosts-relation') + urlpatterns = [ ] diff --git a/apps/common/api.py b/apps/common/api.py index 7ecd77122..56144517e 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -17,7 +17,7 @@ from .utils import get_logger from .mixins import CommonApiMixin __all__ = [ - 'LogTailApi', 'ResourcesIDCacheApi', + 'LogTailApi', 'ResourcesIDCacheApi', 'CommonGenericViewSet' ] logger = get_logger(__file__) diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py index fb6642f85..408c72915 100644 --- a/apps/ops/models/command.py +++ b/apps/ops/models/command.py @@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext from django.db import models - +from common.utils import lazyproperty from orgs.models import Organization from orgs.mixins.models import OrgModelMixin from ..ansible.runner import CommandRunner @@ -40,6 +40,14 @@ class CommandExecution(OrgModelMixin): inv = JMSInventory(self.hosts.all(), run_as=username) return inv + @lazyproperty + def run_as_display(self): + return str(self.run_as) + + @lazyproperty + def user_display(self): + return str(self.user) + @property def result(self): if self._result: diff --git a/apps/orgs/mixins/api.py b/apps/orgs/mixins/api.py index 2ad34831c..635e415bf 100644 --- a/apps/orgs/mixins/api.py +++ b/apps/orgs/mixins/api.py @@ -6,12 +6,12 @@ from rest_framework_bulk import BulkModelViewSet from common.mixins import CommonApiMixin, RelationMixin from orgs.utils import current_org -from ..utils import set_to_root_org, filter_org_queryset +from ..utils import set_to_root_org from ..models import Organization __all__ = [ 'RootOrgViewMixin', 'OrgMembershipModelViewSetMixin', 'OrgModelViewSet', - 'OrgBulkModelViewSet', 'OrgQuerySetMixin', + 'OrgBulkModelViewSet', 'OrgQuerySetMixin', 'OrgGenericViewSet', 'OrgRelationMixin' ] From a43d6ad34df8fd6160b5874b4e046d0823a8b9e2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 4 Jun 2020 20:26:42 +0800 Subject: [PATCH 093/146] =?UTF-8?q?feat:=20=E8=B5=84=E4=BA=A7=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0admin=5Fuser=5Fdisplay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset.py | 5 ++++- apps/assets/models/asset.py | 4 ++++ apps/assets/serializers/asset.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 15b9ec75f..b5356af16 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -33,7 +33,10 @@ class AssetViewSet(OrgBulkModelViewSet): API endpoint that allows Asset to be viewed or edited. """ model = Asset - filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id", "platform__base") + filter_fields = ( + "hostname", "ip", "systemuser__id", "admin_user__id", "platform__base", + "is_active" + ) search_fields = ("hostname", "ip") ordering_fields = ("hostname", "ip", "port", "cpu_cores") serializer_classes = { diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 6b5e716e0..20c040f74 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -244,6 +244,10 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): def platform_base(self): return self.platform.base + @lazyproperty + def admin_user_display(self): + return self.admin_user.name + @lazyproperty def admin_user_username(self): """求可连接性时,直接用用户名去取,避免再查一次admin user diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index 3f9a3eb84..e238c29a7 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -82,7 +82,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): 'created_by', 'date_created', 'hardware_info', ] fields_fk = [ - 'admin_user', 'domain', 'platform' + 'admin_user', 'admin_user_display', 'domain', 'platform' ] fk_only_fields = { 'platform': ['name'] From 34c556d37540cb591ebf8c0f7748772f6974a2a7 Mon Sep 17 00:00:00 2001 From: xinwen Date: Fri, 5 Jun 2020 10:42:03 +0800 Subject: [PATCH 094/146] [Fix] spm (#4082) --- apps/common/api.py | 7 ++++--- apps/common/drf/filters.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/common/api.py b/apps/common/api.py index 56144517e..ab762277a 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -86,12 +86,13 @@ class LogTailApi(generics.RetrieveAPIView): class ResourcesIDCacheApi(APIView): + def post(self, request, *args, **kwargs): spm = str(uuid.uuid4()) - resources_id = request.data.get('resources') - if resources_id: + resources = request.data.get('resources') + if resources is not None: cache_key = KEY_CACHE_RESOURCES_ID.format(spm) - cache.set(cache_key, resources_id, 300) + cache.set(cache_key, resources, 300) return Response({'spm': spm}) diff --git a/apps/common/drf/filters.py b/apps/common/drf/filters.py index 8d091b7d8..7740cadba 100644 --- a/apps/common/drf/filters.py +++ b/apps/common/drf/filters.py @@ -96,7 +96,7 @@ class IDSpmFilter(filters.BaseFilterBackend): return queryset cache_key = const.KEY_CACHE_RESOURCES_ID.format(spm) resources_id = cache.get(cache_key) - if not resources_id or not isinstance(resources_id, list): + if resources_id is None or not isinstance(resources_id, list): return queryset queryset = queryset.filter(id__in=resources_id) return queryset From 85699106581e473aff0e43f516f31ba37d5c7e1d Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 5 Jun 2020 14:07:23 +0800 Subject: [PATCH 095/146] =?UTF-8?q?feat:=20command=20exexution=20audit=20l?= =?UTF-8?q?og=E7=9A=84=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset.py | 3 --- apps/audits/api.py | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index b5356af16..c5ee613e2 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- # -import random - -from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet from rest_framework.generics import RetrieveAPIView from django.shortcuts import get_object_or_404 diff --git a/apps/audits/api.py b/apps/audits/api.py index c6c648f5c..162d50977 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -92,8 +92,8 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): date_range_filter_fields = [ ('date_start', ('date_from', 'date_to')) ] - filter_fields = ['user__name', 'command', 'run_as__name'] - search_fields = ['command'] + filter_fields = ['user__name', 'command', 'run_as__name', 'is_finished'] + search_fields = ['command', 'user__name', 'run_as__name'] ordering = ['-date_created'] From 076b7babcb13c64db82d560aee1cfc0b71345809 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 5 Jun 2020 14:20:34 +0800 Subject: [PATCH 096/146] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E7=9A=84api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/api.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/audits/api.py b/apps/audits/api.py index 162d50977..1d91a7433 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -5,7 +5,7 @@ from django.db.models import F, Value from django.db.models.functions import Concat from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsOrgAdmin -from common.drf.filters import DatetimeRangeFilter, current_user_filter +from common.drf.filters import DatetimeRangeFilter from common.api import CommonGenericViewSet from orgs.mixins.api import OrgGenericViewSet, OrgBulkModelViewSet, OrgRelationMixin from orgs.utils import current_org @@ -13,7 +13,6 @@ from ops.models import CommandExecution from .models import FTPLog, UserLoginLog, OperateLog, PasswordChangeLog from .serializers import FTPLogSerializer, UserLoginLogSerializer, CommandExecutionSerializer from .serializers import OperateLogSerializer, PasswordChangeLogSerializer, CommandExecutionHostsRelationSerializer -from .filters import CurrentOrgMembersFilter class FTPLogViewSet(ListModelMixin, OrgGenericViewSet): @@ -88,7 +87,7 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): model = CommandExecution serializer_class = CommandExecutionSerializer permission_classes = [IsOrgAdmin | IsOrgAuditor] - extra_filter_backends = [DatetimeRangeFilter, CurrentOrgMembersFilter] + extra_filter_backends = [DatetimeRangeFilter] date_range_filter_fields = [ ('date_start', ('date_from', 'date_to')) ] @@ -96,6 +95,11 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet): search_fields = ['command', 'user__name', 'run_as__name'] ordering = ['-date_created'] + def get_queryset(self): + queryset = super().get_queryset() + queryset = queryset.filter(run_as__org_id=current_org.org_id()) + return queryset + class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet): serializer_class = CommandExecutionHostsRelationSerializer From b1640e5592b23a33bd02b866c4f22306bd62a753 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 5 Jun 2020 14:43:40 +0800 Subject: [PATCH 097/146] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9common=20resou?= =?UTF-8?q?rce=20api=E7=9A=84=E6=9D=83=E9=99=90=EF=BC=8C=E5=90=A6=E5=88=99?= =?UTF-8?q?auditor=E6=97=A0=E6=B3=95=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/common/api.py b/apps/common/api.py index ab762277a..06c162539 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -11,6 +11,7 @@ from rest_framework.response import Response from rest_framework import generics, serializers from rest_framework.viewsets import GenericViewSet +from common.permissions import IsValidUser from .http import HttpResponseTemporaryRedirect from .const import KEY_CACHE_RESOURCES_ID from .utils import get_logger @@ -30,7 +31,7 @@ class OutputSerializer(serializers.Serializer): class LogTailApi(generics.RetrieveAPIView): - permission_classes = () + permission_classes = (IsValidUser,) buff_size = 1024 * 10 serializer_class = OutputSerializer end = False @@ -86,6 +87,7 @@ class LogTailApi(generics.RetrieveAPIView): class ResourcesIDCacheApi(APIView): + permission_classes = (IsValidUser,) def post(self, request, *args, **kwargs): spm = str(uuid.uuid4()) From 71202e83f52d936b54af6b0909ceefa0cd8aa010 Mon Sep 17 00:00:00 2001 From: ibuler Date: Fri, 5 Jun 2020 20:15:23 +0800 Subject: [PATCH 098/146] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=9A=84api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/api/mixins.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/users/api/mixins.py b/apps/users/api/mixins.py index 2ff60b615..117c2c28a 100644 --- a/apps/users/api/mixins.py +++ b/apps/users/api/mixins.py @@ -3,10 +3,12 @@ from .. import utils from users.models import User +from orgs.utils import current_org + class UserQuerysetMixin: def get_queryset(self): - if self.request.query_params.get('all'): + if self.request.query_params.get('all') or not current_org.is_real(): queryset = User.objects.exclude(role=User.ROLE_APP) else: queryset = utils.get_current_org_members() From 31daaed4cd0f3ec946c5ca5bacfb7d40b3cd228f Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 8 Jun 2020 16:48:10 +0800 Subject: [PATCH 099/146] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0auth=20confir?= =?UTF-8?q?m=20=E5=88=B0public=20setting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/settings/libs.py | 4 +++- apps/settings/api.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/jumpserver/settings/libs.py b/apps/jumpserver/settings/libs.py index 706fb3647..eb07e299d 100644 --- a/apps/jumpserver/settings/libs.py +++ b/apps/jumpserver/settings/libs.py @@ -40,7 +40,9 @@ REST_FRAMEWORK = { 'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z', 'DATETIME_INPUT_FORMATS': ['iso-8601', '%Y-%m-%d %H:%M:%S %z'], 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', - # 'PAGE_SIZE': 15 + # 'PAGE_SIZE': 100, + # 'MAX_PAGE_SIZE': 5000 + } SWAGGER_SETTINGS = { diff --git a/apps/settings/api.py b/apps/settings/api.py index 6e7fd7248..759c7f70c 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -271,7 +271,8 @@ class PublicSettingApi(generics.RetrieveAPIView): "WINDOWS_SKIP_ALL_MANUAL_PASSWORD": settings.WINDOWS_SKIP_ALL_MANUAL_PASSWORD, "SECURITY_MAX_IDLE_TIME": settings.SECURITY_MAX_IDLE_TIME, "XPACK_ENABLED": settings.XPACK_ENABLED, - "XPACK_LICENSE_IS_VALID": settings.XPACK_LICENSE_IS_VALID + "XPACK_LICENSE_IS_VALID": settings.XPACK_LICENSE_IS_VALID, + "LOGIN_CONFIRM_ENABLE": settings.LOGIN_CONFIRM_ENABLE } } return instance From 7ef09a4ca1ae7845f602d6f55664638981ee93c2 Mon Sep 17 00:00:00 2001 From: xinwen Date: Mon, 8 Jun 2020 17:16:30 +0800 Subject: [PATCH 100/146] =?UTF-8?q?[Update]=20UserSerializer=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20login=5Fconfirm=5Fsettings=20(#4086)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/serializers/user.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 019524cae..fcbde6150 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -40,6 +40,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): login_blocked = serializers.SerializerMethodField() can_update = serializers.SerializerMethodField() can_delete = serializers.SerializerMethodField() + login_confirm_settings = serializers.PrimaryKeyRelatedField(read_only=True, source='login_confirm_setting.reviewers', many=True) key_prefix_block = "_LOGIN_BLOCK_{}" @@ -59,7 +60,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): ] fields = fields_small + [ 'groups', 'role', 'groups_display', 'role_display', - 'can_update', 'can_delete', 'login_blocked', + 'can_update', 'can_delete', 'login_blocked', 'login_confirm_settings' ] extra_kwargs = { From ee4534ac4b345966281da8c3f4a324288fde8740 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 9 Jun 2020 10:17:16 +0800 Subject: [PATCH 101/146] =?UTF-8?q?feat:=20public=20setting=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset_user.py | 12 +++++++++++- apps/settings/api.py | 4 +++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/assets/api/asset_user.py b/apps/assets/api/asset_user.py index 6e6af72ac..932d5b77e 100644 --- a/apps/assets/api/asset_user.py +++ b/apps/assets/api/asset_user.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +import coreapi from django.conf import settings from rest_framework.response import Response from rest_framework import generics, filters @@ -54,6 +55,15 @@ class AssetUserSearchBackend(filters.BaseFilterBackend): class AssetUserLatestFilterBackend(filters.BaseFilterBackend): + def get_schema_fields(self, view): + return [ + coreapi.Field( + name='latest', location='query', required=False, + type='string', example='1', + description='Only the latest version' + ) + ] + def filter_queryset(self, request, queryset, view): latest = request.GET.get('latest') == '1' if latest: @@ -64,7 +74,7 @@ class AssetUserLatestFilterBackend(filters.BaseFilterBackend): class AssetUserViewSet(CommonApiMixin, BulkModelViewSet): serializer_classes = { 'default': serializers.AssetUserWriteSerializer, - 'list': serializers.AssetUserReadSerializer, + 'display': serializers.AssetUserReadSerializer, 'retrieve': serializers.AssetUserReadSerializer, } permission_classes = [IsOrgAdminOrAppUser] diff --git a/apps/settings/api.py b/apps/settings/api.py index 759c7f70c..e4fcb6098 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -272,7 +272,9 @@ class PublicSettingApi(generics.RetrieveAPIView): "SECURITY_MAX_IDLE_TIME": settings.SECURITY_MAX_IDLE_TIME, "XPACK_ENABLED": settings.XPACK_ENABLED, "XPACK_LICENSE_IS_VALID": settings.XPACK_LICENSE_IS_VALID, - "LOGIN_CONFIRM_ENABLE": settings.LOGIN_CONFIRM_ENABLE + "LOGIN_CONFIRM_ENABLE": settings.LOGIN_CONFIRM_ENABLE, + "SECURITY_VIEW_AUTH_NEED_MFA": settings.SECURITY_VIEW_AUTH_NEED_MFA, + "SECURITY_MFA_VERIFY_TTL": settings.SECURITY_MFA_VERIFY_TTL } } return instance From bd802e6a50a391de87739972b5860ebafa302c17 Mon Sep 17 00:00:00 2001 From: Eric_Lee Date: Tue, 9 Jun 2020 14:06:11 +0800 Subject: [PATCH 102/146] add logo urls (#4088) --- apps/jumpserver/conf.py | 25 ++++++++++++++++++++++++- apps/jumpserver/settings/custom.py | 1 + apps/settings/api.py | 3 ++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 6a54ec251..13f25d492 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -16,6 +16,7 @@ import json import yaml from importlib import import_module from django.urls import reverse_lazy +from django.contrib.staticfiles.templatetags.staticfiles import static from urllib.parse import urljoin, urlparse BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -446,6 +447,29 @@ class DynamicConfig: except: return False + def LOGO_URLS(self): + logo_urls = {'logo_logout': static('img/logo.png'), + 'logo_index': static('img/logo_text.png'), + 'login_image': static('img/login_image.png'), + 'favicon': static('img/facio.ico')} + if not HAS_XPACK: + return logo_urls + try: + from xpack.plugins.interface.models import Interface + obj = Interface.interface() + if obj: + if obj.logo_logout: + logo_urls.update({'logo_logout': obj.logo_logout.url}) + if obj.logo_index: + logo_urls.update({'logo_index': obj.logo_index.url}) + if obj.login_image: + logo_urls.update({'login_image': obj.login_image.url}) + if obj.favicon: + logo_urls.update({'favicon': obj.favicon.url}) + except: + pass + return logo_urls + def get_from_db(self, item): if self.db_setting is not None: value = self.db_setting.get(item) @@ -641,4 +665,3 @@ class ConfigManager: @classmethod def get_dynamic_config(cls, config): return DynamicConfig(config) - diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index 3bada5469..0be476d96 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -89,3 +89,4 @@ WINDOWS_SKIP_ALL_MANUAL_PASSWORD = CONFIG.WINDOWS_SKIP_ALL_MANUAL_PASSWORD # XPACK XPACK_LICENSE_IS_VALID = DYNAMIC.XPACK_LICENSE_IS_VALID +LOGO_URLS = DYNAMIC.LOGO_URLS diff --git a/apps/settings/api.py b/apps/settings/api.py index e4fcb6098..974e31a4e 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -274,7 +274,8 @@ class PublicSettingApi(generics.RetrieveAPIView): "XPACK_LICENSE_IS_VALID": settings.XPACK_LICENSE_IS_VALID, "LOGIN_CONFIRM_ENABLE": settings.LOGIN_CONFIRM_ENABLE, "SECURITY_VIEW_AUTH_NEED_MFA": settings.SECURITY_VIEW_AUTH_NEED_MFA, - "SECURITY_MFA_VERIFY_TTL": settings.SECURITY_MFA_VERIFY_TTL + "SECURITY_MFA_VERIFY_TTL": settings.SECURITY_MFA_VERIFY_TTL, + "LOGO_URLS": settings.LOGO_URLS } } return instance From 4468e2d3798c36b9d5f9e53d9b56db06258ee3a5 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 9 Jun 2020 15:08:15 +0800 Subject: [PATCH 103/146] =?UTF-8?q?feat:=20=E9=99=90=E5=88=B6gateway=20?= =?UTF-8?q?=E4=BB=85=E6=9C=89ssh=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/domain.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 630941860..26f1b43f5 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -41,6 +41,16 @@ class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): 'date_updated', 'created_by', 'comment', ] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.protocol_limit_to_ssh() + + def protocol_limit_to_ssh(self): + protocol_field = self.fields['protocol'] + choices = protocol_field.choices + choices.pop('rdp') + protocol_field._choices = choices + class GatewayWithAuthSerializer(GatewaySerializer): def get_field_names(self, declared_fields, info): @@ -51,6 +61,8 @@ class GatewayWithAuthSerializer(GatewaySerializer): return fields + + class DomainWithGatewaySerializer(BulkOrgResourceModelSerializer): gateways = GatewayWithAuthSerializer(many=True, read_only=True) From 82f70cb0dcada46ba97f22e213f0b591d1707ea3 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 9 Jun 2020 16:10:31 +0800 Subject: [PATCH 104/146] =?UTF-8?q?feat:=20ticket=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/tickets/serializers/ticket.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/tickets/serializers/ticket.py b/apps/tickets/serializers/ticket.py index e6ca13a2b..9c7a5a2e3 100644 --- a/apps/tickets/serializers/ticket.py +++ b/apps/tickets/serializers/ticket.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from .. import models @@ -20,6 +21,10 @@ class TicketSerializer(serializers.ModelSerializer): 'user_display', 'assignees_display', 'date_created', 'date_updated', ] + extra_kwargs = { + 'status': {'label': _('Status')}, + 'action': {'label': _('Action')} + } def create(self, validated_data): validated_data.pop('action') From 4d4a10710184f611ede99c4bcf61921a8c717508 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 9 Jun 2020 16:43:07 +0800 Subject: [PATCH 105/146] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9oid=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E9=A1=BA=E5=BA=8F=EF=BC=8C=E6=B7=BB=E5=8A=A0=E4=BB=8E?= =?UTF-8?q?cookie=E4=B8=AD=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/orgs/utils.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index 6870942da..7a4576e23 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -11,12 +11,18 @@ from .models import Organization def get_org_from_request(request): - oid = request.META.get("HTTP_X_JMS_ORG") + # query中优先级最高 + oid = request.GET.get("oid") + + # 其次header + if not oid: + oid = request.META.get("HTTP_X_JMS_ORG") + # 其次cookie + if not oid: + oid = request.COOKIES.get('X-JMS-ORG') + # 其次session if not oid: oid = request.session.get("oid") - request_params_oid = request.GET.get("oid") - if request_params_oid: - oid = request.GET.get("oid") if not oid: oid = Organization.DEFAULT_ID From 865522953a4b26978b924b637f107afe51a9a27e Mon Sep 17 00:00:00 2001 From: xinwen Date: Tue, 9 Jun 2020 20:26:23 +0800 Subject: [PATCH 106/146] =?UTF-8?q?[Feature]=20=20=E4=BD=9C=E4=B8=9A?= =?UTF-8?q?=E4=B8=AD=E5=BF=83/=E4=BB=BB=E5=8A=A1=E5=88=97=E8=A1=A8/?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E8=AF=A6=E6=83=85=20=E6=9C=80=E5=90=8E?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E6=88=90=E5=8A=9F=E6=88=96=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E7=9A=84=E4=B8=BB=E6=9C=BA=20(#4090)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/adhoc.py | 7 ++++++- apps/ops/models/adhoc.py | 18 ++++++++++++++++++ apps/ops/serializers/adhoc.py | 14 ++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py index 5f7c8318e..ee81f869f 100644 --- a/apps/ops/api/adhoc.py +++ b/apps/ops/api/adhoc.py @@ -11,7 +11,7 @@ from common.serializers import CeleryTaskSerializer from orgs.utils import current_org from ..models import Task, AdHoc, AdHocExecution from ..serializers import TaskSerializer, AdHocSerializer, \ - AdHocExecutionSerializer + AdHocExecutionSerializer, TaskDetailSerializer from ..tasks import run_ansible_task __all__ = [ @@ -26,6 +26,11 @@ class TaskViewSet(viewsets.ModelViewSet): serializer_class = TaskSerializer permission_classes = (IsOrgAdmin,) + def get_serializer_class(self): + if self.action == 'retrieve': + return TaskDetailSerializer + return super().get_serializer_class() + def get_queryset(self): queryset = super().get_queryset() queryset = queryset.select_related('latest_execution') diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 013fdc628..433d1c498 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -118,6 +118,24 @@ class Task(PeriodTaskModelMixin, OrgModelMixin): kwargs = {"callback": self.callback} return name, task, args, kwargs + @lazyproperty + def last_success(self): + return self._last_adhocexecution(True, 'contacted') + + @lazyproperty + def last_failure(self): + return self._last_adhocexecution(False, 'dark') + + def _last_adhocexecution(self, is_success, key): + obj = AdHocExecution.objects.filter(task_id=self.id, is_success=is_success).order_by('-date_finished').first() + body = obj.summary.get(key) + if body: + asset, body = body.popitem() + action, body = body.popitem() + return asset, action, body.get('msg', '') + else: + return '', '', '' + def __str__(self): return self.name + '@' + str(self.org_id) diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index 8d9e18e24..0d284cb48 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -59,6 +59,20 @@ class TaskSerializer(serializers.ModelSerializer): ] +class TaskDetailSerializer(TaskSerializer): + last_success = serializers.SerializerMethodField() + last_failure = serializers.SerializerMethodField() + + def get_last_success(self, obj): + return obj.last_success[0], '' + + def get_last_failure(self, obj): + return obj.last_failure[0], ' => '.join(obj.last_failure[1:]) + + class Meta(TaskSerializer.Meta): + fields = TaskSerializer.Meta.fields + ['last_success', 'last_failure'] + + class AdHocSerializer(serializers.ModelSerializer): become_display = serializers.ReadOnlyField() From 1a6597b572bf68d118b02c244f04c963c57e73bb Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 10 Jun 2020 12:58:18 +0800 Subject: [PATCH 107/146] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9command=20fil?= =?UTF-8?q?ter=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset.py | 8 ++++++-- apps/assets/serializers/cmd_filter.py | 12 ++++++++++-- apps/common/drf/renders/csv.py | 1 + 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index c5ee613e2..dee95ed06 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -77,9 +77,13 @@ class AssetPlatformViewSet(ModelViewSet): filter_fields = ['name', 'base'] search_fields = ['name'] + def get_permissions(self): + if self.request.method.lower() in ['get', 'options']: + self.permission_classes = (IsOrgAdmin,) + return super().get_permissions() + def check_object_permissions(self, request, obj): - if request.method.lower() in ['delete', 'put', 'patch'] and \ - obj.internal: + if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal: self.permission_denied( request, message={"detail": "Internal platform"} ) diff --git a/apps/assets/serializers/cmd_filter.py b/apps/assets/serializers/cmd_filter.py index 6559a8189..5a045df9c 100644 --- a/apps/assets/serializers/cmd_filter.py +++ b/apps/assets/serializers/cmd_filter.py @@ -2,7 +2,6 @@ # import re from rest_framework import serializers -from django.utils.translation import ugettext_lazy as _ from common.fields import ChoiceDisplayField from common.serializers import AdaptedBulkListSerializer @@ -27,11 +26,20 @@ class CommandFilterSerializer(BulkOrgResourceModelSerializer): class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer): - serializer_choice_field = ChoiceDisplayField + # serializer_choice_field = ChoiceDisplayField invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]') + type_display = serializers.ReadOnlyField(source='get_type_display') + action_display = serializers.ReadOnlyField(source='get_action_display') class Meta: model = CommandFilterRule + fields_mini = ['id'] + fields_small = fields_mini + [ + 'type', 'type_display', 'content', 'priority', + 'action', 'action_display', + 'comment', 'created_by', 'date_created', 'date_updated' + ] + fields_fk = ['filter'] fields = '__all__' list_serializer_class = AdaptedBulkListSerializer diff --git a/apps/common/drf/renders/csv.py b/apps/common/drf/renders/csv.py index fd862460f..0d90af359 100644 --- a/apps/common/drf/renders/csv.py +++ b/apps/common/drf/renders/csv.py @@ -30,6 +30,7 @@ class JMSCSVRender(BaseRenderer): @staticmethod def _gen_table(data, fields): + data = data[:100] yield ['*{}'.format(f.label) if f.required else f.label for f in fields] for item in data: From 1c0ad08d804748ee1fcf344b1e848ed0e8c72200 Mon Sep 17 00:00:00 2001 From: Eric_Lee Date: Wed, 10 Jun 2020 15:10:43 +0800 Subject: [PATCH 108/146] fix ldap test i18n msg (#4092) --- apps/settings/utils/ldap.py | 48 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/settings/utils/ldap.py b/apps/settings/utils/ldap.py index a13dcc063..4ecd9c8af 100644 --- a/apps/settings/utils/ldap.py +++ b/apps/settings/utils/ldap.py @@ -386,13 +386,13 @@ class LDAPTestUtil(object): try: self._test_server_uri() except LDAPSocketOpenError as e: - error = _("Host or port is disconnected: {}".format(e)) + error = _("Host or port is disconnected: {}").format(e) except LDAPSessionTerminatedByServerError as e: - error = _('The port is not the port of the LDAP service: {}'.format(e)) + error = _('The port is not the port of the LDAP service: {}').format(e) except LDAPSocketReceiveError as e: - error = _('Please add certificate: {}'.format(e)) + error = _('Please add certificate: {}').format(e) except Exception as e: - error = _('Unknown error: {}'.format(e)) + error = _('Unknown error: {}').format(e) else: return raise LDAPInvalidServerError(error) @@ -413,13 +413,13 @@ class LDAPTestUtil(object): try: self._test_bind_dn() except LDAPUserNameIsMandatoryError as e: - error = _('Please enter Bind DN: {}'.format(e)) + error = _('Please enter Bind DN: {}').format(e) except LDAPPasswordIsMandatoryError as e: - error = _('Please enter Password: {}'.format(e)) + error = _('Please enter Password: {}').format(e) except LDAPInvalidDnError as e: - error = _('Please enter correct Bind DN and Password: {}'.format(e)) + error = _('Please enter correct Bind DN and Password: {}').format(e) except Exception as e: - error = _('Unknown error: {}'.format(e)) + error = _('Unknown error: {}').format(e) else: return raise LDAPBindError(error) @@ -433,9 +433,9 @@ class LDAPTestUtil(object): for search_ou in search_ous: util.config.search_ou = search_ou user_entries = util.search_user_entries() - logger.debug('Search ou: {}, count user: {}'.format(search_ou, len(user_entries))) + logger.debug('Search ou: {}, count user: {}').format(search_ou, len(user_entries)) if len(user_entries) == 0: - error = _('Invalid User OU or User search filter: {}'.format(search_ou)) + error = _('Invalid User OU or User search filter: {}').format(search_ou) raise self.LDAPInvalidSearchOuOrFilterError(error) def test_search_ou_and_filter(self): @@ -449,7 +449,7 @@ class LDAPTestUtil(object): error = e raise self.LDAPInvalidAttributeMapError(error) except Exception as e: - error = _('Unknown error: {}'.format(e)) + error = _('Unknown error: {}').format(e) else: return raise self.LDAPInvalidSearchOuOrFilterError(error) @@ -466,7 +466,7 @@ class LDAPTestUtil(object): actually_contain_attr = set(attr_map.keys()) result = should_contain_attr - actually_contain_attr if len(result) != 0: - error = _('LDAP User attr map not include: {}'.format(result)) + error = _('LDAP User attr map not include: {}').format(result) raise self.LDAPInvalidAttributeMapError(error) def test_attr_map(self): @@ -477,7 +477,7 @@ class LDAPTestUtil(object): except self.LDAPInvalidAttributeMapError as e: error = e except Exception as e: - error = _('Unknown error: {}'.format(e)) + error = _('Unknown error: {}').format(e) else: return raise self.LDAPInvalidAttributeMapError(error) @@ -510,20 +510,20 @@ class LDAPTestUtil(object): try: self._test_config() except LDAPInvalidServerError as e: - msg = _('Error (Invalid LDAP server): {}'.format(e)) + msg = _('Error (Invalid LDAP server): {}').format(e) except LDAPBindError as e: - msg = _('Error (Invalid Bind DN): {}'.format(e)) + msg = _('Error (Invalid Bind DN): {}').format(e) except self.LDAPInvalidAttributeMapError as e: - msg = _('Error (Invalid LDAP User attr map): {}'.format(e)) + msg = _('Error (Invalid LDAP User attr map): {}').format(e) except self.LDAPInvalidSearchOuOrFilterError as e: - msg = _('Error (Invalid User OU or User search filter): {}'.format(e)) + msg = _('Error (Invalid User OU or User search filter): {}').format(e) except self.LDAPNotEnabledAuthError as e: - msg = _('Error (Not enabled LDAP authentication): {}'.format(e)) + msg = _('Error (Not enabled LDAP authentication): {}').format(e) except Exception as e: msg = _('Error (Unknown): {}').format(e) else: status = True - msg = _('Succeed: Match {} s user'.format(len(self.user_entries))) + msg = _('Succeed: Match {} s user').format(len(self.user_entries)) if not status: logger.error(msg, exc_info=True) @@ -556,16 +556,16 @@ class LDAPTestUtil(object): try: self._test_login(username, password) except LDAPConfigurationError as e: - msg = _('Authentication failed (configuration incorrect): {}'.format(e)) + msg = _('Authentication failed (configuration incorrect): {}').format(e) except self.LDAPBeforeLoginCheckError as e: - msg = _('Authentication failed (before login check failed): {}'.format(e)) + msg = _('Authentication failed (before login check failed): {}').format(e) except LDAPUser.AuthenticationFailed as e: - msg = _('Authentication failed (username or password incorrect): {}'.format(e)) + msg = _('Authentication failed (username or password incorrect): {}').format(e) except Exception as e: - msg = _("Authentication failed (Unknown): {}".format(e)) + msg = _("Authentication failed (Unknown): {}").format(e) else: status = True - msg = _("Authentication success: {}".format(username)) + msg = _("Authentication success: {}").format(username) if not status: logger.error(msg, exc_info=True) From ec30ef1f8b577e7db3008b5e7d4e1eda0f7e5060 Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 10 Jun 2020 15:57:41 +0800 Subject: [PATCH 109/146] =?UTF-8?q?feat:=20readme=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 +++++++++++++++++++ apps/jumpserver/const.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 21fc8e36a..1e1bcca64 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,25 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 - 云端存储: 审计录像云端存储,永不丢失; - 多租户: 一套系统,多个子公司和部门同时使用。 +## 版本说明 + +从 2.0 开始 JumpServer 版本号规则将发生变更,如下所示 + +大版本.功能版本.bug修复版本 + +``` +如 2.0.1 修复bug 将是 2.0.2 +如 2.0.2 添加新功能后 将会是 2.1.0 +``` + +并且 JumpServer 以后也会像其它优秀开源项目一样,同时维护多个版本,目前计划同时维护 3 个版本 + +``` +如 2.1, 2.2, 2.3 版本我们会同时维护, 发布 2.4 版本后,2.1 停止维护 +``` + +JumpServer 每个月会发布一个功能版本, 修复版本视情况而定 + ## 功能列表 diff --git a/apps/jumpserver/const.py b/apps/jumpserver/const.py index b7caa6114..9a6ed9e6a 100644 --- a/apps/jumpserver/const.py +++ b/apps/jumpserver/const.py @@ -7,6 +7,6 @@ __all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG', 'DYNAMIC'] BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PROJECT_DIR = os.path.dirname(BASE_DIR) -VERSION = '1.5.9' +VERSION = '2.0.0' CONFIG = ConfigManager.load_user_config() DYNAMIC = ConfigManager.get_dynamic_config(CONFIG) From 4695f80172ae02a99ca42e19a377158a593611bb Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 10 Jun 2020 17:17:16 +0800 Subject: [PATCH 110/146] =?UTF-8?q?faet:=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- apps/locale/zh/LC_MESSAGES/django.mo | Bin 89943 -> 89900 bytes apps/locale/zh/LC_MESSAGES/django.po | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 1e1bcca64..f93198e1a 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 ## 特色优势 -- 开源: 零门槛,线上快速获取和安装; -- 分布式: 轻松支持大规模并发访问; +- 开源: 零门槛,线上快速获取和安装, 修复版本视情况而定; +, 修复版本视情况而定- 分布式: 轻松支持大规模并发访问; - 无插件: 仅需浏览器,极致的 Web Terminal 使用体验; - 多云支持: 一套系统,同时管理不同云上面的资产; - 云端存储: 审计录像云端存储,永不丢失; diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 6ea8518e0bfd89937321f69201137f40b0bd5009..04894a0271ce8edce09f6f0db9e2dc317e8e6630 100644 GIT binary patch delta 26517 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 Rs|Iuq?=QWs9?&+%{{fqo9Do1- delta 26551 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 链接 更新\n" " " @@ -5399,7 +5399,7 @@ msgstr "不能和原来的密钥相同" #: users/forms/profile.py:137 users/forms/user.py:90 #: users/serializers/user.py:167 users/serializers/user.py:287 msgid "Not a valid ssh public key" -msgstr "ssh密钥不合法" +msgstr "SSH密钥不合法" #: users/forms/user.py:27 users/models/user.py:472 #: users/templates/users/_select_user_modal.html:15 @@ -5585,7 +5585,7 @@ msgstr "授权的数据库应用" #: users/templates/users/_user_update_pk_modal.html:4 msgid "Update User SSH Public Key" -msgstr "更新ssh密钥" +msgstr "更新SSH密钥" #: users/templates/users/first_login.html:19 #: users/templates/users/first_login_done.html:19 @@ -5764,12 +5764,12 @@ msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱" #: users/templates/users/user_detail.html:456 msgid "Successfully updated the SSH public key." -msgstr "更新ssh密钥成功" +msgstr "更新SSH密钥成功" #: users/templates/users/user_detail.html:457 #: users/templates/users/user_detail.html:461 msgid "User SSH public key update" -msgstr "ssh密钥" +msgstr "SSH密钥" #: users/templates/users/user_detail.html:506 msgid "After unlocking the user, the user can log in normally." @@ -5911,7 +5911,7 @@ msgstr "重置并下载SSH密钥" #: users/templates/users/user_pubkey_update.html:55 msgid "Old public key" -msgstr "原来ssh密钥" +msgstr "原来SSH密钥" #: users/templates/users/user_pubkey_update.html:63 msgid "Fingerprint" @@ -6125,7 +6125,7 @@ msgstr "" #: users/utils.py:175 msgid "SSH Key Reset" -msgstr "重置ssh密钥" +msgstr "重置SSH密钥" #: users/utils.py:177 #, python-format @@ -7480,7 +7480,7 @@ msgstr "创建" #~ msgstr "导入 {} 个用户成功; 导入 {} 这些用户失败,因为对象没有属性'keys'" #~ msgid "Invalid private key" -#~ msgstr "ssh密钥不合法" +#~ msgstr "SSH密钥不合法" #~ msgid "Login JumpServer" #~ msgstr "登录 JumpServer" From 9ea98bf2b254cf42e70fbb230bf342fdd1141d65 Mon Sep 17 00:00:00 2001 From: xinwen Date: Wed, 10 Jun 2020 17:34:56 +0800 Subject: [PATCH 111/146] =?UTF-8?q?[Feature]=20=E6=B7=BB=E5=8A=A0=20?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E7=AE=A1=E7=90=86/=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D/=E4=B8=8B=E8=BD=BD=20api=20(#4093)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/renders/__init__.py | 14 +++++++- apps/terminal/api/session.py | 52 +++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/apps/common/drf/renders/__init__.py b/apps/common/drf/renders/__init__.py index 671c86586..f99b13586 100644 --- a/apps/common/drf/renders/__init__.py +++ b/apps/common/drf/renders/__init__.py @@ -1 +1,13 @@ -from .csv import * \ No newline at end of file +from rest_framework import renderers + +from .csv import * + + +class PassthroughRenderer(renderers.BaseRenderer): + """ + Return data as-is. View should supply a Response. + """ + media_type = '' + format = '' + def render(self, data, accepted_media_type=None, renderer_context=None): + return data diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index a8ed747c2..f340147c3 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -1,15 +1,25 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext as _ +import os +import tarfile + from django.shortcuts import get_object_or_404, reverse +from django.utils.translation import ugettext as _ +from django.utils.encoding import escape_uri_path +from django.http import FileResponse, HttpResponse from django.core.files.storage import default_storage from rest_framework import viewsets, views from rest_framework.response import Response +from rest_framework.decorators import action +from common.utils import model_to_json +from .. import utils +from common.const.http import GET from common.utils import is_uuid, get_logger, get_object_or_none from common.mixins.api import AsyncApiMixin from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsAppUser from common.drf.filters import DatetimeRangeFilter +from common.drf.renders import PassthroughRenderer from orgs.mixins.api import OrgBulkModelViewSet from orgs.utils import tmp_to_root_org, tmp_to_org from users.models import User @@ -41,6 +51,44 @@ class SessionViewSet(OrgBulkModelViewSet): ] extra_filter_backends = [DatetimeRangeFilter] + @staticmethod + def prepare_offline_file(session, local_path): + replay_path = default_storage.path(local_path) + current_dir = os.getcwd() + dir_path = os.path.dirname(replay_path) + replay_filename = os.path.basename(replay_path) + meta_filename = '{}.json'.format(session.id) + offline_filename = '{}.tar'.format(session.id) + os.chdir(dir_path) + + with open(meta_filename, 'wt') as f: + f.write(model_to_json(session)) + + with tarfile.open(offline_filename, 'w') as f: + f.add(replay_filename) + f.add(meta_filename) + file = open(offline_filename, 'rb') + os.chdir(current_dir) + return file + + @action(methods=[GET], detail=True, renderer_classes=(PassthroughRenderer,), url_path='replay/download', url_name='replay-download') + def download(self, request, *args, **kwargs): + session = self.get_object() + local_path, url = utils.get_session_replay_url(session) + if local_path is None: + error = url + return HttpResponse(error) + file = self.prepare_offline_file(session, local_path) + + response = FileResponse(file) + response['Content-Type'] = 'application/octet-stream' + # 这里要注意哦,网上查到的方法都是response['Content-Disposition']='attachment;filename="filename.py"', + # 但是如果文件名是英文名没问题,如果文件名包含中文,下载下来的文件名会被改为url中的path。 + filename = escape_uri_path('{}.tar'.format(session.id)) + disposition = "attachment; filename*=UTF-8''{}".format(filename) + response["Content-Disposition"] = disposition + return response + def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) # 解决guacamole更新session时并发导致幽灵会话的问题 @@ -95,7 +143,7 @@ class SessionReplayViewSet(AsyncApiMixin, viewsets.ViewSet): if session.protocol in ('rdp', 'vnc'): tp = 'guacamole' - download_url = reverse('terminal:session-replay-download', kwargs={'pk': session.id}) + download_url = reverse('api-terminal:session-replay-download', kwargs={'pk': session.id}) data = { 'type': tp, 'src': url, 'user': session.user, 'asset': session.asset, From 66f3706142644f4fc67eba12bba6f9638a687426 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 11 Jun 2020 11:28:37 +0800 Subject: [PATCH 112/146] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0aes=20=20fix:?= =?UTF-8?q?=20=E4=BF=AE=E6=94=B9=E6=97=B6=E5=8C=BA=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/utils/encode.py | 51 +++++++++++++++++++++++++++++++++++ apps/jumpserver/middleware.py | 11 +++++--- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/apps/common/utils/encode.py b/apps/common/utils/encode.py index 097e28292..72e011917 100644 --- a/apps/common/utils/encode.py +++ b/apps/common/utils/encode.py @@ -9,6 +9,7 @@ import time import hashlib from io import StringIO from itertools import chain +from Crypto.Cipher import AES import paramiko import sshpubkeys @@ -225,3 +226,53 @@ def model_to_json(instance, sort_keys=True, indent=2, cls=None): if cls is None: cls = DjangoJSONEncoder return json.dumps(data, sort_keys=sort_keys, indent=indent, cls=cls) + + +class AESCrypto: + """ + AES + 除了MODE_SIV模式key长度为:32, 48, or 64, + 其余key长度为16, 24 or 32 + 详细见AES内部文档 + CBC模式传入iv参数 + 本例使用常用的ECB模式 + """ + + def __init__(self, key): + if len(key) > 32: + key = key[:32] + self.key = self.to_16(key) + + @staticmethod + def to_16(key): + """ + 转为16倍数的bytes数据 + :param key: + :return: + """ + key = bytes(key, encoding="utf8") + while len(key) % 16 != 0: + key += b'\0' + return key # 返回bytes + + def aes(self): + return AES.new(self.key, AES.MODE_ECB) # 初始化加密器 + + def encrypt(self, text): + aes = self.aes() + return str(base64.encodebytes(aes.encrypt(self.to_16(text))), + encoding='utf8').replace('\n', '') # 加密 + + def decrypt(self, text): + aes = self.aes() + return str(aes.decrypt(base64.decodebytes(bytes(text, encoding='utf8'))).rstrip(b'\0').decode("utf8")) # 解密 + + +def get_aes_crypto(key=None): + if key is None: + key = settings.SECRET_KEY + a = AESCrypto(key) + return a + + +aes = get_aes_crypto() diff --git a/apps/jumpserver/middleware.py b/apps/jumpserver/middleware.py index f1589003d..277d8492a 100644 --- a/apps/jumpserver/middleware.py +++ b/apps/jumpserver/middleware.py @@ -16,10 +16,13 @@ class TimezoneMiddleware: def __call__(self, request): tzname = request.META.get('HTTP_X_TZ') - if tzname: - timezone.activate(pytz.timezone(tzname)) - else: - timezone.deactivate() + if not tzname or tzname == 'undefined': + return self.get_response(request) + try: + tz = pytz.timezone(tzname) + timezone.activate(tz) + except pytz.UnknownTimeZoneError: + pass response = self.get_response(request) return response From 75be45ce43838dc77c317b4b9fc09a9276330d52 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 11 Jun 2020 12:10:00 +0800 Subject: [PATCH 113/146] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E5=AF=B9=E7=A7=B0=E5=8A=A0=E5=AF=86=E6=96=B9=E5=BC=8F?= =?UTF-8?q?:=20aes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/fields/model.py | 24 ++++++++++++---- apps/common/utils/__init__.py | 1 + apps/common/utils/crypto.py | 54 +++++++++++++++++++++++++++++++++++ apps/common/utils/encode.py | 50 -------------------------------- 4 files changed, 74 insertions(+), 55 deletions(-) create mode 100644 apps/common/utils/crypto.py diff --git a/apps/common/fields/model.py b/apps/common/fields/model.py index ddb61f7c0..bc5c5538d 100644 --- a/apps/common/fields/model.py +++ b/apps/common/fields/model.py @@ -3,8 +3,9 @@ import json from django.db import models from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import force_text -from ..utils import signer +from ..utils import signer, aes_crypto __all__ = [ @@ -114,11 +115,22 @@ class EncryptMixin: def from_db_value(self, value, expression, connection, context): if value is None: return value - value = signer.unsign(value) + value = force_text(value) + + plain_value = '' + # 优先采用 aes 解密 + try: + plain_value = aes_crypto.decrypt(value) + except (TypeError, ValueError): + pass + + # 如果没有解开,使用原来的signer解密 + if not plain_value: + plain_value = signer.unsign(value) or '' sp = super() if hasattr(sp, 'from_db_value'): - return sp.from_db_value(value, expression, connection, context) - return value + plain_value = sp.from_db_value(plain_value, expression, connection, context) + return plain_value def get_prep_value(self, value): if value is None: @@ -126,7 +138,9 @@ class EncryptMixin: sp = super() if hasattr(sp, 'get_prep_value'): value = sp.get_prep_value(value) - return signer.sign(value) + value = force_text(value) + # 替换新的加密方式 + return aes_crypto.encrypt(value) class EncryptTextField(EncryptMixin, models.TextField): diff --git a/apps/common/utils/__init__.py b/apps/common/utils/__init__.py index 802943072..01850f0cf 100644 --- a/apps/common/utils/__init__.py +++ b/apps/common/utils/__init__.py @@ -6,3 +6,4 @@ from .django import * from .encode import * from .http import * from .ipip import * +from .crypto import * diff --git a/apps/common/utils/crypto.py b/apps/common/utils/crypto.py new file mode 100644 index 000000000..ea3590d6c --- /dev/null +++ b/apps/common/utils/crypto.py @@ -0,0 +1,54 @@ +import base64 +from Crypto.Cipher import AES + +from django.conf import settings + + +class AESCrypto: + """ + AES + 除了MODE_SIV模式key长度为:32, 48, or 64, + 其余key长度为16, 24 or 32 + 详细见AES内部文档 + CBC模式传入iv参数 + 本例使用常用的ECB模式 + """ + + def __init__(self, key): + if len(key) > 32: + key = key[:32] + self.key = self.to_16(key) + + @staticmethod + def to_16(key): + """ + 转为16倍数的bytes数据 + :param key: + :return: + """ + key = bytes(key, encoding="utf8") + while len(key) % 16 != 0: + key += b'\0' + return key # 返回bytes + + def aes(self): + return AES.new(self.key, AES.MODE_ECB) # 初始化加密器 + + def encrypt(self, text): + aes = self.aes() + return str(base64.encodebytes(aes.encrypt(self.to_16(text))), + encoding='utf8').replace('\n', '') # 加密 + + def decrypt(self, text): + aes = self.aes() + return str(aes.decrypt(base64.decodebytes(bytes(text, encoding='utf8'))).rstrip(b'\0').decode("utf8")) # 解密 + + +def get_aes_crypto(key=None): + if key is None: + key = settings.SECRET_KEY + a = AESCrypto(key) + return a + + +aes_crypto = get_aes_crypto() diff --git a/apps/common/utils/encode.py b/apps/common/utils/encode.py index 72e011917..cd130e7fe 100644 --- a/apps/common/utils/encode.py +++ b/apps/common/utils/encode.py @@ -9,7 +9,6 @@ import time import hashlib from io import StringIO from itertools import chain -from Crypto.Cipher import AES import paramiko import sshpubkeys @@ -227,52 +226,3 @@ def model_to_json(instance, sort_keys=True, indent=2, cls=None): cls = DjangoJSONEncoder return json.dumps(data, sort_keys=sort_keys, indent=indent, cls=cls) - -class AESCrypto: - """ - AES - 除了MODE_SIV模式key长度为:32, 48, or 64, - 其余key长度为16, 24 or 32 - 详细见AES内部文档 - CBC模式传入iv参数 - 本例使用常用的ECB模式 - """ - - def __init__(self, key): - if len(key) > 32: - key = key[:32] - self.key = self.to_16(key) - - @staticmethod - def to_16(key): - """ - 转为16倍数的bytes数据 - :param key: - :return: - """ - key = bytes(key, encoding="utf8") - while len(key) % 16 != 0: - key += b'\0' - return key # 返回bytes - - def aes(self): - return AES.new(self.key, AES.MODE_ECB) # 初始化加密器 - - def encrypt(self, text): - aes = self.aes() - return str(base64.encodebytes(aes.encrypt(self.to_16(text))), - encoding='utf8').replace('\n', '') # 加密 - - def decrypt(self, text): - aes = self.aes() - return str(aes.decrypt(base64.decodebytes(bytes(text, encoding='utf8'))).rstrip(b'\0').decode("utf8")) # 解密 - - -def get_aes_crypto(key=None): - if key is None: - key = settings.SECRET_KEY - a = AESCrypto(key) - return a - - -aes = get_aes_crypto() From 148c7ffb43380fa27d211b455117dd8f514913f7 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 11 Jun 2020 14:10:55 +0800 Subject: [PATCH 114/146] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/fields/model.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/common/fields/model.py b/apps/common/fields/model.py index bc5c5538d..945a98ea4 100644 --- a/apps/common/fields/model.py +++ b/apps/common/fields/model.py @@ -112,21 +112,29 @@ class EncryptMixin: """ EncryptMixin要放在最前面 """ + + def decrypt_from_signer(self, value): + return signer.unsign(value) or '' + + def decrypt_from_aes(self, value): + try: + return aes_crypto.decrypt(value) + except (TypeError, ValueError): + pass + def from_db_value(self, value, expression, connection, context): if value is None: return value value = force_text(value) - plain_value = '' # 优先采用 aes 解密 - try: - plain_value = aes_crypto.decrypt(value) - except (TypeError, ValueError): - pass + plain_value = self.decrypt_from_aes(value) # 如果没有解开,使用原来的signer解密 if not plain_value: - plain_value = signer.unsign(value) or '' + plain_value = self.decrypt_from_signer(value) + + # 可能和Json mix,所以要先解密,再json sp = super() if hasattr(sp, 'from_db_value'): plain_value = sp.from_db_value(plain_value, expression, connection, context) @@ -135,6 +143,8 @@ class EncryptMixin: def get_prep_value(self, value): if value is None: return value + + # 先 json 再解密 sp = super() if hasattr(sp, 'get_prep_value'): value = sp.get_prep_value(value) From 213fdd461baabfb5c114cd3c60664a604316dad7 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 11 Jun 2020 14:33:42 +0800 Subject: [PATCH 115/146] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 89900 -> 89900 bytes apps/locale/zh/LC_MESSAGES/django.po | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 04894a0271ce8edce09f6f0db9e2dc317e8e6630..38ab31b19b97d656fcaf1593170b77b3e74cc125 100644 GIT binary patch delta 30 jcmZ3pk9EyH)`l&N`nt@)!5-U Date: Thu, 11 Jun 2020 18:05:18 +0800 Subject: [PATCH 116/146] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9org=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E7=9A=84=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/orgs/serializers.py | 61 +++++++++------------------------------- 1 file changed, 13 insertions(+), 48 deletions(-) diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py index ca5bdddba..ae98420e0 100644 --- a/apps/orgs/serializers.py +++ b/apps/orgs/serializers.py @@ -14,57 +14,22 @@ class OrgSerializer(ModelSerializer): class Meta: model = Organization list_serializer_class = AdaptedBulkListSerializer - fields = '__all__' + fields_mini = ['id', 'name'] + fields_small = fields_mini + [ + 'created_by', 'date_created', 'comment' + ] + fields_m2m = ['users', 'admins', 'auditors'] + fields = fields_small + fields_m2m read_only_fields = ['created_by', 'date_created'] + extra_kwargs = { + 'admins': {'write_only': True}, + 'users': {'write_only': True}, + 'auditors': {'write_only': True}, + } -class OrgReadSerializer(ModelSerializer): - admins = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) - auditors = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) - users = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) - user_groups = serializers.SerializerMethodField() - assets = serializers.SerializerMethodField() - domains = serializers.SerializerMethodField() - admin_users = serializers.SerializerMethodField() - system_users = serializers.SerializerMethodField() - labels = serializers.SerializerMethodField() - perms = serializers.SerializerMethodField() - - class Meta: - model = Organization - fields = '__all__' - - @staticmethod - def get_data_from_model(obj, model): - current_org = get_current_org() - set_current_org(Organization.root()) - if model == Asset: - data = [o.hostname for o in model.objects.filter(org_id=obj.id)] - else: - data = [o.name for o in model.objects.filter(org_id=obj.id)] - set_current_org(current_org) - return data - - def get_user_groups(self, obj): - return self.get_data_from_model(obj, UserGroup) - - def get_assets(self, obj): - return self.get_data_from_model(obj, Asset) - - def get_domains(self, obj): - return self.get_data_from_model(obj, Domain) - - def get_admin_users(self, obj): - return self.get_data_from_model(obj, AdminUser) - - def get_system_users(self, obj): - return self.get_data_from_model(obj, SystemUser) - - def get_labels(self, obj): - return self.get_data_from_model(obj, Label) - - def get_perms(self, obj): - return self.get_data_from_model(obj, AssetPermission) +class OrgReadSerializer(OrgSerializer): + pass class OrgMembershipAdminSerializer(OrgMembershipSerializerMixin, ModelSerializer): From 3393f183992a3e912211bdf8dc2ba0f03c66906e Mon Sep 17 00:00:00 2001 From: xinwen Date: Thu, 11 Jun 2020 18:24:56 +0800 Subject: [PATCH 117/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=20?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=9B=B8=E5=85=B3=20Serializer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/users/api/user.py | 6 +++++- apps/users/serializers/user.py | 14 +++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/users/api/user.py b/apps/users/api/user.py index 8729f4dcc..8fc184375 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -14,6 +14,7 @@ from common.mixins import CommonApiMixin from common.utils import get_logger from orgs.utils import current_org from .. import serializers +from ..serializers import UserSerializer, UserRetrieveSerializer from .mixins import UserQuerysetMixin from ..models import User from ..signals import post_user_create @@ -29,8 +30,11 @@ __all__ = [ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet): filter_fields = ('username', 'email', 'name', 'id', 'source') search_fields = filter_fields - serializer_class = serializers.UserSerializer permission_classes = (IsOrgAdmin, CanUpdateDeleteUser) + serializer_classes = { + 'default': UserSerializer, + 'retrieve': UserRetrieveSerializer + } def get_queryset(self): return super().get_queryset().prefetch_related('groups') diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index fcbde6150..37d820b65 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -16,7 +16,8 @@ __all__ = [ 'UserSerializer', 'UserPKUpdateSerializer', 'ChangeUserPasswordSerializer', 'ResetOTPSerializer', 'UserProfileSerializer', 'UserOrgSerializer', - 'UserUpdatePasswordSerializer', 'UserUpdatePublicKeySerializer' + 'UserUpdatePasswordSerializer', 'UserUpdatePublicKeySerializer', + 'UserRetrieveSerializer' ] @@ -40,7 +41,6 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): login_blocked = serializers.SerializerMethodField() can_update = serializers.SerializerMethodField() can_delete = serializers.SerializerMethodField() - login_confirm_settings = serializers.PrimaryKeyRelatedField(read_only=True, source='login_confirm_setting.reviewers', many=True) key_prefix_block = "_LOGIN_BLOCK_{}" @@ -60,7 +60,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): ] fields = fields_small + [ 'groups', 'role', 'groups_display', 'role_display', - 'can_update', 'can_delete', 'login_blocked', 'login_confirm_settings' + 'can_update', 'can_delete', 'login_blocked', ] extra_kwargs = { @@ -158,6 +158,14 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): return blocked +class UserRetrieveSerializer(UserSerializer): + login_confirm_settings = serializers.PrimaryKeyRelatedField(read_only=True, + source='login_confirm_setting.reviewers', many=True) + + class Meta(UserSerializer.Meta): + fields = UserSerializer.Meta.fields + ['login_confirm_settings'] + + class UserPKUpdateSerializer(serializers.ModelSerializer): class Meta: model = User From 4b2fd0d0dad28dcc564eec3d358d93f9373c46f9 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 11 Jun 2020 19:36:34 +0800 Subject: [PATCH 118/146] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=94=A8=E6=88=B7=E8=BF=87=E6=BB=A4=E6=9D=83=E9=99=90?= =?UTF-8?q?=E8=A7=84=E5=88=99=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/utils/asset_permission.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index 1f3a3e94b..c5e227980 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -169,7 +169,7 @@ class AssetPermissionUtil(AssetPermissionUtilCacheMixin): @property def permissions(self): - if self._permissions: + if self._permissions is not None: return self._permissions if self.object is None: return AssetPermission.objects.none() @@ -351,6 +351,7 @@ class AssetPermissionUtil(AssetPermissionUtilCacheMixin): self.add_favorite_node_if_need(user_tree) self.set_user_tree_to_cache_if_need(user_tree) self.set_user_tree_to_local(user_tree) + print(user_tree) return user_tree # Todo: 是否可以获取多个资产的系统用户 From b14ca141207c05b462e1a45e5773eefef6e7e774 Mon Sep 17 00:00:00 2001 From: xinwen Date: Thu, 11 Jun 2020 20:30:59 +0800 Subject: [PATCH 119/146] [Fix] ops.models.adhoc (#4096) --- apps/ops/models/adhoc.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 433d1c498..e8923df1b 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -128,13 +128,15 @@ class Task(PeriodTaskModelMixin, OrgModelMixin): def _last_adhocexecution(self, is_success, key): obj = AdHocExecution.objects.filter(task_id=self.id, is_success=is_success).order_by('-date_finished').first() - body = obj.summary.get(key) + body = obj.summary and obj.summary.get(key) + if body: asset, body = body.popitem() - action, body = body.popitem() - return asset, action, body.get('msg', '') - else: - return '', '', '' + if body: + action, body = body.popitem() + return asset, action, (body or '') and body.get('msg', '') + return asset, '', '' + return '', '', '' def __str__(self): return self.name + '@' + str(self.org_id) From 787cdbcadf9a690c5367ac2ba361c4135a9d4a73 Mon Sep 17 00:00:00 2001 From: xinwen Date: Thu, 11 Jun 2020 20:30:59 +0800 Subject: [PATCH 120/146] [Fix] ops.models.adhoc (#4096) --- apps/ops/models/adhoc.py | 20 -------------------- apps/ops/serializers/adhoc.py | 10 ++-------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index e8923df1b..013fdc628 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -118,26 +118,6 @@ class Task(PeriodTaskModelMixin, OrgModelMixin): kwargs = {"callback": self.callback} return name, task, args, kwargs - @lazyproperty - def last_success(self): - return self._last_adhocexecution(True, 'contacted') - - @lazyproperty - def last_failure(self): - return self._last_adhocexecution(False, 'dark') - - def _last_adhocexecution(self, is_success, key): - obj = AdHocExecution.objects.filter(task_id=self.id, is_success=is_success).order_by('-date_finished').first() - body = obj.summary and obj.summary.get(key) - - if body: - asset, body = body.popitem() - if body: - action, body = body.popitem() - return asset, action, (body or '') and body.get('msg', '') - return asset, '', '' - return '', '', '' - def __str__(self): return self.name + '@' + str(self.org_id) diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index 0d284cb48..d4e67371a 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -60,14 +60,8 @@ class TaskSerializer(serializers.ModelSerializer): class TaskDetailSerializer(TaskSerializer): - last_success = serializers.SerializerMethodField() - last_failure = serializers.SerializerMethodField() - - def get_last_success(self, obj): - return obj.last_success[0], '' - - def get_last_failure(self, obj): - return obj.last_failure[0], ' => '.join(obj.last_failure[1:]) + last_success = serializers.ListField(source='latest_execution.success_hosts') + last_failure = serializers.DictField(source='latest_execution.failed_hosts') class Meta(TaskSerializer.Meta): fields = TaskSerializer.Meta.fields + ['last_success', 'last_failure'] From 1bb9048910e25d8bc869cd918fc7baeea41c43c6 Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 12 Jun 2020 11:45:03 +0800 Subject: [PATCH 121/146] =?UTF-8?q?[Update]=20=E6=A0=87=E7=AD=BE=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E7=B1=BBassets=E5=AD=97=E6=AE=B5required=E4=B8=BAFals?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/label.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/assets/serializers/label.py b/apps/assets/serializers/label.py index de5ab7ea2..448018eab 100644 --- a/apps/assets/serializers/label.py +++ b/apps/assets/serializers/label.py @@ -20,6 +20,9 @@ class LabelSerializer(BulkOrgResourceModelSerializer): read_only_fields = ( 'category', 'date_created', 'asset_count', 'get_category_display' ) + extra_kwargs = { + 'assets': {'required': False} + } list_serializer_class = AdaptedBulkListSerializer @staticmethod From edcf9921fea13107fc33ae19ac2d5657007a2b06 Mon Sep 17 00:00:00 2001 From: xinwen Date: Mon, 15 Jun 2020 13:24:52 +0800 Subject: [PATCH 122/146] [Update] apps/assets/serializers/system_user.py (#4102) --- apps/assets/serializers/system_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 6a9a31b9f..7f3ad5372 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -34,7 +34,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): 'priority', 'username_same_with_user', 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'auto_generate_key', 'sftp_root', - 'assets_amount', + 'assets_amount', 'date_created', 'created_by' ] extra_kwargs = { 'password': {"write_only": True}, From b51af1f7d747779b15ad9ea556159af73478cf99 Mon Sep 17 00:00:00 2001 From: Bai Date: Mon, 15 Jun 2020 13:49:51 +0800 Subject: [PATCH 123/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9MFA?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E4=B8=8B=E8=BD=BD=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 89900 -> 53698 bytes apps/locale/zh/LC_MESSAGES/django.po | 6162 ++++++----------- apps/static/img/authenticator_android.png | Bin 2443 -> 5907 bytes .../users/user_otp_enable_install_app.html | 2 +- 4 files changed, 2294 insertions(+), 3870 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 38ab31b19b97d656fcaf1593170b77b3e74cc125..331ae38c750c151330b40dbb1e6442977b146203 100644 GIT binary patch delta 19370 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 literal 89900 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

    !7cF6Qu&fW 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\n" "Language-Team: JumpServer team\n" @@ -21,133 +21,14 @@ msgstr "" msgid "Custom" msgstr "自定义" -#: applications/forms/remote_app.py:44 applications/models/remote_app.py:23 -#: applications/templates/applications/remote_app_detail.html:52 -#: applications/templates/applications/remote_app_list.html:27 -#: applications/templates/applications/user_remote_app_list.html:18 -#: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:352 assets/models/authbook.py:27 -#: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:32 -#: assets/serializers/asset_user.py:47 assets/serializers/asset_user.py:84 -#: assets/serializers/system_user.py:44 assets/serializers/system_user.py:176 -#: assets/templates/assets/admin_user_list.html:23 -#: assets/templates/assets/asset_list.html:170 -#: assets/templates/assets/domain_detail.html:55 -#: assets/templates/assets/domain_list.html:22 -#: assets/templates/assets/label_list.html:16 -#: assets/templates/assets/system_user_list.html:28 audits/models.py:20 -#: audits/templates/audits/ftp_log_list.html:45 -#: audits/templates/audits/ftp_log_list.html:75 -#: perms/forms/asset_permission.py:89 perms/models/asset_permission.py:80 -#: perms/templates/perms/asset_permission_asset.html:53 -#: perms/templates/perms/asset_permission_create_update.html:57 -#: perms/templates/perms/asset_permission_list.html:35 -#: perms/templates/perms/asset_permission_list.html:87 templates/index.html:82 -#: terminal/backends/command/models.py:19 terminal/models.py:187 -#: terminal/templates/terminal/command_list.html:31 -#: terminal/templates/terminal/command_list.html:106 -#: terminal/templates/terminal/session_detail.html:52 -#: terminal/templates/terminal/session_list.html:26 -#: terminal/templates/terminal/session_list.html:70 -#: users/templates/users/user_asset_permission.html:40 -#: users/templates/users/user_asset_permission.html:70 -#: users/templates/users/user_granted_remote_app.html:36 -#: xpack/plugins/change_auth_plan/forms.py:74 -#: xpack/plugins/change_auth_plan/models.py:274 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:40 -#: 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:269 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:37 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:47 -#: xpack/plugins/orgs/templates/orgs/org_list.html:17 -#: xpack/plugins/vault/forms.py:13 xpack/plugins/vault/forms.py:15 -msgid "Asset" -msgstr "资产" - -#: applications/forms/remote_app.py:55 -msgid "Target URL" -msgstr "目标URL" - -#: applications/forms/remote_app.py:58 applications/forms/remote_app.py:97 -#: applications/forms/remote_app.py:114 -msgid "Login username" -msgstr "登录账号" - -#: applications/forms/remote_app.py:62 applications/forms/remote_app.py:101 -#: applications/forms/remote_app.py:118 -msgid "Login password" -msgstr "登录密码" - -#: applications/forms/remote_app.py:73 -msgid "Database IP" -msgstr "数据库IP" - -#: applications/forms/remote_app.py:76 -msgid "Database name" -msgstr "数据库名" - -#: applications/forms/remote_app.py:79 -msgid "Database username" -msgstr "数据库账号" - -#: applications/forms/remote_app.py:83 -msgid "Database password" -msgstr "数据库密码" - -#: applications/forms/remote_app.py:94 applications/forms/remote_app.py:111 -msgid "Target address" -msgstr "目标地址" - -#: applications/forms/remote_app.py:108 -msgid "Operating parameter" -msgstr "运行参数" - #: applications/models/database_app.py:18 applications/models/remote_app.py:21 -#: applications/templates/applications/database_app_detail.html:48 -#: applications/templates/applications/database_app_list.html:23 -#: applications/templates/applications/remote_app_detail.html:48 -#: applications/templates/applications/remote_app_list.html:25 -#: applications/templates/applications/user_database_app_list.html:16 -#: applications/templates/applications/user_remote_app_list.html:16 -#: assets/forms/asset.py:21 assets/forms/domain.py:77 assets/forms/user.py:74 -#: assets/forms/user.py:96 assets/models/asset.py:145 assets/models/base.py:232 +#: assets/models/asset.py:145 assets/models/base.py:232 #: assets/models/cluster.py:18 assets/models/cmd_filter.py:21 #: assets/models/domain.py:20 assets/models/group.py:20 -#: assets/models/label.py:18 assets/templates/assets/_node_detail_modal.html:27 -#: assets/templates/assets/admin_user_detail.html:51 -#: assets/templates/assets/admin_user_list.html:21 -#: assets/templates/assets/cmd_filter_detail.html:56 -#: assets/templates/assets/cmd_filter_list.html:22 -#: assets/templates/assets/domain_detail.html:51 -#: assets/templates/assets/domain_gateway_list.html:62 -#: assets/templates/assets/domain_list.html:21 -#: assets/templates/assets/label_list.html:14 -#: assets/templates/assets/platform_detail.html:48 -#: assets/templates/assets/platform_list.html:16 -#: assets/templates/assets/system_user_detail.html:62 -#: assets/templates/assets/system_user_list.html:24 ops/mixin.py:24 -#: ops/templates/ops/task_detail.html:58 ops/templates/ops/task_list.html:11 -#: orgs/models.py:12 perms/models/base.py:48 -#: perms/templates/perms/asset_permission_detail.html:57 -#: perms/templates/perms/asset_permission_list.html:32 -#: perms/templates/perms/asset_permission_list.html:183 -#: perms/templates/perms/asset_permission_user.html:53 -#: perms/templates/perms/database_app_permission_detail.html:57 -#: perms/templates/perms/database_app_permission_list.html:14 -#: perms/templates/perms/database_app_permission_user.html:53 -#: perms/templates/perms/remote_app_permission_detail.html:57 -#: perms/templates/perms/remote_app_permission_list.html:14 -#: perms/templates/perms/remote_app_permission_remote_app.html:49 -#: perms/templates/perms/remote_app_permission_user.html:49 -#: settings/models.py:27 -#: settings/templates/settings/_ldap_list_users_modal.html:32 -#: terminal/models.py:26 terminal/models.py:342 terminal/models.py:374 -#: terminal/models.py:411 terminal/templates/terminal/base_storage_list.html:31 -#: terminal/templates/terminal/terminal_detail.html:43 -#: terminal/templates/terminal/terminal_list.html:30 users/forms/profile.py:20 -#: users/models/group.py:15 users/models/user.py:462 +#: assets/models/label.py:18 ops/mixin.py:24 orgs/models.py:12 +#: perms/models/base.py:48 settings/models.py:27 terminal/models.py:26 +#: terminal/models.py:342 terminal/models.py:374 terminal/models.py:411 +#: users/forms/profile.py:20 users/models/group.py:15 users/models/user.py:466 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -161,58 +42,27 @@ msgstr "运行参数" #: users/templates/users/user_profile.html:51 #: users/templates/users/user_pubkey_update.html:57 #: users/templates/users/user_remote_app_permission.html:36 -#: xpack/plugins/change_auth_plan/forms.py:57 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:59 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12 #: xpack/plugins/cloud/models.py:35 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:47 -#: 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/gathered_user/templates/gathered_user/task_list.html:16 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:47 -#: xpack/plugins/orgs/templates/orgs/org_list.html:12 msgid "Name" msgstr "名称" -#: applications/models/database_app.py:22 -#: applications/templates/applications/database_app_detail.html:52 -#: applications/templates/applications/database_app_list.html:24 -#: applications/templates/applications/user_database_app_list.html:17 -#: assets/models/cmd_filter.py:51 -#: assets/templates/assets/cmd_filter_rule_list.html:53 -#: audits/templates/audits/login_log_list.html:58 -#: perms/templates/perms/remote_app_permission_remote_app.html:50 -#: terminal/models.py:376 terminal/models.py:413 -#: terminal/templates/terminal/base_storage_list.html:32 -#: tickets/models/ticket.py:43 tickets/templates/tickets/ticket_detail.html:33 -#: tickets/templates/tickets/ticket_list.html:35 +#: applications/models/database_app.py:22 assets/models/cmd_filter.py:51 +#: terminal/models.py:376 terminal/models.py:413 tickets/models/ticket.py:43 #: users/templates/users/user_granted_database_app.html:35 msgid "Type" msgstr "类型" -#: applications/models/database_app.py:25 -#: applications/templates/applications/database_app_detail.html:56 -#: applications/templates/applications/database_app_list.html:25 -#: applications/templates/applications/user_database_app_list.html:18 -#: ops/models/adhoc.py:146 +#: applications/models/database_app.py:25 ops/models/adhoc.py:146 #: users/templates/users/user_granted_database_app.html:36 msgid "Host" msgstr "主机" -#: applications/models/database_app.py:27 -#: applications/templates/applications/database_app_detail.html:60 -#: applications/templates/applications/database_app_list.html:26 -#: assets/forms/asset.py:25 assets/models/asset.py:191 +#: applications/models/database_app.py:27 assets/models/asset.py:191 #: assets/models/domain.py:50 -#: assets/templates/assets/domain_gateway_list.html:64 msgid "Port" msgstr "端口" #: applications/models/database_app.py:29 -#: applications/templates/applications/database_app_detail.html:64 -#: applications/templates/applications/database_app_list.html:27 -#: applications/templates/applications/user_database_app_list.html:19 #: users/templates/users/user_granted_database_app.html:37 msgid "Database" msgstr "数据库" @@ -220,68 +70,28 @@ msgstr "数据库" # msgid "Date created" # msgstr "创建日期" #: applications/models/database_app.py:33 applications/models/remote_app.py:45 -#: applications/templates/applications/database_app_detail.html:76 -#: applications/templates/applications/database_app_list.html:28 -#: applications/templates/applications/remote_app_detail.html:72 -#: applications/templates/applications/remote_app_list.html:28 -#: applications/templates/applications/user_database_app_list.html:20 -#: applications/templates/applications/user_remote_app_list.html:19 #: assets/models/asset.py:150 assets/models/asset.py:226 #: assets/models/base.py:237 assets/models/cluster.py:29 #: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:56 #: 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:67 -#: assets/templates/assets/admin_user_list.html:24 -#: assets/templates/assets/asset_detail.html:128 -#: assets/templates/assets/cmd_filter_detail.html:60 -#: assets/templates/assets/cmd_filter_list.html:25 -#: assets/templates/assets/cmd_filter_rule_list.html:57 -#: assets/templates/assets/domain_detail.html:67 -#: assets/templates/assets/domain_gateway_list.html:67 -#: assets/templates/assets/domain_list.html:24 -#: assets/templates/assets/platform_detail.html:64 -#: assets/templates/assets/platform_list.html:18 -#: assets/templates/assets/system_user_detail.html:112 -#: assets/templates/assets/system_user_list.html:29 -#: assets/templates/assets/user_asset_list.html:81 ops/models/adhoc.py:37 -#: orgs/models.py:18 perms/models/base.py:56 -#: perms/templates/perms/asset_permission_detail.html:97 -#: perms/templates/perms/database_app_permission_detail.html:93 -#: perms/templates/perms/remote_app_permission_detail.html:89 -#: settings/models.py:32 terminal/models.py:36 terminal/models.py:381 -#: terminal/models.py:418 terminal/templates/terminal/base_storage_list.html:33 -#: terminal/templates/terminal/terminal_detail.html:63 -#: tickets/templates/tickets/ticket_detail.html:104 users/models/group.py:16 -#: users/models/user.py:495 users/templates/users/user_detail.html:115 +#: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37 +#: orgs/models.py:18 perms/models/base.py:56 settings/models.py:32 +#: terminal/models.py:36 terminal/models.py:381 terminal/models.py:418 +#: users/models/group.py:16 users/models/user.py:499 +#: users/templates/users/user_detail.html:115 #: users/templates/users/user_granted_database_app.html:38 #: users/templates/users/user_granted_remote_app.html:37 #: users/templates/users/user_group_detail.html:62 #: users/templates/users/user_group_list.html:16 #: users/templates/users/user_profile.html:138 -#: xpack/plugins/change_auth_plan/models.py:76 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:115 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19 -#: xpack/plugins/cloud/models.py:53 xpack/plugins/cloud/models.py:139 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:67 -#: xpack/plugins/cloud/templates/cloud/account_list.html:15 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:128 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:18 -#: xpack/plugins/gathered_user/models.py:26 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:59 -#: xpack/plugins/orgs/templates/orgs/org_list.html:23 +#: xpack/plugins/change_auth_plan/models.py:76 xpack/plugins/cloud/models.py:53 +#: xpack/plugins/cloud/models.py:139 xpack/plugins/gathered_user/models.py:26 msgid "Comment" msgstr "备注" #: applications/models/database_app.py:41 #: perms/forms/database_app_permission.py:44 #: perms/models/database_app_permission.py:18 -#: perms/templates/perms/database_app_permission_create_update.html:46 -#: perms/templates/perms/database_app_permission_database_app.html:23 -#: perms/templates/perms/database_app_permission_database_app.html:53 -#: perms/templates/perms/database_app_permission_detail.html:22 -#: perms/templates/perms/database_app_permission_list.html:17 -#: perms/templates/perms/database_app_permission_user.html:23 #: perms/utils/database_app_permission.py:77 templates/_nav.html:66 #: templates/_nav.html:86 templates/_nav_user.html:22 #: users/templates/users/user_database_app_permission.html:39 @@ -289,16 +99,27 @@ msgstr "备注" msgid "DatabaseApp" msgstr "数据库应用" +#: applications/models/remote_app.py:23 assets/models/asset.py:356 +#: assets/models/authbook.py:27 assets/models/gathered_user.py:14 +#: assets/serializers/admin_user.py:32 assets/serializers/asset_user.py:47 +#: assets/serializers/asset_user.py:84 assets/serializers/system_user.py:44 +#: assets/serializers/system_user.py:176 audits/models.py:20 +#: perms/forms/asset_permission.py:89 perms/models/asset_permission.py:80 +#: templates/index.html:82 terminal/backends/command/models.py:19 +#: terminal/models.py:187 users/templates/users/user_asset_permission.html:40 +#: users/templates/users/user_asset_permission.html:70 +#: users/templates/users/user_granted_remote_app.html:36 +#: xpack/plugins/change_auth_plan/models.py:282 +#: xpack/plugins/cloud/models.py:269 +msgid "Asset" +msgstr "资产" + #: applications/models/remote_app.py:28 -#: applications/templates/applications/remote_app_detail.html:56 -#: applications/templates/applications/remote_app_list.html:26 -#: applications/templates/applications/user_remote_app_list.html:17 #: users/templates/users/user_granted_remote_app.html:35 msgid "App type" msgstr "应用类型" #: applications/models/remote_app.py:32 -#: applications/templates/applications/remote_app_detail.html:60 msgid "App path" msgstr "应用路径" @@ -306,66 +127,32 @@ msgstr "应用路径" msgid "Parameters" msgstr "参数" -#: applications/models/remote_app.py:39 -#: applications/templates/applications/database_app_detail.html:72 -#: applications/templates/applications/remote_app_detail.html:68 -#: assets/models/asset.py:224 assets/models/base.py:240 -#: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 -#: assets/models/cmd_filter.py:59 assets/models/group.py:21 -#: assets/templates/assets/admin_user_detail.html:63 -#: assets/templates/assets/asset_detail.html:120 -#: assets/templates/assets/cmd_filter_detail.html:72 -#: assets/templates/assets/system_user_detail.html:108 -#: common/mixins/models.py:49 ops/templates/ops/adhoc_detail.html:84 -#: orgs/models.py:16 perms/models/base.py:54 -#: perms/templates/perms/asset_permission_detail.html:93 -#: perms/templates/perms/database_app_permission_detail.html:89 -#: perms/templates/perms/remote_app_permission_detail.html:85 -#: users/models/user.py:503 users/serializers/group.py:35 -#: users/templates/users/user_detail.html:97 -#: xpack/plugins/change_auth_plan/models.py:80 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:111 -#: xpack/plugins/cloud/models.py:56 xpack/plugins/cloud/models.py:145 -#: xpack/plugins/gathered_user/models.py:30 +#: applications/models/remote_app.py:39 assets/models/asset.py:224 +#: assets/models/base.py:240 assets/models/cluster.py:28 +#: assets/models/cmd_filter.py:26 assets/models/cmd_filter.py:59 +#: assets/models/group.py:21 common/mixins/models.py:49 orgs/models.py:16 +#: perms/models/base.py:54 users/models/user.py:507 +#: users/serializers/group.py:35 users/templates/users/user_detail.html:97 +#: xpack/plugins/change_auth_plan/models.py:80 xpack/plugins/cloud/models.py:56 +#: xpack/plugins/cloud/models.py:145 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "创建者" # msgid "Created by" # msgstr "创建者" -#: applications/models/remote_app.py:42 -#: applications/templates/applications/database_app_detail.html:68 -#: applications/templates/applications/remote_app_detail.html:64 -#: assets/models/asset.py:225 assets/models/base.py:238 -#: assets/models/cluster.py:26 assets/models/domain.py:23 -#: assets/models/gathered_user.py:19 assets/models/group.py:22 -#: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:59 -#: assets/templates/assets/cmd_filter_detail.html:64 -#: assets/templates/assets/domain_detail.html:63 -#: assets/templates/assets/system_user_detail.html:104 +#: applications/models/remote_app.py:42 assets/models/asset.py:225 +#: assets/models/base.py:238 assets/models/cluster.py:26 +#: assets/models/domain.py:23 assets/models/gathered_user.py:19 +#: assets/models/group.py:22 assets/models/label.py:25 #: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:27 -#: ops/templates/ops/adhoc_detail.html:88 ops/templates/ops/task_detail.html:62 -#: orgs/models.py:17 perms/models/base.py:55 -#: perms/templates/perms/asset_permission_detail.html:89 -#: perms/templates/perms/database_app_permission_detail.html:85 -#: perms/templates/perms/remote_app_permission_detail.html:81 -#: terminal/templates/terminal/terminal_detail.html:59 -#: tickets/templates/tickets/ticket_detail.html:52 users/models/group.py:18 +#: orgs/models.py:17 perms/models/base.py:55 users/models/group.py:18 #: users/templates/users/user_group_detail.html:58 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:103 #: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:148 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:63 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:108 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:55 msgid "Date created" msgstr "创建日期" #: applications/models/remote_app.py:49 perms/forms/remote_app_permission.py:46 #: perms/models/remote_app_permission.py:15 -#: perms/templates/perms/remote_app_permission_create_update.html:46 -#: perms/templates/perms/remote_app_permission_detail.html:22 -#: perms/templates/perms/remote_app_permission_list.html:17 -#: perms/templates/perms/remote_app_permission_remote_app.html:22 -#: perms/templates/perms/remote_app_permission_user.html:22 #: perms/utils/remote_app_permission.py:76 templates/_nav.html:64 #: templates/_nav.html:82 templates/_nav_user.html:16 #: users/templates/users/user_remote_app_permission.html:39 @@ -373,349 +160,6 @@ msgstr "创建日期" msgid "RemoteApp" msgstr "远程应用" -#: applications/templates/applications/database_app_create_update.html:12 -#: applications/templates/applications/remote_app_create_update.html:12 -#: assets/templates/assets/_system_user.html:73 -#: assets/templates/assets/admin_user_create_update.html:41 -#: assets/templates/assets/asset_bulk_update.html:23 -#: assets/templates/assets/asset_create.html:81 -#: assets/templates/assets/cmd_filter_create_update.html:15 -#: assets/templates/assets/cmd_filter_rule_create_update.html:36 -#: assets/templates/assets/domain_create_update.html:16 -#: assets/templates/assets/gateway_create_update.html:54 -#: assets/templates/assets/label_create_update.html:18 -#: assets/templates/assets/platform_create_update.html:20 -#: perms/templates/perms/asset_permission_create_update.html:127 -#: perms/templates/perms/database_app_permission_create_update.html:82 -#: perms/templates/perms/remote_app_permission_create_update.html:82 -#: settings/templates/settings/basic_setting.html:45 -#: settings/templates/settings/email_content_setting.html:35 -#: settings/templates/settings/email_setting.html:46 -#: settings/templates/settings/ldap_setting.html:45 -#: settings/templates/settings/security_setting.html:54 -#: settings/templates/settings/terminal_setting.html:53 -#: terminal/templates/terminal/base_storage_create_update.html:12 -#: terminal/templates/terminal/terminal_update.html:43 -#: users/templates/users/_user.html:51 -#: users/templates/users/user_bulk_update.html:23 -#: users/templates/users/user_detail.html:168 -#: users/templates/users/user_group_create_update.html:27 -#: users/templates/users/user_password_update.html:75 -#: users/templates/users/user_profile.html:209 -#: users/templates/users/user_profile_update.html:67 -#: users/templates/users/user_pubkey_update.html:74 -#: users/templates/users/user_pubkey_update.html:80 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:65 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:29 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:52 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:40 -#: xpack/plugins/interface/templates/interface/interface.html:72 -#: xpack/plugins/orgs/templates/orgs/org_create_update.html:29 -#: xpack/plugins/vault/templates/vault/vault_create.html:41 -msgid "Reset" -msgstr "重置" - -#: applications/templates/applications/database_app_create_update.html:13 -#: applications/templates/applications/remote_app_create_update.html:14 -#: assets/templates/assets/_system_user.html:74 -#: assets/templates/assets/admin_user_create_update.html:42 -#: assets/templates/assets/asset_bulk_update.html:24 -#: assets/templates/assets/asset_create.html:82 -#: assets/templates/assets/asset_list.html:45 -#: assets/templates/assets/cmd_filter_create_update.html:16 -#: assets/templates/assets/cmd_filter_rule_create_update.html:37 -#: assets/templates/assets/domain_create_update.html:17 -#: assets/templates/assets/gateway_create_update.html:55 -#: assets/templates/assets/label_create_update.html:19 -#: assets/templates/assets/platform_create_update.html:21 -#: audits/templates/audits/login_log_list.html:95 -#: perms/templates/perms/asset_permission_create_update.html:128 -#: perms/templates/perms/database_app_permission_create_update.html:83 -#: perms/templates/perms/remote_app_permission_create_update.html:83 -#: settings/templates/settings/basic_setting.html:46 -#: settings/templates/settings/email_content_setting.html:36 -#: settings/templates/settings/email_setting.html:47 -#: settings/templates/settings/ldap_setting.html:49 -#: settings/templates/settings/security_setting.html:55 -#: settings/templates/settings/terminal_setting.html:55 -#: terminal/templates/terminal/base_storage_create_update.html:13 -#: terminal/templates/terminal/command_list.html:48 -#: terminal/templates/terminal/session_list.html:50 -#: terminal/templates/terminal/terminal_update.html:44 -#: users/templates/users/_user.html:52 -#: users/templates/users/forgot_password.html:24 -#: users/templates/users/user_bulk_update.html:24 -#: users/templates/users/user_list.html:40 -#: users/templates/users/user_password_update.html:76 -#: users/templates/users/user_profile_update.html:68 -#: users/templates/users/user_pubkey_update.html:81 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:66 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67 -#: xpack/plugins/interface/templates/interface/interface.html:74 -#: xpack/plugins/vault/templates/vault/vault_create.html:42 -msgid "Submit" -msgstr "提交" - -#: applications/templates/applications/database_app_detail.html:13 -#: applications/templates/applications/remote_app_detail.html:13 -#: assets/templates/assets/admin_user_assets.html:13 -#: assets/templates/assets/admin_user_detail.html:13 -#: assets/templates/assets/cmd_filter_detail.html:14 -#: assets/templates/assets/cmd_filter_rule_list.html:14 -#: assets/templates/assets/domain_detail.html:13 -#: assets/templates/assets/domain_gateway_list.html:15 -#: assets/templates/assets/platform_detail.html:13 -#: assets/templates/assets/system_user_assets.html:22 -#: assets/templates/assets/system_user_detail.html:13 -#: assets/templates/assets/system_user_users.html:21 -#: ops/templates/ops/adhoc_history.html:128 -#: ops/templates/ops/task_adhoc.html:114 -#: ops/templates/ops/task_history.html:135 -#: perms/templates/perms/asset_permission_asset.html:14 -#: perms/templates/perms/asset_permission_detail.html:13 -#: perms/templates/perms/asset_permission_user.html:14 -#: perms/templates/perms/database_app_permission_database_app.html:14 -#: perms/templates/perms/database_app_permission_detail.html:13 -#: perms/templates/perms/database_app_permission_user.html:14 -#: perms/templates/perms/remote_app_permission_detail.html:13 -#: perms/templates/perms/remote_app_permission_remote_app.html:13 -#: perms/templates/perms/remote_app_permission_user.html:13 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:13 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:18 -#: 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:91 -msgid "Detail" -msgstr "详情" - -#: applications/templates/applications/database_app_detail.html:16 -#: applications/templates/applications/database_app_list.html:53 -#: applications/templates/applications/remote_app_detail.html:16 -#: applications/templates/applications/remote_app_list.html:59 -#: assets/templates/assets/_asset_user_list.html:67 -#: assets/templates/assets/admin_user_detail.html:19 -#: assets/templates/assets/admin_user_list.html:46 -#: assets/templates/assets/asset_detail.html:24 -#: assets/templates/assets/asset_list.html:89 -#: assets/templates/assets/cmd_filter_detail.html:24 -#: assets/templates/assets/cmd_filter_list.html:56 -#: assets/templates/assets/cmd_filter_rule_list.html:81 -#: assets/templates/assets/domain_detail.html:19 -#: assets/templates/assets/domain_detail.html:94 -#: assets/templates/assets/domain_gateway_list.html:92 -#: assets/templates/assets/domain_list.html:50 -#: assets/templates/assets/label_list.html:39 -#: assets/templates/assets/platform_detail.html:16 -#: assets/templates/assets/platform_list.html:40 -#: assets/templates/assets/system_user_detail.html:30 -#: assets/templates/assets/system_user_list.html:55 audits/models.py:34 -#: perms/templates/perms/asset_permission_detail.html:25 -#: perms/templates/perms/asset_permission_list.html:144 -#: perms/templates/perms/database_app_permission_detail.html:25 -#: perms/templates/perms/database_app_permission_list.html:64 -#: perms/templates/perms/remote_app_permission_detail.html:25 -#: perms/templates/perms/remote_app_permission_list.html:64 -#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6 -#: terminal/templates/terminal/base_storage_list.html:63 -#: terminal/templates/terminal/base_storage_list.html:70 -#: terminal/templates/terminal/terminal_detail.html:16 -#: terminal/templates/terminal/terminal_list.html:74 -#: users/templates/users/user_asset_permission.html:127 -#: users/templates/users/user_database_app_permission.html:110 -#: users/templates/users/user_detail.html:12 -#: users/templates/users/user_group_detail.html:23 -#: users/templates/users/user_group_list.html:51 -#: users/templates/users/user_list.html:84 -#: users/templates/users/user_list.html:87 -#: users/templates/users/user_profile.html:181 -#: users/templates/users/user_profile.html:191 -#: users/templates/users/user_profile.html:201 -#: users/templates/users/user_remote_app_permission.html:110 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:27 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:56 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:20 -#: xpack/plugins/cloud/templates/cloud/account_list.html:40 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:26 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:60 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:46 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:20 -#: xpack/plugins/orgs/templates/orgs/org_list.html:93 -msgid "Update" -msgstr "更新" - -#: applications/templates/applications/database_app_detail.html:20 -#: applications/templates/applications/database_app_list.html:54 -#: applications/templates/applications/remote_app_detail.html:20 -#: applications/templates/applications/remote_app_list.html:60 -#: assets/templates/assets/_asset_user_list.html:70 -#: assets/templates/assets/admin_user_detail.html:23 -#: assets/templates/assets/admin_user_list.html:47 -#: assets/templates/assets/asset_detail.html:28 -#: assets/templates/assets/asset_list.html:90 -#: assets/templates/assets/cmd_filter_detail.html:28 -#: assets/templates/assets/cmd_filter_list.html:57 -#: assets/templates/assets/cmd_filter_rule_list.html:82 -#: assets/templates/assets/domain_detail.html:23 -#: assets/templates/assets/domain_detail.html:95 -#: assets/templates/assets/domain_gateway_list.html:93 -#: assets/templates/assets/domain_list.html:51 -#: assets/templates/assets/label_list.html:40 -#: assets/templates/assets/platform_detail.html:20 -#: assets/templates/assets/platform_list.html:41 -#: assets/templates/assets/system_user_detail.html:34 -#: assets/templates/assets/system_user_list.html:56 audits/models.py:35 -#: authentication/templates/authentication/_access_key_modal.html:65 -#: ops/templates/ops/task_list.html:74 -#: perms/templates/perms/asset_permission_detail.html:29 -#: perms/templates/perms/asset_permission_list.html:145 -#: perms/templates/perms/database_app_permission_detail.html:29 -#: perms/templates/perms/database_app_permission_list.html:65 -#: perms/templates/perms/remote_app_permission_detail.html:29 -#: perms/templates/perms/remote_app_permission_list.html:65 -#: terminal/templates/terminal/base_storage_list.html:60 -#: terminal/templates/terminal/base_storage_list.html:67 -#: terminal/templates/terminal/terminal_list.html:76 -#: users/templates/users/user_asset_permission.html:128 -#: users/templates/users/user_database_app_permission.html:111 -#: users/templates/users/user_detail.html:16 -#: users/templates/users/user_group_detail.html:27 -#: users/templates/users/user_group_list.html:53 -#: users/templates/users/user_list.html:94 -#: users/templates/users/user_list.html:98 -#: users/templates/users/user_remote_app_permission.html:111 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:31 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:58 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:24 -#: xpack/plugins/cloud/templates/cloud/account_list.html:42 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:61 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:47 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:24 -#: xpack/plugins/orgs/templates/orgs/org_list.html:95 -msgid "Delete" -msgstr "删除" - -#: applications/templates/applications/database_app_list.html:9 -#: applications/views/database_app.py:69 applications/views/database_app.py:84 -msgid "Create DatabaseApp" -msgstr "创建数据库应用" - -#: applications/templates/applications/database_app_list.html:29 -#: applications/templates/applications/remote_app_list.html:29 -#: applications/templates/applications/user_database_app_list.html:21 -#: applications/templates/applications/user_remote_app_list.html:20 -#: assets/models/cmd_filter.py:55 -#: assets/templates/assets/_asset_user_list.html:25 -#: assets/templates/assets/admin_user_list.html:25 -#: assets/templates/assets/asset_list.html:28 -#: assets/templates/assets/cmd_filter_list.html:26 -#: assets/templates/assets/cmd_filter_rule_list.html:58 -#: assets/templates/assets/domain_gateway_list.html:68 -#: assets/templates/assets/domain_list.html:25 -#: assets/templates/assets/label_list.html:17 -#: assets/templates/assets/platform_list.html:19 -#: assets/templates/assets/system_user_list.html:30 -#: assets/templates/assets/system_user_users.html:64 audits/models.py:39 -#: audits/templates/audits/operate_log_list.html:45 -#: audits/templates/audits/operate_log_list.html:71 -#: authentication/templates/authentication/_access_key_modal.html:34 -#: ops/templates/ops/adhoc_history.html:57 ops/templates/ops/task_adhoc.html:62 -#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:17 -#: perms/forms/asset_permission.py:20 -#: perms/templates/perms/asset_permission_asset.html:54 -#: perms/templates/perms/asset_permission_create_update.html:63 -#: perms/templates/perms/asset_permission_list.html:39 -#: perms/templates/perms/asset_permission_list.html:96 -#: perms/templates/perms/asset_permission_user.html:54 -#: perms/templates/perms/database_app_permission_database_app.html:54 -#: perms/templates/perms/database_app_permission_list.html:20 -#: perms/templates/perms/database_app_permission_user.html:54 -#: perms/templates/perms/remote_app_permission_list.html:20 -#: terminal/templates/terminal/base_storage_list.html:34 -#: terminal/templates/terminal/session_list.html:34 -#: terminal/templates/terminal/terminal_list.html:37 -#: tickets/templates/tickets/ticket_list.html:108 -#: users/templates/users/_granted_assets.html:29 -#: users/templates/users/user_asset_permission.html:44 -#: users/templates/users/user_asset_permission.html:79 -#: users/templates/users/user_database_app_permission.html:42 -#: users/templates/users/user_group_list.html:17 -#: users/templates/users/user_list.html:20 -#: users/templates/users/user_remote_app_permission.html:42 -#: 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_history.html:52 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:19 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:20 -#: xpack/plugins/orgs/templates/orgs/org_list.html:24 -msgid "Action" -msgstr "动作" - -#: applications/templates/applications/remote_app_list.html:4 -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:5 -msgid "Download application loader" -msgstr "下载应用加载器" - -#: applications/templates/applications/remote_app_list.html:11 -#: applications/views/remote_app.py:69 -msgid "Create RemoteApp" -msgstr "创建远程应用" - -#: applications/templates/applications/user_database_app_list.html:61 -#: applications/templates/applications/user_remote_app_list.html:52 -#: perms/models/asset_permission.py:32 -msgid "Connect" -msgstr "连接" - -#: applications/views/database_app.py:26 users/models/user.py:156 -msgid "Application" -msgstr "应用程序" - -#: applications/views/database_app.py:27 -msgid "DatabaseApp list" -msgstr "数据库应用列表" - -#: applications/views/database_app.py:68 applications/views/database_app.py:83 -#: applications/views/database_app.py:99 applications/views/remote_app.py:28 -#: applications/views/remote_app.py:68 applications/views/remote_app.py:96 -#: applications/views/remote_app.py:112 templates/_nav.html:60 -msgid "Applications" -msgstr "应用管理" - -#: applications/views/database_app.py:100 -msgid "DatabaseApp detail" -msgstr "数据库应用详情" - -#: applications/views/database_app.py:112 -msgid "My DatabaseApp" -msgstr "我的数据库应用" - -#: applications/views/remote_app.py:29 -msgid "RemoteApp list" -msgstr "远程应用列表" - -#: applications/views/remote_app.py:97 -msgid "Update RemoteApp" -msgstr "更新远程应用" - -#: applications/views/remote_app.py:113 -msgid "RemoteApp detail" -msgstr "远程应用详情" - -#: applications/views/remote_app.py:125 -msgid "My RemoteApp" -msgstr "我的远程应用" - #: assets/api/admin_user.py:46 msgid "Deleted failed, There are related assets" msgstr "删除失败,存在关联资产" @@ -736,259 +180,15 @@ msgstr "不能移除资产的管理用户账号" msgid "Latest version could not be delete" msgstr "最新版本的不能被删除" -#: assets/forms/asset.py:83 assets/models/asset.py:195 -#: assets/models/user.py:109 assets/templates/assets/asset_detail.html:186 -#: assets/templates/assets/asset_detail.html:194 -#: assets/templates/assets/system_user_assets.html:118 -#: perms/models/asset_permission.py:81 -#: xpack/plugins/change_auth_plan/models.py:55 -#: xpack/plugins/gathered_user/models.py:24 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:17 -msgid "Nodes" -msgstr "节点" - -#: assets/forms/asset.py:86 assets/models/asset.py:199 -#: assets/models/cluster.py:19 assets/models/user.py:65 -#: assets/templates/assets/admin_user_list.html:62 -#: assets/templates/assets/asset_detail.html:72 templates/_nav.html:44 -#: xpack/plugins/cloud/models.py:133 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:65 -#: xpack/plugins/orgs/templates/orgs/org_list.html:19 -msgid "Admin user" -msgstr "管理用户" - -#: assets/forms/asset.py:89 assets/forms/asset.py:131 -#: assets/templates/assets/asset_create.html:48 -#: assets/templates/assets/asset_create.html:50 -#: assets/templates/assets/asset_list.html:13 -#: xpack/plugins/orgs/templates/orgs/org_list.html:21 -msgid "Label" -msgstr "标签" - -#: assets/forms/asset.py:92 assets/models/asset.py:194 -#: assets/models/domain.py:26 assets/models/domain.py:52 -#: assets/templates/assets/asset_detail.html:76 -#: assets/templates/assets/user_asset_list.html:80 -#: xpack/plugins/orgs/templates/orgs/org_list.html:18 -msgid "Domain" -msgstr "网域" - -#: assets/forms/asset.py:95 assets/models/asset.py:169 -#: assets/models/asset.py:193 assets/serializers/asset.py:67 -#: assets/templates/assets/asset_detail.html:100 -#: assets/templates/assets/user_asset_list.html:78 -msgid "Platform" -msgstr "系统平台" - -#: assets/forms/asset.py:99 assets/forms/asset.py:134 assets/models/node.py:497 -#: assets/serializers/system_user.py:43 assets/serializers/system_user.py:175 -#: assets/templates/assets/asset_create.html:42 -#: perms/forms/asset_permission.py:92 perms/forms/asset_permission.py:99 -#: perms/templates/perms/asset_permission_list.html:36 -#: perms/templates/perms/asset_permission_list.html:90 -#: perms/templates/perms/asset_permission_list.html:189 -#: users/templates/users/user_asset_permission.html:41 -#: users/templates/users/user_asset_permission.html:73 -#: users/templates/users/user_asset_permission.html:158 -#: xpack/plugins/change_auth_plan/forms.py:75 -#: 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:129 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:61 -msgid "Node" -msgstr "节点" - -#: assets/forms/asset.py:103 -msgid "" -"root or other NOPASSWD sudo privilege user existed in asset,If asset is " -"windows or other set any one, more see admin user left menu" -msgstr "" -"root或其他拥有NOPASSWD: ALL权限的用户, 如果是windows或其它硬件可以随意设置一" -"个, 更多信息查看左侧 `管理用户` 菜单" - -#: assets/forms/asset.py:106 -msgid "Windows 2016 RDP protocol is different, If is window 2016, set it" -msgstr "Windows 2016的RDP协议与之前不同,如果是请设置" - -#: assets/forms/asset.py:107 -msgid "" -"If your have some network not connect with each other, you can set domain" -msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,使用网域网关跳转登录" - -#: assets/forms/asset.py:114 assets/forms/asset.py:118 -#: assets/forms/domain.py:17 assets/forms/label.py:15 -#: assets/templates/assets/system_user_assets.html:102 -#: perms/templates/perms/asset_permission_asset.html:74 -#: xpack/plugins/change_auth_plan/forms.py:65 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:70 -msgid "Select assets" -msgstr "选择资产" - -#: assets/forms/cmd_filter.py:38 -msgid "Content should not be contain: {}" -msgstr "内容不能包含: {}" - -#: assets/forms/domain.py:55 assets/models/domain.py:67 -msgid "Password should not contain special characters" -msgstr "不能包含特殊字符" - -#: assets/forms/domain.py:74 -msgid "SSH gateway support proxy SSH,RDP,VNC" -msgstr "SSH网关,支持代理SSH,RDP和VNC" - -#: assets/forms/domain.py:78 assets/forms/user.py:75 assets/forms/user.py:97 -#: assets/models/base.py:233 assets/models/gathered_user.py:15 -#: assets/templates/assets/_asset_user_auth_update_modal.html:15 -#: assets/templates/assets/_asset_user_auth_view_modal.html:21 -#: assets/templates/assets/_asset_user_list.html:21 -#: assets/templates/assets/admin_user_detail.html:55 -#: assets/templates/assets/admin_user_list.html:22 -#: assets/templates/assets/domain_gateway_list.html:66 -#: assets/templates/assets/system_user_detail.html:66 -#: assets/templates/assets/system_user_list.html:25 audits/models.py:81 -#: audits/templates/audits/login_log_list.html:57 authentication/forms.py:10 -#: authentication/templates/authentication/login.html:21 -#: authentication/templates/authentication/xpack_login.html:93 -#: ops/models/adhoc.py:148 perms/templates/perms/asset_permission_list.html:185 -#: perms/templates/perms/remote_app_permission_user.html:50 -#: settings/templates/settings/_ldap_list_users_modal.html:31 -#: settings/templates/settings/_ldap_test_user_login_modal.html:10 -#: users/forms/profile.py:19 users/models/user.py:460 -#: users/templates/users/_select_user_modal.html:14 -#: users/templates/users/user_detail.html:53 -#: users/templates/users/user_list.html:15 -#: users/templates/users/user_profile.html:47 -#: xpack/plugins/change_auth_plan/forms.py:59 -#: xpack/plugins/change_auth_plan/models.py:46 -#: xpack/plugins/change_auth_plan/models.py:270 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:63 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:13 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:25 -msgid "Username" -msgstr "用户名" - -#: assets/forms/platform.py:20 ops/templates/ops/task_detail.html:85 -#: ops/templates/ops/task_detail.html:95 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:82 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:72 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:82 -msgid "Yes" -msgstr "是" - -#: assets/forms/platform.py:21 ops/templates/ops/task_detail.html:87 -#: ops/templates/ops/task_detail.html:97 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:84 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:74 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:84 -msgid "No" -msgstr "否" - -#: assets/forms/platform.py:24 -msgid "RDP security" -msgstr "" - -#: assets/forms/platform.py:28 -msgid "RDP console" -msgstr "" - -#: assets/forms/platform.py:40 assets/templates/assets/platform_detail.html:52 -#: assets/templates/assets/platform_list.html:17 -msgid "Base platform" -msgstr "基础平台" - -#: assets/forms/user.py:25 -msgid "Password or private key passphrase" -msgstr "密码或密钥密码" - -#: assets/forms/user.py:26 assets/models/base.py:234 -#: assets/serializers/asset_user.py:71 -#: assets/templates/assets/_asset_user_auth_update_modal.html:21 -#: assets/templates/assets/_asset_user_auth_view_modal.html:27 -#: authentication/forms.py:12 -#: authentication/templates/authentication/login.html:29 -#: authentication/templates/authentication/xpack_login.html:101 -#: settings/forms/ldap.py:22 -#: settings/templates/settings/_ldap_test_user_login_modal.html:16 -#: users/forms/user.py:22 users/forms/user.py:193 -#: users/templates/users/user_otp_check_password.html:13 -#: users/templates/users/user_password_update.html:44 -#: users/templates/users/user_password_verify.html:18 -#: users/templates/users/user_profile_update.html:41 -#: users/templates/users/user_pubkey_update.html:41 -#: users/templates/users/user_update.html:20 -#: xpack/plugins/change_auth_plan/models.py:67 -#: xpack/plugins/change_auth_plan/models.py:190 -#: xpack/plugins/change_auth_plan/models.py:277 -msgid "Password" -msgstr "密码" - -#: assets/forms/user.py:29 assets/serializers/asset_user.py:79 -#: assets/templates/assets/_asset_user_auth_update_modal.html:27 -#: users/models/user.py:489 -msgid "Private key" -msgstr "ssh私钥" - -#: assets/forms/user.py:41 -msgid "Invalid private key, Only support RSA/DSA format key" -msgstr "不合法的密钥,仅支持RSA/DSA格式的密钥" - -#: assets/forms/user.py:52 -msgid "Password and private key file must be input one" -msgstr "密码和私钥, 必须输入一个" - -#: assets/forms/user.py:99 assets/models/cmd_filter.py:32 -#: assets/models/user.py:119 assets/templates/assets/_system_user.html:63 -#: assets/templates/assets/system_user_detail.html:155 -msgid "Command filter" -msgstr "命令过滤器" - -#: assets/forms/user.py:103 assets/models/user.py:108 -#: assets/templates/assets/system_user_detail.html:68 -msgid "Username same with user" -msgstr "用户名与用户相同" - -#: assets/forms/user.py:106 -msgid "Auto push system user to asset" -msgstr "自动推送系统用户到资产" - -#: assets/forms/user.py:107 -msgid "" -"1-100, High level will be using login asset as default, if user was granted " -"more than 2 system user" -msgstr "" -"1-100, 1最低优先级,100最高优先级。授权多个用户时,高优先级的系统用户将会作为" -"默认登录用户" - -#: assets/forms/user.py:109 -msgid "" -"If you choose manual login mode, you do not need to fill in the username and " -"password." -msgstr "如果选择手动登录模式,用户名和密码可以不填写" - -#: assets/forms/user.py:111 -msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig" -msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" - -#: assets/forms/user.py:112 -msgid "SFTP root dir, tmp, home or custom" -msgstr "SFTP的起始路径,tmp目录, 用户home目录或者自定义" - -#: assets/forms/user.py:113 -msgid "Username is dynamic, When connect asset, using current user's username" -msgstr "用户名是动态的,登录资产时使用当前用户的用户名登录" - #: assets/models/asset.py:146 xpack/plugins/cloud/providers/base.py:16 msgid "Base" msgstr "基础" -#: assets/models/asset.py:147 assets/templates/assets/platform_detail.html:56 +#: assets/models/asset.py:147 msgid "Charset" msgstr "编码" -#: assets/models/asset.py:148 assets/templates/assets/platform_detail.html:60 -#: tickets/models/ticket.py:38 +#: assets/models/asset.py:148 tickets/models/ticket.py:38 msgid "Meta" msgstr "元数据" @@ -996,84 +196,76 @@ msgstr "元数据" msgid "Internal" msgstr "内部的" +#: assets/models/asset.py:169 assets/models/asset.py:193 +#: assets/serializers/asset.py:67 +msgid "Platform" +msgstr "系统平台" + #: assets/models/asset.py:186 assets/models/domain.py:49 -#: assets/serializers/asset_user.py:46 -#: assets/templates/assets/_asset_list_modal.html:47 -#: assets/templates/assets/_asset_user_list.html:20 -#: assets/templates/assets/asset_detail.html:60 -#: assets/templates/assets/asset_list.html:25 -#: assets/templates/assets/domain_gateway_list.html:63 -#: assets/templates/assets/user_asset_list.html:76 -#: audits/templates/audits/login_log_list.html:60 -#: perms/templates/perms/asset_permission_list.html:187 -#: settings/forms/terminal.py:16 settings/serializers/settings.py:52 +#: assets/serializers/asset_user.py:46 settings/serializers/settings.py:52 #: users/templates/users/_granted_assets.html:26 #: users/templates/users/user_asset_permission.html:156 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:50 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:24 msgid "IP" msgstr "IP" #: assets/models/asset.py:187 assets/serializers/asset_user.py:45 -#: assets/serializers/gathered_user.py:20 -#: assets/templates/assets/_asset_list_modal.html:46 -#: assets/templates/assets/_asset_user_auth_update_modal.html:9 -#: assets/templates/assets/_asset_user_auth_view_modal.html:15 -#: assets/templates/assets/_asset_user_list.html:19 -#: assets/templates/assets/asset_detail.html:56 -#: assets/templates/assets/asset_list.html:24 -#: assets/templates/assets/user_asset_list.html:75 -#: perms/templates/perms/asset_permission_list.html:188 -#: settings/forms/terminal.py:15 settings/serializers/settings.py:51 +#: assets/serializers/gathered_user.py:20 settings/serializers/settings.py:51 #: users/templates/users/_granted_assets.html:25 #: users/templates/users/user_asset_permission.html:157 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:49 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:23 msgid "Hostname" msgstr "主机名" #: assets/models/asset.py:190 assets/models/domain.py:51 -#: assets/models/user.py:114 assets/templates/assets/asset_detail.html:68 -#: assets/templates/assets/domain_gateway_list.html:65 -#: assets/templates/assets/system_user_detail.html:78 -#: assets/templates/assets/system_user_list.html:26 -#: terminal/forms/storage.py:145 -#: terminal/templates/terminal/session_detail.html:60 -#: terminal/templates/terminal/session_list.html:29 -#: terminal/templates/terminal/session_list.html:73 +#: assets/models/user.py:114 terminal/serializers/session.py:29 msgid "Protocol" msgstr "协议" #: assets/models/asset.py:192 assets/serializers/asset.py:69 -#: assets/templates/assets/asset_create.html:24 -#: assets/templates/assets/user_asset_list.html:77 #: perms/serializers/user_permission.py:60 msgid "Protocols" msgstr "协议组" +#: assets/models/asset.py:194 assets/models/domain.py:26 +#: assets/models/domain.py:52 +msgid "Domain" +msgstr "网域" + +#: assets/models/asset.py:195 assets/models/user.py:109 +#: perms/models/asset_permission.py:81 +#: xpack/plugins/change_auth_plan/models.py:55 +#: xpack/plugins/gathered_user/models.py:24 +msgid "Nodes" +msgstr "节点" + #: assets/models/asset.py:196 assets/models/cmd_filter.py:22 #: assets/models/domain.py:54 assets/models/label.py:22 -#: assets/templates/assets/asset_detail.html:108 authentication/models.py:45 +#: authentication/models.py:45 msgid "Is active" msgstr "激活" -#: assets/models/asset.py:202 assets/templates/assets/asset_detail.html:64 +#: assets/models/asset.py:199 assets/models/cluster.py:19 +#: assets/models/user.py:65 templates/_nav.html:44 +#: xpack/plugins/cloud/models.py:133 +msgid "Admin user" +msgstr "管理用户" + +#: assets/models/asset.py:202 msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:203 assets/templates/assets/asset_detail.html:116 +#: assets/models/asset.py:203 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:206 assets/templates/assets/asset_detail.html:80 +#: assets/models/asset.py:206 msgid "Vendor" msgstr "制造商" -#: assets/models/asset.py:207 assets/templates/assets/asset_detail.html:84 +#: assets/models/asset.py:207 msgid "Model" msgstr "型号" -#: assets/models/asset.py:208 assets/templates/assets/asset_detail.html:112 +#: assets/models/asset.py:208 msgid "Serial number" msgstr "序列号" @@ -1093,7 +285,7 @@ msgstr "CPU核数" msgid "CPU vcpus" msgstr "CPU总数" -#: assets/models/asset.py:214 assets/templates/assets/asset_detail.html:92 +#: assets/models/asset.py:214 msgid "Memory" msgstr "内存" @@ -1105,7 +297,7 @@ msgstr "硬盘大小" msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:218 assets/templates/assets/asset_detail.html:104 +#: assets/models/asset.py:218 msgid "OS" msgstr "操作系统" @@ -1121,8 +313,7 @@ msgstr "系统架构" msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:223 assets/templates/assets/asset_create.html:46 -#: assets/templates/assets/asset_detail.html:220 templates/_nav.html:46 +#: assets/models/asset.py:223 templates/_nav.html:46 msgid "Labels" msgstr "标签管理" @@ -1130,15 +321,11 @@ msgstr "标签管理" msgid "Bulk delete deny" msgstr "拒绝批量删除" -#: assets/models/authbook.py:28 ops/templates/ops/task_detail.html:70 +#: assets/models/authbook.py:28 msgid "Latest version" msgstr "最新版本" #: assets/models/authbook.py:29 -#: assets/templates/assets/_asset_user_list.html:22 -#: ops/templates/ops/adhoc_history.html:56 -#: ops/templates/ops/adhoc_history_detail.html:55 -#: ops/templates/ops/task_adhoc.html:56 ops/templates/ops/task_history.html:62 msgid "Version" msgstr "版本" @@ -1146,23 +333,51 @@ msgstr "版本" msgid "AuthBook" msgstr "" +#: assets/models/base.py:233 assets/models/gathered_user.py:15 +#: audits/models.py:81 authentication/forms.py:10 +#: authentication/templates/authentication/login.html:21 +#: authentication/templates/authentication/xpack_login.html:93 +#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:464 +#: users/templates/users/_select_user_modal.html:14 +#: users/templates/users/user_detail.html:53 +#: users/templates/users/user_list.html:15 +#: users/templates/users/user_profile.html:47 +#: xpack/plugins/change_auth_plan/models.py:46 +#: xpack/plugins/change_auth_plan/models.py:278 +msgid "Username" +msgstr "用户名" + +#: assets/models/base.py:234 assets/serializers/asset_user.py:71 +#: authentication/forms.py:12 +#: authentication/templates/authentication/login.html:29 +#: authentication/templates/authentication/xpack_login.html:101 +#: users/forms/user.py:22 users/forms/user.py:193 +#: users/templates/users/user_otp_check_password.html:13 +#: users/templates/users/user_password_update.html:43 +#: users/templates/users/user_password_verify.html:18 +#: users/templates/users/user_profile_update.html:41 +#: users/templates/users/user_pubkey_update.html:41 +#: users/templates/users/user_update.html:20 +#: xpack/plugins/change_auth_plan/models.py:67 +#: xpack/plugins/change_auth_plan/models.py:190 +#: xpack/plugins/change_auth_plan/models.py:285 +msgid "Password" +msgstr "密码" + #: assets/models/base.py:235 xpack/plugins/change_auth_plan/models.py:71 #: xpack/plugins/change_auth_plan/models.py:197 -#: xpack/plugins/change_auth_plan/models.py:284 +#: xpack/plugins/change_auth_plan/models.py:292 msgid "SSH private key" msgstr "SSH密钥" #: assets/models/base.py:236 xpack/plugins/change_auth_plan/models.py:74 #: xpack/plugins/change_auth_plan/models.py:193 -#: xpack/plugins/change_auth_plan/models.py:280 +#: xpack/plugins/change_auth_plan/models.py:288 msgid "SSH public key" msgstr "SSH公钥" #: assets/models/base.py:239 assets/models/gathered_user.py:20 -#: assets/templates/assets/cmd_filter_detail.html:68 common/mixins/models.py:51 -#: ops/models/adhoc.py:39 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:107 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:29 +#: common/mixins/models.py:51 ops/models/adhoc.py:39 msgid "Date updated" msgstr "更新日期" @@ -1174,7 +389,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:481 +#: assets/models/cluster.py:22 users/models/user.py:485 #: users/templates/users/user_detail.html:62 msgid "Phone" msgstr "手机" @@ -1200,7 +415,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:622 +#: users/models/user.py:626 msgid "System" msgstr "系统" @@ -1224,16 +439,16 @@ msgstr "北京电信" msgid "BGP full netcom" msgstr "BGP全网通" +#: assets/models/cmd_filter.py:32 assets/models/user.py:119 +msgid "Command filter" +msgstr "命令过滤器" + #: assets/models/cmd_filter.py:39 msgid "Regex" msgstr "正则表达式" #: assets/models/cmd_filter.py:40 ops/models/command.py:23 -#: ops/templates/ops/command_execution_list.html:67 terminal/models.py:196 -#: terminal/templates/terminal/command_list.html:28 -#: terminal/templates/terminal/command_list.html:108 -#: terminal/templates/terminal/session_commands.html:49 -#: terminal/templates/terminal/session_list.html:31 +#: terminal/models.py:196 msgid "Command" msgstr "命令" @@ -1250,7 +465,6 @@ msgid "Filter" msgstr "过滤器" #: assets/models/cmd_filter.py:52 assets/models/user.py:113 -#: assets/templates/assets/cmd_filter_rule_list.html:55 msgid "Priority" msgstr "优先级" @@ -1258,9 +472,7 @@ msgstr "优先级" msgid "1-100, the higher will be match first" msgstr "优先级可选范围为1-100,1最低优先级,100最高优先级" -#: assets/models/cmd_filter.py:54 -#: assets/templates/assets/cmd_filter_rule_list.html:54 -#: xpack/plugins/license/models.py:29 +#: assets/models/cmd_filter.py:54 xpack/plugins/license/models.py:29 msgid "Content" msgstr "内容" @@ -1268,29 +480,40 @@ msgstr "内容" msgid "One line one command" msgstr "每行一个命令" +#: assets/models/cmd_filter.py:55 audits/models.py:39 +#: authentication/templates/authentication/_access_key_modal.html:34 +#: perms/forms/asset_permission.py:20 tickets/serializers/ticket.py:26 +#: users/templates/users/_granted_assets.html:29 +#: users/templates/users/user_asset_permission.html:44 +#: users/templates/users/user_asset_permission.html:79 +#: users/templates/users/user_database_app_permission.html:42 +#: users/templates/users/user_group_list.html:17 +#: users/templates/users/user_list.html:20 +#: users/templates/users/user_remote_app_permission.html:42 +msgid "Action" +msgstr "动作" + #: assets/models/cmd_filter.py:63 msgid "Command filter rule" msgstr "命令过滤规则" -#: assets/models/domain.py:61 assets/templates/assets/domain_detail.html:16 -#: assets/templates/assets/domain_detail.html:59 -#: assets/templates/assets/domain_gateway_list.html:21 -#: assets/templates/assets/domain_list.html:23 +#: assets/models/domain.py:61 msgid "Gateway" msgstr "网关" +#: assets/models/domain.py:67 +msgid "Password should not contain special characters" +msgstr "不能包含特殊字符" + #: assets/models/gathered_user.py:16 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:28 msgid "Present" msgstr "存在" #: assets/models/gathered_user.py:17 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:26 msgid "Date last login" msgstr "最后登录日期" #: assets/models/gathered_user.py:18 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:27 msgid "IP last login" msgstr "最后登录IP" @@ -1306,34 +529,14 @@ msgstr "资产组" msgid "Default asset group" msgstr "默认资产组" -#: assets/models/label.py:15 assets/templates/assets/system_user_users.html:63 -#: audits/models.py:18 audits/models.py:38 audits/models.py:51 -#: audits/serializers.py:69 audits/templates/audits/ftp_log_list.html:37 -#: audits/templates/audits/ftp_log_list.html:74 -#: audits/templates/audits/operate_log_list.html:37 -#: audits/templates/audits/password_change_log_list.html:37 -#: audits/templates/audits/password_change_log_list.html:54 -#: authentication/models.py:43 ops/templates/ops/command_execution_list.html:41 -#: ops/templates/ops/command_execution_list.html:66 +#: assets/models/label.py:15 audits/models.py:18 audits/models.py:38 +#: audits/models.py:51 audits/serializers.py:76 authentication/models.py:43 #: perms/forms/asset_permission.py:83 perms/forms/database_app_permission.py:38 #: perms/forms/remote_app_permission.py:40 perms/models/base.py:49 -#: perms/templates/perms/asset_permission_create_update.html:52 -#: perms/templates/perms/asset_permission_list.html:33 -#: perms/templates/perms/asset_permission_list.html:81 -#: perms/templates/perms/database_app_permission_create_update.html:41 -#: perms/templates/perms/database_app_permission_list.html:15 -#: perms/templates/perms/remote_app_permission_create_update.html:41 -#: perms/templates/perms/remote_app_permission_list.html:15 #: templates/index.html:78 terminal/backends/command/models.py:18 -#: terminal/models.py:185 terminal/templates/terminal/command_list.html:30 -#: terminal/templates/terminal/command_list.html:105 -#: terminal/templates/terminal/session_detail.html:48 -#: terminal/templates/terminal/session_list.html:25 -#: terminal/templates/terminal/session_list.html:69 tickets/models/ticket.py:33 -#: tickets/models/ticket.py:128 tickets/templates/tickets/ticket_detail.html:32 -#: tickets/templates/tickets/ticket_list.html:34 -#: tickets/templates/tickets/ticket_list.html:103 users/forms/group.py:15 -#: users/models/user.py:155 users/models/user.py:171 users/models/user.py:610 +#: terminal/models.py:185 tickets/models/ticket.py:33 +#: tickets/models/ticket.py:128 users/forms/group.py:15 +#: users/models/user.py:159 users/models/user.py:175 users/models/user.py:614 #: users/serializers/group.py:20 #: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:64 @@ -1344,14 +547,10 @@ msgstr "默认资产组" #: users/templates/users/user_list.html:135 #: users/templates/users/user_remote_app_permission.html:37 #: users/templates/users/user_remote_app_permission.html:58 -#: users/views/profile/base.py:46 xpack/plugins/orgs/forms.py:27 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:108 -#: xpack/plugins/orgs/templates/orgs/org_list.html:15 msgid "User" msgstr "用户" -#: assets/models/label.py:19 assets/models/node.py:488 -#: assets/templates/assets/label_list.html:15 settings/models.py:28 +#: assets/models/label.py:19 assets/models/node.py:488 settings/models.py:28 msgid "Value" msgstr "值" @@ -1375,10 +574,20 @@ msgstr "空" msgid "favorite" msgstr "收藏夹" -#: assets/models/node.py:487 assets/templates/assets/_node_detail_modal.html:39 +#: assets/models/node.py:487 msgid "Key" msgstr "键" +#: assets/models/node.py:497 assets/serializers/system_user.py:43 +#: assets/serializers/system_user.py:175 perms/forms/asset_permission.py:92 +#: perms/forms/asset_permission.py:99 +#: users/templates/users/user_asset_permission.html:41 +#: users/templates/users/user_asset_permission.html:73 +#: users/templates/users/user_asset_permission.html:158 +#: xpack/plugins/cloud/models.py:129 +msgid "Node" +msgstr "节点" + #: assets/models/user.py:105 msgid "Automatic login" msgstr "自动登录" @@ -1387,38 +596,17 @@ msgstr "自动登录" msgid "Manually login" msgstr "手动登录" -#: assets/models/user.py:110 -#: assets/templates/assets/_asset_group_bulk_update_modal.html:11 -#: assets/templates/assets/system_user_assets.html:94 -#: assets/views/admin_user.py:30 assets/views/admin_user.py:49 -#: assets/views/admin_user.py:67 assets/views/admin_user.py:84 -#: assets/views/admin_user.py:108 assets/views/asset.py:37 -#: assets/views/asset.py:54 assets/views/asset.py:103 assets/views/asset.py:130 -#: assets/views/asset.py:170 assets/views/asset.py:199 -#: assets/views/cmd_filter.py:31 assets/views/cmd_filter.py:48 -#: assets/views/cmd_filter.py:66 assets/views/cmd_filter.py:84 -#: assets/views/cmd_filter.py:104 assets/views/cmd_filter.py:138 -#: assets/views/cmd_filter.py:173 assets/views/domain.py:31 -#: assets/views/domain.py:48 assets/views/domain.py:66 -#: assets/views/domain.py:81 assets/views/domain.py:107 -#: assets/views/domain.py:136 assets/views/domain.py:157 -#: assets/views/label.py:27 assets/views/label.py:45 assets/views/label.py:73 -#: assets/views/platform.py:22 assets/views/platform.py:38 -#: assets/views/platform.py:58 assets/views/platform.py:74 -#: assets/views/system_user.py:30 assets/views/system_user.py:47 -#: assets/views/system_user.py:64 assets/views/system_user.py:80 -#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:51 +#: assets/models/user.py:108 +msgid "Username same with user" +msgstr "用户名与用户相同" + +#: assets/models/user.py:110 templates/_nav.html:39 +#: xpack/plugins/change_auth_plan/models.py:51 msgid "Assets" msgstr "资产管理" -#: assets/models/user.py:111 assets/templates/assets/system_user_users.html:76 -#: templates/_nav.html:17 users/views/group.py:28 users/views/group.py:45 -#: users/views/group.py:63 users/views/group.py:82 users/views/group.py:99 -#: users/views/login.py:163 users/views/profile/password.py:40 -#: users/views/profile/pubkey.py:36 users/views/user.py:50 -#: users/views/user.py:67 users/views/user.py:111 users/views/user.py:178 -#: users/views/user.py:206 users/views/user.py:220 users/views/user.py:234 -#: users/views/user.py:248 users/views/user.py:262 users/views/user.py:276 +#: assets/models/user.py:111 templates/_nav.html:17 +#: users/views/profile/password.py:40 users/views/profile/pubkey.py:36 msgid "Users" msgstr "用户管理" @@ -1427,22 +615,19 @@ msgstr "用户管理" msgid "User groups" msgstr "用户组" -#: assets/models/user.py:115 assets/templates/assets/_system_user.html:56 -#: assets/templates/assets/system_user_detail.html:130 -#: assets/templates/assets/system_user_update.html:10 +#: assets/models/user.py:115 msgid "Auto push" msgstr "自动推送" -#: assets/models/user.py:116 assets/templates/assets/system_user_detail.html:82 +#: assets/models/user.py:116 msgid "Sudo" msgstr "Sudo" -#: assets/models/user.py:117 assets/templates/assets/system_user_detail.html:87 +#: assets/models/user.py:117 msgid "Shell" msgstr "Shell" -#: assets/models/user.py:118 assets/templates/assets/system_user_detail.html:74 -#: assets/templates/assets/system_user_list.html:27 +#: assets/models/user.py:118 msgid "Login mode" msgstr "登录模式" @@ -1450,27 +635,12 @@ msgstr "登录模式" msgid "SFTP Root" msgstr "SFTP根路径" -#: assets/models/user.py:195 assets/templates/assets/system_user_list.html:73 -#: audits/models.py:21 audits/templates/audits/ftp_log_list.html:53 -#: audits/templates/audits/ftp_log_list.html:76 +#: assets/models/user.py:195 audits/models.py:21 #: perms/forms/asset_permission.py:95 perms/forms/remote_app_permission.py:49 #: perms/models/asset_permission.py:82 #: perms/models/database_app_permission.py:22 -#: perms/models/remote_app_permission.py:16 -#: perms/templates/perms/asset_permission_asset.html:124 -#: perms/templates/perms/asset_permission_list.html:37 -#: perms/templates/perms/asset_permission_list.html:93 -#: perms/templates/perms/asset_permission_list.html:190 -#: perms/templates/perms/database_app_permission_database_app.html:94 -#: perms/templates/perms/database_app_permission_list.html:18 -#: perms/templates/perms/remote_app_permission_detail.html:126 -#: perms/templates/perms/remote_app_permission_list.html:18 -#: templates/_nav.html:45 terminal/backends/command/models.py:20 -#: terminal/models.py:189 terminal/templates/terminal/command_list.html:32 -#: terminal/templates/terminal/command_list.html:107 -#: terminal/templates/terminal/session_detail.html:56 -#: terminal/templates/terminal/session_list.html:27 -#: terminal/templates/terminal/session_list.html:71 +#: perms/models/remote_app_permission.py:16 templates/_nav.html:45 +#: terminal/backends/command/models.py:20 terminal/models.py:189 #: users/templates/users/_granted_assets.html:27 #: users/templates/users/user_asset_permission.html:42 #: users/templates/users/user_asset_permission.html:76 @@ -1479,7 +649,6 @@ msgstr "SFTP根路径" #: users/templates/users/user_database_app_permission.html:67 #: users/templates/users/user_remote_app_permission.html:40 #: users/templates/users/user_remote_app_permission.html:67 -#: xpack/plugins/orgs/templates/orgs/org_list.html:20 msgid "System user" msgstr "系统用户" @@ -1493,7 +662,6 @@ msgid "Unreachable" msgstr "不可达" #: assets/models/utils.py:44 assets/tasks/const.py:50 -#: assets/templates/assets/asset_list.html:27 msgid "Reachable" msgstr "可连接" @@ -1522,15 +690,7 @@ msgid "Connectivity" msgstr "连接" #: assets/serializers/asset_user.py:44 -#: assets/templates/assets/_node_detail_modal.html:18 -#: audits/templates/audits/login_log_list.html:56 #: authentication/templates/authentication/_access_key_modal.html:30 -#: ops/templates/ops/adhoc_detail.html:47 -#: ops/templates/ops/adhoc_history_detail.html:47 -#: ops/templates/ops/task_detail.html:54 -#: terminal/templates/terminal/session_list.html:24 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:45 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:44 msgid "ID" msgstr "ID" @@ -1539,14 +699,17 @@ msgid "Backend" msgstr "后端" #: assets/serializers/asset_user.py:75 users/forms/profile.py:148 -#: users/models/user.py:492 users/templates/users/first_login.html:42 -#: users/templates/users/user_password_update.html:49 +#: users/models/user.py:496 users/templates/users/user_password_update.html:48 #: users/templates/users/user_profile.html:69 #: users/templates/users/user_profile_update.html:46 #: users/templates/users/user_pubkey_update.html:46 msgid "Public key" msgstr "SSH公钥" +#: assets/serializers/asset_user.py:79 users/models/user.py:493 +msgid "Private key" +msgstr "ssh私钥" + #: assets/serializers/base.py:45 msgid "" "Not support openssh format key, using ssh-keygen -t rsa -m pem to generate" @@ -1692,852 +855,78 @@ msgstr "为了安全,禁止推送用户 {}" msgid "No assets matched, stop task" msgstr "没有匹配到资产,结束任务" -#: assets/templates/assets/_asset_group_bulk_update_modal.html:5 -msgid "Update asset group" -msgstr "更新用户组" - -#: assets/templates/assets/_asset_group_bulk_update_modal.html:8 -msgid "Hint: only change the field you want to update." -msgstr "仅修改你需要更新的字段" - -#: assets/templates/assets/_asset_group_bulk_update_modal.html:13 -msgid "Select Asset" -msgstr "选择资产" - -#: assets/templates/assets/_asset_group_bulk_update_modal.html:21 -#: assets/templates/assets/cmd_filter_detail.html:84 -#: assets/templates/assets/cmd_filter_list.html:24 -#: perms/forms/database_app_permission.py:47 -msgid "System users" -msgstr "系统用户" - -#: assets/templates/assets/_asset_group_bulk_update_modal.html:23 -msgid "Select System Users" -msgstr "选择系统用户" - -#: assets/templates/assets/_asset_group_bulk_update_modal.html:34 -msgid "Enable-MFA" -msgstr "启用多因子认证" - -#: assets/templates/assets/_asset_list_modal.html:7 -#: assets/templates/assets/system_user_assets.html:26 -#: assets/templates/assets/system_user_detail.html:18 -#: assets/templates/assets/system_user_users.html:25 assets/views/asset.py:38 -#: templates/_nav.html:42 xpack/plugins/change_auth_plan/views.py:118 -msgid "Asset list" -msgstr "资产列表" - -#: assets/templates/assets/_asset_list_modal.html:33 -#: assets/templates/assets/_node_tree.html:39 -#: ops/templates/ops/command_execution_create.html:62 -#: ops/templates/ops/command_execution_create.html:112 -#: settings/templates/settings/_ldap_list_users_modal.html:41 -#: users/templates/users/_granted_assets.html:7 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:65 -msgid "Loading" -msgstr "加载中" - -#: assets/templates/assets/_asset_user_auth_update_modal.html:4 -msgid "Update asset user auth" -msgstr "更新资产用户认证信息" - -#: assets/templates/assets/_asset_user_auth_update_modal.html:23 -#: settings/templates/settings/_ldap_test_user_login_modal.html:18 -#: xpack/plugins/change_auth_plan/forms.py:61 -msgid "Please input password" -msgstr "请输入密码" - -#: assets/templates/assets/_asset_user_auth_update_modal.html:68 -#: assets/templates/assets/asset_detail.html:300 -#: users/templates/users/user_detail.html:356 -#: users/templates/users/user_detail.html:383 -#: xpack/plugins/interface/views.py:35 -msgid "Update successfully!" -msgstr "更新成功" - -#: assets/templates/assets/_asset_user_auth_view_modal.html:5 -msgid "Asset user auth" -msgstr "资产用户信息" - -#: assets/templates/assets/_asset_user_auth_view_modal.html:54 -#: assets/templates/assets/_node_detail_modal.html:56 -#: authentication/templates/authentication/login_wait_confirm.html:114 -msgid "Copy success" -msgstr "复制成功" - -#: assets/templates/assets/_asset_user_auth_view_modal.html:68 -msgid "Get auth info error" -msgstr "获取认证信息错误" - -#: assets/templates/assets/_asset_user_auth_view_modal.html:101 -#: assets/templates/assets/_node_detail_modal.html:67 -#: assets/templates/assets/_user_asset_detail_modal.html:23 -#: authentication/templates/authentication/_access_key_modal.html:155 -#: authentication/templates/authentication/_mfa_confirm_modal.html:53 -#: settings/templates/settings/_ldap_list_users_modal.html:171 -#: templates/_modal.html:22 tickets/models/ticket.py:68 -#: tickets/templates/tickets/ticket_detail.html:103 -msgid "Close" -msgstr "关闭" - -#: assets/templates/assets/_asset_user_list.html:24 audits/models.py:43 -#: audits/models.py:54 audits/templates/audits/operate_log_list.html:75 -#: audits/templates/audits/password_change_log_list.html:57 -#: ops/templates/ops/task_adhoc.html:61 -#: terminal/templates/terminal/command_list.html:34 -#: terminal/templates/terminal/session_commands.html:51 -#: tickets/templates/tickets/ticket_list.html:37 -msgid "Datetime" -msgstr "日期" - -#: assets/templates/assets/_asset_user_list.html:41 -#: assets/templates/assets/asset_list.html:59 -msgid "Test datetime: " -msgstr "测试日期: " - -#: assets/templates/assets/_asset_user_list.html:44 -msgid "Only latest version" -msgstr "仅最新版本" - -#: assets/templates/assets/_asset_user_list.html:66 -msgid "View" -msgstr "查看" - -#: assets/templates/assets/_asset_user_list.html:68 -#: assets/templates/assets/admin_user_assets.html:56 -#: assets/templates/assets/asset_asset_user_list.html:57 -#: assets/templates/assets/asset_detail.html:174 -#: assets/templates/assets/system_user_assets.html:74 -#: terminal/templates/terminal/base_storage_list.html:72 -msgid "Test" -msgstr "测试" - -#: assets/templates/assets/_asset_user_list.html:69 -#: assets/templates/assets/system_user_assets.html:83 -msgid "Push" -msgstr "推送" - -#: assets/templates/assets/_asset_user_list.html:167 -#: authentication/templates/authentication/_access_key_modal.html:147 -msgid "Delete success" -msgstr "删除成功" - -#: assets/templates/assets/_gateway_test_modal.html:4 -msgid "Test gateway test connection" -msgstr "测试连接网关" - -#: assets/templates/assets/_gateway_test_modal.html:10 terminal/models.py:28 -msgid "SSH Port" -msgstr "SSH端口" - -#: assets/templates/assets/_gateway_test_modal.html:13 -msgid "If use nat, set the ssh real port" -msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口" - -#: assets/templates/assets/_node_detail_modal.html:11 -#: assets/templates/assets/asset_list.html:124 -msgid "Node detail" -msgstr "节点详情" - -#: assets/templates/assets/_node_detail_modal.html:33 -msgid "Full name" -msgstr "全称" - -#: assets/templates/assets/_node_tree.html:49 -msgid "Add node" -msgstr "新建节点" - -#: assets/templates/assets/_node_tree.html:50 -msgid "Rename node" -msgstr "重命名节点" - -#: assets/templates/assets/_node_tree.html:51 -msgid "Delete node" -msgstr "删除节点" - -#: assets/templates/assets/_node_tree.html:166 -msgid "Create node failed" -msgstr "创建节点失败" - -#: assets/templates/assets/_node_tree.html:250 -msgid "Rename success" -msgstr "重命名成功" - -#: assets/templates/assets/_system_user.html:33 -#: assets/templates/assets/asset_create.html:16 -#: assets/templates/assets/gateway_create_update.html:33 -#: perms/templates/perms/asset_permission_create_update.html:48 -#: perms/templates/perms/database_app_permission_create_update.html:37 -#: perms/templates/perms/remote_app_permission_create_update.html:37 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:37 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:23 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:23 -msgid "Basic" -msgstr "基本" - -#: assets/templates/assets/_system_user.html:41 -#: assets/templates/assets/asset_create.html:38 -#: assets/templates/assets/gateway_create_update.html:41 -#: users/templates/users/_user.html:21 -msgid "Auth" -msgstr "认证" - -#: assets/templates/assets/_system_user.html:45 -msgid "Auto generate key" -msgstr "自动生成密钥" - -#: assets/templates/assets/_system_user.html:66 -#: assets/templates/assets/asset_create.html:74 -#: assets/templates/assets/gateway_create_update.html:49 -#: perms/templates/perms/asset_permission_create_update.html:97 -#: perms/templates/perms/database_app_permission_create_update.html:51 -#: perms/templates/perms/remote_app_permission_create_update.html:51 -#: terminal/templates/terminal/terminal_update.html:38 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:61 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:47 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:35 -msgid "Other" -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:18 assets/views/asset.py:200 -msgid "Asset detail" -msgstr "资产详情" - -#: assets/templates/assets/admin_user_assets.html:16 -#: assets/templates/assets/admin_user_detail.html:16 -msgid "Assets list" -msgstr "资产列表" - -#: assets/templates/assets/admin_user_assets.html:24 -#: perms/templates/perms/asset_permission_asset.html:31 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:27 -msgid "Asset list of " -msgstr "资产列表" - -#: assets/templates/assets/admin_user_assets.html:47 -#: assets/templates/assets/system_user_assets.html:65 -#: assets/templates/assets/system_user_detail.html:124 -#: perms/templates/perms/asset_permission_detail.html:109 -#: perms/templates/perms/database_app_permission_detail.html:105 -#: perms/templates/perms/remote_app_permission_detail.html:101 -msgid "Quick update" -msgstr "快速更新" - -#: assets/templates/assets/admin_user_assets.html:53 -#: assets/templates/assets/asset_asset_user_list.html:54 -#: assets/templates/assets/asset_detail.html:171 -msgid "Test connective" -msgstr "测试可连接性" - -#: assets/templates/assets/admin_user_detail.html:78 -msgid "Replace node assets admin user with this" -msgstr "替换资产的管理员" - -#: assets/templates/assets/admin_user_detail.html:86 -#: assets/templates/assets/system_user_assets.html:126 -#: perms/templates/perms/asset_permission_asset.html:99 -#: xpack/plugins/change_auth_plan/forms.py:69 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:95 -#: xpack/plugins/gathered_user/forms.py:33 -msgid "Select nodes" -msgstr "选择节点" - -#: assets/templates/assets/admin_user_detail.html:95 -#: assets/templates/assets/asset_detail.html:200 -#: assets/templates/assets/asset_list.html:258 -#: assets/templates/assets/cmd_filter_detail.html:101 -#: assets/templates/assets/system_user_assets.html:108 -#: assets/templates/assets/system_user_assets.html:132 -#: assets/templates/assets/system_user_detail.html:172 -#: assets/templates/assets/system_user_users.html:90 -#: authentication/templates/authentication/_mfa_confirm_modal.html:20 -#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:122 -#: users/templates/users/user_detail.html:264 -#: users/templates/users/user_detail.html:417 -#: users/templates/users/user_detail.html:443 -#: users/templates/users/user_detail.html:466 -#: users/templates/users/user_detail.html:511 -#: users/templates/users/user_group_create_update.html:28 -#: users/templates/users/user_list.html:184 -#: users/templates/users/user_password_verify.html:20 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:30 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:53 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:41 -#: xpack/plugins/interface/templates/interface/interface.html:103 -#: xpack/plugins/orgs/templates/orgs/org_create_update.html:30 -msgid "Confirm" -msgstr "确认" - -#: assets/templates/assets/admin_user_list.html:4 -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:5 -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:13 -#: assets/views/admin_user.py:50 -msgid "Create admin user" -msgstr "创建管理用户" - -#: assets/templates/assets/asset_asset_user_list.html:16 -#: assets/templates/assets/asset_detail.html:21 assets/views/asset.py:55 -msgid "Asset user list" -msgstr "资产用户列表" - -#: assets/templates/assets/asset_asset_user_list.html:24 -msgid "Asset users of" -msgstr "资产用户" - -#: assets/templates/assets/asset_asset_user_list.html:47 -#: assets/templates/assets/asset_detail.html:140 -#: terminal/templates/terminal/session_detail.html:87 -#: users/templates/users/user_detail.html:126 -#: users/templates/users/user_profile.html:150 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:126 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:139 -#: xpack/plugins/license/templates/license/license_detail.html:80 -msgid "Quick modify" -msgstr "快速修改" - -#: assets/templates/assets/asset_bulk_update.html:8 -#: users/templates/users/user_bulk_update.html:8 -msgid "Select properties that need to be modified" -msgstr "选择需要修改属性" - -#: assets/templates/assets/asset_bulk_update.html:10 -#: users/templates/users/user_bulk_update.html:10 -msgid "Select all" -msgstr "全选" - -#: assets/templates/assets/asset_detail.html:88 -msgid "CPU" -msgstr "CPU" - -#: assets/templates/assets/asset_detail.html:96 -msgid "Disk" -msgstr "硬盘" - -#: assets/templates/assets/asset_detail.html:124 -#: users/templates/users/user_detail.html:101 -#: users/templates/users/user_profile.html:106 -msgid "Date joined" -msgstr "创建日期" - -#: assets/templates/assets/asset_detail.html:146 authentication/models.py:19 -#: authentication/templates/authentication/_access_key_modal.html:32 -#: perms/models/base.py:51 -#: perms/templates/perms/asset_permission_create_update.html:99 -#: perms/templates/perms/asset_permission_detail.html:115 -#: perms/templates/perms/database_app_permission_create_update.html:53 -#: perms/templates/perms/database_app_permission_detail.html:111 -#: perms/templates/perms/remote_app_permission_create_update.html:53 -#: perms/templates/perms/remote_app_permission_detail.html:107 -#: terminal/templates/terminal/terminal_list.html:35 -#: users/templates/users/_select_user_modal.html:18 -#: users/templates/users/user_detail.html:132 -#: users/templates/users/user_profile.html:63 -msgid "Active" -msgstr "激活中" - -#: assets/templates/assets/asset_detail.html:163 -msgid "Refresh hardware" -msgstr "更新硬件信息" - -#: assets/templates/assets/asset_detail.html:166 -#: authentication/templates/authentication/login_wait_confirm.html:42 -msgid "Refresh" -msgstr "刷新" - -#: assets/templates/assets/asset_list.html:6 -msgid "" -"The left side is the asset tree, right click to create, delete, and change " -"the tree node, authorization asset is also organized as a node, and the " -"right side is the asset under that node" -msgstr "" -"左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的," -"右侧是属于该节点下的资产" - -#: assets/templates/assets/asset_list.html:10 assets/views/asset.py:104 -msgid "Create asset" -msgstr "创建资产" - -#: assets/templates/assets/asset_list.html:26 -msgid "Hardware" -msgstr "硬件" - -#: assets/templates/assets/asset_list.html:37 -#: users/templates/users/user_list.html:30 -msgid "Delete selected" -msgstr "批量删除" - -#: assets/templates/assets/asset_list.html:38 -#: users/templates/users/user_list.html:34 -msgid "Update selected" -msgstr "批量更新" - -#: assets/templates/assets/asset_list.html:39 -msgid "Remove from this node" -msgstr "从节点移除" - -#: assets/templates/assets/asset_list.html:40 -#: users/templates/users/user_list.html:35 -msgid "Deactive selected" -msgstr "禁用所选" - -#: assets/templates/assets/asset_list.html:41 -#: users/templates/users/user_list.html:36 -msgid "Active selected" -msgstr "激活所选" - -#: assets/templates/assets/asset_list.html:115 -msgid "Add assets to node" -msgstr "添加资产到节点" - -#: assets/templates/assets/asset_list.html:116 -msgid "Move assets to node" -msgstr "移动资产到节点" - -#: assets/templates/assets/asset_list.html:118 -msgid "Refresh node hardware info" -msgstr "更新节点资产硬件信息" - -#: assets/templates/assets/asset_list.html:119 -msgid "Test node connective" -msgstr "测试节点资产可连接性" - -#: assets/templates/assets/asset_list.html:121 -msgid "Display only current node assets" -msgstr "仅显示当前节点资产" - -#: assets/templates/assets/asset_list.html:122 -msgid "Displays all child node assets" -msgstr "显示所有子节点资产" - -#: assets/templates/assets/asset_list.html:252 -#: users/templates/users/user_detail.html:411 -#: users/templates/users/user_detail.html:437 -#: users/templates/users/user_detail.html:505 -#: users/templates/users/user_list.html:178 -#: xpack/plugins/interface/templates/interface/interface.html:97 -msgid "Are you sure?" -msgstr "你确认吗?" - -#: assets/templates/assets/asset_list.html:253 -msgid "This will delete the selected assets !!!" -msgstr "删除选择资产" - -#: assets/templates/assets/asset_list.html:256 -#: users/templates/users/user_detail.html:415 -#: users/templates/users/user_detail.html:441 -#: users/templates/users/user_detail.html:509 -#: users/templates/users/user_list.html:182 -#: xpack/plugins/interface/templates/interface/interface.html:101 -msgid "Cancel" -msgstr "取消" - -#: assets/templates/assets/asset_list.html:262 -msgid "Asset Deleting failed." -msgstr "删除失败" - -#: assets/templates/assets/asset_list.html:263 -#: assets/templates/assets/asset_list.html:272 -msgid "Asset Delete" -msgstr "删除" - -#: assets/templates/assets/asset_list.html:271 -msgid "Asset Deleted." -msgstr "已被删除" - -#: assets/templates/assets/asset_list.html:310 -msgid "Please select node" -msgstr "请选择节点" - -#: assets/templates/assets/asset_update.html:7 -msgid "Configuration" -msgstr "配置" - -#: assets/templates/assets/cmd_filter_detail.html:20 -#: assets/templates/assets/cmd_filter_list.html:23 -#: assets/templates/assets/cmd_filter_rule_list.html:18 -msgid "Rules" -msgstr "规则" - -#: assets/templates/assets/cmd_filter_detail.html:92 -msgid "Binding to system user" -msgstr "绑定到系统用户" - -#: assets/templates/assets/cmd_filter_list.html:5 -msgid "" -"System user bound some command filter, each command filter has some rules," -msgstr "系统用户可以绑定一些命令过滤器,一个过滤器可以定义一些规则" - -#: assets/templates/assets/cmd_filter_list.html:6 -msgid "When user login asset with this system user, then run a command," -msgstr "当用户使用这个系统用户登录资产,然后执行一个命令" - -#: assets/templates/assets/cmd_filter_list.html:7 -msgid "The command will be filter by rules, higher priority rule run first," -msgstr "这个命令需要被绑定过滤器的所有规则匹配,高优先级先被匹配," - -#: assets/templates/assets/cmd_filter_list.html:8 -msgid "" -"When a rule matched, if rule action is allow, then allow command execute," -msgstr "当一个规则匹配到了,如果规则的动作是允许,这个命令会被放行," - -#: assets/templates/assets/cmd_filter_list.html:9 -msgid "else if action is deny, then command with be deny," -msgstr "如果规则的动作是禁止,命令将会被禁止执行," - -#: assets/templates/assets/cmd_filter_list.html:10 -msgid "else match next rule, if none matched, allowed" -msgstr "否则就匹配下一个规则,如果最后没有匹配到规则,则允许执行" - -#: assets/templates/assets/cmd_filter_list.html:14 -#: assets/views/cmd_filter.py:49 -msgid "Create command filter" -msgstr "创建命令过滤器" - -#: assets/templates/assets/cmd_filter_rule_list.html:28 -#: assets/views/cmd_filter.py:105 -msgid "Command filter rule list" -msgstr "命令过滤器规则列表" - -#: assets/templates/assets/cmd_filter_rule_list.html:45 -msgid "Create rule" -msgstr "创建规则" - -#: assets/templates/assets/cmd_filter_rule_list.html:56 -msgid "Strategy" -msgstr "策略" - -#: assets/templates/assets/delete_confirm.html:6 -#: perms/templates/perms/delete_confirm.html:6 templates/delete_confirm.html:6 -msgid "Confirm delete" -msgstr "确认删除" - -#: assets/templates/assets/delete_confirm.html:11 -#: templates/delete_confirm.html:11 -msgid "Are you sure delete" -msgstr "您确定删除吗?" - -#: assets/templates/assets/domain_gateway_list.html:32 -msgid "Gateway list" -msgstr "网关列表" - -#: assets/templates/assets/domain_gateway_list.html:51 -#: assets/views/domain.py:137 -msgid "Create gateway" -msgstr "创建网关" - -#: assets/templates/assets/domain_gateway_list.html:94 -#: assets/templates/assets/domain_gateway_list.html:96 -#: settings/templates/settings/email_setting.html:45 -#: settings/templates/settings/ldap_setting.html:46 -msgid "Test connection" -msgstr "测试连接" - -#: assets/templates/assets/domain_gateway_list.html:136 -msgid "Can be connected" -msgstr "可连接" - -#: assets/templates/assets/domain_list.html:6 -msgid "" -"The domain function is added to address the fact that some environments " -"(such as the hybrid cloud) cannot be connected directly by jumping on the " -"gateway server." -msgstr "" -"网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过" -"网关服务器进行跳转登录。" - -#: assets/templates/assets/domain_list.html:8 -msgid "JMS => Domain gateway => Target assets" -msgstr "JMS => 网域网关 => 目标资产" - -#: assets/templates/assets/domain_list.html:13 assets/views/domain.py:49 -msgid "Create domain" -msgstr "创建网域" - -#: assets/templates/assets/label_list.html:6 assets/views/label.py:46 -msgid "Create label" -msgstr "创建标签" - -#: assets/templates/assets/platform_list.html:8 assets/views/platform.py:39 -msgid "Create platform" -msgstr "创建系统平台" - -#: assets/templates/assets/system_user_assets.html:32 -#: assets/templates/assets/system_user_detail.html:25 -#: assets/templates/assets/system_user_users.html:31 templates/_nav.html:20 -#: users/views/user.py:51 -msgid "User list" -msgstr "用户列表" - -#: assets/templates/assets/system_user_assets.html:71 -msgid "Test assets connective" -msgstr "测试资产可连接性" - -#: assets/templates/assets/system_user_assets.html:80 -msgid "Push system user now" -msgstr "立刻推送系统" - -#: assets/templates/assets/system_user_assets.html:279 -#: assets/templates/assets/system_user_users.html:205 -msgid "Have existed: " -msgstr "已经存在: " - -#: assets/templates/assets/system_user_detail.html:93 -msgid "Home" -msgstr "家目录" - -#: assets/templates/assets/system_user_detail.html:99 -msgid "Uid" -msgstr "Uid" - -#: assets/templates/assets/system_user_detail.html:163 -msgid "Binding command filters" -msgstr "绑定命令过滤器" - -#: assets/templates/assets/system_user_list.html:5 -msgid "" -"System user is JumpServer jump login assets used by the users, can be " -"understood as the user login assets, such as web, sa, the dba (` ssh " -"web@some-host `), rather than using a user the username login server jump (` " -"ssh xiaoming@some-host `); " -msgstr "" -"系统用户是 JumpServer 跳转登录资产时使用的用户,可以理解为登录资产用户,如 " -"web,sa,dba(`ssh web@some-host`),而不是使用某个用户的用户名跳转登录服务器" -"(`ssh xiaoming@some-host`);" - -#: assets/templates/assets/system_user_list.html:6 -msgid "" -"In simple terms, users log into JumpServer using their own username, and " -"JumpServer uses system users to log into assets. " -msgstr "" -"简单来说是用户使用自己的用户名登录 JumpServer,JumpServer 使用系统用户登录资" -"产。" - -#: assets/templates/assets/system_user_list.html:7 -msgid "" -"When system users are created, if you choose auto push JumpServer to use " -"Ansible push system users into the asset, if the asset (Switch) does not " -"support ansible, please manually fill in the account password." -msgstr "" -"系统用户创建时,如果选择了自动推送,JumpServer 会使用 Ansible 自动推送系统用" -"户到资产中,如果资产(交换机)不支持 Ansible,请手动填写账号密码。" - -#: assets/templates/assets/system_user_list.html:16 -#: assets/views/system_user.py:48 -msgid "Create system user" -msgstr "创建系统用户" - -#: assets/templates/assets/system_user_users.html:84 users/forms/group.py:19 -#: users/forms/user.py:143 users/forms/user.py:148 -#: xpack/plugins/orgs/forms.py:17 -msgid "Select users" -msgstr "选择用户" - -#: assets/templates/assets/system_user_users.html:118 -#: users/templates/users/user_list.html:106 -#: users/templates/users/user_list.html:110 -msgid "Remove" -msgstr "移除" - -#: assets/templates/assets/system_user_users.html:176 -msgid "Remove success" -msgstr "移除成功" - -#: assets/views/admin_user.py:31 -msgid "Admin user list" -msgstr "管理用户列表" - -#: assets/views/admin_user.py:68 -msgid "Update admin user" -msgstr "更新管理用户" - -#: assets/views/admin_user.py:85 -msgid "Admin user detail" -msgstr "管理用户详情" - -#: assets/views/admin_user.py:109 -msgid "Admin user assets" -msgstr "管理用户关联资产" - -#: assets/views/asset.py:67 templates/_nav_user.html:4 -msgid "My assets" -msgstr "我的资产" - -#: assets/views/asset.py:131 -msgid "Update asset" -msgstr "更新资产" - -#: assets/views/asset.py:143 -msgid "Bulk update asset success" -msgstr "批量更新资产成功" - -#: assets/views/asset.py:171 -msgid "Bulk update asset" -msgstr "批量更新资产" - -#: assets/views/cmd_filter.py:32 -msgid "Command filter list" -msgstr "命令过滤器列表" - -#: assets/views/cmd_filter.py:67 -msgid "Update command filter" -msgstr "更新命令过滤器" - -#: assets/views/cmd_filter.py:85 -msgid "Command filter detail" -msgstr "命令过滤器详情" - -#: assets/views/cmd_filter.py:139 -msgid "Create command filter rule" -msgstr "创建命令过滤器规则" - -#: assets/views/cmd_filter.py:174 -msgid "Update command filter rule" -msgstr "更新命令过滤器规则" - -#: assets/views/domain.py:32 templates/_nav.html:43 -msgid "Domain list" -msgstr "网域列表" - -#: assets/views/domain.py:67 -msgid "Update domain" -msgstr "更新网域" - -#: assets/views/domain.py:82 -msgid "Domain detail" -msgstr "网域详情" - -#: assets/views/domain.py:108 -msgid "Domain gateway list" -msgstr "域网关列表" - -#: assets/views/domain.py:158 -msgid "Update gateway" -msgstr "创建网关" - -#: assets/views/label.py:28 -msgid "Label list" -msgstr "标签列表" - -#: assets/views/label.py:56 -msgid "Tips: Avoid using label names reserved internally: {}" -msgstr "提示: 请避免使用内部预留标签名: {}" - -#: assets/views/label.py:74 -msgid "Update label" -msgstr "更新标签" - -#: assets/views/platform.py:23 templates/_nav.html:49 -msgid "Platform list" -msgstr "平台列表" - -#: assets/views/platform.py:59 -msgid "Update platform" -msgstr "更新系统平台" - -#: assets/views/platform.py:75 -msgid "Platform detail" -msgstr "平台详情" - -#: assets/views/system_user.py:31 -msgid "System user list" -msgstr "系统用户列表" - -#: assets/views/system_user.py:65 -msgid "Update system user" -msgstr "更新系统用户" - -#: assets/views/system_user.py:81 -msgid "System user detail" -msgstr "系统用户详情" - -#: assets/views/system_user.py:103 assets/views/system_user.py:118 -msgid "assets" -msgstr "资产管理" - -#: assets/views/system_user.py:104 -msgid "System user assets" -msgstr "系统用户关联资产" - -#: assets/views/system_user.py:119 -msgid "System user users" -msgstr "系统用户关联用户" - #: audits/models.py:19 audits/models.py:42 audits/models.py:53 -#: audits/templates/audits/ftp_log_list.html:77 -#: audits/templates/audits/operate_log_list.html:74 -#: audits/templates/audits/password_change_log_list.html:56 -#: terminal/models.py:192 terminal/templates/terminal/session_detail.html:68 -#: terminal/templates/terminal/session_list.html:28 -#: terminal/templates/terminal/session_list.html:72 -#: terminal/templates/terminal/terminal_detail.html:47 +#: terminal/models.py:192 msgid "Remote addr" msgstr "远端地址" -#: audits/models.py:22 audits/templates/audits/ftp_log_list.html:78 +#: audits/models.py:22 msgid "Operate" msgstr "操作" -#: audits/models.py:23 audits/templates/audits/ftp_log_list.html:60 -#: audits/templates/audits/ftp_log_list.html:79 +#: audits/models.py:23 msgid "Filename" msgstr "文件名" #: audits/models.py:24 audits/models.py:77 -#: audits/templates/audits/ftp_log_list.html:80 -#: ops/templates/ops/command_execution_list.html:71 -#: ops/templates/ops/task_list.html:14 #: users/templates/users/user_detail.html:487 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:14 msgid "Success" msgstr "成功" -#: audits/models.py:25 audits/templates/audits/ftp_log_list.html:81 -#: ops/models/command.py:28 ops/templates/ops/adhoc_history.html:50 -#: ops/templates/ops/adhoc_history_detail.html:59 -#: ops/templates/ops/command_execution_list.html:72 -#: ops/templates/ops/task_history.html:56 perms/models/base.py:52 -#: perms/templates/perms/asset_permission_detail.html:81 -#: perms/templates/perms/database_app_permission_detail.html:77 -#: perms/templates/perms/remote_app_permission_detail.html:73 -#: terminal/models.py:199 terminal/templates/terminal/session_detail.html:72 -#: terminal/templates/terminal/session_list.html:32 -#: xpack/plugins/change_auth_plan/models.py:176 -#: xpack/plugins/change_auth_plan/models.py:299 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17 +#: audits/models.py:25 ops/models/command.py:28 perms/models/base.py:52 +#: terminal/models.py:199 xpack/plugins/change_auth_plan/models.py:176 +#: xpack/plugins/change_auth_plan/models.py:307 #: xpack/plugins/gathered_user/models.py:76 msgid "Date start" msgstr "开始日期" #: audits/models.py:33 #: authentication/templates/authentication/_access_key_modal.html:22 -#: xpack/plugins/vault/templates/vault/vault.html:7 msgid "Create" msgstr "创建" -#: audits/models.py:40 audits/templates/audits/operate_log_list.html:53 -#: audits/templates/audits/operate_log_list.html:72 +#: audits/models.py:34 templates/_csv_import_export.html:18 +#: templates/_csv_update_modal.html:6 +#: users/templates/users/user_asset_permission.html:127 +#: users/templates/users/user_database_app_permission.html:110 +#: users/templates/users/user_detail.html:12 +#: users/templates/users/user_group_detail.html:23 +#: users/templates/users/user_group_list.html:51 +#: users/templates/users/user_list.html:84 +#: users/templates/users/user_list.html:87 +#: users/templates/users/user_profile.html:181 +#: users/templates/users/user_profile.html:191 +#: users/templates/users/user_profile.html:201 +#: users/templates/users/user_remote_app_permission.html:110 +msgid "Update" +msgstr "更新" + +#: audits/models.py:35 +#: authentication/templates/authentication/_access_key_modal.html:65 +#: users/templates/users/user_asset_permission.html:128 +#: users/templates/users/user_database_app_permission.html:111 +#: users/templates/users/user_detail.html:16 +#: users/templates/users/user_group_detail.html:27 +#: users/templates/users/user_group_list.html:53 +#: users/templates/users/user_list.html:94 +#: users/templates/users/user_list.html:98 +#: users/templates/users/user_remote_app_permission.html:111 +msgid "Delete" +msgstr "删除" + +#: audits/models.py:40 msgid "Resource Type" msgstr "资源类型" -#: audits/models.py:41 audits/templates/audits/operate_log_list.html:73 +#: audits/models.py:41 msgid "Resource" msgstr "资源" -#: audits/models.py:52 audits/templates/audits/password_change_log_list.html:55 +#: audits/models.py:43 audits/models.py:54 +msgid "Datetime" +msgstr "日期" + +#: audits/models.py:52 msgid "Change by" msgstr "修改者" @@ -2574,30 +963,22 @@ msgstr "登录城市" msgid "User agent" msgstr "Agent" -#: audits/models.py:86 audits/templates/audits/login_log_list.html:62 +#: audits/models.py:86 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: authentication/templates/authentication/login_otp.html:6 -#: settings/forms/security.py:16 users/forms/profile.py:52 -#: users/models/user.py:484 users/templates/users/first_login.html:45 -#: users/templates/users/user_detail.html:77 +#: users/forms/profile.py:52 users/models/user.py:488 +#: users/serializers/user.py:216 users/templates/users/user_detail.html:77 #: users/templates/users/user_profile.html:87 msgid "MFA" msgstr "多因子认证" -#: audits/models.py:87 audits/templates/audits/login_log_list.html:63 -#: xpack/plugins/change_auth_plan/models.py:295 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15 +#: audits/models.py:87 xpack/plugins/change_auth_plan/models.py:303 #: xpack/plugins/cloud/models.py:217 msgid "Reason" msgstr "原因" -#: audits/models.py:88 audits/templates/audits/login_log_list.html:64 -#: tickets/templates/tickets/ticket_detail.html:34 -#: tickets/templates/tickets/ticket_list.html:36 -#: tickets/templates/tickets/ticket_list.html:104 +#: audits/models.py:88 tickets/serializers/ticket.py:25 #: xpack/plugins/cloud/models.py:214 xpack/plugins/cloud/models.py:272 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:50 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:48 msgid "Status" msgstr "状态" @@ -2605,97 +986,23 @@ msgstr "状态" msgid "Date login" msgstr "登录日期" -#: audits/serializers.py:65 ops/models/command.py:24 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:56 +#: audits/serializers.py:61 audits/serializers.py:73 ops/models/adhoc.py:240 +msgid "Is success" +msgstr "是否成功" + +#: audits/serializers.py:72 ops/models/command.py:24 #: xpack/plugins/cloud/models.py:212 msgid "Result" msgstr "结果" -#: audits/serializers.py:66 ops/models/adhoc.py:240 -#: ops/templates/ops/adhoc_history.html:54 -#: ops/templates/ops/task_history.html:60 -msgid "Is success" -msgstr "是否成功" - -#: audits/serializers.py:67 ops/templates/ops/adhoc_detail.html:51 -#: ops/templates/ops/command_execution_list.html:65 -#: ops/templates/ops/task_adhoc.html:57 ops/templates/ops/task_list.html:13 -#: terminal/forms/storage.py:151 +#: audits/serializers.py:74 msgid "Hosts" msgstr "主机" -#: audits/serializers.py:68 ops/templates/ops/adhoc_detail.html:70 -#: ops/templates/ops/adhoc_detail.html:75 -#: ops/templates/ops/command_execution_list.html:68 -#: ops/templates/ops/task_adhoc.html:59 +#: audits/serializers.py:75 msgid "Run as" msgstr "运行用户" -#: audits/templates/audits/login_log_list.html:34 -#: perms/templates/perms/asset_permission_user.html:74 -#: perms/templates/perms/database_app_permission_user.html:74 -#: perms/templates/perms/remote_app_permission_user.html:83 -msgid "Select user" -msgstr "选择用户" - -#: audits/templates/audits/login_log_list.html:41 -#: audits/templates/audits/login_log_list.html:46 -#: audits/templates/audits/operate_log_list.html:62 -#: audits/templates/audits/password_change_log_list.html:46 -#: ops/templates/ops/command_execution_list.html:49 -#: ops/templates/ops/command_execution_list.html:54 -#: templates/_base_list.html:37 templates/_user_profile.html:23 -msgid "Search" -msgstr "搜索" - -#: audits/templates/audits/login_log_list.html:59 -msgid "UA" -msgstr "Agent" - -#: audits/templates/audits/login_log_list.html:61 authentication/models.py:63 -msgid "City" -msgstr "城市" - -#: audits/templates/audits/login_log_list.html:65 -#: authentication/templates/authentication/_access_key_modal.html:33 -#: ops/templates/ops/task_list.html:15 -msgid "Date" -msgstr "日期" - -#: audits/templates/audits/login_log_list.html:91 -#: templates/_csv_import_export.html:8 -msgid "Export" -msgstr "导出" - -#: audits/templates/audits/operate_log_list.html:70 -msgid "Handlers" -msgstr "操作者" - -#: audits/views.py:86 audits/views.py:131 audits/views.py:168 -#: audits/views.py:213 audits/views.py:245 templates/_nav.html:146 -msgid "Audits" -msgstr "日志审计" - -#: audits/views.py:87 templates/_nav.html:150 -msgid "FTP log" -msgstr "FTP日志" - -#: audits/views.py:132 templates/_nav.html:151 -msgid "Operate log" -msgstr "操作日志" - -#: audits/views.py:169 templates/_nav.html:152 -msgid "Password change log" -msgstr "改密日志" - -#: audits/views.py:214 templates/_nav.html:149 -msgid "Login log" -msgstr "登录日志" - -#: audits/views.py:246 -msgid "Command execution log" -msgstr "命令执行" - #: authentication/backends/api.py:53 msgid "Invalid signature header. No credentials provided." msgstr "" @@ -2824,6 +1131,14 @@ msgstr "登录复核 {}" msgid "MFA code" msgstr "多因子认证验证码" +#: authentication/models.py:19 +#: authentication/templates/authentication/_access_key_modal.html:32 +#: perms/models/base.py:51 users/templates/users/_select_user_modal.html:18 +#: users/templates/users/user_detail.html:132 +#: users/templates/users/user_profile.html:63 +msgid "Active" +msgstr "激活中" + #: authentication/models.py:39 msgid "Private Token" msgstr "SSH密钥" @@ -2837,6 +1152,10 @@ msgstr "审批人" msgid "Login confirm" msgstr "登录复核" +#: authentication/models.py:63 +msgid "City" +msgstr "城市" + #: authentication/templates/authentication/_access_key_modal.html:6 msgid "API key list" msgstr "API Key列表" @@ -2853,13 +1172,18 @@ msgstr "文档" msgid "Secret" msgstr "密文" +#: authentication/templates/authentication/_access_key_modal.html:33 +msgid "Date" +msgstr "日期" + #: authentication/templates/authentication/_access_key_modal.html:48 #: users/templates/users/_granted_assets.html:75 msgid "Show" msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: users/models/user.py:382 users/templates/users/user_profile.html:94 +#: users/models/user.py:386 users/serializers/user.py:213 +#: users/templates/users/user_profile.html:94 #: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:166 #: users/templates/users/user_verify_mfa.html:32 @@ -2867,11 +1191,22 @@ msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:383 users/templates/users/user_profile.html:92 +#: users/models/user.py:387 users/serializers/user.py:214 +#: users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:170 msgid "Enable" msgstr "启用" +#: authentication/templates/authentication/_access_key_modal.html:147 +msgid "Delete success" +msgstr "删除成功" + +#: authentication/templates/authentication/_access_key_modal.html:155 +#: authentication/templates/authentication/_mfa_confirm_modal.html:53 +#: templates/_modal.html:22 tickets/models/ticket.py:68 +msgid "Close" +msgstr "关闭" + #: authentication/templates/authentication/_mfa_confirm_modal.html:5 msgid "MFA confirm" msgstr "多因子认证校验" @@ -2880,6 +1215,18 @@ msgstr "多因子认证校验" msgid "Need MFA for view auth" msgstr "需要多因子认证来查看账号信息" +#: authentication/templates/authentication/_mfa_confirm_modal.html:20 +#: templates/_modal.html:23 users/templates/users/user_detail.html:264 +#: users/templates/users/user_detail.html:417 +#: users/templates/users/user_detail.html:443 +#: users/templates/users/user_detail.html:466 +#: users/templates/users/user_detail.html:511 +#: users/templates/users/user_group_create_update.html:28 +#: users/templates/users/user_list.html:184 +#: users/templates/users/user_password_verify.html:20 +msgid "Confirm" +msgstr "确认" + #: authentication/templates/authentication/_mfa_confirm_modal.html:25 msgid "Code error" msgstr "代码错误" @@ -2887,7 +1234,7 @@ msgstr "代码错误" #: authentication/templates/authentication/login.html:6 #: authentication/templates/authentication/login.html:39 #: authentication/templates/authentication/xpack_login.html:112 -#: templates/_base_only_msg_content.html:52 templates/_header_bar.html:83 +#: templates/_base_only_msg_content.html:51 templates/_header_bar.html:83 msgid "Login" msgstr "登录" @@ -2920,7 +1267,6 @@ msgid "Open Google Authenticator and enter the 6-bit dynamic code" msgstr "请打开 Google Authenticator,输入6位动态码" #: authentication/templates/authentication/login_otp.html:26 -#: users/templates/users/first_login.html:100 #: users/templates/users/user_otp_check_password.html:15 #: users/templates/users/user_otp_enable_bind.html:24 #: users/templates/users/user_otp_enable_install_app.html:29 @@ -2932,15 +1278,23 @@ msgstr "下一步" msgid "Can't provide security? Please contact the administrator!" msgstr "如果不能提供多因子认证验证码,请联系管理员!" -#: authentication/templates/authentication/login_wait_confirm.html:47 +#: authentication/templates/authentication/login_wait_confirm.html:41 +msgid "Refresh" +msgstr "刷新" + +#: authentication/templates/authentication/login_wait_confirm.html:46 msgid "Copy link" msgstr "复制链接" -#: authentication/templates/authentication/login_wait_confirm.html:52 +#: authentication/templates/authentication/login_wait_confirm.html:51 #: templates/flash_message_standalone.html:34 msgid "Return" msgstr "返回" +#: authentication/templates/authentication/login_wait_confirm.html:113 +msgid "Copy success" +msgstr "复制成功" + #: authentication/templates/authentication/xpack_login.html:74 msgid "Welcome back, please enter username and password to login" msgstr "欢迎回来,请输入用户名和密码登录" @@ -2987,31 +1341,31 @@ msgstr "不是合法json" msgid "Not a string type" msgstr "不是字符类型" -#: common/fields/model.py:79 +#: common/fields/model.py:80 msgid "Marshal dict data to char field" msgstr "" -#: common/fields/model.py:83 +#: common/fields/model.py:84 msgid "Marshal dict data to text field" msgstr "" -#: common/fields/model.py:95 +#: common/fields/model.py:96 msgid "Marshal list data to char field" msgstr "" -#: common/fields/model.py:99 +#: common/fields/model.py:100 msgid "Marshal list data to text field" msgstr "" -#: common/fields/model.py:103 +#: common/fields/model.py:104 msgid "Marshal data to char field" msgstr "" -#: common/fields/model.py:107 +#: common/fields/model.py:108 msgid "Marshal data to text field" msgstr "" -#: common/fields/model.py:133 +#: common/fields/model.py:157 msgid "Encrypt field using Secret Key" msgstr "" @@ -3039,11 +1393,11 @@ msgstr "字段必须唯一" msgid "

    Flow service unavailable, check it

    " msgstr "" -#: jumpserver/views/index.py:23 templates/_nav.html:7 +#: jumpserver/views/index.py:26 templates/_nav.html:7 msgid "Dashboard" msgstr "仪表盘" -#: jumpserver/views/other.py:25 +#: jumpserver/views/other.py:26 msgid "" "
    Luna is a separately deployed program, you need to deploy Luna, koko, " "configure nginx for url distribution,
    If you see this page, " @@ -3052,11 +1406,11 @@ msgstr "" "
    Luna是单独部署的一个程序,你需要部署luna,koko,
    如果你看到了" "这个页面,证明你访问的不是nginx监听的端口,祝你好运
    " -#: jumpserver/views/other.py:73 +#: jumpserver/views/other.py:76 msgid "Websocket server run on port: {}, you should proxy it on nginx" msgstr "Websocket 服务运行在端口: {}, 请检查nginx是否代理是否设置" -#: jumpserver/views/other.py:81 +#: jumpserver/views/other.py:90 msgid "" "
    Koko is a separately deployed program, you need to deploy Koko, " "configure nginx for url distribution,
    If you see this page, " @@ -3075,26 +1429,15 @@ msgid "Not has host {} permission" msgstr "没有该主机 {} 权限" #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:98 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:98 msgid "Cycle perform" msgstr "周期执行" #: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:111 ops/mixin.py:150 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:90 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:90 msgid "Regularly perform" msgstr "定期执行" #: ops/mixin.py:108 ops/mixin.py:147 #: xpack/plugins/change_auth_plan/serializers.py:53 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:54 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:79 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:17 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:40 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:79 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:16 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:28 msgid "Periodic perform" msgstr "定时执行" @@ -3133,16 +1476,15 @@ msgstr "提示:(单位: 时)" msgid "Callback" msgstr "回调" -#: ops/models/adhoc.py:143 ops/templates/ops/adhoc_detail.html:112 +#: ops/models/adhoc.py:143 msgid "Tasks" msgstr "任务" -#: ops/models/adhoc.py:144 ops/templates/ops/adhoc_detail.html:55 -#: ops/templates/ops/task_adhoc.html:58 +#: ops/models/adhoc.py:144 msgid "Pattern" msgstr "模式" -#: ops/models/adhoc.py:145 ops/templates/ops/adhoc_detail.html:59 +#: ops/models/adhoc.py:145 msgid "Options" msgstr "选项" @@ -3150,14 +1492,11 @@ msgstr "选项" msgid "Run as admin" msgstr "再次执行" -#: ops/models/adhoc.py:149 ops/templates/ops/adhoc_detail.html:80 -#: ops/templates/ops/task_adhoc.html:60 +#: ops/models/adhoc.py:149 msgid "Become" msgstr "Become" #: ops/models/adhoc.py:150 users/templates/users/user_group_detail.html:54 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:59 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:51 msgid "Create by" msgstr "创建者" @@ -3177,21 +1516,14 @@ msgstr "开始时间" msgid "End time" msgstr "完成时间" -#: ops/models/adhoc.py:238 ops/templates/ops/adhoc_history.html:55 -#: ops/templates/ops/task_history.html:61 ops/templates/ops/task_list.html:16 -#: xpack/plugins/change_auth_plan/models.py:179 -#: xpack/plugins/change_auth_plan/models.py:302 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16 +#: ops/models/adhoc.py:238 xpack/plugins/change_auth_plan/models.py:179 +#: xpack/plugins/change_auth_plan/models.py:310 #: xpack/plugins/gathered_user/models.py:79 msgid "Time" msgstr "时间" #: ops/models/adhoc.py:239 ops/models/command.py:26 -#: ops/templates/ops/adhoc_detail.html:104 -#: ops/templates/ops/adhoc_history.html:53 -#: ops/templates/ops/adhoc_history_detail.html:67 -#: ops/templates/ops/task_detail.html:82 ops/templates/ops/task_history.html:59 +#: terminal/serializers/session.py:30 msgid "Is finished" msgstr "是否完成" @@ -3215,15 +1547,15 @@ msgstr "{} 任务结束" msgid "Date finished" msgstr "结束日期" -#: ops/models/command.py:64 +#: ops/models/command.py:72 msgid "Task start" msgstr "任务开始" -#: ops/models/command.py:86 +#: ops/models/command.py:94 msgid "Command `{}` is forbidden ........" msgstr "命令 `{}` 不允许被执行 ......." -#: ops/models/command.py:93 +#: ops/models/command.py:101 msgid "Task end" msgstr "任务结束" @@ -3235,197 +1567,10 @@ msgstr "定期清除任务历史" msgid "Clean celery log period" msgstr "定期清除Celery日志" -#: ops/templates/ops/adhoc_detail.html:17 -#: ops/templates/ops/adhoc_history.html:17 -msgid "Version detail" -msgstr "版本详情" - -#: ops/templates/ops/adhoc_detail.html:20 -#: ops/templates/ops/adhoc_history.html:20 ops/views/adhoc.py:106 -msgid "Version run execution" -msgstr "执行历史" - -#: ops/templates/ops/adhoc_detail.html:92 ops/templates/ops/task_list.html:12 -#: xpack/plugins/change_auth_plan/serializers.py:54 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:18 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:19 -msgid "Run times" -msgstr "执行次数" - -#: ops/templates/ops/adhoc_detail.html:96 ops/templates/ops/task_detail.html:74 -msgid "Last run" -msgstr "最后运行" - -#: ops/templates/ops/adhoc_detail.html:100 -#: ops/templates/ops/adhoc_history_detail.html:63 -#: ops/templates/ops/task_detail.html:78 -msgid "Time delta" -msgstr "运行时间" - -#: ops/templates/ops/adhoc_detail.html:108 -#: ops/templates/ops/adhoc_history_detail.html:71 -#: ops/templates/ops/task_detail.html:92 -msgid "Is success " -msgstr "成功" - -#: ops/templates/ops/adhoc_detail.html:129 -#: ops/templates/ops/task_detail.html:119 -msgid "Last run failed hosts" -msgstr "最后运行失败主机" - -#: ops/templates/ops/adhoc_detail.html:149 -#: ops/templates/ops/adhoc_detail.html:174 -#: ops/templates/ops/task_detail.html:139 -#: ops/templates/ops/task_detail.html:164 -msgid "No hosts" -msgstr "没有主机" - -#: ops/templates/ops/adhoc_detail.html:159 -#: ops/templates/ops/task_detail.html:149 -msgid "Last run success hosts" -msgstr "最后运行成功主机" - -#: ops/templates/ops/adhoc_history.html:28 -#: ops/templates/ops/task_history.html:34 -msgid "Executions of " -msgstr "执行历史 " - -#: ops/templates/ops/adhoc_history.html:51 -#: ops/templates/ops/task_history.html:57 -msgid "F/S/T" -msgstr "失败/成功/总" - -#: ops/templates/ops/adhoc_history.html:52 -#: ops/templates/ops/task_history.html:58 -msgid "Ratio" -msgstr "比例" - -#: ops/templates/ops/adhoc_history_detail.html:17 ops/views/adhoc.py:120 -msgid "Execution detail" -msgstr "执行历史详情" - -#: ops/templates/ops/adhoc_history_detail.html:20 -#: ops/templates/ops/command_execution_list.html:69 -#: terminal/backends/command/models.py:22 -msgid "Output" -msgstr "输出" - -#: ops/templates/ops/adhoc_history_detail.html:28 -msgid "Execution detail of" -msgstr "执行历史详情" - -#: ops/templates/ops/adhoc_history_detail.html:51 -msgid "Task name" -msgstr "任务名称" - -#: ops/templates/ops/adhoc_history_detail.html:82 -msgid "Failed assets" -msgstr "失败资产" - -#: ops/templates/ops/adhoc_history_detail.html:102 -#: ops/templates/ops/adhoc_history_detail.html:127 -msgid "No assets" -msgstr "没有资产" - -#: ops/templates/ops/adhoc_history_detail.html:112 -msgid "Success assets" -msgstr "成功资产" - #: ops/templates/ops/celery_task_log.html:4 msgid "Task log" msgstr "任务列表" -#: ops/templates/ops/command_execution_create.html:93 -#: terminal/templates/terminal/session_detail.html:97 -#: terminal/templates/terminal/session_detail.html:114 -msgid "Go" -msgstr "" - -#: ops/templates/ops/command_execution_create.html:159 -msgid "Asset configuration does not include the SSH protocol" -msgstr "资产配置不包含 SSH 协议" - -#: ops/templates/ops/command_execution_create.html:183 -msgid "Selected assets" -msgstr "已选择资产" - -#: ops/templates/ops/command_execution_create.html:186 -msgid "In total" -msgstr "总共" - -#: ops/templates/ops/command_execution_create.html:223 -msgid "" -"Select the left asset, select the running system user, execute command in " -"batch" -msgstr "选择左侧资产, 选择运行的系统用户,批量执行命令" - -#: ops/templates/ops/command_execution_create.html:267 -msgid "Unselected assets" -msgstr "没有选中资产" - -#: ops/templates/ops/command_execution_create.html:271 -msgid "No input command" -msgstr "没有输入命令" - -#: ops/templates/ops/command_execution_create.html:275 -msgid "No system user was selected" -msgstr "没有选择系统用户" - -#: ops/templates/ops/command_execution_create.html:285 -msgid "Pending" -msgstr "等待" - -#: ops/templates/ops/command_execution_list.html:70 -#: xpack/plugins/change_auth_plan/models.py:266 -msgid "Finished" -msgstr "结束" - -#: ops/templates/ops/task_adhoc.html:17 ops/templates/ops/task_detail.html:18 -#: ops/templates/ops/task_history.html:17 ops/views/adhoc.py:50 -#: ops/views/adhoc.py:92 -msgid "Task detail" -msgstr "任务详情" - -#: ops/templates/ops/task_adhoc.html:20 ops/templates/ops/task_detail.html:21 -#: ops/templates/ops/task_history.html:20 ops/views/adhoc.py:64 -msgid "Task versions" -msgstr "任务各版本" - -#: ops/templates/ops/task_adhoc.html:23 ops/templates/ops/task_detail.html:24 -#: ops/templates/ops/task_history.html:23 -msgid "Execution" -msgstr "执行历史" - -#: ops/templates/ops/task_adhoc.html:26 ops/templates/ops/task_detail.html:27 -#: ops/templates/ops/task_history.html:26 -msgid "Last execution output" -msgstr "最后执行输出" - -#: ops/templates/ops/task_adhoc.html:34 -msgid "Versions of " -msgstr "版本" - -#: ops/templates/ops/task_detail.html:66 -msgid "Total versions" -msgstr "版本数量" - -#: ops/templates/ops/task_detail.html:102 -msgid "Contents" -msgstr "内容" - -#: ops/templates/ops/task_list.html:73 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:135 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:54 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:148 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:58 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:44 -msgid "Run" -msgstr "执行" - -#: ops/templates/ops/task_list.html:114 -msgid "Task start: " -msgstr "任务开始: " - #: ops/utils.py:60 msgid "Update task content: {}" msgstr "更新任务内容: {}" @@ -3434,29 +1579,6 @@ msgstr "更新任务内容: {}" msgid "Disk used more than 80%: {} => {}" msgstr "磁盘使用率超过 80%: {} => {}" -#: ops/views/adhoc.py:31 ops/views/adhoc.py:49 ops/views/adhoc.py:63 -#: ops/views/adhoc.py:77 ops/views/adhoc.py:91 ops/views/adhoc.py:105 -#: ops/views/adhoc.py:119 ops/views/command.py:48 ops/views/command.py:79 -msgid "Ops" -msgstr "作业中心" - -#: ops/views/adhoc.py:32 templates/_nav.html:124 -#: xpack/plugins/gathered_user/views.py:35 -msgid "Task list" -msgstr "任务列表" - -#: ops/views/adhoc.py:78 -msgid "Task execution list" -msgstr "任务执行列表" - -#: ops/views/command.py:49 -msgid "Command execution list" -msgstr "命令执行列表" - -#: ops/views/command.py:80 templates/_nav_user.html:31 -msgid "Command execution" -msgstr "命令执行" - #: orgs/mixins/models.py:56 orgs/mixins/serializers.py:26 orgs/models.py:31 msgid "Organization" msgstr "组织" @@ -3477,13 +1599,8 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件" #: perms/forms/asset_permission.py:86 perms/forms/database_app_permission.py:41 #: perms/forms/remote_app_permission.py:43 perms/models/base.py:50 -#: perms/templates/perms/asset_permission_list.html:34 -#: perms/templates/perms/asset_permission_list.html:84 -#: perms/templates/perms/asset_permission_list.html:186 -#: perms/templates/perms/database_app_permission_list.html:16 -#: perms/templates/perms/remote_app_permission_list.html:16 #: templates/_nav.html:21 users/forms/user.py:168 users/models/group.py:31 -#: users/models/user.py:468 users/templates/users/_select_user_modal.html:16 +#: users/models/user.py:472 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_asset_permission.html:39 #: users/templates/users/user_asset_permission.html:67 #: users/templates/users/user_database_app_permission.html:38 @@ -3492,7 +1609,6 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件" #: users/templates/users/user_list.html:17 #: users/templates/users/user_remote_app_permission.html:38 #: users/templates/users/user_remote_app_permission.html:61 -#: xpack/plugins/orgs/templates/orgs/org_list.html:16 msgid "User group" msgstr "用户组" @@ -3504,11 +1620,18 @@ msgstr "用户和用户组至少选一个" msgid "Asset or group at least one required" msgstr "资产和节点至少选一个" -#: perms/models/asset_permission.py:31 settings/forms/terminal.py:19 -#: settings/serializers/settings.py:56 +#: perms/forms/database_app_permission.py:47 +msgid "System users" +msgstr "系统用户" + +#: perms/models/asset_permission.py:31 settings/serializers/settings.py:56 msgid "All" msgstr "全部" +#: perms/models/asset_permission.py:32 +msgid "Connect" +msgstr "连接" + #: perms/models/asset_permission.py:33 msgid "Upload file" msgstr "上传文件" @@ -3526,328 +1649,26 @@ msgid "Actions" msgstr "动作" #: perms/models/asset_permission.py:87 templates/_nav.html:78 -#: tickets/templates/tickets/ticket_list.html:22 #: users/templates/users/_user_detail_nav_header.html:31 -#: users/views/user.py:221 msgid "Asset permission" msgstr "资产授权" -#: perms/models/base.py:53 -#: perms/templates/perms/asset_permission_detail.html:85 -#: perms/templates/perms/database_app_permission_detail.html:81 -#: perms/templates/perms/remote_app_permission_detail.html:77 -#: users/models/user.py:500 users/templates/users/user_detail.html:93 +#: perms/models/base.py:53 users/models/user.py:504 +#: users/templates/users/user_detail.html:93 #: users/templates/users/user_profile.html:120 msgid "Date expired" msgstr "失效日期" #: perms/models/database_app_permission.py:27 #: users/templates/users/_user_detail_nav_header.html:61 -#: users/views/user.py:277 msgid "DatabaseApp permission" msgstr "数据库应用授权" #: perms/models/remote_app_permission.py:20 #: users/templates/users/_user_detail_nav_header.html:47 -#: users/views/user.py:249 msgid "RemoteApp permission" msgstr "远程应用授权" -#: perms/templates/perms/asset_permission_asset.html:18 -#: perms/templates/perms/asset_permission_detail.html:17 -#: perms/templates/perms/asset_permission_user.html:18 -#: perms/templates/perms/database_app_permission_database_app.html:18 -#: perms/templates/perms/database_app_permission_detail.html:17 -#: perms/templates/perms/database_app_permission_user.html:18 -#: perms/templates/perms/remote_app_permission_detail.html:17 -#: perms/templates/perms/remote_app_permission_remote_app.html:17 -#: perms/templates/perms/remote_app_permission_user.html:17 -msgid "Users and user groups" -msgstr "用户或用户组" - -#: perms/templates/perms/asset_permission_asset.html:23 -#: perms/templates/perms/asset_permission_detail.html:22 -#: perms/templates/perms/asset_permission_user.html:23 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:16 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:21 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:20 -msgid "Assets and node" -msgstr "资产或节点" - -#: perms/templates/perms/asset_permission_asset.html:66 -msgid "Add asset to this permission" -msgstr "添加资产" - -#: perms/templates/perms/asset_permission_asset.html:80 -#: perms/templates/perms/asset_permission_asset.html:141 -#: perms/templates/perms/asset_permission_user.html:80 -#: perms/templates/perms/asset_permission_user.html:108 -#: perms/templates/perms/database_app_permission_database_app.html:83 -#: perms/templates/perms/database_app_permission_database_app.html:111 -#: perms/templates/perms/database_app_permission_user.html:80 -#: perms/templates/perms/database_app_permission_user.html:108 -#: perms/templates/perms/remote_app_permission_detail.html:143 -#: perms/templates/perms/remote_app_permission_remote_app.html:92 -#: perms/templates/perms/remote_app_permission_user.html:92 -#: perms/templates/perms/remote_app_permission_user.html:120 -#: users/templates/users/user_group_detail.html:87 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:76 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:88 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:125 -msgid "Add" -msgstr "添加" - -#: perms/templates/perms/asset_permission_asset.html:91 -msgid "Add node to this permission" -msgstr "添加节点" - -#: perms/templates/perms/asset_permission_asset.html:105 -#: users/templates/users/user_detail.html:226 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:101 -msgid "Join" -msgstr "加入" - -#: perms/templates/perms/asset_permission_asset.html:132 -#: perms/templates/perms/database_app_permission_database_app.html:102 -#: perms/templates/perms/remote_app_permission_detail.html:134 -msgid "Select system users" -msgstr "选择系统用户" - -#: perms/templates/perms/asset_permission_create_update.html:105 -#: perms/templates/perms/database_app_permission_create_update.html:59 -#: perms/templates/perms/remote_app_permission_create_update.html:59 -msgid "Validity period" -msgstr "有效期" - -#: perms/templates/perms/asset_permission_detail.html:61 -#: perms/templates/perms/database_app_permission_detail.html:61 -#: perms/templates/perms/remote_app_permission_detail.html:61 -msgid "User count" -msgstr "用户数量" - -#: perms/templates/perms/asset_permission_detail.html:65 -#: perms/templates/perms/database_app_permission_detail.html:65 -#: perms/templates/perms/remote_app_permission_detail.html:65 -msgid "User group count" -msgstr "用户组数量" - -#: perms/templates/perms/asset_permission_detail.html:69 -#: xpack/plugins/license/templates/license/license_detail.html:63 -msgid "Asset count" -msgstr "资产数量" - -#: perms/templates/perms/asset_permission_detail.html:73 -msgid "Node count" -msgstr "节点数量" - -#: perms/templates/perms/asset_permission_detail.html:77 -#: perms/templates/perms/database_app_permission_detail.html:73 -msgid "System user count" -msgstr "系统用户数量" - -#: perms/templates/perms/asset_permission_list.html:21 -#: perms/templates/perms/database_app_permission_list.html:6 -#: perms/templates/perms/remote_app_permission_list.html:6 -msgid "Create permission" -msgstr "创建授权规则" - -#: perms/templates/perms/asset_permission_list.html:25 -msgid "Refresh permission cache" -msgstr "刷新授权缓存" - -#: perms/templates/perms/asset_permission_list.html:38 -#: perms/templates/perms/asset_permission_list.html:184 -#: perms/templates/perms/database_app_permission_list.html:19 -#: perms/templates/perms/remote_app_permission_list.html:19 -#: users/templates/users/user_asset_permission.html:43 -#: users/templates/users/user_asset_permission.html:155 -#: users/templates/users/user_database_app_permission.html:41 -#: users/templates/users/user_list.html:19 -#: users/templates/users/user_remote_app_permission.html:41 -#: xpack/plugins/cloud/models.py:50 xpack/plugins/cloud/serializers.py:32 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:55 -#: xpack/plugins/cloud/templates/cloud/account_list.html:14 -msgid "Validity" -msgstr "有效" - -#: perms/templates/perms/asset_permission_list.html:191 -#: users/templates/users/user_asset_permission.html:160 -msgid "Inherit" -msgstr "继承" - -#: perms/templates/perms/asset_permission_list.html:192 -#: users/templates/users/user_asset_permission.html:161 -msgid "Include" -msgstr "包含" - -#: perms/templates/perms/asset_permission_list.html:193 -#: users/templates/users/user_asset_permission.html:162 -msgid "Exclude" -msgstr "不包含" - -#: perms/templates/perms/asset_permission_list.html:211 -msgid "Refresh success" -msgstr "刷新成功" - -#: perms/templates/perms/asset_permission_user.html:31 -#: perms/templates/perms/database_app_permission_user.html:31 -#: perms/templates/perms/remote_app_permission_user.html:30 -msgid "User list of " -msgstr "用户列表" - -#: perms/templates/perms/asset_permission_user.html:66 -msgid "Add user to asset permission" -msgstr "添加用户" - -#: perms/templates/perms/asset_permission_user.html:91 -msgid "Add user group to asset permission" -msgstr "添加用户组" - -#: perms/templates/perms/asset_permission_user.html:99 -#: perms/templates/perms/database_app_permission_user.html:99 -#: perms/templates/perms/remote_app_permission_user.html:111 -msgid "Select user groups" -msgstr "选择用户组" - -#: perms/templates/perms/database_app_permission_database_app.html:31 -msgid "DatabaseApp list of " -msgstr "数据库应用列表" - -#: perms/templates/perms/database_app_permission_database_app.html:66 -msgid "Add DatabaseApp to this permission" -msgstr "添加数据库应用" - -#: perms/templates/perms/database_app_permission_database_app.html:74 -msgid "Select DatabaseApp" -msgstr "选择数据库应用" - -#: perms/templates/perms/database_app_permission_detail.html:69 -msgid "DatabaseApp count" -msgstr "数据库应用数量" - -#: perms/templates/perms/database_app_permission_user.html:66 -msgid "Add user to permission" -msgstr "添加用户" - -#: perms/templates/perms/database_app_permission_user.html:91 -msgid "Add user group to permission" -msgstr "添加用户组" - -#: perms/templates/perms/remote_app_permission_detail.html:69 -msgid "RemoteApp count" -msgstr "远程应用数量" - -#: perms/templates/perms/remote_app_permission_remote_app.html:30 -msgid "RemoteApp list of " -msgstr "远程应用列表" - -#: perms/templates/perms/remote_app_permission_remote_app.html:75 -msgid "Add RemoteApp to this permission" -msgstr "添加远程应用" - -#: perms/templates/perms/remote_app_permission_remote_app.html:83 -msgid "Select RemoteApp" -msgstr "选择远程应用" - -#: perms/templates/perms/remote_app_permission_user.html:75 -msgid "Add user to this permission" -msgstr "添加用户" - -#: perms/templates/perms/remote_app_permission_user.html:103 -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:82 perms/views/asset_permission.py:99 -#: perms/views/asset_permission.py:136 perms/views/asset_permission.py:169 -#: perms/views/database_app_permission.py:33 -#: perms/views/database_app_permission.py:48 -#: perms/views/database_app_permission.py:64 -#: perms/views/database_app_permission.py:79 -#: perms/views/database_app_permission.py:108 -#: perms/views/database_app_permission.py:143 -#: perms/views/remote_app_permission.py:33 -#: perms/views/remote_app_permission.py:49 -#: perms/views/remote_app_permission.py:66 -#: perms/views/remote_app_permission.py:84 -#: perms/views/remote_app_permission.py:116 -#: perms/views/remote_app_permission.py:149 templates/_nav.html:75 -#: xpack/plugins/orgs/templates/orgs/org_list.html:22 -msgid "Perms" -msgstr "权限管理" - -#: perms/views/asset_permission.py:34 -msgid "Asset permission list" -msgstr "资产授权列表" - -#: perms/views/asset_permission.py:66 -msgid "Create asset permission" -msgstr "创建权限规则" - -#: perms/views/asset_permission.py:83 -msgid "Update asset permission" -msgstr "更新资产授权" - -#: perms/views/asset_permission.py:100 -msgid "Asset permission detail" -msgstr "资产授权详情" - -#: perms/views/asset_permission.py:137 -msgid "Asset permission user list" -msgstr "资产授权用户列表" - -#: perms/views/asset_permission.py:171 -msgid "Asset permission asset list" -msgstr "资产授权资产列表" - -#: perms/views/database_app_permission.py:34 -msgid "DatabaseApp permission list" -msgstr "数据库应用授权列表" - -#: perms/views/database_app_permission.py:49 -msgid "Create DatabaseApp permission" -msgstr "创建数据库应用授权规则" - -#: perms/views/database_app_permission.py:65 -msgid "Update DatabaseApp permission" -msgstr "更新数据库应用授权规则" - -#: perms/views/database_app_permission.py:80 -msgid "DatabaseApp permission detail" -msgstr "数据库应用授权详情" - -#: perms/views/database_app_permission.py:109 -msgid "DatabaseApp permission user list" -msgstr "数据库应用授权用户列表" - -#: perms/views/database_app_permission.py:149 -msgid "DatabaseApp permission DatabaseApp list" -msgstr "数据库应用授权数据库应用列表" - -#: perms/views/remote_app_permission.py:34 -msgid "RemoteApp permission list" -msgstr "远程应用授权列表" - -#: perms/views/remote_app_permission.py:50 -msgid "Create RemoteApp permission" -msgstr "创建远程应用授权规则" - -#: perms/views/remote_app_permission.py:67 -msgid "Update RemoteApp permission" -msgstr "更新远程应用授权规则" - -#: perms/views/remote_app_permission.py:85 -msgid "RemoteApp permission detail" -msgstr "远程应用授权详情" - -#: perms/views/remote_app_permission.py:117 -msgid "RemoteApp permission user list" -msgstr "远程应用授权用户列表" - -#: perms/views/remote_app_permission.py:150 -msgid "RemoteApp permission RemoteApp list" -msgstr "远程应用授权远程应用列表" - #: settings/api.py:33 msgid "Test mail sent to {}, please check" msgstr "邮件已经发送{}, 请检查" @@ -3860,411 +1681,14 @@ msgstr "获取 LDAP 用户为 None" msgid "Imported {} users successfully" msgstr "导入 {} 个用户成功" -#: settings/forms/basic.py:13 -msgid "Current SITE URL" -msgstr "当前站点URL" - -#: settings/forms/basic.py:17 -msgid "User Guide URL" -msgstr "用户向导URL" - -#: settings/forms/basic.py:18 -msgid "User first login update profile done redirect to it" -msgstr "用户第一次登录,修改profile后重定向到地址" - -#: settings/forms/basic.py:21 -msgid "Email Subject Prefix" -msgstr "Email主题前缀" - -#: settings/forms/basic.py:22 -msgid "Tips: Some word will be intercept by mail provider" -msgstr "提示: 一些关键字可能会被邮件提供商拦截,如 跳板机、JumpServer" - -#: settings/forms/email.py:15 -msgid "SMTP host" -msgstr "SMTP主机" - -#: settings/forms/email.py:17 -msgid "SMTP port" -msgstr "SMTP端口" - -#: settings/forms/email.py:19 -msgid "SMTP user" -msgstr "SMTP账号" - -#: settings/forms/email.py:22 -msgid "SMTP password" -msgstr "SMTP密码" - -#: settings/forms/email.py:24 -msgid "Tips: Some provider use token except password" -msgstr "提示:一些邮件提供商需要输入的是Token" - -#: settings/forms/email.py:27 -msgid "Send user" -msgstr "发送账号" - -#: settings/forms/email.py:29 -msgid "Tips: Send mail account, default SMTP account as the send account" -msgstr "提示:发送邮件账号,默认使用SMTP账号作为发送账号" - -#: settings/forms/email.py:33 -msgid "Test recipient" -msgstr "测试收件人" - -#: settings/forms/email.py:34 -msgid "Tips: Used only as a test mail recipient" -msgstr "提示:仅用来作为测试邮件收件人" - -#: settings/forms/email.py:37 -msgid "Use SSL" -msgstr "使用SSL" - -#: settings/forms/email.py:38 -msgid "If SMTP port is 465, may be select" -msgstr "如果SMTP端口是465,通常需要启用SSL" - -#: settings/forms/email.py:41 -msgid "Use TLS" -msgstr "使用TLS" - -#: settings/forms/email.py:42 -msgid "If SMTP port is 587, may be select" -msgstr "如果SMTP端口是587,通常需要启用TLS" - -#: settings/forms/email.py:48 -msgid "Create user email subject" -msgstr "创建用户邮件的主题" - -#: settings/forms/email.py:49 -msgid "" -"Tips: When creating a user, send the subject of the email (eg:Create account " -"successfully)" -msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" - -#: settings/forms/email.py:53 -msgid "Create user honorific" -msgstr "创建用户邮件的敬语" - -#: settings/forms/email.py:54 -msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" -msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)" - -#: settings/forms/email.py:59 -msgid "Create user email content" -msgstr "创建用户邮件的内容" - -#: settings/forms/email.py:60 -msgid "Tips:When creating a user, send the content of the email" -msgstr "提示: 创建用户时,发送设置密码邮件的内容" - -#: settings/forms/email.py:63 -msgid "Signature" -msgstr "署名" - -#: settings/forms/email.py:64 -msgid "Tips: Email signature (eg:jumpserver)" -msgstr "提示: 邮件的署名 (例如: jumpserver)" - -#: settings/forms/ldap.py:16 -msgid "LDAP server" -msgstr "LDAP地址" - -#: settings/forms/ldap.py:19 -msgid "Bind DN" -msgstr "绑定DN" - -#: settings/forms/ldap.py:26 -msgid "User OU" -msgstr "用户OU" - -#: settings/forms/ldap.py:27 -msgid "Use | split User OUs" -msgstr "使用|分隔各OU" - -#: settings/forms/ldap.py:31 -msgid "User search filter" -msgstr "用户过滤器" - -#: settings/forms/ldap.py:32 -#, python-format -msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" -msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" - -#: settings/forms/ldap.py:35 -msgid "User attr map" -msgstr "LDAP属性映射" - -#: settings/forms/ldap.py:37 -msgid "" -"User attr map present how to map LDAP user attr to jumpserver, username,name," -"email is jumpserver attr" -msgstr "" -"用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name," -"email 是jumpserver的属性" - -#: settings/forms/ldap.py:46 -msgid "Enable LDAP auth" -msgstr "启用LDAP认证" - -#: settings/forms/security.py:18 -msgid "" -"After opening, all user login must use MFA(valid for all users, including " -"administrators)" -msgstr "开启后,所有用户登录必须使用多因子认证(对所有用户有效,包括管理员)" - -#: settings/forms/security.py:24 -msgid "Batch execute commands" -msgstr "批量命令" - -#: settings/forms/security.py:25 -msgid "Allow user batch execute commands" -msgstr "允许用户批量执行命令" - -#: settings/forms/security.py:28 -msgid "Service account registration" -msgstr "终端注册" - -#: settings/forms/security.py:29 -msgid "" -"Allow using bootstrap token register service account, when terminal setup, " -"can disable it" -msgstr "允许使用bootstrap token注册终端, 当终端注册成功后可以禁止" - -#: settings/forms/security.py:35 -msgid "Limit the number of login failures" -msgstr "限制登录失败次数" - -#: settings/forms/security.py:39 -msgid "No logon interval" -msgstr "禁止登录时间间隔" - -#: settings/forms/security.py:41 -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/security.py:48 -msgid "Connection max idle time" -msgstr "连接最大空闲时间" - -#: settings/forms/security.py:50 -msgid "If idle time more than it, disconnect connection Unit: minute" -msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)" - -#: settings/forms/security.py:56 -msgid "Password expiration time" -msgstr "密码过期时间" - -#: settings/forms/security.py:58 -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 " -"will be automatic sent to the user by system within 5 days (daily) before " -"the password expires" -msgstr "" -"提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" -"提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" - -#: settings/forms/security.py:67 -msgid "Password minimum length" -msgstr "密码最小长度 " - -#: settings/forms/security.py:71 -msgid "Must contain capital letters" -msgstr "必须包含大写字母" - -#: settings/forms/security.py:73 -msgid "" -"After opening, the user password changes and resets must contain uppercase " -"letters" -msgstr "开启后,用户密码修改、重置必须包含大写字母" - -#: settings/forms/security.py:78 -msgid "Must contain lowercase letters" -msgstr "必须包含小写字母" - -#: settings/forms/security.py:79 -msgid "" -"After opening, the user password changes and resets must contain lowercase " -"letters" -msgstr "开启后,用户密码修改、重置必须包含小写字母" - -#: settings/forms/security.py:84 -msgid "Must contain numeric characters" -msgstr "必须包含数字字符" - -#: settings/forms/security.py:85 -msgid "" -"After opening, the user password changes and resets must contain numeric " -"characters" -msgstr "开启后,用户密码修改、重置必须包含数字字符" - -#: settings/forms/security.py:90 -msgid "Must contain special characters" -msgstr "必须包含特殊字符" - -#: settings/forms/security.py:91 -msgid "" -"After opening, the user password changes and resets must contain special " -"characters" -msgstr "开启后,用户密码修改、重置必须包含特殊字符" - -#: settings/forms/terminal.py:20 settings/serializers/settings.py:57 -msgid "Auto" -msgstr "自动" - -#: settings/forms/terminal.py:27 -msgid "Password auth" -msgstr "密码认证" - -#: settings/forms/terminal.py:30 -msgid "Public key auth" -msgstr "密钥认证" - -#: settings/forms/terminal.py:33 -msgid "Heartbeat interval" -msgstr "心跳间隔" - -#: settings/forms/terminal.py:34 -msgid "Units: seconds" -msgstr "单位: 秒" - -#: settings/forms/terminal.py:37 -msgid "List sort by" -msgstr "资产列表排序" - -#: settings/forms/terminal.py:40 -msgid "List page size" -msgstr "资产分页每页数量" - -#: settings/forms/terminal.py:43 -msgid "Session keep duration" -msgstr "会话保留时长" - -#: settings/forms/terminal.py:44 -msgid "" -"Units: days, Session, record, command will be delete if more than duration, " -"only in database" -msgstr "" -"单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不" -"受影响)" - -#: settings/forms/terminal.py:48 -msgid "Telnet login regex" -msgstr "Telnet 成功正则表达式" - -#: settings/forms/terminal.py:49 -msgid "ex: Last\\s*login|success|成功" -msgstr "" -"登录telnet服务器成功后的提示正则表达式,如: Last\\s*login|success|成功 " - #: settings/models.py:96 users/templates/users/reset_password.html:29 #: users/templates/users/user_profile.html:20 msgid "Setting" msgstr "设置" -#: settings/templates/settings/_ldap_list_users_modal.html:7 -msgid "LDAP user list" -msgstr "LDAP 用户列表" - -#: settings/templates/settings/_ldap_list_users_modal.html:9 -msgid "Please submit the LDAP configuration before import" -msgstr "请先提交LDAP配置再进行导入" - -#: settings/templates/settings/_ldap_list_users_modal.html:26 -msgid "Refresh cache" -msgstr "刷新缓存" - -#: settings/templates/settings/_ldap_list_users_modal.html:33 -#: users/forms/profile.py:89 users/models/user.py:464 -#: users/templates/users/user_detail.html:57 -#: users/templates/users/user_profile.html:59 -msgid "Email" -msgstr "邮件" - -#: settings/templates/settings/_ldap_list_users_modal.html:34 -msgid "Existing" -msgstr "已存在" - -#: settings/templates/settings/_ldap_list_users_modal.html:144 -msgid "" -"User is not currently selected, please check the user you want to import" -msgstr "当前无勾选用户,请勾选你想要导入的用户" - -#: settings/templates/settings/_ldap_list_users_modal.html:172 -#: templates/_csv_import_export.html:13 templates/_csv_import_modal.html:5 -#: xpack/plugins/license/templates/license/license_detail.html:88 -msgid "Import" -msgstr "导入" - -#: settings/templates/settings/_ldap_test_user_login_modal.html:4 -msgid "Test LDAP user login" -msgstr "测试LDAP 用户登录" - -#: settings/templates/settings/_ldap_test_user_login_modal.html:5 -msgid "Save the configuration before testing the login" -msgstr "请先提交LDAP配置再进行测试登录" - -#: settings/templates/settings/_ldap_test_user_login_modal.html:12 -msgid "Please input username" -msgstr "请输入用户名" - -#: settings/templates/settings/_setting_tabs.html:4 -#: settings/templates/settings/terminal_setting.html:31 settings/views.py:20 -msgid "Basic setting" -msgstr "基本设置" - -#: settings/templates/settings/_setting_tabs.html:7 settings/views.py:47 -msgid "Email setting" -msgstr "邮件设置" - -#: settings/templates/settings/_setting_tabs.html:10 settings/views.py:162 -msgid "Email content setting" -msgstr "邮件内容设置" - -#: settings/templates/settings/_setting_tabs.html:13 settings/views.py:74 -msgid "LDAP setting" -msgstr "LDAP设置" - -#: settings/templates/settings/_setting_tabs.html:16 settings/views.py:106 -msgid "Terminal setting" -msgstr "终端设置" - -#: settings/templates/settings/_setting_tabs.html:19 -#: settings/templates/settings/security_setting.html:26 settings/views.py:135 -msgid "Security setting" -msgstr "安全设置" - -#: settings/templates/settings/email_content_setting.html:26 -msgid "Create User setting" -msgstr "创建用户设置" - -#: settings/templates/settings/ldap_setting.html:47 -msgid "Test login" -msgstr "测试登录" - -#: settings/templates/settings/ldap_setting.html:48 -msgid "Bulk import" -msgstr "一键导入" - -#: settings/templates/settings/security_setting.html:30 -msgid "Password check rule" -msgstr "密码校验规则" - -#: settings/templates/settings/terminal_setting.html:7 -msgid "Command and Replay storage configuration migrated to" -msgstr "命令和录像存储配置已迁移到" - -#: settings/templates/settings/terminal_setting.html:8 -msgid "Sessions -> Terminal -> Storage configuration" -msgstr "会话管理 -> 终端管理 -> 存储配置" - -#: settings/templates/settings/terminal_setting.html:9 -msgid "Here" -msgstr "这里" +#: settings/serializers/settings.py:57 +msgid "Auto" +msgstr "自动" #: settings/utils/ldap.py:389 msgid "Host or port is disconnected: {}" @@ -4363,23 +1787,16 @@ msgstr "认证失败: (未知): {}" msgid "Authentication success: {}" msgstr "认证成功: {}" -#: settings/views.py:19 settings/views.py:46 settings/views.py:73 -#: settings/views.py:105 settings/views.py:134 settings/views.py:161 -#: templates/_nav.html:187 -msgid "Settings" -msgstr "系统设置" +#: templates/_base_list.html:37 templates/_user_profile.html:23 +msgid "Search" +msgstr "搜索" -#: settings/views.py:30 settings/views.py:57 settings/views.py:84 -#: settings/views.py:118 settings/views.py:145 settings/views.py:172 -msgid "Update setting successfully" -msgstr "更新设置成功" - -#: templates/_base_only_msg_content.html:28 xpack/plugins/interface/api.py:17 +#: templates/_base_only_msg_content.html:27 xpack/plugins/interface/api.py:18 #: xpack/plugins/interface/models.py:36 msgid "Welcome to the JumpServer open source fortress" msgstr "欢迎使用JumpServer开源堡垒机" -#: templates/_base_only_msg_content.html:33 +#: templates/_base_only_msg_content.html:32 msgid "" "The world's first fully open source fortress, using the GNU GPL v2.0 open " "source protocol, is a professional operation and maintenance audit system in " @@ -4388,7 +1805,7 @@ msgstr "" "全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计" "系统。" -#: templates/_base_only_msg_content.html:36 +#: templates/_base_only_msg_content.html:35 msgid "" "Developed using Python/Django, following the Web 2.0 specification and " "equipped with industry-leading Web Terminal solutions, with beautiful " @@ -4397,7 +1814,7 @@ msgstr "" "使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web " "Terminal 解决方案,交互界面美观、用户体验好。" -#: templates/_base_only_msg_content.html:39 +#: templates/_base_only_msg_content.html:38 msgid "" "Distributed architecture is adopted to support multi-machine room deployment " "across regions, central node provides API, and each machine room deploys " @@ -4407,10 +1824,18 @@ msgstr "" "采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点," "可横向扩展、无并发访问限制。" -#: templates/_base_only_msg_content.html:42 +#: templates/_base_only_msg_content.html:41 msgid "Changes the world, starting with a little bit." msgstr "改变世界,从一点点开始。" +#: templates/_csv_import_export.html:8 +msgid "Export" +msgstr "导出" + +#: templates/_csv_import_export.html:13 templates/_csv_import_modal.html:5 +msgid "Import" +msgstr "导入" + #: templates/_csv_import_modal.html:12 msgid "Download the imported template or use the exported CSV file format" msgstr "下载导入的模板或使用导出的csv格式" @@ -4439,7 +1864,7 @@ msgstr "下载更新模版" msgid "Help" msgstr "帮助" -#: templates/_header_bar.html:19 templates/_without_nav_base.html:28 +#: templates/_header_bar.html:19 templates/_without_nav_base.html:27 msgid "Docs" msgstr "文档" @@ -4450,13 +1875,11 @@ msgstr "商业支持" #: templates/_header_bar.html:70 templates/_nav.html:30 #: templates/_nav_user.html:37 users/forms/profile.py:31 #: users/templates/users/_user.html:44 -#: users/templates/users/first_login.html:39 -#: users/templates/users/user_password_update.html:40 +#: users/templates/users/user_password_update.html:39 #: users/templates/users/user_profile.html:17 #: users/templates/users/user_profile_update.html:37 #: users/templates/users/user_profile_update.html:61 #: users/templates/users/user_pubkey_update.html:37 -#: users/views/profile/base.py:27 msgid "Profile" msgstr "个人信息" @@ -4549,15 +1972,35 @@ msgstr "" "\"%(user_pubkey_update)s\"> 链接 更新\n" " " +#: templates/_nav.html:20 +msgid "User list" +msgstr "用户列表" + +#: templates/_nav.html:42 +msgid "Asset list" +msgstr "资产列表" + +#: templates/_nav.html:43 +msgid "Domain list" +msgstr "网域列表" + #: templates/_nav.html:47 msgid "Command filters" msgstr "命令过滤" -#: templates/_nav.html:97 terminal/views/command.py:21 -#: terminal/views/session.py:48 terminal/views/session.py:59 -#: terminal/views/session.py:74 terminal/views/session.py:97 -#: terminal/views/terminal.py:32 terminal/views/terminal.py:48 -#: terminal/views/terminal.py:61 +#: templates/_nav.html:49 +msgid "Platform list" +msgstr "平台列表" + +#: templates/_nav.html:60 +msgid "Applications" +msgstr "应用管理" + +#: templates/_nav.html:75 +msgid "Perms" +msgstr "权限管理" + +#: templates/_nav.html:97 msgid "Sessions" msgstr "会话管理" @@ -4565,12 +2008,11 @@ msgstr "会话管理" msgid "Session online" msgstr "在线会话" -#: templates/_nav.html:101 terminal/views/session.py:60 +#: templates/_nav.html:101 msgid "Session offline" msgstr "历史会话" -#: templates/_nav.html:102 terminal/templates/terminal/session_commands.html:21 -#: terminal/templates/terminal/session_detail.html:21 +#: templates/_nav.html:102 msgid "Commands" msgstr "命令记录" @@ -4582,10 +2024,7 @@ msgstr "Web终端" msgid "File manager" msgstr "文件管理" -#: templates/_nav.html:110 terminal/views/storage.py:27 -#: terminal/views/storage.py:42 terminal/views/storage.py:96 -#: terminal/views/storage.py:120 terminal/views/storage.py:149 -#: terminal/views/storage.py:175 +#: templates/_nav.html:110 msgid "Terminal" msgstr "终端管理" @@ -4593,6 +2032,10 @@ msgstr "终端管理" msgid "Job Center" msgstr "作业中心" +#: templates/_nav.html:124 +msgid "Task list" +msgstr "任务列表" + #: templates/_nav.html:125 templates/_nav.html:153 msgid "Batch command" msgstr "批量命令" @@ -4601,15 +2044,35 @@ msgstr "批量命令" msgid "Task monitor" msgstr "任务监控" -#: templates/_nav.html:137 tickets/views.py:19 tickets/views.py:37 +#: templates/_nav.html:137 msgid "Tickets" msgstr "工单管理" +#: templates/_nav.html:146 +msgid "Audits" +msgstr "日志审计" + +#: templates/_nav.html:149 +msgid "Login log" +msgstr "登录日志" + +#: templates/_nav.html:150 +msgid "FTP log" +msgstr "FTP日志" + +#: templates/_nav.html:151 +msgid "Operate log" +msgstr "操作日志" + +#: templates/_nav.html:152 +msgid "Password change log" +msgstr "改密日志" + #: templates/_nav.html:163 msgid "XPack" msgstr "" -#: templates/_nav.html:171 xpack/plugins/cloud/views.py:28 +#: templates/_nav.html:171 msgid "Account list" msgstr "账户列表" @@ -4617,16 +2080,28 @@ msgstr "账户列表" msgid "Sync instance" msgstr "同步实例" +#: templates/_nav.html:187 +msgid "Settings" +msgstr "系统设置" + +#: templates/_nav_user.html:4 +msgid "My assets" +msgstr "我的资产" + #: templates/_nav_user.html:10 msgid "My Applications" msgstr "我的应用" +#: templates/_nav_user.html:31 +msgid "Command execution" +msgstr "命令执行" + #: templates/_pagination.html:59 msgid "" "Displays the results of items _START_ to _END_; A total of _TOTAL_ entries" msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项" -#: templates/_without_nav_base.html:26 +#: templates/_without_nav_base.html:25 msgid "Home page" msgstr "首页" @@ -4638,6 +2113,14 @@ msgstr "语言播放验证码" msgid "Captcha" msgstr "验证码" +#: templates/delete_confirm.html:6 +msgid "Confirm delete" +msgstr "确认删除" + +#: templates/delete_confirm.html:11 +msgid "Are you sure delete" +msgstr "您确定删除吗?" + #: templates/index.html:11 msgid "Total users" msgstr "用户总数" @@ -4788,19 +2271,19 @@ msgstr "登录了" msgid "Filters" msgstr "过滤" -#: terminal/api/session.py:142 +#: terminal/api/session.py:190 msgid "Session does not exist: {}" msgstr "会话不存在: {}" -#: terminal/api/session.py:145 +#: terminal/api/session.py:193 msgid "Session is finished or the protocol not supported" msgstr "会话已经完成或协议不支持" -#: terminal/api/session.py:150 +#: terminal/api/session.py:198 msgid "User does not exist: {}" msgstr "用户不存在: {}" -#: terminal/api/session.py:154 +#: terminal/api/session.py:202 msgid "User does not have permission" msgstr "用户没有权限" @@ -4821,14 +2304,10 @@ msgid "Test failure: Account invalid" msgstr "测试失败: 账户无效" #: terminal/backends/command/models.py:14 -#: terminal/templates/terminal/command_list.html:110 -#: terminal/templates/terminal/command_list.html:205 msgid "Ordinary" msgstr "普通" #: terminal/backends/command/models.py:15 -#: terminal/templates/terminal/command_list.html:111 -#: terminal/templates/terminal/command_list.html:202 msgid "Dangerous" msgstr "危险" @@ -4836,134 +2315,38 @@ msgstr "危险" msgid "Input" msgstr "输入" +#: terminal/backends/command/models.py:22 +msgid "Output" +msgstr "输出" + #: terminal/backends/command/models.py:23 -#: terminal/templates/terminal/command_list.html:33 -#: terminal/templates/terminal/terminal_list.html:34 msgid "Session" msgstr "会话" #: terminal/backends/command/models.py:24 -#: terminal/templates/terminal/command_list.html:29 -#: terminal/templates/terminal/command_list.html:109 msgid "Risk level" msgstr "风险等级" -#: terminal/forms/storage.py:41 -msgid "Container name" -msgstr "容器名称" - -#: terminal/forms/storage.py:44 xpack/plugins/cloud/serializers.py:75 -msgid "Account name" -msgstr "账户名称" - -#: terminal/forms/storage.py:47 -msgid "Account key" -msgstr "账户密钥" - -#: terminal/forms/storage.py:55 -msgid "Endpoint suffix" -msgstr "端点后缀" - -#: terminal/forms/storage.py:61 terminal/forms/storage.py:84 -#: terminal/forms/storage.py:108 terminal/forms/storage.py:125 -msgid "Bucket" -msgstr "桶名称" - -#: terminal/forms/storage.py:64 terminal/forms/storage.py:87 -#: terminal/forms/storage.py:111 terminal/forms/storage.py:128 -msgid "Access key" -msgstr "" - -#: terminal/forms/storage.py:68 terminal/forms/storage.py:91 -#: terminal/forms/storage.py:115 terminal/forms/storage.py:132 -msgid "Secret key" -msgstr "" - -#: terminal/forms/storage.py:72 terminal/forms/storage.py:95 -#: terminal/forms/storage.py:119 terminal/forms/storage.py:139 -msgid "Endpoint" -msgstr "端点" - -#: terminal/forms/storage.py:74 -#, python-brace-format -msgid "" -"\n" -" OSS: http://{REGION_NAME}.aliyuncs.com
    \n" -" Example: http://oss-cn-hangzhou.aliyuncs.com\n" -" " -msgstr "" - -#: terminal/forms/storage.py:97 -#, python-brace-format -msgid "" -"\n" -" S3: http://s3.{REGION_NAME}.amazonaws.com
    \n" -" S3(China): http://s3.{REGION_NAME}.amazonaws.com.cn
    \n" -" Example: http://s3.cn-north-1.amazonaws.com.cn\n" -" " -msgstr "" - -#: terminal/forms/storage.py:136 xpack/plugins/cloud/models.py:266 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:29 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:112 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:46 -msgid "Region" -msgstr "地域" - -#: terminal/forms/storage.py:153 -msgid "" -"\n" -" Tips: If there are multiple hosts, separate them with a comma " -"(,) \n" -"
    \n" -" eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com\n" -" " -msgstr "" -"\n" -" 提示: 如果有多台主机,请使用逗号 ( , ) 进行分割\n" -"
    \n" -" eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com\n" -" " - -#: terminal/forms/storage.py:161 -msgid "Index" -msgstr "索引" - -#: terminal/forms/storage.py:164 -msgid "Doc type" -msgstr "文档类型" - -#: terminal/forms/terminal.py:25 terminal/models.py:30 -#: terminal/templates/terminal/base_storage_list.html:10 -msgid "Command storage" -msgstr "命令存储" - -#: terminal/forms/terminal.py:26 -msgid "Command can store in server db or ES, default to server, more see docs" -msgstr "" -"命令支持存储到服务器端数据库、ES中,默认存储的服务器端数据库,更多查看文档" - -#: terminal/forms/terminal.py:30 terminal/models.py:31 -#: terminal/templates/terminal/base_storage_list.html:9 -msgid "Replay storage" -msgstr "录像存储" - -#: terminal/forms/terminal.py:31 -msgid "" -"Replay file can store in server disk, AWS S3, Aliyun OSS, default to server, " -"more see docs" -msgstr "" -"录像文件支持存储到服务器端硬盘、AWS S3、 阿里云 OSS 中,默认存储到服务器端硬" -"盘, 更多查看文档" - #: terminal/models.py:27 msgid "Remote Address" msgstr "远端地址" +#: terminal/models.py:28 +msgid "SSH Port" +msgstr "SSH端口" + #: terminal/models.py:29 msgid "HTTP Port" msgstr "HTTP端口" +#: terminal/models.py:30 +msgid "Command storage" +msgstr "命令存储" + +#: terminal/models.py:31 +msgid "Replay storage" +msgstr "录像存储" + #: terminal/models.py:154 msgid "Session Online" msgstr "在线会话" @@ -4988,11 +2371,11 @@ msgstr "线程数" msgid "Boot Time" msgstr "运行时间" -#: terminal/models.py:195 terminal/templates/terminal/session_list.html:135 +#: terminal/models.py:195 msgid "Replay" msgstr "回放" -#: terminal/models.py:200 terminal/templates/terminal/session_detail.html:76 +#: terminal/models.py:200 msgid "Date end" msgstr "结束日期" @@ -5000,191 +2383,11 @@ msgstr "结束日期" msgid "Args" msgstr "参数" -#: terminal/templates/terminal/command_list.html:44 -msgid "Export command" -msgstr "导出命令" - -#: terminal/templates/terminal/command_list.html:210 -msgid "Goto" -msgstr "转到" - -#: terminal/templates/terminal/command_storage_list.html:5 -#: terminal/views/storage.py:150 -msgid "Create command storage" -msgstr "创建命令存储" - -#: terminal/templates/terminal/replay_storage_list.html:5 -#: terminal/views/storage.py:97 -msgid "Create replay storage" -msgstr "创建录像存储" - -#: terminal/templates/terminal/session_commands.html:18 -#: terminal/templates/terminal/session_detail.html:18 -#: terminal/views/session.py:75 terminal/views/session.py:98 -msgid "Session detail" -msgstr "会话详情" - -#: terminal/templates/terminal/session_commands.html:29 -#: terminal/views/command.py:22 -msgid "Command list" -msgstr "命令记录列表" - -#: terminal/templates/terminal/session_commands.html:68 -msgid "There is no command about this session" -msgstr "该会话没有命令记录" - -#: terminal/templates/terminal/session_detail.html:64 -#: terminal/templates/terminal/session_list.html:30 -msgid "Login from" -msgstr "登录来源" - -#: terminal/templates/terminal/session_detail.html:94 -msgid "Replay session" -msgstr "回放会话" - -#: terminal/templates/terminal/session_detail.html:102 -msgid "Download replay" -msgstr "下载录像" - -#: terminal/templates/terminal/session_detail.html:105 -#: terminal/templates/terminal/session_list.html:137 -msgid "Download" -msgstr "下载" - -#: terminal/templates/terminal/session_detail.html:111 -msgid "Monitor session" -msgstr "监控" - -#: terminal/templates/terminal/session_detail.html:119 -msgid "Terminate session" -msgstr "终止会话" - -#: terminal/templates/terminal/session_detail.html:161 -msgid "Terminate success" -msgstr "终断成功" - -#: terminal/templates/terminal/session_list.html:33 -msgid "Duration" -msgstr "时长" - -#: terminal/templates/terminal/session_list.html:45 -msgid "Terminate selected" -msgstr "终断所选" - -#: terminal/templates/terminal/session_list.html:46 -msgid "Confirm finished" -msgstr "确认已完成" - -#: terminal/templates/terminal/session_list.html:90 -msgid "Terminate task send, waiting ..." -msgstr "终断任务已发送,请等待" - -#: terminal/templates/terminal/session_list.html:143 -msgid "Terminate" -msgstr "终断" - -#: terminal/templates/terminal/session_list.html:149 -msgid "Monitoring" -msgstr "监控" - -#: terminal/templates/terminal/session_list.html:179 -msgid "Finish session success" -msgstr "标记会话完成成功" - -#: terminal/templates/terminal/session_list.html:247 -msgid "Visit doc for replay play offline: " -msgstr "访问文档查看如何离线播放: " - -#: terminal/templates/terminal/terminal_detail.html:13 -#: terminal/views/terminal.py:62 -msgid "Terminal detail" -msgstr "终端详情" - -#: terminal/templates/terminal/terminal_detail.html:51 -msgid "SSH port" -msgstr "SSH端口" - -#: terminal/templates/terminal/terminal_detail.html:55 -msgid "Http port" -msgstr "HTTP端口" - -#: terminal/templates/terminal/terminal_list.html:21 -msgid "Storage configuration" -msgstr "存储配置" - -#: terminal/templates/terminal/terminal_list.html:31 -msgid "Addr" -msgstr "地址" - -#: terminal/templates/terminal/terminal_list.html:36 -msgid "Alive" -msgstr "在线" - -#: terminal/templates/terminal/terminal_list.html:79 -msgid "Accept" -msgstr "接受" - -#: terminal/templates/terminal/terminal_list.html:81 -#: tickets/models/ticket.py:31 tickets/templates/tickets/ticket_detail.html:101 -#: tickets/templates/tickets/ticket_list.html:110 -msgid "Reject" -msgstr "拒绝" - -#: terminal/templates/terminal/terminal_modal_accept.html:5 -msgid "Accept terminal registration" -msgstr "接受终端注册" - -#: terminal/templates/terminal/terminal_update.html:31 -msgid "Info" -msgstr "信息" - -#: terminal/views/session.py:49 -msgid "Session online list" -msgstr "在线会话" - -#: terminal/views/storage.py:28 -msgid "Replay storage list" -msgstr "录像存储列表" - -#: terminal/views/storage.py:43 -msgid "Command storage list" -msgstr "命令存储列表" - -#: terminal/views/storage.py:121 -msgid "Update replay storage" -msgstr "更新录像存储" - -#: terminal/views/storage.py:176 -msgid "Update command storage" -msgstr "更新命令存储" - -#: terminal/views/terminal.py:33 -msgid "Terminal list" -msgstr "终端列表" - -#: terminal/views/terminal.py:48 -msgid "Update terminal" -msgstr "更新终端" - -#: terminal/views/terminal.py:111 terminal/views/terminal.py:112 -msgid "Redirect to web terminal" -msgstr "重定向到web terminal" - -#: terminal/views/terminal.py:119 -msgid "Connect ssh terminal" -msgstr "连接ssh终端" - -#: terminal/views/terminal.py:120 -msgid "" -"You should use your ssh client tools connect terminal: {}

    {}" -msgstr "你可以使用ssh客户端工具连接终端" - #: tickets/models/ticket.py:18 tickets/models/ticket.py:70 -#: tickets/templates/tickets/ticket_list.html:105 msgid "Open" msgstr "开启" -#: tickets/models/ticket.py:19 tickets/templates/tickets/ticket_list.html:106 +#: tickets/models/ticket.py:19 msgid "Closed" msgstr "关闭" @@ -5192,17 +2395,19 @@ msgstr "关闭" msgid "General" msgstr "一般" -#: tickets/models/ticket.py:30 tickets/templates/tickets/ticket_detail.html:100 -#: tickets/templates/tickets/ticket_list.html:109 +#: tickets/models/ticket.py:30 msgid "Approve" msgstr "同意" +#: tickets/models/ticket.py:31 +msgid "Reject" +msgstr "拒绝" + #: tickets/models/ticket.py:34 tickets/models/ticket.py:129 msgid "User display name" msgstr "用户显示名称" -#: tickets/models/ticket.py:36 tickets/templates/tickets/ticket_list.html:33 -#: tickets/templates/tickets/ticket_list.html:102 +#: tickets/models/ticket.py:36 msgid "Title" msgstr "标题" @@ -5210,7 +2415,7 @@ msgstr "标题" msgid "Body" msgstr "内容" -#: tickets/models/ticket.py:39 tickets/templates/tickets/ticket_detail.html:51 +#: tickets/models/ticket.py:39 msgid "Assignee" msgstr "处理人" @@ -5218,7 +2423,7 @@ msgstr "处理人" msgid "Assignee display name" msgstr "处理人名称" -#: tickets/models/ticket.py:41 tickets/templates/tickets/ticket_detail.html:50 +#: tickets/models/ticket.py:41 msgid "Assignees" msgstr "待处理人" @@ -5234,23 +2439,6 @@ msgstr "{} {} 这个工单" msgid "this ticket" msgstr "这个工单" -#: tickets/templates/tickets/ticket_detail.html:66 -#: tickets/templates/tickets/ticket_detail.html:80 -msgid "ago" -msgstr "前" - -#: tickets/templates/tickets/ticket_list.html:9 -msgid "My tickets" -msgstr "我的工单" - -#: tickets/templates/tickets/ticket_list.html:10 -msgid "Assigned me" -msgstr "待处理" - -#: tickets/templates/tickets/ticket_list.html:19 -msgid "Create ticket" -msgstr "提交工单" - #: tickets/utils.py:18 msgid "New ticket" msgstr "新工单" @@ -5315,18 +2503,14 @@ msgstr "" " \n" " " -#: tickets/views.py:20 -msgid "Ticket list" -msgstr "工单列表" - -#: tickets/views.py:38 -msgid "Ticket detail" -msgstr "工单详情" - -#: users/api/user.py:113 +#: users/api/user.py:117 msgid "Could not reset self otp, use profile reset instead" msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置" +#: users/forms/group.py:19 users/forms/user.py:143 users/forms/user.py:148 +msgid "Select users" +msgstr "选择用户" + #: users/forms/profile.py:37 msgid "" "When enabled, you will enter the MFA binding process the next time you log " @@ -5349,9 +2533,7 @@ msgstr "" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "设置复杂密码,并启用多因子认证)" -#: users/forms/profile.py:64 users/templates/users/first_login.html:48 -#: users/templates/users/first_login.html:102 -#: users/templates/users/first_login.html:128 +#: users/forms/profile.py:64 msgid "Finish" msgstr "完成" @@ -5367,6 +2549,12 @@ msgstr "确认密码" msgid "Password does not match" msgstr "密码不一致" +#: users/forms/profile.py:89 users/models/user.py:468 +#: users/templates/users/user_detail.html:57 +#: users/templates/users/user_profile.html:59 +msgid "Email" +msgstr "邮件" + #: users/forms/profile.py:96 msgid "Old password" msgstr "原来密码" @@ -5397,11 +2585,12 @@ msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" #: users/forms/profile.py:137 users/forms/user.py:90 -#: users/serializers/user.py:167 users/serializers/user.py:287 +#: users/serializers/user.py:177 users/serializers/user.py:258 +#: users/serializers/user.py:316 msgid "Not a valid ssh public key" msgstr "SSH密钥不合法" -#: users/forms/user.py:27 users/models/user.py:472 +#: users/forms/user.py:27 users/models/user.py:476 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:73 #: users/templates/users/user_list.html:16 @@ -5409,7 +2598,7 @@ msgstr "SSH密钥不合法" msgid "Role" msgstr "角色" -#: users/forms/user.py:31 users/models/user.py:507 +#: users/forms/user.py:31 users/models/user.py:511 #: users/templates/users/user_detail.html:89 #: users/templates/users/user_list.html:18 #: users/templates/users/user_profile.html:102 @@ -5424,67 +2613,66 @@ msgstr "复制用户公钥到这里" msgid "Join user groups" msgstr "添加到用户组" -#: users/forms/user.py:103 users/views/login.py:123 -#: users/views/profile/password.py:57 +#: users/forms/user.py:103 users/views/profile/password.py:57 +#: users/views/profile/reset.py:123 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/forms/user.py:124 users/serializers/user.py:28 +#: users/forms/user.py:124 users/serializers/user.py:30 msgid "Reset link will be generated and sent to the user" msgstr "生成重置密码链接,通过邮件发送给用户" -#: users/forms/user.py:125 users/serializers/user.py:29 +#: users/forms/user.py:125 users/serializers/user.py:31 msgid "Set password" msgstr "设置密码" -#: users/forms/user.py:132 users/serializers/user.py:36 +#: users/forms/user.py:132 users/serializers/user.py:38 #: xpack/plugins/change_auth_plan/models.py:60 #: xpack/plugins/change_auth_plan/serializers.py:30 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:45 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:67 -#: 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/models/user.py:154 users/models/user.py:618 +#: users/models/user.py:158 users/models/user.py:622 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:157 xpack/plugins/orgs/forms.py:29 -#: xpack/plugins/orgs/templates/orgs/org_list.html:14 +#: users/models/user.py:160 +msgid "Application" +msgstr "应用程序" + +#: users/models/user.py:161 msgid "Auditor" msgstr "审计员" -#: users/models/user.py:167 +#: users/models/user.py:171 msgid "Org admin" msgstr "组织管理员" -#: users/models/user.py:169 +#: users/models/user.py:173 msgid "Org auditor" msgstr "组织审计员" -#: users/models/user.py:384 users/templates/users/user_profile.html:90 +#: users/models/user.py:388 users/templates/users/user_profile.html:90 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:451 +#: users/models/user.py:455 msgid "Local" msgstr "数据库" -#: users/models/user.py:475 +#: users/models/user.py:479 msgid "Avatar" msgstr "头像" -#: users/models/user.py:478 users/templates/users/user_detail.html:68 +#: users/models/user.py:482 users/templates/users/user_detail.html:68 msgid "Wechat" msgstr "微信" -#: users/models/user.py:511 +#: users/models/user.py:515 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:621 +#: users/models/user.py:625 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -5492,39 +2680,39 @@ msgstr "Administrator是初始的超级管理员" msgid "Auditors cannot be join in the user group" msgstr "审计员不能被加入到用户组" -#: users/serializers/user.py:67 +#: users/serializers/user.py:69 users/serializers/user.py:229 msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:68 +#: users/serializers/user.py:70 msgid "Is valid" msgstr "账户是否有效" -#: users/serializers/user.py:69 +#: users/serializers/user.py:71 msgid "Is expired" msgstr " 是否过期" -#: users/serializers/user.py:70 +#: users/serializers/user.py:72 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:74 +#: users/serializers/user.py:76 msgid "Groups name" msgstr "用户组名" -#: users/serializers/user.py:75 +#: users/serializers/user.py:77 msgid "Source name" msgstr "用户来源名" -#: users/serializers/user.py:76 +#: users/serializers/user.py:78 msgid "Role name" msgstr "角色名" -#: users/serializers/user.py:95 +#: users/serializers/user.py:97 msgid "Role limit to {}" msgstr "角色只能为 {}" -#: users/serializers/user.py:107 users/serializers/user.py:253 +#: users/serializers/user.py:109 users/serializers/user.py:282 msgid "Password does not match security rules" msgstr "密码不满足安全规则" @@ -5539,8 +2727,6 @@ msgstr "安全令牌验证" #: users/templates/users/_base_otp.html:14 users/templates/users/_user.html:13 #: users/templates/users/user_profile_update.html:55 #: xpack/plugins/cloud/models.py:119 -#: 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 "账户" @@ -5548,6 +2734,10 @@ msgstr "账户" msgid "Follow these steps to complete the binding operation" msgstr "请按照以下步骤完成绑定操作" +#: users/templates/users/_granted_assets.html:7 +msgid "Loading" +msgstr "加载中" + #: users/templates/users/_select_user_modal.html:5 msgid "Please Select User" msgstr "选择用户" @@ -5556,12 +2746,37 @@ msgstr "选择用户" msgid "Asset num" msgstr "资产数量" +#: users/templates/users/_user.html:21 +msgid "Auth" +msgstr "认证" + #: users/templates/users/_user.html:27 msgid "Security and Role" msgstr "角色安全" +#: users/templates/users/_user.html:51 +#: users/templates/users/user_bulk_update.html:23 +#: users/templates/users/user_detail.html:168 +#: users/templates/users/user_group_create_update.html:27 +#: users/templates/users/user_password_update.html:74 +#: users/templates/users/user_profile.html:209 +#: users/templates/users/user_profile_update.html:67 +#: users/templates/users/user_pubkey_update.html:74 +#: users/templates/users/user_pubkey_update.html:80 +msgid "Reset" +msgstr "重置" + +#: users/templates/users/_user.html:52 +#: users/templates/users/forgot_password.html:24 +#: users/templates/users/user_bulk_update.html:24 +#: users/templates/users/user_list.html:40 +#: users/templates/users/user_password_update.html:75 +#: users/templates/users/user_profile_update.html:68 +#: users/templates/users/user_pubkey_update.html:81 +msgid "Submit" +msgstr "提交" + #: users/templates/users/_user_detail_nav_header.html:11 -#: users/views/user.py:179 msgid "User detail" msgstr "用户详情" @@ -5587,28 +2802,11 @@ msgstr "授权的数据库应用" msgid "Update User SSH Public Key" msgstr "更新SSH密钥" -#: users/templates/users/first_login.html:19 +#: users/templates/users/first_login.html:6 #: users/templates/users/first_login_done.html:19 msgid "First Login" msgstr "首次登录" -#: users/templates/users/first_login.html:65 -msgid "I agree with the terms and conditions." -msgstr "我同意条款和条件" - -#: users/templates/users/first_login.html:66 -msgid "Please choose the terms and conditions." -msgstr "请选择同意条款和条件" - -#: users/templates/users/first_login.html:70 -#: users/templates/users/user_update.html:32 -msgid "User auth from {}, ssh key login is not supported" -msgstr "用户认证源来自 {}, 不支持使用 SSH Key 登录" - -#: users/templates/users/first_login.html:96 -msgid "Previous" -msgstr "上一步" - #: users/templates/users/first_login_done.html:31 msgid "Welcome to use jumpserver, visit " msgstr "欢迎使用 JumpServer 堡垒机" @@ -5633,62 +2831,91 @@ msgstr "重置密码" #: users/templates/users/reset_password.html:23 #: users/templates/users/user_create.html:13 -#: users/templates/users/user_password_update.html:65 +#: users/templates/users/user_password_update.html:64 #: users/templates/users/user_update.html:13 msgid "Your password must satisfy" msgstr "您的密码必须满足:" #: users/templates/users/reset_password.html:24 #: users/templates/users/user_create.html:14 -#: users/templates/users/user_password_update.html:66 +#: users/templates/users/user_password_update.html:65 #: users/templates/users/user_update.html:14 msgid "Password strength" msgstr "密码强度:" #: users/templates/users/reset_password.html:48 #: users/templates/users/user_create.html:33 -#: users/templates/users/user_password_update.html:103 +#: users/templates/users/user_password_update.html:102 #: users/templates/users/user_update.html:55 msgid "Very weak" msgstr "很弱" #: users/templates/users/reset_password.html:49 #: users/templates/users/user_create.html:34 -#: users/templates/users/user_password_update.html:104 +#: users/templates/users/user_password_update.html:103 #: users/templates/users/user_update.html:56 msgid "Weak" msgstr "弱" #: users/templates/users/reset_password.html:50 #: users/templates/users/user_create.html:35 -#: users/templates/users/user_password_update.html:105 +#: users/templates/users/user_password_update.html:104 #: users/templates/users/user_update.html:57 msgid "Normal" msgstr "正常" #: users/templates/users/reset_password.html:51 #: users/templates/users/user_create.html:36 -#: users/templates/users/user_password_update.html:106 +#: users/templates/users/user_password_update.html:105 #: users/templates/users/user_update.html:58 msgid "Medium" msgstr "一般" #: users/templates/users/reset_password.html:52 #: users/templates/users/user_create.html:37 -#: users/templates/users/user_password_update.html:107 +#: users/templates/users/user_password_update.html:106 #: users/templates/users/user_update.html:59 msgid "Strong" msgstr "强" #: users/templates/users/reset_password.html:53 #: users/templates/users/user_create.html:38 -#: users/templates/users/user_password_update.html:108 +#: users/templates/users/user_password_update.html:107 #: users/templates/users/user_update.html:60 msgid "Very strong" msgstr "很强" +#: users/templates/users/user_asset_permission.html:43 +#: users/templates/users/user_asset_permission.html:155 +#: users/templates/users/user_database_app_permission.html:41 +#: users/templates/users/user_list.html:19 +#: users/templates/users/user_remote_app_permission.html:41 +#: xpack/plugins/cloud/models.py:50 xpack/plugins/cloud/serializers.py:32 +msgid "Validity" +msgstr "有效" + +#: users/templates/users/user_asset_permission.html:160 +msgid "Inherit" +msgstr "继承" + +#: users/templates/users/user_asset_permission.html:161 +msgid "Include" +msgstr "包含" + +#: users/templates/users/user_asset_permission.html:162 +msgid "Exclude" +msgstr "不包含" + +#: users/templates/users/user_bulk_update.html:8 +msgid "Select properties that need to be modified" +msgstr "选择需要修改属性" + +#: users/templates/users/user_bulk_update.html:10 +msgid "Select all" +msgstr "全选" + #: users/templates/users/user_create.html:4 -#: users/templates/users/user_list.html:7 users/views/user.py:68 +#: users/templates/users/user_list.html:7 msgid "Create user" msgstr "创建用户" @@ -5696,6 +2923,11 @@ msgstr "创建用户" msgid "Force enabled" msgstr "强制启用" +#: users/templates/users/user_detail.html:101 +#: users/templates/users/user_profile.html:106 +msgid "Date joined" +msgstr "创建日期" + #: users/templates/users/user_detail.html:105 #: users/templates/users/user_profile.html:110 msgid "Last login" @@ -5706,6 +2938,11 @@ msgstr "最后登录" msgid "Last password updated" msgstr "最后更新密码" +#: users/templates/users/user_detail.html:126 +#: users/templates/users/user_profile.html:150 +msgid "Quick modify" +msgstr "快速修改" + #: users/templates/users/user_detail.html:148 msgid "Force enabled MFA" msgstr "强制启用多因子认证" @@ -5736,6 +2973,15 @@ msgstr "解除登录限制" msgid "Unblock" msgstr "解除" +#: users/templates/users/user_detail.html:226 +msgid "Join" +msgstr "加入" + +#: users/templates/users/user_detail.html:356 +#: users/templates/users/user_detail.html:383 +msgid "Update successfully!" +msgstr "更新成功" + #: users/templates/users/user_detail.html:365 msgid "Goto profile page enable MFA" msgstr "请去个人信息页面启用自己的多因子认证" @@ -5744,10 +2990,24 @@ msgstr "请去个人信息页面启用自己的多因子认证" msgid "An e-mail has been sent to the user`s mailbox." msgstr "已发送邮件到用户邮箱" +#: users/templates/users/user_detail.html:411 +#: users/templates/users/user_detail.html:437 +#: users/templates/users/user_detail.html:505 +#: users/templates/users/user_list.html:178 +msgid "Are you sure?" +msgstr "你确认吗?" + #: users/templates/users/user_detail.html:412 msgid "This will reset the user password and send a reset mail" msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱" +#: users/templates/users/user_detail.html:415 +#: users/templates/users/user_detail.html:441 +#: users/templates/users/user_detail.html:509 +#: users/templates/users/user_list.html:182 +msgid "Cancel" +msgstr "取消" + #: users/templates/users/user_detail.html:427 msgid "" "The reset-ssh-public-key E-mail has been sent successfully. Please inform " @@ -5781,23 +3041,46 @@ msgstr "重置用户多因子认证成功" #: users/templates/users/user_group_detail.html:17 #: users/templates/users/user_group_granted_asset.html:18 -#: users/views/group.py:83 msgid "User group detail" msgstr "用户组详情" #: users/templates/users/user_group_detail.html:81 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:116 msgid "Add user" msgstr "添加用户" -#: users/templates/users/user_group_list.html:7 users/views/group.py:46 +#: users/templates/users/user_group_detail.html:87 +msgid "Add" +msgstr "添加" + +#: users/templates/users/user_group_list.html:7 msgid "Create user group" msgstr "创建用户组" +#: users/templates/users/user_list.html:30 +msgid "Delete selected" +msgstr "批量删除" + #: users/templates/users/user_list.html:32 msgid "Remove selected" msgstr "批量移除" +#: users/templates/users/user_list.html:34 +msgid "Update selected" +msgstr "批量更新" + +#: users/templates/users/user_list.html:35 +msgid "Deactive selected" +msgstr "禁用所选" + +#: users/templates/users/user_list.html:36 +msgid "Active selected" +msgstr "激活所选" + +#: users/templates/users/user_list.html:106 +#: users/templates/users/user_list.html:110 +msgid "Remove" +msgstr "移除" + #: users/templates/users/user_list.html:179 msgid "This will delete the selected users !!!" msgstr "删除选中用户 !!!" @@ -5863,8 +3146,10 @@ msgid "Install app" msgstr "安装应用" #: users/templates/users/user_otp_enable_install_app.html:13 -msgid "Download and install the Google Authenticator application on your phone" -msgstr "请在手机端下载并安装 Google Authenticator 应用" +msgid "" +"Download and install the Google Authenticator application on your phone or " +"applet of WeChat" +msgstr "请在手机端或微信小程序下载并安装 Google Authenticator 应用" #: users/templates/users/user_otp_enable_install_app.html:18 msgid "Android downloads" @@ -5878,7 +3163,7 @@ msgstr "iPhone手机下载" msgid "" "After installation, click the next step to enter the binding page (if " "installed, go to the next step directly)." -msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接进入下一步" +msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接进入下一步)" #: users/templates/users/user_password_verify.html:8 #: users/templates/users/user_password_verify.html:9 @@ -5931,15 +3216,19 @@ msgid "" "corresponding private key." msgstr "新的公钥已设置成功,请下载对应的私钥" -#: users/templates/users/user_update.html:4 users/views/user.py:112 +#: users/templates/users/user_update.html:4 msgid "Update user" msgstr "更新用户" -#: users/templates/users/user_update.html:22 users/views/login.py:49 -#: users/views/login.py:116 +#: users/templates/users/user_update.html:22 users/views/profile/reset.py:49 +#: users/views/profile/reset.py:116 msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" +#: users/templates/users/user_update.html:32 +msgid "User auth from {}, ssh key login is not supported" +msgstr "用户认证源来自 {}, 不支持使用 SSH Key 登录" + #: users/templates/users/user_verify_mfa.html:11 msgid "" "The account protection has been opened, please complete the following " @@ -6152,51 +3441,6 @@ msgstr "" "
    \n" " " -#: users/views/group.py:29 -msgid "User group list" -msgstr "用户组列表" - -#: users/views/group.py:64 -msgid "Update user group" -msgstr "更新用户组" - -#: users/views/group.py:100 -msgid "User group granted asset" -msgstr "用户组授权资产" - -#: users/views/login.py:45 -msgid "Email address invalid, please input again" -msgstr "邮箱地址错误,重新输入" - -#: users/views/login.py:62 -msgid "Send reset password message" -msgstr "发送重置密码邮件" - -#: users/views/login.py:63 -msgid "Send reset password mail success, login your mail box and follow it " -msgstr "" -"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" - -#: users/views/login.py:76 -msgid "Reset password success" -msgstr "重置密码成功" - -#: users/views/login.py:77 -msgid "Reset password success, return to login page" -msgstr "重置密码成功,返回到登录页面" - -#: users/views/login.py:101 users/views/login.py:111 -msgid "Token invalid or expired" -msgstr "Token错误或失效" - -#: users/views/login.py:163 -msgid "First login" -msgstr "首次登录" - -#: users/views/profile/base.py:47 -msgid "Profile setting" -msgstr "个人信息设置" - #: users/views/profile/otp.py:145 msgid "MFA enable success" msgstr "多因子认证启用成功" @@ -6225,48 +3469,34 @@ msgstr "用户名或密码无效" msgid "Public key update" msgstr "密钥更新" -#: users/views/user.py:130 -msgid "Bulk update user success" -msgstr "批量更新用户成功" +#: users/views/profile/reset.py:45 +msgid "Email address invalid, please input again" +msgstr "邮箱地址错误,重新输入" -#: users/views/user.py:158 -msgid "Bulk update user" -msgstr "批量更新用户" +#: users/views/profile/reset.py:62 +msgid "Send reset password message" +msgstr "发送重置密码邮件" -#: users/views/user.py:207 -msgid "User granted assets" -msgstr "用户授权资产" - -#: users/views/user.py:235 -msgid "User granted RemoteApp" -msgstr "用户授权远程应用" - -#: users/views/user.py:263 -msgid "User granted DatabaseApp" -msgstr "用户授权数据库应用" - -#: xpack/plugins/change_auth_plan/forms.py:21 -msgid "Password length" -msgstr "密码长度" - -#: xpack/plugins/change_auth_plan/forms.py:79 -msgid "" -"Tips: The username of the user on the asset to be modified. if the user " -"exists, change the password; If the user does not exist, create the user." +#: users/views/profile/reset.py:63 +msgid "Send reset password mail success, login your mail box and follow it " msgstr "" -"提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如果" -"用户不存在,则创建用户。" +"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" + +#: users/views/profile/reset.py:76 +msgid "Reset password success" +msgstr "重置密码成功" + +#: users/views/profile/reset.py:77 +msgid "Reset password success, return to login page" +msgstr "重置密码成功,返回到登录页面" + +#: users/views/profile/reset.py:101 users/views/profile/reset.py:111 +msgid "Token invalid or expired" +msgstr "Token错误或失效" #: xpack/plugins/change_auth_plan/meta.py:9 #: xpack/plugins/change_auth_plan/models.py:88 #: xpack/plugins/change_auth_plan/models.py:183 -#: xpack/plugins/change_auth_plan/views.py:33 -#: xpack/plugins/change_auth_plan/views.py:50 -#: xpack/plugins/change_auth_plan/views.py:74 -#: xpack/plugins/change_auth_plan/views.py:90 -#: xpack/plugins/change_auth_plan/views.py:117 -#: xpack/plugins/change_auth_plan/views.py:132 -#: xpack/plugins/change_auth_plan/views.py:147 msgid "Change auth plan" msgstr "改密计划" @@ -6283,7 +3513,6 @@ msgid "All assets use different random password" msgstr "所有资产使用不同的随机密码" #: xpack/plugins/change_auth_plan/models.py:64 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:72 msgid "Password rules" msgstr "密码规则" @@ -6292,38 +3521,46 @@ msgid "Change auth plan snapshot" msgstr "改密计划快照" #: xpack/plugins/change_auth_plan/models.py:202 -#: xpack/plugins/change_auth_plan/models.py:288 +#: xpack/plugins/change_auth_plan/models.py:296 msgid "Change auth plan execution" msgstr "改密计划执行" -#: xpack/plugins/change_auth_plan/models.py:261 +#: xpack/plugins/change_auth_plan/models.py:269 msgid "Ready" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:262 +#: xpack/plugins/change_auth_plan/models.py:270 msgid "Preflight check" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:263 +#: xpack/plugins/change_auth_plan/models.py:271 msgid "Change auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:264 +#: xpack/plugins/change_auth_plan/models.py:272 msgid "Verify auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:265 +#: xpack/plugins/change_auth_plan/models.py:273 msgid "Keep auth" msgstr "" -#: xpack/plugins/change_auth_plan/models.py:292 +#: xpack/plugins/change_auth_plan/models.py:274 +msgid "Finished" +msgstr "结束" + +#: xpack/plugins/change_auth_plan/models.py:300 msgid "Step" msgstr "步骤" -#: xpack/plugins/change_auth_plan/models.py:309 +#: xpack/plugins/change_auth_plan/models.py:317 msgid "Change auth plan task" msgstr "改密计划任务" +#: xpack/plugins/change_auth_plan/serializers.py:54 +msgid "Run times" +msgstr "执行次数" + #: xpack/plugins/change_auth_plan/serializers.py:70 msgid "* Please enter custom password" msgstr "* 请输入自定义密码" @@ -6336,63 +3573,6 @@ msgstr "* 请输入正确的密码长度" msgid "* Password length range 6-30 bits" msgstr "* 密码长度范围 6-30 位" -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:19 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:24 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:23 -#: xpack/plugins/change_auth_plan/views.py:133 -msgid "Plan execution list" -msgstr "执行列表" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:62 -msgid "Add asset to this plan" -msgstr "添加资产" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:87 -msgid "Add node to this plan" -msgstr "添加节点" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:7 -msgid "" -"When the user password on the asset is changed, the action is performed " -"using the admin user associated with the asset" -msgstr "更改资产上的用户密码时,将会使用与该资产关联的管理用户进行操作" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74 -msgid "Length" -msgstr "长度" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:132 -msgid "Run plan manually" -msgstr "手动执行计划" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:176 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:102 -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:90 -msgid "Execute failed" -msgstr "执行失败" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:31 -msgid "Execution list of plan" -msgstr "执行历史列表" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:104 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:106 -msgid "Log" -msgstr "日志" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:61 -msgid "Retry" -msgstr "重试" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:95 -msgid "Run failed" -msgstr "执行失败" - -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:5 -#: xpack/plugins/change_auth_plan/views.py:51 -msgid "Create plan" -msgstr "创建计划" - #: xpack/plugins/change_auth_plan/utils.py:437 msgid "Invalid/incorrect password" msgstr "无效/错误 密码" @@ -6405,56 +3585,7 @@ msgstr "连接主机失败" msgid "Data could not be sent to remote" msgstr "无法将数据发送到远程" -#: xpack/plugins/change_auth_plan/views.py:34 -msgid "Plan list" -msgstr "计划列表" - -#: xpack/plugins/change_auth_plan/views.py:75 -msgid "Update plan" -msgstr "更新计划" - -#: xpack/plugins/change_auth_plan/views.py:148 -msgid "Plan execution task list" -msgstr "执行任务列表" - -#: xpack/plugins/cloud/forms.py:15 -msgid "Access Key" -msgstr "" - -#: xpack/plugins/cloud/forms.py:19 -msgid "Secret Key" -msgstr "" - -#: xpack/plugins/cloud/forms.py:56 -msgid "Select account" -msgstr "选择账户" - -#: xpack/plugins/cloud/forms.py:62 -msgid "Select regions" -msgstr "选择地域" - -#: xpack/plugins/cloud/forms.py:68 -msgid "Select instances" -msgstr "选择实例" - -#: xpack/plugins/cloud/forms.py:74 -msgid "Select node" -msgstr "选择节点" - -#: xpack/plugins/cloud/forms.py:80 xpack/plugins/orgs/forms.py:20 -msgid "Select admins" -msgstr "选择管理员" - -#: xpack/plugins/cloud/forms.py:85 -msgid "Tips: The asset information is always covered" -msgstr "" - -#: xpack/plugins/cloud/meta.py:9 xpack/plugins/cloud/views.py:27 -#: xpack/plugins/cloud/views.py:44 xpack/plugins/cloud/views.py:62 -#: xpack/plugins/cloud/views.py:78 xpack/plugins/cloud/views.py:92 -#: xpack/plugins/cloud/views.py:109 xpack/plugins/cloud/views.py:127 -#: xpack/plugins/cloud/views.py:143 xpack/plugins/cloud/views.py:158 -#: xpack/plugins/cloud/views.py:172 +#: xpack/plugins/cloud/meta.py:9 msgid "Cloud center" msgstr "云管中心" @@ -6467,8 +3598,6 @@ msgid "Unavailable" msgstr "无效" #: xpack/plugins/cloud/models.py:39 xpack/plugins/cloud/serializers.py:31 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:51 -#: xpack/plugins/cloud/templates/cloud/account_list.html:13 msgid "Provider" msgstr "云服务商" @@ -6481,7 +3610,6 @@ msgid "Access key secret" msgstr "" #: xpack/plugins/cloud/models.py:64 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:26 msgid "Cloud account" msgstr "云账号" @@ -6494,13 +3622,10 @@ msgid "Instances" msgstr "实例" #: xpack/plugins/cloud/models.py:136 xpack/plugins/cloud/serializers.py:77 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:69 msgid "Covered always" msgstr "总是被覆盖" #: xpack/plugins/cloud/models.py:142 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:104 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:17 msgid "Date last sync" msgstr "最后同步日期" @@ -6513,8 +3638,6 @@ msgid "Succeed" msgstr "成功" #: xpack/plugins/cloud/models.py:220 xpack/plugins/cloud/models.py:275 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:51 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:49 msgid "Date sync" msgstr "同步日期" @@ -6539,15 +3662,18 @@ msgid "Sync instance task history" msgstr "同步实例任务历史" #: xpack/plugins/cloud/models.py:263 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:45 msgid "Instance" msgstr "实例" -#: xpack/plugins/cloud/providers/aliyun.py:16 +#: xpack/plugins/cloud/models.py:266 +msgid "Region" +msgstr "地域" + +#: xpack/plugins/cloud/providers/aliyun.py:19 msgid "Alibaba Cloud" msgstr "阿里云" -#: xpack/plugins/cloud/providers/aws.py:14 +#: xpack/plugins/cloud/providers/aws.py:15 msgid "AWS (International)" msgstr "AWS (国际)" @@ -6555,53 +3681,63 @@ msgstr "AWS (国际)" msgid "AWS (China)" msgstr "AWS (中国)" -#: xpack/plugins/cloud/providers/huaweicloud.py:13 +#: xpack/plugins/cloud/providers/huaweicloud.py:17 msgid "Huawei Cloud" msgstr "华为云" -#: xpack/plugins/cloud/providers/huaweicloud.py:16 -msgid "CN North-Beijing4" -msgstr "华北-北京4" - -#: xpack/plugins/cloud/providers/huaweicloud.py:17 -msgid "CN East-Shanghai1" -msgstr "华东-上海1" - -#: xpack/plugins/cloud/providers/huaweicloud.py:18 -msgid "CN East-Shanghai2" -msgstr "华东-上海2" - -#: xpack/plugins/cloud/providers/huaweicloud.py:19 -msgid "CN South-Guangzhou" -msgstr "华南-广州" - #: xpack/plugins/cloud/providers/huaweicloud.py:20 -msgid "CN Southwest-Guiyang1" -msgstr "西南-贵阳1" +msgid "AF-Johannesburg" +msgstr "非洲-约翰内斯堡" #: xpack/plugins/cloud/providers/huaweicloud.py:21 -#, fuzzy -#| msgid "AP-Hong Kong" -msgid "AP-Hong-Kong" -msgstr "亚太-香港" - -#: xpack/plugins/cloud/providers/huaweicloud.py:22 msgid "AP-Bangkok" msgstr "亚太-曼谷" +#: xpack/plugins/cloud/providers/huaweicloud.py:22 +msgid "AP-Hong Kong" +msgstr "亚太-香港" + #: xpack/plugins/cloud/providers/huaweicloud.py:23 msgid "AP-Singapore" msgstr "亚太-新加坡" #: xpack/plugins/cloud/providers/huaweicloud.py:24 -msgid "AF-Johannesburg" -msgstr "非洲-约翰内斯堡" +msgid "CN East-Shanghai1" +msgstr "华东-上海1" #: xpack/plugins/cloud/providers/huaweicloud.py:25 +msgid "CN East-Shanghai2" +msgstr "华东-上海2" + +#: xpack/plugins/cloud/providers/huaweicloud.py:26 +msgid "CN North-Beijing1" +msgstr "华北-北京1" + +#: xpack/plugins/cloud/providers/huaweicloud.py:27 +msgid "CN North-Beijing4" +msgstr "华北-北京4" + +#: xpack/plugins/cloud/providers/huaweicloud.py:28 +msgid "CN Northeast-Dalian" +msgstr "华北-大连" + +#: xpack/plugins/cloud/providers/huaweicloud.py:29 +msgid "CN South-Guangzhou" +msgstr "华南-广州" + +#: xpack/plugins/cloud/providers/huaweicloud.py:30 +msgid "CN Southwest-Guiyang1" +msgstr "西南-贵阳1" + +#: xpack/plugins/cloud/providers/huaweicloud.py:31 +msgid "EU-Paris" +msgstr "欧洲-巴黎" + +#: xpack/plugins/cloud/providers/huaweicloud.py:32 msgid "LA-Santiago" msgstr "拉美-圣地亚哥" -#: xpack/plugins/cloud/providers/qcloud.py:14 +#: xpack/plugins/cloud/providers/qcloud.py:17 msgid "Tencent Cloud" msgstr "腾讯云" @@ -6610,121 +3746,27 @@ msgid "History count" msgstr "用户数量" #: xpack/plugins/cloud/serializers.py:54 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:15 msgid "Instance count" msgstr "实例个数" +#: xpack/plugins/cloud/serializers.py:75 +msgid "Account name" +msgstr "账户名称" + #: xpack/plugins/cloud/serializers.py:76 #: xpack/plugins/gathered_user/serializers.py:20 msgid "Periodic display" msgstr "定时执行" -#: xpack/plugins/cloud/templates/cloud/account_detail.html:17 -#: xpack/plugins/cloud/views.py:79 -msgid "Account detail" -msgstr "账户详情" - -#: xpack/plugins/cloud/templates/cloud/account_list.html:5 -#: xpack/plugins/cloud/views.py:45 -msgid "Create account" -msgstr "创建账户" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:33 -msgid "Node & AdminUser" -msgstr "节点 & 管理用户" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:66 -msgid "Load failed" -msgstr "加载失败" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:17 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:19 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:18 -#: xpack/plugins/cloud/views.py:144 -msgid "Sync task detail" -msgstr "同步任务详情" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:20 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:22 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:21 -#: xpack/plugins/cloud/views.py:159 -msgid "Sync task history" -msgstr "同步历史列表" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:23 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:25 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:24 -#: xpack/plugins/cloud/views.py:173 -msgid "Sync instance list" -msgstr "同步实例列表" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:145 -msgid "Run task manually" -msgstr "手动执行任务" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:188 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:102 -msgid "Sync success" -msgstr "同步成功" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:46 -msgid "New count" -msgstr "新增" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:47 -msgid "Unsync count" -msgstr "未同步" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:48 -msgid "Synced count" -msgstr "已同步" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:49 -msgid "Released count" -msgstr "已释放" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 -msgid "Delete released assets" -msgstr "删除已释放的资产" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:5 -msgid "Create sync instance task" -msgstr "创建同步实例任务" - -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:14 -msgid "Run count" -msgstr "执行次数" - #: xpack/plugins/cloud/utils.py:38 msgid "Account unavailable" msgstr "账户无效" -#: xpack/plugins/cloud/views.py:63 -msgid "Update account" -msgstr "更新账户" - -#: xpack/plugins/cloud/views.py:93 -msgid "Sync instance task list" -msgstr "同步实例任务列表" - -#: xpack/plugins/cloud/views.py:110 -msgid "Create sync Instance task" -msgstr "创建同步实例任务" - -#: xpack/plugins/cloud/views.py:128 -msgid "Update sync Instance task" -msgstr "更新同步实例任务" - #: xpack/plugins/gathered_user/meta.py:11 -#: xpack/plugins/gathered_user/views.py:21 -#: xpack/plugins/gathered_user/views.py:34 -#: xpack/plugins/gathered_user/views.py:49 -#: xpack/plugins/gathered_user/views.py:69 msgid "Gathered user" msgstr "收集用户" #: xpack/plugins/gathered_user/models.py:39 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:13 msgid "Gather user task" msgstr "收集用户任务" @@ -6744,119 +3786,47 @@ msgstr "资产为空,请更改节点" msgid "Executed times" msgstr "执行次数" -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:170 -msgid "Asset user" -msgstr "资产用户" +#: xpack/plugins/interface/api.py:68 +msgid "It is already in the default setting state!" +msgstr "当前已经是初始化状态!" -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:7 -#: xpack/plugins/gathered_user/views.py:50 -msgid "Create task" -msgstr "创建任务" - -#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:18 -msgid "Periodic" -msgstr "定时执行" - -#: xpack/plugins/gathered_user/views.py:22 -msgid "Gathered user list" -msgstr "收集用户列表" - -#: xpack/plugins/gathered_user/views.py:70 -msgid "Update task" -msgstr "更新任务" - -#: xpack/plugins/interface/forms.py:17 xpack/plugins/interface/models.py:15 -msgid "Title of login page" -msgstr "登录页面标题" - -#: xpack/plugins/interface/forms.py:19 -msgid "" -"Tips: This will be displayed on the enterprise user login page. (eg: Welcome " -"to the JumpServer open source fortress)" -msgstr "提示:将会显示在企业版用户登录页面(eg: 欢迎使用JumpServer开源堡垒机)" - -#: xpack/plugins/interface/forms.py:25 xpack/plugins/interface/models.py:19 -msgid "Image of login page" -msgstr "登录页面图片" - -#: xpack/plugins/interface/forms.py:27 -msgid "" -"Tips: This will be displayed on the enterprise user login page. (suggest " -"image size: 492px*472px)" -msgstr "提示:将会显示在企业版用户登录页面(建议图片大小为: 492*472px)" - -#: xpack/plugins/interface/forms.py:33 xpack/plugins/interface/models.py:23 -msgid "Website icon" -msgstr "网站图标" - -#: xpack/plugins/interface/forms.py:35 -msgid "Tips: website icon. (suggest image size: 16px*16px)" -msgstr "提示:网站图标(建议图片大小为: 16px*16px)" - -#: xpack/plugins/interface/forms.py:40 xpack/plugins/interface/models.py:27 -msgid "Logo of management page" -msgstr "管理页面logo" - -#: xpack/plugins/interface/forms.py:42 -msgid "" -"Tips: This will appear at the top left of the administration page. (suggest " -"image size: 185px*55px)" -msgstr "提示:将会显示在管理页面左上方(建议图片大小为: 185px*55px)" - -#: xpack/plugins/interface/forms.py:48 xpack/plugins/interface/models.py:31 -msgid "Logo of logout page" -msgstr "退出页面logo" - -#: xpack/plugins/interface/forms.py:50 -msgid "" -"Tips: This will be displayed on the enterprise user logout page. (suggest " -"image size: 82px*82px)" -msgstr "提示:将会显示在企业版用户退出页面(建议图片大小为:82px*82px)" +#: xpack/plugins/interface/api.py:72 +msgid "Restore default successfully." +msgstr "恢复默认成功!" #: xpack/plugins/interface/meta.py:10 msgid "Interface settings" msgstr "界面设置" -#: xpack/plugins/interface/templates/interface/interface.html:15 -#: xpack/plugins/interface/views.py:24 xpack/plugins/interface/views.py:25 -msgid "Interface setting" -msgstr "界面设置" +#: xpack/plugins/interface/models.py:15 +msgid "Title of login page" +msgstr "登录页面标题" -#: xpack/plugins/interface/templates/interface/interface.html:73 -#: xpack/plugins/interface/templates/interface/interface.html:108 -#: xpack/plugins/interface/templates/interface/interface.html:115 -msgid "Restore Default" -msgstr "恢复默认" +#: xpack/plugins/interface/models.py:19 +msgid "Image of login page" +msgstr "登录页面图片" -#: xpack/plugins/interface/templates/interface/interface.html:98 -msgid "This will restore default Settings of the interface !!!" -msgstr "您确定要恢复默认初始化吗?" +#: xpack/plugins/interface/models.py:23 +msgid "Website icon" +msgstr "网站图标" -#: xpack/plugins/interface/templates/interface/interface.html:107 -#: xpack/plugins/interface/views.py:55 -msgid "Restore default successfully." -msgstr "恢复默认成功!" +#: xpack/plugins/interface/models.py:27 +msgid "Logo of management page" +msgstr "管理页面logo" -#: xpack/plugins/interface/templates/interface/interface.html:114 -msgid "Restore default failed." -msgstr "恢复默认失败!" +#: xpack/plugins/interface/models.py:31 +msgid "Logo of logout page" +msgstr "退出页面logo" -#: xpack/plugins/interface/views.py:51 -msgid "It is already in the default setting state!" -msgstr "当前已经是初始化状态!" - -#: xpack/plugins/license/api.py:46 xpack/plugins/license/views.py:47 +#: xpack/plugins/license/api.py:46 msgid "License import successfully" msgstr "许可证导入成功" -#: xpack/plugins/license/api.py:47 xpack/plugins/license/views.py:49 +#: xpack/plugins/license/api.py:47 msgid "License is invalid" msgstr "无效的许可证" #: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94 -#: xpack/plugins/license/templates/license/license_detail.html:41 -#: xpack/plugins/license/templates/license/license_detail.html:46 -#: xpack/plugins/license/views.py:32 msgid "License" msgstr "许可证" @@ -6872,135 +3842,1589 @@ msgstr "企业版" msgid "Ultimate edition" msgstr "旗舰版" -#: xpack/plugins/license/templates/license/_license_import_modal.html:4 -#: xpack/plugins/license/templates/license/license_detail.html:86 -msgid "Import license" -msgstr "导入许可证" +#~ msgid "Target URL" +#~ msgstr "目标URL" -#: xpack/plugins/license/templates/license/_license_import_modal.html:9 -msgid "License file" -msgstr "许可证文件" +#~ msgid "Login username" +#~ msgstr "登录账号" -#: xpack/plugins/license/templates/license/license_detail.html:11 -msgid "Please Import License" -msgstr "请导入许可证" +#~ msgid "Login password" +#~ msgstr "登录密码" -#: xpack/plugins/license/templates/license/license_detail.html:13 -#: xpack/plugins/license/templates/license/license_detail.html:47 -msgid "License has expired" -msgstr "许可证已经过期" +#~ msgid "Database IP" +#~ msgstr "数据库IP" -#: xpack/plugins/license/templates/license/license_detail.html:15 -msgid "The license will at " -msgstr "许可证将在 " +#~ msgid "Database name" +#~ msgstr "数据库名" -#: xpack/plugins/license/templates/license/license_detail.html:15 -msgid " expired." -msgstr " 过期。" +#~ msgid "Database username" +#~ msgstr "数据库账号" -#: xpack/plugins/license/templates/license/license_detail.html:28 -#: xpack/plugins/license/views.py:33 -msgid "License detail" -msgstr "许可证详情" +#~ msgid "Database password" +#~ msgstr "数据库密码" -#: xpack/plugins/license/templates/license/license_detail.html:42 -msgid "No license" -msgstr "暂无许可证" +#~ msgid "Target address" +#~ msgstr "目标地址" -#: xpack/plugins/license/templates/license/license_detail.html:51 -msgid "Subscription ID" -msgstr "订阅授权ID" +#~ msgid "Operating parameter" +#~ msgstr "运行参数" -#: xpack/plugins/license/templates/license/license_detail.html:55 -msgid "Corporation" -msgstr "公司" +#~ msgid "Detail" +#~ msgstr "详情" -#: xpack/plugins/license/templates/license/license_detail.html:59 -msgid "Expired" -msgstr "过期时间" +#~ msgid "Create DatabaseApp" +#~ msgstr "创建数据库应用" -#: xpack/plugins/license/templates/license/license_detail.html:67 -msgid "Edition" -msgstr "版本" +#~ 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 应用" -#: xpack/plugins/license/templates/license/license_detail.html:93 -msgid "Technology consulting" -msgstr "技术咨询" +#~ msgid "Download application loader" +#~ msgstr "下载应用加载器" -#: xpack/plugins/license/templates/license/license_detail.html:96 -msgid "Consult" -msgstr "咨询" +#~ msgid "Create RemoteApp" +#~ msgstr "创建远程应用" -#: xpack/plugins/orgs/forms.py:23 -msgid "Select auditor" -msgstr "选择审计员" +#~ msgid "DatabaseApp list" +#~ msgstr "数据库应用列表" -#: xpack/plugins/orgs/forms.py:28 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:71 -#: xpack/plugins/orgs/templates/orgs/org_list.html:13 -msgid "Admin" -msgstr "管理员" +#~ msgid "DatabaseApp detail" +#~ msgstr "数据库应用详情" -#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:26 -#: xpack/plugins/orgs/views.py:43 xpack/plugins/orgs/views.py:61 -#: xpack/plugins/orgs/views.py:79 -msgid "Organizations" -msgstr "组织管理" +#~ msgid "My DatabaseApp" +#~ msgstr "我的数据库应用" -#: xpack/plugins/orgs/templates/orgs/org_detail.html:17 -#: xpack/plugins/orgs/views.py:80 -msgid "Org detail" -msgstr "组织详情" +#~ msgid "RemoteApp list" +#~ msgstr "远程应用列表" -#: xpack/plugins/orgs/templates/orgs/org_detail.html:79 -msgid "Add admin" -msgstr "添加管理员" +#~ msgid "Update RemoteApp" +#~ msgstr "更新远程应用" -#: xpack/plugins/orgs/templates/orgs/org_list.html:5 -msgid "Create organization " -msgstr "创建组织" +#~ msgid "RemoteApp detail" +#~ msgstr "远程应用详情" -#: xpack/plugins/orgs/views.py:27 -msgid "Org list" -msgstr "组织列表" +#~ msgid "My RemoteApp" +#~ msgstr "我的远程应用" -#: xpack/plugins/orgs/views.py:44 -msgid "Create org" -msgstr "创建组织" +#~ msgid "Label" +#~ msgstr "标签" -#: xpack/plugins/orgs/views.py:62 -msgid "Update org" -msgstr "更新组织" +#~ msgid "" +#~ "root or other NOPASSWD sudo privilege user existed in asset,If asset is " +#~ "windows or other set any one, more see admin user left menu" +#~ msgstr "" +#~ "root或其他拥有NOPASSWD: ALL权限的用户, 如果是windows或其它硬件可以随意设置" +#~ "一个, 更多信息查看左侧 `管理用户` 菜单" -#: xpack/plugins/vault/meta.py:11 xpack/plugins/vault/views.py:23 -#: xpack/plugins/vault/views.py:38 -msgid "Vault" -msgstr "密码匣子" +#~ msgid "Windows 2016 RDP protocol is different, If is window 2016, set it" +#~ msgstr "Windows 2016的RDP协议与之前不同,如果是请设置" -#: xpack/plugins/vault/templates/vault/_xpack_import_modal.html:4 -msgid "Import vault" -msgstr "导入密码" +#~ msgid "" +#~ "If your have some network not connect with each other, you can set domain" +#~ msgstr "" +#~ "如果有多个的互相隔离的网络,设置资产属于的网域,使用网域网关跳转登录" -#: xpack/plugins/vault/templates/vault/vault.html:64 -msgid "vault" -msgstr "密码匣子" +#~ msgid "Select assets" +#~ msgstr "选择资产" -#: xpack/plugins/vault/views.py:24 -msgid "vault list" -msgstr "密码匣子" +#~ msgid "Content should not be contain: {}" +#~ msgstr "内容不能包含: {}" -#: xpack/plugins/vault/views.py:39 -msgid "vault create" -msgstr "创建" +#~ msgid "SSH gateway support proxy SSH,RDP,VNC" +#~ msgstr "SSH网关,支持代理SSH,RDP和VNC" -#~ msgid "CN North-Beijing1" -#~ msgstr "华北-北京1" +#~ msgid "Yes" +#~ msgstr "是" -#~ msgid "CN Northeast-Dalian" -#~ msgstr "华北-大连" +#~ msgid "No" +#~ msgstr "否" -#~ msgid "EU-Paris" -#~ msgstr "欧洲-巴黎" +#~ msgid "Base platform" +#~ msgstr "基础平台" + +#~ msgid "Password or private key passphrase" +#~ msgstr "密码或密钥密码" + +#~ msgid "Invalid private key, Only support RSA/DSA format key" +#~ msgstr "不合法的密钥,仅支持RSA/DSA格式的密钥" + +#~ msgid "Password and private key file must be input one" +#~ msgstr "密码和私钥, 必须输入一个" + +#~ msgid "Auto push system user to asset" +#~ msgstr "自动推送系统用户到资产" + +#~ msgid "" +#~ "1-100, High level will be using login asset as default, if user was " +#~ "granted more than 2 system user" +#~ msgstr "" +#~ "1-100, 1最低优先级,100最高优先级。授权多个用户时,高优先级的系统用户将会" +#~ "作为默认登录用户" + +#~ msgid "" +#~ "If you choose manual login mode, you do not need to fill in the username " +#~ "and password." +#~ msgstr "如果选择手动登录模式,用户名和密码可以不填写" + +#~ msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig" +#~ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" + +#~ msgid "SFTP root dir, tmp, home or custom" +#~ msgstr "SFTP的起始路径,tmp目录, 用户home目录或者自定义" + +#~ msgid "" +#~ "Username is dynamic, When connect asset, using current user's username" +#~ msgstr "用户名是动态的,登录资产时使用当前用户的用户名登录" + +#~ msgid "Update asset group" +#~ msgstr "更新用户组" + +#~ msgid "Hint: only change the field you want to update." +#~ msgstr "仅修改你需要更新的字段" + +#~ msgid "Select Asset" +#~ msgstr "选择资产" + +#~ msgid "Select System Users" +#~ msgstr "选择系统用户" + +#~ msgid "Enable-MFA" +#~ msgstr "启用多因子认证" + +#~ msgid "Update asset user auth" +#~ msgstr "更新资产用户认证信息" + +#~ msgid "Please input password" +#~ msgstr "请输入密码" + +#~ msgid "Asset user auth" +#~ msgstr "资产用户信息" + +#~ msgid "Get auth info error" +#~ msgstr "获取认证信息错误" + +#~ msgid "Test datetime: " +#~ msgstr "测试日期: " + +#~ msgid "Only latest version" +#~ msgstr "仅最新版本" + +#~ msgid "View" +#~ msgstr "查看" + +#~ msgid "Test" +#~ msgstr "测试" + +#~ msgid "Push" +#~ msgstr "推送" + +#~ msgid "Test gateway test connection" +#~ msgstr "测试连接网关" + +#~ msgid "If use nat, set the ssh real port" +#~ msgstr "如果使用了nat端口映射,请设置为ssh真实监听的端口" + +#~ msgid "Node detail" +#~ msgstr "节点详情" + +#~ msgid "Full name" +#~ msgstr "全称" + +#~ msgid "Add node" +#~ msgstr "新建节点" + +#~ msgid "Rename node" +#~ msgstr "重命名节点" + +#~ msgid "Delete node" +#~ msgstr "删除节点" + +#~ msgid "Create node failed" +#~ msgstr "创建节点失败" + +#~ msgid "Rename success" +#~ msgstr "重命名成功" + +#~ msgid "Basic" +#~ msgstr "基本" + +#~ msgid "Auto generate key" +#~ msgstr "自动生成密钥" + +#~ msgid "Other" +#~ msgstr "其它" + +#~ msgid "Asset detail" +#~ msgstr "资产详情" + +#~ msgid "Assets list" +#~ msgstr "资产列表" + +#~ msgid "Asset list of " +#~ msgstr "资产列表" + +#~ msgid "Quick update" +#~ msgstr "快速更新" + +#~ msgid "Test connective" +#~ msgstr "测试可连接性" + +#~ msgid "Replace node assets admin user with this" +#~ msgstr "替换资产的管理员" + +#~ msgid "Select nodes" +#~ msgstr "选择节点" + +#~ msgid "" +#~ "Admin users are asset (charged server) on the root, or have NOPASSWD: ALL " +#~ "sudo permissions users, " +#~ msgstr "" +#~ "管理用户是资产(被控服务器)上的 root,或拥有 NOPASSWD: ALL sudo 权限的用" +#~ "户," + +#~ msgid "" +#~ "JumpServer users of the system using the user to `push system user`, " +#~ "`get assets hardware information`, etc. " +#~ msgstr "JumpServer 使用该用户来 `推送系统用户`、`获取资产硬件信息` 等。" + +#~ msgid "Create admin user" +#~ msgstr "创建管理用户" + +#~ msgid "Asset user list" +#~ msgstr "资产用户列表" + +#~ msgid "Asset users of" +#~ msgstr "资产用户" + +#~ msgid "CPU" +#~ msgstr "CPU" + +#~ msgid "Disk" +#~ msgstr "硬盘" + +#~ msgid "Refresh hardware" +#~ msgstr "更新硬件信息" + +#~ msgid "" +#~ "The left side is the asset tree, right click to create, delete, and " +#~ "change the tree node, authorization asset is also organized as a node, " +#~ "and the right side is the asset under that node" +#~ msgstr "" +#~ "左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织" +#~ "的,右侧是属于该节点下的资产" + +#~ msgid "Create asset" +#~ msgstr "创建资产" + +#~ msgid "Hardware" +#~ msgstr "硬件" + +#~ msgid "Remove from this node" +#~ msgstr "从节点移除" + +#~ msgid "Add assets to node" +#~ msgstr "添加资产到节点" + +#~ msgid "Move assets to node" +#~ msgstr "移动资产到节点" + +#~ msgid "Refresh node hardware info" +#~ msgstr "更新节点资产硬件信息" + +#~ msgid "Test node connective" +#~ msgstr "测试节点资产可连接性" + +#~ msgid "Display only current node assets" +#~ msgstr "仅显示当前节点资产" + +#~ msgid "Displays all child node assets" +#~ msgstr "显示所有子节点资产" + +#~ msgid "This will delete the selected assets !!!" +#~ msgstr "删除选择资产" + +#~ msgid "Asset Deleting failed." +#~ msgstr "删除失败" + +#~ msgid "Asset Delete" +#~ msgstr "删除" + +#~ msgid "Asset Deleted." +#~ msgstr "已被删除" + +#~ msgid "Please select node" +#~ msgstr "请选择节点" + +#~ msgid "Configuration" +#~ msgstr "配置" + +#~ msgid "Rules" +#~ msgstr "规则" + +#~ msgid "Binding to system user" +#~ msgstr "绑定到系统用户" + +#~ msgid "" +#~ "System user bound some command filter, each command filter has some rules," +#~ msgstr "系统用户可以绑定一些命令过滤器,一个过滤器可以定义一些规则" + +#~ msgid "When user login asset with this system user, then run a command," +#~ msgstr "当用户使用这个系统用户登录资产,然后执行一个命令" + +#~ msgid "The command will be filter by rules, higher priority rule run first," +#~ msgstr "这个命令需要被绑定过滤器的所有规则匹配,高优先级先被匹配," + +#~ msgid "" +#~ "When a rule matched, if rule action is allow, then allow command execute," +#~ msgstr "当一个规则匹配到了,如果规则的动作是允许,这个命令会被放行," + +#~ msgid "else if action is deny, then command with be deny," +#~ msgstr "如果规则的动作是禁止,命令将会被禁止执行," + +#~ msgid "else match next rule, if none matched, allowed" +#~ msgstr "否则就匹配下一个规则,如果最后没有匹配到规则,则允许执行" + +#~ msgid "Create command filter" +#~ msgstr "创建命令过滤器" + +#~ msgid "Command filter rule list" +#~ msgstr "命令过滤器规则列表" + +#~ msgid "Create rule" +#~ msgstr "创建规则" + +#~ msgid "Strategy" +#~ msgstr "策略" + +#~ msgid "Gateway list" +#~ msgstr "网关列表" + +#~ msgid "Create gateway" +#~ msgstr "创建网关" + +#~ msgid "Test connection" +#~ msgstr "测试连接" + +#~ msgid "Can be connected" +#~ msgstr "可连接" + +#~ msgid "" +#~ "The domain function is added to address the fact that some environments " +#~ "(such as the hybrid cloud) cannot be connected directly by jumping on the " +#~ "gateway server." +#~ msgstr "" +#~ "网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通" +#~ "过网关服务器进行跳转登录。" + +#~ msgid "JMS => Domain gateway => Target assets" +#~ msgstr "JMS => 网域网关 => 目标资产" + +#~ msgid "Create domain" +#~ msgstr "创建网域" + +#~ msgid "Create label" +#~ msgstr "创建标签" + +#~ msgid "Create platform" +#~ msgstr "创建系统平台" + +#~ msgid "Test assets connective" +#~ msgstr "测试资产可连接性" + +#~ msgid "Push system user now" +#~ msgstr "立刻推送系统" + +#~ msgid "Have existed: " +#~ msgstr "已经存在: " + +#~ msgid "Home" +#~ msgstr "家目录" + +#~ msgid "Uid" +#~ msgstr "Uid" + +#~ msgid "Binding command filters" +#~ msgstr "绑定命令过滤器" + +#~ msgid "" +#~ "System user is JumpServer jump login assets used by the users, can be " +#~ "understood as the user login assets, such as web, sa, the dba (` ssh " +#~ "web@some-host `), rather than using a user the username login server jump " +#~ "(` ssh xiaoming@some-host `); " +#~ msgstr "" +#~ "系统用户是 JumpServer 跳转登录资产时使用的用户,可以理解为登录资产用户," +#~ "如 web,sa,dba(`ssh web@some-host`),而不是使用某个用户的用户名跳转登录" +#~ "服务器(`ssh xiaoming@some-host`);" + +#~ msgid "" +#~ "In simple terms, users log into JumpServer using their own username, and " +#~ "JumpServer uses system users to log into assets. " +#~ msgstr "" +#~ "简单来说是用户使用自己的用户名登录 JumpServer,JumpServer 使用系统用户登录" +#~ "资产。" + +#~ msgid "" +#~ "When system users are created, if you choose auto push JumpServer to use " +#~ "Ansible push system users into the asset, if the asset (Switch) does not " +#~ "support ansible, please manually fill in the account password." +#~ msgstr "" +#~ "系统用户创建时,如果选择了自动推送,JumpServer 会使用 Ansible 自动推送系统" +#~ "用户到资产中,如果资产(交换机)不支持 Ansible,请手动填写账号密码。" + +#~ msgid "Create system user" +#~ msgstr "创建系统用户" + +#~ msgid "Remove success" +#~ msgstr "移除成功" + +#~ msgid "Admin user list" +#~ msgstr "管理用户列表" + +#~ msgid "Update admin user" +#~ msgstr "更新管理用户" + +#~ msgid "Admin user detail" +#~ msgstr "管理用户详情" + +#~ msgid "Admin user assets" +#~ msgstr "管理用户关联资产" + +#~ msgid "Update asset" +#~ msgstr "更新资产" + +#~ msgid "Bulk update asset success" +#~ msgstr "批量更新资产成功" + +#~ msgid "Bulk update asset" +#~ msgstr "批量更新资产" + +#~ msgid "Command filter list" +#~ msgstr "命令过滤器列表" + +#~ msgid "Update command filter" +#~ msgstr "更新命令过滤器" + +#~ msgid "Command filter detail" +#~ msgstr "命令过滤器详情" + +#~ msgid "Create command filter rule" +#~ msgstr "创建命令过滤器规则" + +#~ msgid "Update command filter rule" +#~ msgstr "更新命令过滤器规则" + +#~ msgid "Update domain" +#~ msgstr "更新网域" + +#~ msgid "Domain detail" +#~ msgstr "网域详情" + +#~ msgid "Domain gateway list" +#~ msgstr "域网关列表" + +#~ msgid "Update gateway" +#~ msgstr "创建网关" + +#~ msgid "Label list" +#~ msgstr "标签列表" + +#~ msgid "Tips: Avoid using label names reserved internally: {}" +#~ msgstr "提示: 请避免使用内部预留标签名: {}" + +#~ msgid "Update label" +#~ msgstr "更新标签" + +#~ msgid "Update platform" +#~ msgstr "更新系统平台" + +#~ msgid "Platform detail" +#~ msgstr "平台详情" + +#~ msgid "System user list" +#~ msgstr "系统用户列表" + +#~ msgid "Update system user" +#~ msgstr "更新系统用户" + +#~ msgid "System user detail" +#~ msgstr "系统用户详情" + +#~ msgid "assets" +#~ msgstr "资产管理" + +#~ msgid "System user assets" +#~ msgstr "系统用户关联资产" + +#~ msgid "System user users" +#~ msgstr "系统用户关联用户" + +#~ msgid "Select user" +#~ msgstr "选择用户" + +#~ msgid "UA" +#~ msgstr "Agent" + +#~ msgid "Handlers" +#~ msgstr "操作者" + +#~ msgid "Command execution log" +#~ msgstr "命令执行" + +#~ msgid "Version detail" +#~ msgstr "版本详情" + +#~ msgid "Version run execution" +#~ msgstr "执行历史" + +#~ msgid "Last run" +#~ msgstr "最后运行" + +#~ msgid "Time delta" +#~ msgstr "运行时间" + +#~ msgid "Is success " +#~ msgstr "成功" + +#~ msgid "Last run failed hosts" +#~ msgstr "最后运行失败主机" + +#~ msgid "No hosts" +#~ msgstr "没有主机" + +#~ msgid "Last run success hosts" +#~ msgstr "最后运行成功主机" + +#~ msgid "Executions of " +#~ msgstr "执行历史 " + +#~ msgid "F/S/T" +#~ msgstr "失败/成功/总" + +#~ msgid "Ratio" +#~ msgstr "比例" + +#~ msgid "Execution detail" +#~ msgstr "执行历史详情" + +#~ msgid "Execution detail of" +#~ msgstr "执行历史详情" + +#~ msgid "Task name" +#~ msgstr "任务名称" + +#~ msgid "Failed assets" +#~ msgstr "失败资产" + +#~ msgid "No assets" +#~ msgstr "没有资产" + +#~ msgid "Success assets" +#~ msgstr "成功资产" + +#~ msgid "Asset configuration does not include the SSH protocol" +#~ msgstr "资产配置不包含 SSH 协议" + +#~ msgid "Selected assets" +#~ msgstr "已选择资产" + +#~ msgid "In total" +#~ msgstr "总共" + +#~ msgid "" +#~ "Select the left asset, select the running system user, execute command in " +#~ "batch" +#~ msgstr "选择左侧资产, 选择运行的系统用户,批量执行命令" + +#~ msgid "Unselected assets" +#~ msgstr "没有选中资产" + +#~ msgid "No input command" +#~ msgstr "没有输入命令" + +#~ msgid "No system user was selected" +#~ msgstr "没有选择系统用户" + +#~ msgid "Pending" +#~ msgstr "等待" + +#~ msgid "Task detail" +#~ msgstr "任务详情" + +#~ msgid "Task versions" +#~ msgstr "任务各版本" + +#~ msgid "Execution" +#~ msgstr "执行历史" + +#~ msgid "Last execution output" +#~ msgstr "最后执行输出" + +#~ msgid "Versions of " +#~ msgstr "版本" + +#~ msgid "Total versions" +#~ msgstr "版本数量" + +#~ msgid "Contents" +#~ msgstr "内容" + +#~ msgid "Run" +#~ msgstr "执行" + +#~ msgid "Task start: " +#~ msgstr "任务开始: " + +#~ msgid "Ops" +#~ msgstr "作业中心" + +#~ msgid "Task execution list" +#~ msgstr "任务执行列表" + +#~ msgid "Command execution list" +#~ msgstr "命令执行列表" + +#~ msgid "Users and user groups" +#~ msgstr "用户或用户组" + +#~ msgid "Assets and node" +#~ msgstr "资产或节点" + +#~ msgid "Add asset to this permission" +#~ msgstr "添加资产" + +#~ msgid "Add node to this permission" +#~ msgstr "添加节点" + +#~ msgid "Select system users" +#~ msgstr "选择系统用户" + +#~ msgid "Validity period" +#~ msgstr "有效期" + +#~ msgid "User count" +#~ msgstr "用户数量" + +#~ msgid "User group count" +#~ msgstr "用户组数量" + +#~ msgid "Asset count" +#~ msgstr "资产数量" + +#~ msgid "Node count" +#~ msgstr "节点数量" + +#~ msgid "System user count" +#~ msgstr "系统用户数量" + +#~ msgid "Create permission" +#~ msgstr "创建授权规则" + +#~ msgid "Refresh permission cache" +#~ msgstr "刷新授权缓存" + +#~ msgid "Refresh success" +#~ msgstr "刷新成功" + +#~ msgid "User list of " +#~ msgstr "用户列表" + +#~ msgid "Add user to asset permission" +#~ msgstr "添加用户" + +#~ msgid "Add user group to asset permission" +#~ msgstr "添加用户组" + +#~ msgid "Select user groups" +#~ msgstr "选择用户组" + +#~ msgid "DatabaseApp list of " +#~ msgstr "数据库应用列表" + +#~ msgid "Add DatabaseApp to this permission" +#~ msgstr "添加数据库应用" + +#~ msgid "Select DatabaseApp" +#~ msgstr "选择数据库应用" + +#~ msgid "DatabaseApp count" +#~ msgstr "数据库应用数量" + +#~ msgid "Add user to permission" +#~ msgstr "添加用户" + +#~ msgid "Add user group to permission" +#~ msgstr "添加用户组" + +#~ msgid "RemoteApp count" +#~ msgstr "远程应用数量" + +#~ msgid "RemoteApp list of " +#~ msgstr "远程应用列表" + +#~ msgid "Add RemoteApp to this permission" +#~ msgstr "添加远程应用" + +#~ msgid "Select RemoteApp" +#~ msgstr "选择远程应用" + +#~ msgid "Add user to this permission" +#~ msgstr "添加用户" + +#~ msgid "Add user group to this permission" +#~ msgstr "添加用户组" + +#~ msgid "Asset permission list" +#~ msgstr "资产授权列表" + +#~ msgid "Create asset permission" +#~ msgstr "创建权限规则" + +#~ msgid "Update asset permission" +#~ msgstr "更新资产授权" + +#~ msgid "Asset permission detail" +#~ msgstr "资产授权详情" + +#~ msgid "Asset permission user list" +#~ msgstr "资产授权用户列表" + +#~ msgid "Asset permission asset list" +#~ msgstr "资产授权资产列表" + +#~ msgid "DatabaseApp permission list" +#~ msgstr "数据库应用授权列表" + +#~ msgid "Create DatabaseApp permission" +#~ msgstr "创建数据库应用授权规则" + +#~ msgid "Update DatabaseApp permission" +#~ msgstr "更新数据库应用授权规则" + +#~ msgid "DatabaseApp permission detail" +#~ msgstr "数据库应用授权详情" + +#~ msgid "DatabaseApp permission user list" +#~ msgstr "数据库应用授权用户列表" + +#~ msgid "DatabaseApp permission DatabaseApp list" +#~ msgstr "数据库应用授权数据库应用列表" + +#~ msgid "RemoteApp permission list" +#~ msgstr "远程应用授权列表" + +#~ msgid "Create RemoteApp permission" +#~ msgstr "创建远程应用授权规则" + +#~ msgid "Update RemoteApp permission" +#~ msgstr "更新远程应用授权规则" + +#~ msgid "RemoteApp permission detail" +#~ msgstr "远程应用授权详情" + +#~ msgid "RemoteApp permission user list" +#~ msgstr "远程应用授权用户列表" + +#~ msgid "RemoteApp permission RemoteApp list" +#~ msgstr "远程应用授权远程应用列表" + +#~ msgid "Current SITE URL" +#~ msgstr "当前站点URL" + +#~ msgid "User Guide URL" +#~ msgstr "用户向导URL" + +#~ msgid "User first login update profile done redirect to it" +#~ msgstr "用户第一次登录,修改profile后重定向到地址" + +#~ msgid "Email Subject Prefix" +#~ msgstr "Email主题前缀" + +#~ msgid "Tips: Some word will be intercept by mail provider" +#~ msgstr "提示: 一些关键字可能会被邮件提供商拦截,如 跳板机、JumpServer" + +#~ msgid "SMTP host" +#~ msgstr "SMTP主机" + +#~ msgid "SMTP port" +#~ msgstr "SMTP端口" + +#~ msgid "SMTP user" +#~ msgstr "SMTP账号" + +#~ msgid "SMTP password" +#~ msgstr "SMTP密码" + +#~ msgid "Tips: Some provider use token except password" +#~ msgstr "提示:一些邮件提供商需要输入的是Token" + +#~ msgid "Send user" +#~ msgstr "发送账号" + +#~ msgid "Tips: Send mail account, default SMTP account as the send account" +#~ msgstr "提示:发送邮件账号,默认使用SMTP账号作为发送账号" + +#~ msgid "Test recipient" +#~ msgstr "测试收件人" + +#~ msgid "Tips: Used only as a test mail recipient" +#~ msgstr "提示:仅用来作为测试邮件收件人" + +#~ msgid "Use SSL" +#~ msgstr "使用SSL" + +#~ msgid "If SMTP port is 465, may be select" +#~ msgstr "如果SMTP端口是465,通常需要启用SSL" + +#~ msgid "Use TLS" +#~ msgstr "使用TLS" + +#~ msgid "If SMTP port is 587, may be select" +#~ msgstr "如果SMTP端口是587,通常需要启用TLS" + +#~ msgid "Create user email subject" +#~ msgstr "创建用户邮件的主题" + +#~ msgid "" +#~ "Tips: When creating a user, send the subject of the email (eg:Create " +#~ "account successfully)" +#~ msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" + +#~ msgid "Create user honorific" +#~ msgstr "创建用户邮件的敬语" + +#~ msgid "" +#~ "Tips: When creating a user, send the honorific of the email (eg:Hello)" +#~ msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)" + +#~ msgid "Create user email content" +#~ msgstr "创建用户邮件的内容" + +#~ msgid "Tips:When creating a user, send the content of the email" +#~ msgstr "提示: 创建用户时,发送设置密码邮件的内容" + +#~ msgid "Signature" +#~ msgstr "署名" + +#~ msgid "Tips: Email signature (eg:jumpserver)" +#~ msgstr "提示: 邮件的署名 (例如: jumpserver)" + +#~ msgid "LDAP server" +#~ msgstr "LDAP地址" + +#~ msgid "Bind DN" +#~ msgstr "绑定DN" + +#~ msgid "User OU" +#~ msgstr "用户OU" + +#~ msgid "Use | split User OUs" +#~ msgstr "使用|分隔各OU" + +#~ msgid "User search filter" +#~ msgstr "用户过滤器" + +#, python-format +#~ msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" +#~ msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" + +#~ msgid "User attr map" +#~ msgstr "LDAP属性映射" + +#~ msgid "" +#~ "User attr map present how to map LDAP user attr to jumpserver, username," +#~ "name,email is jumpserver attr" +#~ msgstr "" +#~ "用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, " +#~ "name,email 是jumpserver的属性" + +#~ msgid "Enable LDAP auth" +#~ msgstr "启用LDAP认证" + +#~ msgid "" +#~ "After opening, all user login must use MFA(valid for all users, including " +#~ "administrators)" +#~ msgstr "" +#~ "开启后,所有用户登录必须使用多因子认证(对所有用户有效,包括管理员)" + +#~ msgid "Batch execute commands" +#~ msgstr "批量命令" + +#~ msgid "Allow user batch execute commands" +#~ msgstr "允许用户批量执行命令" + +#~ msgid "Service account registration" +#~ msgstr "终端注册" + +#~ msgid "" +#~ "Allow using bootstrap token register service account, when terminal " +#~ "setup, can disable it" +#~ msgstr "允许使用bootstrap token注册终端, 当终端注册成功后可以禁止" + +#~ msgid "Limit the number of login failures" +#~ msgstr "限制登录失败次数" + +#~ msgid "No logon interval" +#~ msgstr "禁止登录时间间隔" + +#~ 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 "" +#~ "提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" + +#~ msgid "Connection max idle time" +#~ msgstr "连接最大空闲时间" + +#~ msgid "If idle time more than it, disconnect connection Unit: minute" +#~ msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)" + +#~ msgid "Password expiration time" +#~ msgstr "密码过期时间" + +#~ 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 will be automatic sent to the user by system within 5 days " +#~ "(daily) before the password expires" +#~ msgstr "" +#~ "提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码" +#~ "过期提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" + +#~ msgid "Password minimum length" +#~ msgstr "密码最小长度 " + +#~ msgid "Must contain capital letters" +#~ msgstr "必须包含大写字母" + +#~ msgid "" +#~ "After opening, the user password changes and resets must contain " +#~ "uppercase letters" +#~ msgstr "开启后,用户密码修改、重置必须包含大写字母" + +#~ msgid "Must contain lowercase letters" +#~ msgstr "必须包含小写字母" + +#~ msgid "" +#~ "After opening, the user password changes and resets must contain " +#~ "lowercase letters" +#~ msgstr "开启后,用户密码修改、重置必须包含小写字母" + +#~ msgid "Must contain numeric characters" +#~ msgstr "必须包含数字字符" + +#~ msgid "" +#~ "After opening, the user password changes and resets must contain numeric " +#~ "characters" +#~ msgstr "开启后,用户密码修改、重置必须包含数字字符" + +#~ msgid "Must contain special characters" +#~ msgstr "必须包含特殊字符" + +#~ msgid "" +#~ "After opening, the user password changes and resets must contain special " +#~ "characters" +#~ msgstr "开启后,用户密码修改、重置必须包含特殊字符" + +#~ msgid "Password auth" +#~ msgstr "密码认证" + +#~ msgid "Public key auth" +#~ msgstr "密钥认证" + +#~ msgid "Heartbeat interval" +#~ msgstr "心跳间隔" + +#~ msgid "Units: seconds" +#~ msgstr "单位: 秒" + +#~ msgid "List sort by" +#~ msgstr "资产列表排序" + +#~ msgid "List page size" +#~ msgstr "资产分页每页数量" + +#~ msgid "Session keep duration" +#~ msgstr "会话保留时长" + +#~ msgid "" +#~ "Units: days, Session, record, command will be delete if more than " +#~ "duration, only in database" +#~ msgstr "" +#~ "单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss" +#~ "等不受影响)" + +#~ msgid "Telnet login regex" +#~ msgstr "Telnet 成功正则表达式" + +#~ msgid "ex: Last\\s*login|success|成功" +#~ msgstr "" +#~ "登录telnet服务器成功后的提示正则表达式,如: Last\\s*login|success|成功 " + +#~ msgid "LDAP user list" +#~ msgstr "LDAP 用户列表" + +#~ msgid "Please submit the LDAP configuration before import" +#~ msgstr "请先提交LDAP配置再进行导入" + +#~ msgid "Refresh cache" +#~ msgstr "刷新缓存" + +#~ msgid "Existing" +#~ msgstr "已存在" + +#~ msgid "" +#~ "User is not currently selected, please check the user you want to import" +#~ msgstr "当前无勾选用户,请勾选你想要导入的用户" + +#~ msgid "Test LDAP user login" +#~ msgstr "测试LDAP 用户登录" + +#~ msgid "Save the configuration before testing the login" +#~ msgstr "请先提交LDAP配置再进行测试登录" + +#~ msgid "Please input username" +#~ msgstr "请输入用户名" + +#~ msgid "Basic setting" +#~ msgstr "基本设置" + +#~ msgid "Email setting" +#~ msgstr "邮件设置" + +#~ msgid "Email content setting" +#~ msgstr "邮件内容设置" + +#~ msgid "LDAP setting" +#~ msgstr "LDAP设置" + +#~ msgid "Terminal setting" +#~ msgstr "终端设置" + +#~ msgid "Security setting" +#~ msgstr "安全设置" + +#~ msgid "Create User setting" +#~ msgstr "创建用户设置" + +#~ msgid "Test login" +#~ msgstr "测试登录" + +#~ msgid "Bulk import" +#~ msgstr "一键导入" + +#~ msgid "Password check rule" +#~ msgstr "密码校验规则" + +#~ msgid "Command and Replay storage configuration migrated to" +#~ msgstr "命令和录像存储配置已迁移到" + +#~ msgid "Sessions -> Terminal -> Storage configuration" +#~ msgstr "会话管理 -> 终端管理 -> 存储配置" + +#~ msgid "Here" +#~ msgstr "这里" + +#~ msgid "Update setting successfully" +#~ msgstr "更新设置成功" + +#~ msgid "Container name" +#~ msgstr "容器名称" + +#~ msgid "Account key" +#~ msgstr "账户密钥" + +#~ msgid "Endpoint suffix" +#~ msgstr "端点后缀" + +#~ msgid "Bucket" +#~ msgstr "桶名称" + +#~ msgid "Endpoint" +#~ msgstr "端点" + +#~ msgid "" +#~ "\n" +#~ " Tips: If there are multiple hosts, separate them with a comma " +#~ "(,) \n" +#~ "
    \n" +#~ " eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com\n" +#~ " " +#~ msgstr "" +#~ "\n" +#~ " 提示: 如果有多台主机,请使用逗号 ( , ) 进行分割\n" +#~ "
    \n" +#~ " eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com\n" +#~ " " + +#~ msgid "Index" +#~ msgstr "索引" + +#~ msgid "Doc type" +#~ msgstr "文档类型" + +#~ msgid "" +#~ "Command can store in server db or ES, default to server, more see docs" +#~ msgstr "" +#~ "命令支持存储到服务器端数据库、ES中,默认存储的服务器端数据库,更多查看文档" + +#~ msgid "" +#~ "Replay file can store in server disk, AWS S3, Aliyun OSS, default to " +#~ "server, more see docs" +#~ msgstr "" +#~ "录像文件支持存储到服务器端硬盘、AWS S3、 阿里云 OSS 中,默认存储到服务器端" +#~ "硬盘, 更多查看文档" + +#~ msgid "Export command" +#~ msgstr "导出命令" + +#~ msgid "Goto" +#~ msgstr "转到" + +#~ msgid "Create command storage" +#~ msgstr "创建命令存储" + +#~ msgid "Create replay storage" +#~ msgstr "创建录像存储" + +#~ msgid "Session detail" +#~ msgstr "会话详情" + +#~ msgid "Command list" +#~ msgstr "命令记录列表" + +#~ msgid "There is no command about this session" +#~ msgstr "该会话没有命令记录" + +#~ msgid "Login from" +#~ msgstr "登录来源" + +#~ msgid "Replay session" +#~ msgstr "回放会话" + +#~ msgid "Download replay" +#~ msgstr "下载录像" + +#~ msgid "Download" +#~ msgstr "下载" + +#~ msgid "Monitor session" +#~ msgstr "监控" + +#~ msgid "Terminate session" +#~ msgstr "终止会话" + +#~ msgid "Terminate success" +#~ msgstr "终断成功" + +#~ msgid "Duration" +#~ msgstr "时长" + +#~ msgid "Terminate selected" +#~ msgstr "终断所选" + +#~ msgid "Confirm finished" +#~ msgstr "确认已完成" + +#~ msgid "Terminate task send, waiting ..." +#~ msgstr "终断任务已发送,请等待" + +#~ msgid "Terminate" +#~ msgstr "终断" + +#~ msgid "Monitoring" +#~ msgstr "监控" + +#~ msgid "Finish session success" +#~ msgstr "标记会话完成成功" + +#~ msgid "Visit doc for replay play offline: " +#~ msgstr "访问文档查看如何离线播放: " + +#~ msgid "Terminal detail" +#~ msgstr "终端详情" + +#~ msgid "SSH port" +#~ msgstr "SSH端口" + +#~ msgid "Http port" +#~ msgstr "HTTP端口" + +#~ msgid "Storage configuration" +#~ msgstr "存储配置" + +#~ msgid "Addr" +#~ msgstr "地址" + +#~ msgid "Alive" +#~ msgstr "在线" + +#~ msgid "Accept" +#~ msgstr "接受" + +#~ msgid "Accept terminal registration" +#~ msgstr "接受终端注册" + +#~ msgid "Info" +#~ msgstr "信息" + +#~ msgid "Session online list" +#~ msgstr "在线会话" + +#~ msgid "Replay storage list" +#~ msgstr "录像存储列表" + +#~ msgid "Command storage list" +#~ msgstr "命令存储列表" + +#~ msgid "Update replay storage" +#~ msgstr "更新录像存储" + +#~ msgid "Update command storage" +#~ msgstr "更新命令存储" + +#~ msgid "Terminal list" +#~ msgstr "终端列表" + +#~ msgid "Update terminal" +#~ msgstr "更新终端" + +#~ msgid "Redirect to web terminal" +#~ msgstr "重定向到web terminal" + +#~ msgid "Connect ssh terminal" +#~ msgstr "连接ssh终端" + +#~ msgid "" +#~ "You should use your ssh client tools connect terminal: {}

    {}" +#~ msgstr "你可以使用ssh客户端工具连接终端" + +#~ msgid "ago" +#~ msgstr "前" + +#~ msgid "My tickets" +#~ msgstr "我的工单" + +#~ msgid "Assigned me" +#~ msgstr "待处理" + +#~ msgid "Create ticket" +#~ msgstr "提交工单" + +#~ msgid "Ticket list" +#~ msgstr "工单列表" + +#~ msgid "Ticket detail" +#~ msgstr "工单详情" + +#~ msgid "I agree with the terms and conditions." +#~ msgstr "我同意条款和条件" + +#~ msgid "Please choose the terms and conditions." +#~ msgstr "请选择同意条款和条件" + +#~ msgid "Previous" +#~ msgstr "上一步" + +#~ msgid "User group list" +#~ msgstr "用户组列表" + +#~ msgid "Update user group" +#~ msgstr "更新用户组" + +#~ msgid "User group granted asset" +#~ msgstr "用户组授权资产" + +#~ msgid "First login" +#~ msgstr "首次登录" + +#~ msgid "Profile setting" +#~ msgstr "个人信息设置" + +#~ msgid "Bulk update user success" +#~ msgstr "批量更新用户成功" + +#~ msgid "Bulk update user" +#~ msgstr "批量更新用户" + +#~ msgid "User granted assets" +#~ msgstr "用户授权资产" + +#~ msgid "User granted RemoteApp" +#~ msgstr "用户授权远程应用" + +#~ msgid "User granted DatabaseApp" +#~ msgstr "用户授权数据库应用" + +#~ msgid "Password length" +#~ msgstr "密码长度" + +#~ msgid "" +#~ "Tips: The username of the user on the asset to be modified. if the user " +#~ "exists, change the password; If the user does not exist, create the user." +#~ msgstr "" +#~ "提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如" +#~ "果用户不存在,则创建用户。" + +#~ msgid "Plan execution list" +#~ msgstr "执行列表" + +#~ msgid "Add asset to this plan" +#~ msgstr "添加资产" + +#~ msgid "Add node to this plan" +#~ msgstr "添加节点" + +#~ msgid "" +#~ "When the user password on the asset is changed, the action is performed " +#~ "using the admin user associated with the asset" +#~ msgstr "更改资产上的用户密码时,将会使用与该资产关联的管理用户进行操作" + +#~ msgid "Length" +#~ msgstr "长度" + +#~ msgid "Run plan manually" +#~ msgstr "手动执行计划" + +#~ msgid "Execute failed" +#~ msgstr "执行失败" + +#~ msgid "Execution list of plan" +#~ msgstr "执行历史列表" + +#~ msgid "Log" +#~ msgstr "日志" + +#~ msgid "Retry" +#~ msgstr "重试" + +#~ msgid "Run failed" +#~ msgstr "执行失败" + +#~ msgid "Create plan" +#~ msgstr "创建计划" + +#~ msgid "Plan list" +#~ msgstr "计划列表" + +#~ msgid "Update plan" +#~ msgstr "更新计划" + +#~ msgid "Plan execution task list" +#~ msgstr "执行任务列表" + +#~ msgid "Select account" +#~ msgstr "选择账户" + +#~ msgid "Select regions" +#~ msgstr "选择地域" + +#~ msgid "Select instances" +#~ msgstr "选择实例" + +#~ msgid "Select node" +#~ msgstr "选择节点" + +#~ msgid "Select admins" +#~ msgstr "选择管理员" + +#~ msgid "Account detail" +#~ msgstr "账户详情" + +#~ msgid "Create account" +#~ msgstr "创建账户" + +#~ msgid "Node & AdminUser" +#~ msgstr "节点 & 管理用户" + +#~ msgid "Load failed" +#~ msgstr "加载失败" + +#~ msgid "Sync task detail" +#~ msgstr "同步任务详情" + +#~ msgid "Sync task history" +#~ msgstr "同步历史列表" + +#~ msgid "Sync instance list" +#~ msgstr "同步实例列表" + +#~ msgid "Run task manually" +#~ msgstr "手动执行任务" + +#~ msgid "Sync success" +#~ msgstr "同步成功" + +#~ msgid "New count" +#~ msgstr "新增" + +#~ msgid "Unsync count" +#~ msgstr "未同步" + +#~ msgid "Synced count" +#~ msgstr "已同步" + +#~ msgid "Released count" +#~ msgstr "已释放" + +#~ msgid "Delete released assets" +#~ msgstr "删除已释放的资产" + +#~ msgid "Create sync instance task" +#~ msgstr "创建同步实例任务" + +#~ msgid "Run count" +#~ msgstr "执行次数" + +#~ msgid "Update account" +#~ msgstr "更新账户" + +#~ msgid "Sync instance task list" +#~ msgstr "同步实例任务列表" + +#~ msgid "Create sync Instance task" +#~ msgstr "创建同步实例任务" + +#~ msgid "Update sync Instance task" +#~ msgstr "更新同步实例任务" + +#~ msgid "Asset user" +#~ msgstr "资产用户" + +#~ msgid "Create task" +#~ msgstr "创建任务" + +#~ msgid "Periodic" +#~ msgstr "定时执行" + +#~ msgid "Gathered user list" +#~ msgstr "收集用户列表" + +#~ msgid "Update task" +#~ msgstr "更新任务" + +#~ msgid "" +#~ "Tips: This will be displayed on the enterprise user login page. (eg: " +#~ "Welcome to the JumpServer open source fortress)" +#~ msgstr "" +#~ "提示:将会显示在企业版用户登录页面(eg: 欢迎使用JumpServer开源堡垒机)" + +#~ msgid "" +#~ "Tips: This will be displayed on the enterprise user login page. (suggest " +#~ "image size: 492px*472px)" +#~ msgstr "提示:将会显示在企业版用户登录页面(建议图片大小为: 492*472px)" + +#~ msgid "Tips: website icon. (suggest image size: 16px*16px)" +#~ msgstr "提示:网站图标(建议图片大小为: 16px*16px)" + +#~ msgid "" +#~ "Tips: This will appear at the top left of the administration page. " +#~ "(suggest image size: 185px*55px)" +#~ msgstr "提示:将会显示在管理页面左上方(建议图片大小为: 185px*55px)" + +#~ msgid "" +#~ "Tips: This will be displayed on the enterprise user logout page. (suggest " +#~ "image size: 82px*82px)" +#~ msgstr "提示:将会显示在企业版用户退出页面(建议图片大小为:82px*82px)" + +#~ msgid "Interface setting" +#~ msgstr "界面设置" + +#~ msgid "Restore Default" +#~ msgstr "恢复默认" + +#~ msgid "This will restore default Settings of the interface !!!" +#~ msgstr "您确定要恢复默认初始化吗?" + +#~ msgid "Restore default failed." +#~ msgstr "恢复默认失败!" + +#~ msgid "Import license" +#~ msgstr "导入许可证" + +#~ msgid "License file" +#~ msgstr "许可证文件" + +#~ msgid "Please Import License" +#~ msgstr "请导入许可证" + +#~ msgid "License has expired" +#~ msgstr "许可证已经过期" + +#~ msgid "The license will at " +#~ msgstr "许可证将在 " + +#~ msgid " expired." +#~ msgstr " 过期。" + +#~ msgid "License detail" +#~ msgstr "许可证详情" + +#~ msgid "No license" +#~ msgstr "暂无许可证" + +#~ msgid "Subscription ID" +#~ msgstr "订阅授权ID" + +#~ msgid "Corporation" +#~ msgstr "公司" + +#~ msgid "Expired" +#~ msgstr "过期时间" + +#~ msgid "Edition" +#~ msgstr "版本" + +#~ msgid "Technology consulting" +#~ msgstr "技术咨询" + +#~ msgid "Consult" +#~ msgstr "咨询" + +#~ msgid "Select auditor" +#~ msgstr "选择审计员" + +#~ msgid "Admin" +#~ msgstr "管理员" + +#~ msgid "Organizations" +#~ msgstr "组织管理" + +#~ msgid "Org detail" +#~ msgstr "组织详情" + +#~ msgid "Add admin" +#~ msgstr "添加管理员" + +#~ msgid "Create organization " +#~ msgstr "创建组织" + +#~ msgid "Org list" +#~ msgstr "组织列表" + +#~ msgid "Create org" +#~ msgstr "创建组织" + +#~ msgid "Update org" +#~ msgstr "更新组织" + +#~ msgid "Vault" +#~ msgstr "密码匣子" + +#~ msgid "Import vault" +#~ msgstr "导入密码" + +#~ msgid "vault" +#~ msgstr "密码匣子" + +#~ msgid "vault list" +#~ msgstr "密码匣子" + +#~ msgid "vault create" +#~ msgstr "创建" #~ msgid "Org users" #~ msgstr "组织用户" diff --git a/apps/static/img/authenticator_android.png b/apps/static/img/authenticator_android.png index cb357525d6bae2703b65ce994d75bf235a3c23a5..9387e83ee2cd46d0053d4554102e8396fc0157f7 100644 GIT binary patch literal 5907 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 Ur>mdKI;Vst0JY&~aR2}S diff --git a/apps/users/templates/users/user_otp_enable_install_app.html b/apps/users/templates/users/user_otp_enable_install_app.html index 37b3e612b..8ac03416e 100644 --- a/apps/users/templates/users/user_otp_enable_install_app.html +++ b/apps/users/templates/users/user_otp_enable_install_app.html @@ -10,7 +10,7 @@

    - {% trans 'Download and install the Google Authenticator application on your phone' %} + {% trans 'Download and install the Google Authenticator application on your phone or applet of WeChat' %}

    From f26f7ca1e71a89779f9af820e02fac7cbfb54cde Mon Sep 17 00:00:00 2001 From: Bai Date: Mon, 15 Jun 2020 16:34:51 +0800 Subject: [PATCH 124/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9parsers?= =?UTF-8?q?=EF=BC=8C=E5=A4=84=E7=90=86dict=E5=AD=97=E6=AE=B5=E5=80=BC?= =?UTF-8?q?=EF=BC=9B=E8=A7=A3=E5=86=B3remote-app=20csv=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E6=97=B6=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/parsers/csv.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/common/drf/parsers/csv.py b/apps/common/drf/parsers/csv.py index 254c73375..95a9ec732 100644 --- a/apps/common/drf/parsers/csv.py +++ b/apps/common/drf/parsers/csv.py @@ -58,6 +58,12 @@ class JMSCSVParser(BaseParser): col = col.replace("“", '"').replace("”", '"').\ replace("‘", '"').replace('’', '"').replace("'", '"') col = json.loads(col) + # 字典转换 + 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 @@ -68,7 +74,7 @@ class JMSCSVParser(BaseParser): """ _row_data = {} for k, v in row_data.items(): - if isinstance(v, list) \ + if isinstance(v, list) or isinstance(v, dict)\ or isinstance(v, str) and k.strip() and v.strip(): _row_data[k] = v return _row_data From 5bea782b9fb4fd2adb71c3dbf80658239b1b584f Mon Sep 17 00:00:00 2001 From: Eric_Lee Date: Mon, 15 Jun 2020 17:24:05 +0800 Subject: [PATCH 125/146] add assignee field for ticket (#4104) --- apps/tickets/serializers/ticket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tickets/serializers/ticket.py b/apps/tickets/serializers/ticket.py index 9c7a5a2e3..0f564ad70 100644 --- a/apps/tickets/serializers/ticket.py +++ b/apps/tickets/serializers/ticket.py @@ -13,7 +13,7 @@ class TicketSerializer(serializers.ModelSerializer): model = models.Ticket fields = [ 'id', 'user', 'user_display', 'title', 'body', - 'assignees', 'assignees_display', + 'assignees', 'assignees_display', 'assignee', 'assignee_display', 'status', 'action', 'date_created', 'date_updated', 'type', 'type_display', 'action_display', ] From 451690fe8b1b02230671f043486b369b4c7d477a Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 15 Jun 2020 17:44:40 +0800 Subject: [PATCH 126/146] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E9=87=8D?= =?UTF-8?q?=E5=AE=9A=E5=90=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/urls.py | 1 + requirements/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index a231c640d..60c04676f 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -87,6 +87,7 @@ if settings.DEBUG: # 兼容之前的 old_app_pattern = '|'.join(apps) +old_app_pattern = r'^{}'.format(old_app_pattern) urlpatterns += [re_path(old_app_pattern, views.redirect_old_apps_view)] diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 64aa9d0dc..932ab3b09 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -14,7 +14,7 @@ coreapi==2.3.3 coreschema==0.0.4 cryptography==2.8 decorator==4.1.2 -Django==2.2.10 +Django==2.2.13 django-auth-ldap==1.7.0 django-bootstrap3==9.1.0 django-celery-beat==1.4.0 From c5a9a85818fe6f24ee29321d5eed99c3d7cbf47a Mon Sep 17 00:00:00 2001 From: xinwen Date: Mon, 15 Jun 2020 19:42:36 +0800 Subject: [PATCH 127/146] =?UTF-8?q?[Update]=20=E5=AE=8C=E5=96=84=20?= =?UTF-8?q?=E4=BD=9C=E4=B8=9A=E4=B8=AD=E5=BF=83/=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=88=97=E8=A1=A8=20(#4105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ops/api/adhoc.py | 16 ++++++++--- apps/ops/models/adhoc.py | 4 +++ apps/ops/serializers/adhoc.py | 53 ++++++++++++++++++++++++++--------- 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/apps/ops/api/adhoc.py b/apps/ops/api/adhoc.py index ee81f869f..aef4c9b06 100644 --- a/apps/ops/api/adhoc.py +++ b/apps/ops/api/adhoc.py @@ -4,14 +4,17 @@ from django.shortcuts import get_object_or_404 from rest_framework import viewsets, generics from rest_framework.views import Response -from django.db.models import Count, Q from common.permissions import IsOrgAdmin from common.serializers import CeleryTaskSerializer -from orgs.utils import current_org from ..models import Task, AdHoc, AdHocExecution -from ..serializers import TaskSerializer, AdHocSerializer, \ - AdHocExecutionSerializer, TaskDetailSerializer +from ..serializers import ( + TaskSerializer, + AdHocSerializer, + AdHocExecutionSerializer, + TaskDetailSerializer, + AdHocDetailSerializer, +) from ..tasks import run_ansible_task __all__ = [ @@ -53,6 +56,11 @@ class AdHocViewSet(viewsets.ModelViewSet): serializer_class = AdHocSerializer permission_classes = (IsOrgAdmin,) + def get_serializer_class(self): + if self.action == 'retrieve': + return AdHocDetailSerializer + return super().get_serializer_class() + def get_queryset(self): task_id = self.request.query_params.get('task') if task_id: diff --git a/apps/ops/models/adhoc.py b/apps/ops/models/adhoc.py index 013fdc628..36ebd77e0 100644 --- a/apps/ops/models/adhoc.py +++ b/apps/ops/models/adhoc.py @@ -150,6 +150,10 @@ class AdHoc(OrgModelMixin): created_by = models.CharField(max_length=64, default='', blank=True, null=True, verbose_name=_('Create by')) date_created = models.DateTimeField(auto_now_add=True, db_index=True) + @lazyproperty + def run_times(self): + return self.execution.count() + @property def inventory(self): if self.become: diff --git a/apps/ops/serializers/adhoc.py b/apps/ops/serializers/adhoc.py index d4e67371a..f07c1ca47 100644 --- a/apps/ops/serializers/adhoc.py +++ b/apps/ops/serializers/adhoc.py @@ -8,10 +8,16 @@ from ..models import Task, AdHoc, AdHocExecution, CommandExecution class AdHocExecutionSerializer(serializers.ModelSerializer): stat = serializers.SerializerMethodField() + last_success = serializers.ListField(source='success_hosts') + last_failure = serializers.DictField(source='failed_hosts') class Meta: model = AdHocExecution - fields = '__all__' + fields = [ + 'id', 'task', 'task_display', 'hosts_amount', 'adhoc', 'date_start', 'stat', + 'date_finished', 'timedelta', 'is_finished', 'is_success', 'result', 'summary', + 'short_id', 'adhoc_short_id', 'last_success', 'last_failure' + ] @staticmethod def get_task(obj): @@ -28,17 +34,15 @@ class AdHocExecutionSerializer(serializers.ModelSerializer): "failed": count_failed_hosts } - def get_field_names(self, declared_fields, info): - fields = super().get_field_names(declared_fields, info) - fields.extend(['short_id', 'adhoc_short_id']) - return fields - class AdHocExecutionExcludeResultSerializer(AdHocExecutionSerializer): - def get_field_names(self, declared_fields, info): - fields = super().get_field_names(declared_fields, info) - fields = [i for i in fields if i not in ['result', 'summary']] - return fields + class Meta: + model = AdHocExecution + fields = [ + 'id', 'task', 'task_display', 'hosts_amount', 'adhoc', 'date_start', 'stat', + 'date_finished', 'timedelta', 'is_finished', 'is_success', + 'short_id', 'adhoc_short_id', 'last_success', 'last_failure' + ] class TaskSerializer(serializers.ModelSerializer): @@ -60,15 +64,15 @@ class TaskSerializer(serializers.ModelSerializer): class TaskDetailSerializer(TaskSerializer): - last_success = serializers.ListField(source='latest_execution.success_hosts') - last_failure = serializers.DictField(source='latest_execution.failed_hosts') + contents = serializers.ListField(source='latest_adhoc.tasks') class Meta(TaskSerializer.Meta): - fields = TaskSerializer.Meta.fields + ['last_success', 'last_failure'] + fields = TaskSerializer.Meta.fields + ['contents'] class AdHocSerializer(serializers.ModelSerializer): become_display = serializers.ReadOnlyField() + tasks = serializers.ListField() class Meta: model = AdHoc @@ -86,6 +90,29 @@ class AdHocSerializer(serializers.ModelSerializer): } +class AdHocExecutionNestSerializer(serializers.ModelSerializer): + last_success = serializers.ListField(source='success_hosts') + last_failure = serializers.DictField(source='failed_hosts') + last_run = serializers.CharField(source='short_id') + + class Meta: + model = AdHocExecution + fields = ( + 'last_success', 'last_failure', 'last_run', 'timedelta', 'is_finished', + 'is_success' + ) + + +class AdHocDetailSerializer(AdHocSerializer): + latest_execution = AdHocExecutionNestSerializer(allow_null=True) + task_name = serializers.CharField(source='task.name') + + class Meta(AdHocSerializer.Meta): + fields = AdHocSerializer.Meta.fields + [ + 'latest_execution', 'created_by', 'run_times', 'task_name' + ] + + class CommandExecutionSerializer(serializers.ModelSerializer): result = serializers.JSONField(read_only=True) log_url = serializers.SerializerMethodField() From f4fa011714221334857932d98fc8756da34aa2a1 Mon Sep 17 00:00:00 2001 From: Bai Date: Mon, 15 Jun 2020 20:31:50 +0800 Subject: [PATCH 128/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9mfa?= =?UTF-8?q?=E6=A0=A1=E9=AA=8Ccode=E6=97=A0=E6=95=88=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/mfa.py | 3 ++- apps/locale/zh/LC_MESSAGES/django.mo | Bin 53698 -> 53741 bytes apps/locale/zh/LC_MESSAGES/django.po | 28 +++++++++++++++------------ 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/apps/authentication/api/mfa.py b/apps/authentication/api/mfa.py index cc4f2ab6b..f95593bbe 100644 --- a/apps/authentication/api/mfa.py +++ b/apps/authentication/api/mfa.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # import time +from django.utils.translation import ugettext as _ from rest_framework.permissions import AllowAny from rest_framework.generics import CreateAPIView from rest_framework.serializers import ValidationError @@ -56,4 +57,4 @@ class UserOtpVerifyApi(CreateAPIView): request.session["MFA_VERIFY_TIME"] = int(time.time()) return Response({"ok": "1"}) else: - return Response({"error": "Code not valid"}, status=400) + return Response({"error": _("Code is invalid")}, status=400) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 331ae38c750c151330b40dbb1e6442977b146203..7889bc373db0089146fd70ffa3c08747b233e0a0 100644 GIT binary patch delta 15987 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 delta 15946 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\n" "Language-Team: JumpServer team\n" @@ -986,7 +986,7 @@ msgstr "状态" msgid "Date login" msgstr "登录日期" -#: audits/serializers.py:61 audits/serializers.py:73 ops/models/adhoc.py:240 +#: audits/serializers.py:61 audits/serializers.py:73 ops/models/adhoc.py:244 msgid "Is success" msgstr "是否成功" @@ -1003,6 +1003,10 @@ msgstr "主机" msgid "Run as" msgstr "运行用户" +#: authentication/api/mfa.py:60 +msgid "Code is invalid" +msgstr "Code无效" + #: authentication/backends/api.py:53 msgid "Invalid signature header. No credentials provided." msgstr "" @@ -1500,46 +1504,46 @@ msgstr "Become" msgid "Create by" msgstr "创建者" -#: ops/models/adhoc.py:233 +#: ops/models/adhoc.py:237 msgid "Task display" msgstr "任务展示" -#: ops/models/adhoc.py:234 +#: ops/models/adhoc.py:238 msgid "Host amount" msgstr "主机数量" -#: ops/models/adhoc.py:236 +#: ops/models/adhoc.py:240 msgid "Start time" msgstr "开始时间" -#: ops/models/adhoc.py:237 +#: ops/models/adhoc.py:241 msgid "End time" msgstr "完成时间" -#: ops/models/adhoc.py:238 xpack/plugins/change_auth_plan/models.py:179 +#: ops/models/adhoc.py:242 xpack/plugins/change_auth_plan/models.py:179 #: xpack/plugins/change_auth_plan/models.py:310 #: xpack/plugins/gathered_user/models.py:79 msgid "Time" msgstr "时间" -#: ops/models/adhoc.py:239 ops/models/command.py:26 +#: ops/models/adhoc.py:243 ops/models/command.py:26 #: terminal/serializers/session.py:30 msgid "Is finished" msgstr "是否完成" -#: ops/models/adhoc.py:241 +#: ops/models/adhoc.py:245 msgid "Adhoc raw result" msgstr "结果" -#: ops/models/adhoc.py:242 +#: ops/models/adhoc.py:246 msgid "Adhoc result summary" msgstr "汇总" -#: ops/models/adhoc.py:282 xpack/plugins/change_auth_plan/utils.py:137 +#: ops/models/adhoc.py:286 xpack/plugins/change_auth_plan/utils.py:137 msgid "{} Start task: {}" msgstr "{} 任务开始: {}" -#: ops/models/adhoc.py:291 xpack/plugins/change_auth_plan/utils.py:149 +#: ops/models/adhoc.py:295 xpack/plugins/change_auth_plan/utils.py:149 msgid "{} Task finish" msgstr "{} 任务结束" From 1f15937139910b2f41598eef473571a85dee663f Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Jun 2020 11:04:59 +0800 Subject: [PATCH 129/146] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=EF=BC=8C=E6=B7=BB=E5=8A=A0settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 53698 -> 53837 bytes apps/locale/zh/LC_MESSAGES/django.po | 10 +++++++++- apps/settings/api.py | 9 ++++++++- apps/users/serializers/user.py | 4 ++-- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 331ae38c750c151330b40dbb1e6442977b146203..02bf6ef03ac7c50816be079c8bf34378babcf748 100644 GIT binary patch delta 13329 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 WZ3}G@(PUe{ciXky_Fc~C!v6>3{H_H6 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index a374c7e20..e68fdec58 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: 2020-06-15 12:32+0800\n" +"POT-Creation-Date: 2020-06-16 11:02+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -2716,6 +2716,14 @@ msgstr "角色只能为 {}" msgid "Password does not match security rules" msgstr "密码不满足安全规则" +#: users/serializers/user.py:274 +msgid "The old password is incorrect" +msgstr "旧密码错误" + +#: users/serializers/user.py:288 +msgid "The newly set password is inconsistent" +msgstr "两次密码不一致" + #: users/serializers_v2/user.py:36 msgid "name not unique" msgstr "名称重复" diff --git a/apps/settings/api.py b/apps/settings/api.py index 974e31a4e..35ccd5f5b 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -275,7 +275,14 @@ class PublicSettingApi(generics.RetrieveAPIView): "LOGIN_CONFIRM_ENABLE": settings.LOGIN_CONFIRM_ENABLE, "SECURITY_VIEW_AUTH_NEED_MFA": settings.SECURITY_VIEW_AUTH_NEED_MFA, "SECURITY_MFA_VERIFY_TTL": settings.SECURITY_MFA_VERIFY_TTL, - "LOGO_URLS": settings.LOGO_URLS + "LOGO_URLS": settings.LOGO_URLS, + "PASSWORD_RULE": { + 'SECURITY_PASSWORD_MIN_LENGTH': settings.SECURITY_PASSWORD_MIN_LENGTH, + 'SECURITY_PASSWORD_UPPER_CASE': settings.SECURITY_PASSWORD_UPPER_CASE, + 'SECURITY_PASSWORD_LOWER_CASE': settings.SECURITY_PASSWORD_LOWER_CASE, + 'SECURITY_PASSWORD_NUMBER': settings.SECURITY_PASSWORD_NUMBER, + 'SECURITY_PASSWORD_SPECIAL_CHAR': settings.SECURITY_PASSWORD_SPECIAL_CHAR, + } } } return instance diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 37d820b65..b01bddf42 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -271,7 +271,7 @@ class UserUpdatePasswordSerializer(serializers.ModelSerializer): def validate_old_password(self, value): if not self.instance.check_password(value): - msg = 'The old password is incorrect' + msg = _('The old password is incorrect') raise serializers.ValidationError(msg) return value @@ -285,7 +285,7 @@ class UserUpdatePasswordSerializer(serializers.ModelSerializer): def validate_new_password_again(self, value): if value != self.initial_data.get('new_password', ''): - msg = 'The newly set password is inconsistent' + msg = _('The newly set password is inconsistent') raise serializers.ValidationError(msg) return value From da4ad11a6965bd1279fd0e3d47d99663311841a4 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Jun 2020 13:39:49 +0800 Subject: [PATCH 130/146] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9session=20comm?= =?UTF-8?q?and=20=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/command.py | 3 ++- apps/terminal/backends/command/serializers.py | 22 +++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/terminal/api/command.py b/apps/terminal/api/command.py index a7798ce08..749da4fcb 100644 --- a/apps/terminal/api/command.py +++ b/apps/terminal/api/command.py @@ -26,7 +26,8 @@ class CommandQueryMixin: command_store = get_command_storage() permission_classes = [IsOrgAdminOrAppUser | IsOrgAuditor] filter_fields = [ - "asset", "system_user", "user", "session", + "asset", "system_user", "user", "session", "risk_level", + "input" ] default_days_ago = 5 diff --git a/apps/terminal/backends/command/serializers.py b/apps/terminal/backends/command/serializers.py index 657aa2356..4df584dbb 100644 --- a/apps/terminal/backends/command/serializers.py +++ b/apps/terminal/backends/command/serializers.py @@ -1,18 +1,26 @@ # ~*~ coding: utf-8 ~*~ +from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from .models import AbstractSessionCommand + class SessionCommandSerializer(serializers.Serializer): """使用这个类作为基础Command Log Serializer类, 用来序列化""" id = serializers.UUIDField(read_only=True) - user = serializers.CharField(max_length=64) - asset = serializers.CharField(max_length=128) - system_user = serializers.CharField(max_length=64) - input = serializers.CharField(max_length=128) - output = serializers.CharField(max_length=1024, allow_blank=True) - session = serializers.CharField(max_length=36) - risk_level = serializers.IntegerField(required=False) + user = serializers.CharField(max_length=64, label=_("User")) + asset = serializers.CharField(max_length=128, label=_("Asset")) + system_user = serializers.CharField(max_length=64, label=_("System user")) + input = serializers.CharField(max_length=128, label=_("Command")) + output = serializers.CharField(max_length=1024, allow_blank=True, label=_("Output")) + session = serializers.CharField(max_length=36, label=_("Session")) + risk_level = serializers.ChoiceField(required=False, label=_("Risk level"), choices=AbstractSessionCommand.RISK_LEVEL_CHOICES) + risk_level_display = serializers.SerializerMethodField() org_id = serializers.CharField(max_length=36, required=False, default='', allow_null=True, allow_blank=True) timestamp = serializers.IntegerField() + @staticmethod + def get_risk_level_display(obj): + risk_mapper = dict(AbstractSessionCommand.RISK_LEVEL_CHOICES) + return risk_mapper.get(obj.risk_level) From 9347405f08bc46bd49778a5be64b347d956f3774 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Jun 2020 13:50:21 +0800 Subject: [PATCH 131/146] =?UTF-8?q?feat:=20terminal=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/api/terminal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/terminal/api/terminal.py b/apps/terminal/api/terminal.py index b0f6b6bca..705848fe0 100644 --- a/apps/terminal/api/terminal.py +++ b/apps/terminal/api/terminal.py @@ -27,6 +27,7 @@ class TerminalViewSet(viewsets.ModelViewSet): queryset = Terminal.objects.filter(is_deleted=False) serializer_class = serializers.TerminalSerializer permission_classes = (IsSuperUser,) + filter_fields = ['name', 'remote_addr'] def create(self, request, *args, **kwargs): name = request.data.get('name') From 80d94074e71f2f12c0699c2bdd8f0b095335a28c Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Jun 2020 14:56:19 +0800 Subject: [PATCH 132/146] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E7=9A=84=E6=A0=87=E7=AD=BE=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/filters.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/assets/filters.py b/apps/assets/filters.py index 94a49a3f5..13d8f9e60 100644 --- a/apps/assets/filters.py +++ b/apps/assets/filters.py @@ -65,7 +65,7 @@ class AssetByNodeFilterBackend(filters.BaseFilterBackend): class LabelFilterBackend(filters.BaseFilterBackend): - sep = '#' + sep = ':' query_arg = 'label' def get_schema_fields(self, view): @@ -84,6 +84,8 @@ class LabelFilterBackend(filters.BaseFilterBackend): q = None for kv in labels_query: + if '#' in kv: + self.sep = '#' if self.sep not in kv: continue key, value = kv.strip().split(self.sep)[:2] From bcba408517cecc8f7749b1d2b5c4555fd05892c4 Mon Sep 17 00:00:00 2001 From: Bai Date: Tue, 16 Jun 2020 15:04:01 +0800 Subject: [PATCH 133/146] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E6=88=B7source=E9=BB=98=E8=AE=A4local?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0027_auto_20200616_1503.py | 18 ++++++++++++++++++ apps/users/models/user.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 apps/users/migrations/0027_auto_20200616_1503.py diff --git a/apps/users/migrations/0027_auto_20200616_1503.py b/apps/users/migrations/0027_auto_20200616_1503.py new file mode 100644 index 000000000..2a7fb014e --- /dev/null +++ b/apps/users/migrations/0027_auto_20200616_1503.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.10 on 2020-06-16 07:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0026_auto_20200508_2105'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='source', + field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS')], default='local', max_length=30, verbose_name='Source'), + ), + ] diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 3fb058396..d0ab12c38 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -507,7 +507,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): max_length=30, default='', blank=True, verbose_name=_('Created by') ) source = models.CharField( - max_length=30, default=SOURCE_LDAP, choices=SOURCE_CHOICES, + max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES, verbose_name=_('Source') ) date_password_last_updated = models.DateTimeField( From 19e34270d1261544e8e6b68fc2c3efa735b8aa74 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Jun 2020 15:30:25 +0800 Subject: [PATCH 134/146] =?UTF-8?q?feat:=20gather=20user=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/gathered_user.py | 2 +- apps/assets/serializers/asset.py | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/apps/assets/api/gathered_user.py b/apps/assets/api/gathered_user.py index b9f137648..e024fedb1 100644 --- a/apps/assets/api/gathered_user.py +++ b/apps/assets/api/gathered_user.py @@ -18,5 +18,5 @@ class GatheredUserViewSet(OrgModelViewSet): permission_classes = [IsOrgAdmin] extra_filter_backends = [AssetRelatedByNodeFilterBackend] - filter_fields = ['asset', 'username', 'present'] + filter_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__hostname'] search_fields = ['username', 'asset__ip', 'asset__hostname'] diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index e238c29a7..e36f41946 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -144,14 +144,8 @@ class AssetDisplaySerializer(AssetSerializer): connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity")) class Meta(AssetSerializer.Meta): - fields = [ - 'id', 'ip', 'hostname', 'protocol', 'port', - 'protocols', 'is_active', 'public_ip', - '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', + fields = AssetSerializer.Meta.fields + [ + 'connectivity', ] @classmethod From 7ebe1c29161c485177284b4ecef3ef4563dae305 Mon Sep 17 00:00:00 2001 From: xinwen Date: Tue, 16 Jun 2020 15:54:19 +0800 Subject: [PATCH 135/146] =?UTF-8?q?[Update]=20=E4=BC=98=E5=8C=96=20dynamic?= =?UTF-8?q?=20settings=20(#4107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/const.py | 38 ++++++++++++++++++++++++++++++++++++-- apps/users/models/user.py | 3 ++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/apps/jumpserver/const.py b/apps/jumpserver/const.py index 9a6ed9e6a..9dad76cd5 100644 --- a/apps/jumpserver/const.py +++ b/apps/jumpserver/const.py @@ -1,12 +1,46 @@ # -*- coding: utf-8 -*- # import os -from .conf import ConfigManager -__all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG', 'DYNAMIC'] +from werkzeug.local import LocalProxy + +from .conf import ConfigManager +from common.local import thread_local + +__all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG', 'DYNAMIC', 'LOCAL_DYNAMIC_SETTINGS'] BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PROJECT_DIR = os.path.dirname(BASE_DIR) VERSION = '2.0.0' CONFIG = ConfigManager.load_user_config() DYNAMIC = ConfigManager.get_dynamic_config(CONFIG) + + +class _Settings: + pass + + +def get_dynamic_cfg_from_thread_local(): + KEY = 'dynamic_config' + + try: + cfg = getattr(thread_local, KEY) + except AttributeError: + cfg = _Settings() + setattr(thread_local, KEY, cfg) + + return cfg + + +class DynamicDefaultLocalProxy(LocalProxy): + def __getattr__(self, item): + try: + value = super().__getattr__(item) + except AttributeError: + value = getattr(DYNAMIC, item)() + setattr(self, item, value) + + return value + + +LOCAL_DYNAMIC_SETTINGS = DynamicDefaultLocalProxy(get_dynamic_cfg_from_thread_local) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index d0ab12c38..d30141d23 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -16,6 +16,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.shortcuts import reverse +from jumpserver.const import LOCAL_DYNAMIC_SETTINGS from orgs.utils import current_org from common.utils import signer, date_expired_default, get_logger, lazyproperty from common import fields @@ -394,7 +395,7 @@ class MFAMixin: @property def mfa_force_enabled(self): - if settings.SECURITY_MFA_AUTH: + if LOCAL_DYNAMIC_SETTINGS.SECURITY_MFA_AUTH: return True return self.mfa_level == 2 From 0ccd806eca0255deeda216e83392cde647e0bd0d Mon Sep 17 00:00:00 2001 From: Eric_Lee Date: Tue, 16 Jun 2020 16:12:59 +0800 Subject: [PATCH 136/146] add system user perm api (#4108) --- apps/perms/api/__init__.py | 1 + apps/perms/api/system_user_permission.py | 21 +++++++++++++++++++ apps/perms/serializers/__init__.py | 2 +- .../serializers/system_user_permission.py | 18 ++++++++++++++++ apps/perms/urls/api_urls.py | 6 +++--- apps/perms/urls/system_user_permission.py | 6 ++++++ 6 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 apps/perms/api/system_user_permission.py create mode 100644 apps/perms/serializers/system_user_permission.py create mode 100644 apps/perms/urls/system_user_permission.py diff --git a/apps/perms/api/__init__.py b/apps/perms/api/__init__.py index f12a8cc38..cba965d00 100644 --- a/apps/perms/api/__init__.py +++ b/apps/perms/api/__init__.py @@ -11,3 +11,4 @@ from .user_remote_app_permission import * from .database_app_permission import * from .database_app_permission_relation import * from .user_database_app_permission import * +from .system_user_permission import * diff --git a/apps/perms/api/system_user_permission.py b/apps/perms/api/system_user_permission.py new file mode 100644 index 000000000..aa0ceda39 --- /dev/null +++ b/apps/perms/api/system_user_permission.py @@ -0,0 +1,21 @@ + + +from rest_framework import generics +from common.permissions import IsValidUser +from orgs.utils import tmp_to_root_org +from .. import serializers + + +class SystemUserPermission(generics.ListAPIView): + permission_classes = (IsValidUser,) + serializer_class = serializers.SystemUserSerializer + + def get_queryset(self): + return self.get_user_system_users() + + def get_user_system_users(self): + from perms.utils import AssetPermissionUtil + user = self.request.user + with tmp_to_root_org(): + util = AssetPermissionUtil(user) + return util.get_system_users() diff --git a/apps/perms/serializers/__init__.py b/apps/perms/serializers/__init__.py index 7b8945827..43f221d6e 100644 --- a/apps/perms/serializers/__init__.py +++ b/apps/perms/serializers/__init__.py @@ -1,6 +1,6 @@ # coding: utf-8 # - +from .system_user_permission import * from .asset_permission import * from .user_permission import * from .remote_app_permission import * diff --git a/apps/perms/serializers/system_user_permission.py b/apps/perms/serializers/system_user_permission.py new file mode 100644 index 000000000..a383ffbd0 --- /dev/null +++ b/apps/perms/serializers/system_user_permission.py @@ -0,0 +1,18 @@ +from rest_framework import serializers +from ..hands import SystemUser + +__all__ = [ + 'SystemUserSerializer', +] + + +class SystemUserSerializer(serializers.ModelSerializer): + class Meta: + model = SystemUser + fields = [ + 'id', 'name', 'username', 'protocol', + 'login_mode', 'login_mode_display', + 'priority', 'username_same_with_user', + 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', + 'sftp_root', 'date_created', 'created_by' + ] diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index 9ec3b754f..d70a824cc 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -5,10 +5,10 @@ from common import api as capi from .asset_permission import asset_permission_urlpatterns from .remote_app_permission import remote_app_permission_urlpatterns from .database_app_permission import database_app_permission_urlpatterns +from .system_user_permission import system_users_permission_urlpatterns app_name = 'perms' - old_version_urlpatterns = [ re_path('(?Puser|user-group|asset-permission|remote-app-permission)/.*', capi.redirect_plural_name_api) ] @@ -16,5 +16,5 @@ old_version_urlpatterns = [ urlpatterns = asset_permission_urlpatterns + \ remote_app_permission_urlpatterns + \ database_app_permission_urlpatterns + \ - old_version_urlpatterns - + old_version_urlpatterns + \ + system_users_permission_urlpatterns diff --git a/apps/perms/urls/system_user_permission.py b/apps/perms/urls/system_user_permission.py new file mode 100644 index 000000000..e5a5ba1e4 --- /dev/null +++ b/apps/perms/urls/system_user_permission.py @@ -0,0 +1,6 @@ +from django.urls import path +from .. import api + +system_users_permission_urlpatterns = [ + path('system-users-permission/', api.SystemUserPermission.as_view(), name='system-users-permission'), +] From 3318df17712da1a11bdc9847b967082e25eb1274 Mon Sep 17 00:00:00 2001 From: xinwen Date: Tue, 16 Jun 2020 16:33:53 +0800 Subject: [PATCH 137/146] =?UTF-8?q?[Fix]=20=E6=97=A5=E5=BF=97=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1/FTP=E6=97=A5=E5=BF=97=20(#4109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/audits/api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/audits/api.py b/apps/audits/api.py index 1d91a7433..9d6d0339e 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -from rest_framework.mixins import ListModelMixin +from rest_framework.mixins import ListModelMixin, CreateModelMixin from django.db.models import F, Value from django.db.models.functions import Concat @@ -15,7 +15,9 @@ from .serializers import FTPLogSerializer, UserLoginLogSerializer, CommandExecut from .serializers import OperateLogSerializer, PasswordChangeLogSerializer, CommandExecutionHostsRelationSerializer -class FTPLogViewSet(ListModelMixin, OrgGenericViewSet): +class FTPLogViewSet(CreateModelMixin, + ListModelMixin, + OrgGenericViewSet): model = FTPLog serializer_class = FTPLogSerializer permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,) From 6d30fe797c55bff1ed7ac643f0e42dca6b88a0cf Mon Sep 17 00:00:00 2001 From: Eric_Lee Date: Tue, 16 Jun 2020 17:25:57 +0800 Subject: [PATCH 138/146] fix ldap test bug (#4110) --- apps/settings/utils/ldap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/settings/utils/ldap.py b/apps/settings/utils/ldap.py index 4ecd9c8af..516472d90 100644 --- a/apps/settings/utils/ldap.py +++ b/apps/settings/utils/ldap.py @@ -433,7 +433,7 @@ class LDAPTestUtil(object): for search_ou in search_ous: util.config.search_ou = search_ou user_entries = util.search_user_entries() - logger.debug('Search ou: {}, count user: {}').format(search_ou, len(user_entries)) + logger.debug('Search ou: {}, count user: {}'.format(search_ou, len(user_entries))) if len(user_entries) == 0: error = _('Invalid User OU or User search filter: {}').format(search_ou) raise self.LDAPInvalidSearchOuOrFilterError(error) From 220ccda04d6f09dfdf7ac42ab4b8aa2b3de47cdf Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 16 Jun 2020 17:55:18 +0800 Subject: [PATCH 139/146] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9domain?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E7=9A=84assets=E4=B8=8D=E6=98=AF=E5=BF=85?= =?UTF-8?q?=E5=A1=AB=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/domain.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 26f1b43f5..64c5eae66 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -15,11 +15,18 @@ class DomainSerializer(BulkOrgResourceModelSerializer): class Meta: model = Domain - fields = [ - 'id', 'name', 'asset_count', 'gateway_count', 'comment', 'assets', - 'date_created' + fields_mini = ['id', 'name'] + fields_small = fields_mini + [ + 'comment', 'date_created' ] - read_only_fields = ( 'asset_count', 'gateway_count', 'date_created') + fields_m2m = [ + 'asset_count', 'assets', 'gateway_count', + ] + fields = fields_small + fields_m2m + read_only_fields = ('asset_count', 'gateway_count', 'date_created') + extra_kwargs = { + 'assets': {'required': False} + } list_serializer_class = AdaptedBulkListSerializer @staticmethod From 8ad71b6dd91e7f013499e95e7363cd7328118819 Mon Sep 17 00:00:00 2001 From: xinwen Date: Tue, 16 Jun 2020 18:11:23 +0800 Subject: [PATCH 140/146] [Update] Move LOCAL_DYNAMIC_SETTINGS (#4113) --- apps/common/local.py | 33 ++++++++++++++++++++++++++++++++- apps/jumpserver/const.py | 33 +-------------------------------- apps/users/models/user.py | 2 +- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/apps/common/local.py b/apps/common/local.py index 37a2ccb0b..e075d29ff 100644 --- a/apps/common/local.py +++ b/apps/common/local.py @@ -1,9 +1,40 @@ # -*- coding: utf-8 -*- # -from werkzeug.local import Local +from jumpserver.const import DYNAMIC +from werkzeug.local import Local, LocalProxy thread_local = Local() def _find(attr): return getattr(thread_local, attr, None) + + +class _Settings: + pass + + +def get_dynamic_cfg_from_thread_local(): + KEY = 'dynamic_config' + + try: + cfg = getattr(thread_local, KEY) + except AttributeError: + cfg = _Settings() + setattr(thread_local, KEY, cfg) + + return cfg + + +class DynamicDefaultLocalProxy(LocalProxy): + def __getattr__(self, item): + try: + value = super().__getattr__(item) + except AttributeError: + value = getattr(DYNAMIC, item)() + setattr(self, item, value) + + return value + + +LOCAL_DYNAMIC_SETTINGS = DynamicDefaultLocalProxy(get_dynamic_cfg_from_thread_local) diff --git a/apps/jumpserver/const.py b/apps/jumpserver/const.py index 9dad76cd5..c2889870e 100644 --- a/apps/jumpserver/const.py +++ b/apps/jumpserver/const.py @@ -2,12 +2,9 @@ # import os -from werkzeug.local import LocalProxy - from .conf import ConfigManager -from common.local import thread_local -__all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG', 'DYNAMIC', 'LOCAL_DYNAMIC_SETTINGS'] +__all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG', 'DYNAMIC'] BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) PROJECT_DIR = os.path.dirname(BASE_DIR) @@ -16,31 +13,3 @@ CONFIG = ConfigManager.load_user_config() DYNAMIC = ConfigManager.get_dynamic_config(CONFIG) -class _Settings: - pass - - -def get_dynamic_cfg_from_thread_local(): - KEY = 'dynamic_config' - - try: - cfg = getattr(thread_local, KEY) - except AttributeError: - cfg = _Settings() - setattr(thread_local, KEY, cfg) - - return cfg - - -class DynamicDefaultLocalProxy(LocalProxy): - def __getattr__(self, item): - try: - value = super().__getattr__(item) - except AttributeError: - value = getattr(DYNAMIC, item)() - setattr(self, item, value) - - return value - - -LOCAL_DYNAMIC_SETTINGS = DynamicDefaultLocalProxy(get_dynamic_cfg_from_thread_local) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index d30141d23..89bfe4254 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -16,7 +16,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.shortcuts import reverse -from jumpserver.const import LOCAL_DYNAMIC_SETTINGS +from common.local import LOCAL_DYNAMIC_SETTINGS from orgs.utils import current_org from common.utils import signer, date_expired_default, get_logger, lazyproperty from common import fields From 46941037dd9f9d6650aebeea3b24927acf2f7179 Mon Sep 17 00:00:00 2001 From: Eric_Lee Date: Wed, 17 Jun 2020 15:03:41 +0800 Subject: [PATCH 141/146] add SECURITY_COMMAND_EXECUTION (#4114) --- apps/settings/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/settings/api.py b/apps/settings/api.py index 35ccd5f5b..786c43bad 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -275,6 +275,7 @@ class PublicSettingApi(generics.RetrieveAPIView): "LOGIN_CONFIRM_ENABLE": settings.LOGIN_CONFIRM_ENABLE, "SECURITY_VIEW_AUTH_NEED_MFA": settings.SECURITY_VIEW_AUTH_NEED_MFA, "SECURITY_MFA_VERIFY_TTL": settings.SECURITY_MFA_VERIFY_TTL, + "SECURITY_COMMAND_EXECUTION": settings.SECURITY_COMMAND_EXECUTION, "LOGO_URLS": settings.LOGO_URLS, "PASSWORD_RULE": { 'SECURITY_PASSWORD_MIN_LENGTH': settings.SECURITY_PASSWORD_MIN_LENGTH, From 64064cb526edcbeece8b426fe289d176a4c8d068 Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 17 Jun 2020 17:47:45 +0800 Subject: [PATCH 142/146] =?UTF-8?q?[Update]=20=E6=B3=A8=E9=87=8A=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E6=A0=91print?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/utils/asset_permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index c5e227980..b78d5f930 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -351,7 +351,7 @@ class AssetPermissionUtil(AssetPermissionUtilCacheMixin): self.add_favorite_node_if_need(user_tree) self.set_user_tree_to_cache_if_need(user_tree) self.set_user_tree_to_local(user_tree) - print(user_tree) + # print(user_tree) return user_tree # Todo: 是否可以获取多个资产的系统用户 From 0bfe25596657b8a56b8c7ed5751c2117b4c3aae1 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Thu, 18 Jun 2020 14:58:00 +0800 Subject: [PATCH 143/146] Update README.md --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f93198e1a..521fbe714 100644 --- a/README.md +++ b/README.md @@ -24,19 +24,18 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 ## 版本说明 -从 2.0 开始 JumpServer 版本号规则将发生变更,如下所示 - -大版本.功能版本.bug修复版本 +自 v2.0.0 发布后, JumpServer 版本号命名将变更为:v大版本.功能版本.Bug修复版本。比如: ``` -如 2.0.1 修复bug 将是 2.0.2 -如 2.0.2 添加新功能后 将会是 2.1.0 +v2.0.1 是 v2.0.0 之后的Bug修复版本; +v2.1.0 是 v2.0.0 之后的功能版本。 ``` -并且 JumpServer 以后也会像其它优秀开源项目一样,同时维护多个版本,目前计划同时维护 3 个版本 +像其它优秀开源项目一样,JumpServer 每个月会发布一个功能版本,并同时维护 3 个功能版本。比如: ``` -如 2.1, 2.2, 2.3 版本我们会同时维护, 发布 2.4 版本后,2.1 停止维护 +在 v2.4 发布前,我们会同时维护 v2.1, v2.2, v2.3; +在 v2.4 发布后,我们会同时维护 v2.2, v2.3, v2.4,v2.1 会停止维护。 ``` JumpServer 每个月会发布一个功能版本, 修复版本视情况而定 From 3c6cfaa6cfc6690a2637ead9817877aa157a2b16 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Thu, 18 Jun 2020 14:59:13 +0800 Subject: [PATCH 144/146] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 521fbe714..01eb19e58 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,6 @@ v2.1.0 是 v2.0.0 之后的功能版本。 在 v2.4 发布后,我们会同时维护 v2.2, v2.3, v2.4,v2.1 会停止维护。 ``` -JumpServer 每个月会发布一个功能版本, 修复版本视情况而定 - ## 功能列表
    From 923f0ed47750ee30bb197fb808b560361b0373e1 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Thu, 18 Jun 2020 15:01:08 +0800 Subject: [PATCH 145/146] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 01eb19e58..07e6f6c91 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ v2.1.0 是 v2.0.0 之后的功能版本。 像其它优秀开源项目一样,JumpServer 每个月会发布一个功能版本,并同时维护 3 个功能版本。比如: ``` -在 v2.4 发布前,我们会同时维护 v2.1, v2.2, v2.3; -在 v2.4 发布后,我们会同时维护 v2.2, v2.3, v2.4,v2.1 会停止维护。 +在 v2.4 发布前,我们会同时维护 v2.1、v2.2、v2.3; +在 v2.4 发布后,我们会同时维护 v2.2、v2.3、v2.4;v2.1 会停止维护。 ``` ## 功能列表 From f1e5c7c2bb6dccdbc1060418ffbed752aba5b8fe Mon Sep 17 00:00:00 2001 From: Michael Bai Date: Sat, 20 Jun 2020 16:18:58 +0800 Subject: [PATCH 146/146] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=94=B9=E5=AF=86?= =?UTF-8?q?=E8=AE=A1=E5=88=92=E5=AE=89=E5=85=A8=E6=A8=A1=E5=BC=8F=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/conf.py | 3 ++- apps/jumpserver/settings/custom.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 13f25d492..2b4a5b65f 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -259,7 +259,8 @@ class Config(dict): 'WINDOWS_SKIP_ALL_MANUAL_PASSWORD': False, 'ORG_CHANGE_TO_URL': '', 'LANGUAGE_CODE': 'zh', - 'TIME_ZONE': 'Asia/Shanghai' + 'TIME_ZONE': 'Asia/Shanghai', + 'CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED': True } def compatible_auth_openid_of_key(self): diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index 0be476d96..efcf2f4cc 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -90,3 +90,5 @@ WINDOWS_SKIP_ALL_MANUAL_PASSWORD = CONFIG.WINDOWS_SKIP_ALL_MANUAL_PASSWORD XPACK_LICENSE_IS_VALID = DYNAMIC.XPACK_LICENSE_IS_VALID LOGO_URLS = DYNAMIC.LOGO_URLS + +CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = CONFIG.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED