diff --git a/apps/applications/api/account.py b/apps/applications/api/account.py index 8b28bcc37..4fc06807e 100644 --- a/apps/applications/api/account.py +++ b/apps/applications/api/account.py @@ -44,11 +44,7 @@ class ApplicationAccountViewSet(JMSBulkModelViewSet): permission_classes = (IsOrgAdmin,) def get_queryset(self): - queryset = Account.objects.all() \ - .annotate(type=F('app__type')) \ - .annotate(app_display=F('app__name')) \ - .annotate(systemuser_display=F('systemuser__name')) \ - .annotate(category=F('app__category')) + queryset = Account.get_queryset() return queryset diff --git a/apps/applications/models/account.py b/apps/applications/models/account.py index ff514590a..f95b0d0f5 100644 --- a/apps/applications/models/account.py +++ b/apps/applications/models/account.py @@ -1,5 +1,6 @@ from django.db import models from simple_history.models import HistoricalRecords +from django.db.models import F from django.utils.translation import ugettext_lazy as _ from common.utils import lazyproperty @@ -7,8 +8,12 @@ from assets.models.base import BaseUser class Account(BaseUser): - app = models.ForeignKey('applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Database')) - systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")) + app = models.ForeignKey( + 'applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Database') + ) + systemuser = models.ForeignKey( + 'assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user") + ) version = models.IntegerField(default=1, verbose_name=_('Version')) history = HistoricalRecords() @@ -60,6 +65,10 @@ class Account(BaseUser): def type(self): return self.app.type + @lazyproperty + def attrs(self): + return self.app.attrs + @lazyproperty def app_display(self): return self.systemuser.name @@ -84,5 +93,14 @@ class Account(BaseUser): app = '*' return '{}@{}'.format(username, app) + @classmethod + def get_queryset(cls): + queryset = cls.objects.all() \ + .annotate(type=F('app__type')) \ + .annotate(app_display=F('app__name')) \ + .annotate(systemuser_display=F('systemuser__name')) \ + .annotate(category=F('app__category')) + return queryset + def __str__(self): return self.smart_name diff --git a/apps/applications/serializers/application.py b/apps/applications/serializers/application.py index 2d2273dc7..b1827b49a 100644 --- a/apps/applications/serializers/application.py +++ b/apps/applications/serializers/application.py @@ -1,6 +1,5 @@ # coding: utf-8 # - from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ @@ -9,7 +8,8 @@ from assets.serializers.base import AuthSerializerMixin from common.drf.serializers import MethodSerializer from .attrs import ( category_serializer_classes_mapping, - type_serializer_classes_mapping + type_serializer_classes_mapping, + type_secret_serializer_classes_mapping ) from .. import models from .. import const @@ -23,17 +23,28 @@ __all__ = [ class AppSerializerMixin(serializers.Serializer): attrs = MethodSerializer() + @property + def app(self): + if isinstance(self.instance, models.Application): + instance = self.instance + else: + instance = None + return instance + def get_attrs_serializer(self): default_serializer = serializers.Serializer(read_only=True) - if isinstance(self.instance, models.Application): - _type = self.instance.type - _category = self.instance.category + instance = self.app + if instance: + _type = instance.type + _category = instance.category else: _type = self.context['request'].query_params.get('type') _category = self.context['request'].query_params.get('category') - if _type: - serializer_class = type_serializer_classes_mapping.get(_type) + if isinstance(self, AppAccountSecretSerializer): + serializer_class = type_secret_serializer_classes_mapping.get(_type) + else: + serializer_class = type_serializer_classes_mapping.get(_type) elif _category: serializer_class = category_serializer_classes_mapping.get(_category) else: @@ -84,11 +95,13 @@ class MiniAppSerializer(serializers.ModelSerializer): fields = AppSerializer.Meta.fields_mini -class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): +class AppAccountSerializer(AppSerializerMixin, AuthSerializerMixin, BulkOrgResourceModelSerializer): category = serializers.ChoiceField(label=_('Category'), choices=const.AppCategory.choices, read_only=True) category_display = serializers.SerializerMethodField(label=_('Category display')) type = serializers.ChoiceField(label=_('Type'), choices=const.AppType.choices, read_only=True) type_display = serializers.SerializerMethodField(label=_('Type display')) + date_created = serializers.DateTimeField(label=_('Date created'), format="%Y/%m/%d %H:%M:%S", read_only=True) + date_updated = serializers.DateTimeField(label=_('Date updated'), format="%Y/%m/%d %H:%M:%S", read_only=True) category_mapper = dict(const.AppCategory.choices) type_mapper = dict(const.AppType.choices) @@ -96,10 +109,11 @@ class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class Meta: model = models.Account fields_mini = ['id', 'username', 'version'] - fields_write_only = ['password', 'private_key', 'passphrase'] + fields_write_only = ['password', 'private_key', 'public_key', 'passphrase'] + fields_other = ['date_created', 'date_updated'] fields_fk = ['systemuser', 'systemuser_display', 'app', 'app_display'] - fields = fields_mini + fields_fk + fields_write_only + [ - 'type', 'type_display', 'category', 'category_display', + fields = fields_mini + fields_fk + fields_write_only + fields_other + [ + 'type', 'type_display', 'category', 'category_display', 'attrs' ] extra_kwargs = { 'username': {'default': '', 'required': False}, @@ -112,6 +126,14 @@ class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): 'ignore_conflicts': True } + @property + def app(self): + if isinstance(self.instance, models.Account): + instance = self.instance.app + else: + instance = None + return instance + def get_category_display(self, obj): return self.category_mapper.get(obj.category) @@ -131,6 +153,10 @@ class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class AppAccountSecretSerializer(AppAccountSerializer): class Meta(AppAccountSerializer.Meta): + fields_backup = [ + 'id', 'app_display', 'attrs', 'username', 'password', 'private_key', + 'public_key', 'date_created', 'date_updated', 'version' + ] extra_kwargs = { 'password': {'write_only': False}, 'private_key': {'write_only': False}, diff --git a/apps/applications/serializers/attrs/application_category/cloud.py b/apps/applications/serializers/attrs/application_category/cloud.py index f5fa71810..64306b24e 100644 --- a/apps/applications/serializers/attrs/application_category/cloud.py +++ b/apps/applications/serializers/attrs/application_category/cloud.py @@ -1,7 +1,6 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ - __all__ = ['CloudSerializer'] diff --git a/apps/applications/serializers/attrs/application_category/remote_app.py b/apps/applications/serializers/attrs/application_category/remote_app.py index bace9173b..ad2610791 100644 --- a/apps/applications/serializers/attrs/application_category/remote_app.py +++ b/apps/applications/serializers/attrs/application_category/remote_app.py @@ -10,7 +10,6 @@ from assets.models import Asset logger = get_logger(__file__) - __all__ = ['RemoteAppSerializer'] diff --git a/apps/applications/serializers/attrs/application_type/chrome.py b/apps/applications/serializers/attrs/application_type/chrome.py index 5b9e5147a..96b4587e7 100644 --- a/apps/applications/serializers/attrs/application_type/chrome.py +++ b/apps/applications/serializers/attrs/application_type/chrome.py @@ -3,8 +3,7 @@ from rest_framework import serializers from ..application_category import RemoteAppSerializer - -__all__ = ['ChromeSerializer'] +__all__ = ['ChromeSerializer', 'ChromeSecretSerializer'] class ChromeSerializer(RemoteAppSerializer): @@ -17,10 +16,16 @@ class ChromeSerializer(RemoteAppSerializer): max_length=128, allow_blank=True, required=False, label=_('Target URL'), allow_null=True, ) chrome_username = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Username'), allow_null=True, + max_length=128, allow_blank=True, required=False, label=_('Chrome username'), allow_null=True, ) chrome_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'), + max_length=128, allow_blank=True, required=False, write_only=True, label=_('Chrome password'), allow_null=True ) + +class ChromeSecretSerializer(ChromeSerializer): + chrome_password = serializers.CharField( + max_length=128, allow_blank=True, required=False, read_only=True, label=_('Chrome password'), + allow_null=True + ) diff --git a/apps/applications/serializers/attrs/application_type/custom.py b/apps/applications/serializers/attrs/application_type/custom.py index 0cbc09cf9..0a58c28b1 100644 --- a/apps/applications/serializers/attrs/application_type/custom.py +++ b/apps/applications/serializers/attrs/application_type/custom.py @@ -1,11 +1,9 @@ - from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from ..application_category import RemoteAppSerializer - -__all__ = ['CustomSerializer'] +__all__ = ['CustomSerializer', 'CustomSecretSerializer'] class CustomSerializer(RemoteAppSerializer): @@ -18,10 +16,17 @@ class CustomSerializer(RemoteAppSerializer): allow_null=True, ) custom_username = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Username'), + max_length=128, allow_blank=True, required=False, label=_('Custom Username'), allow_null=True, ) custom_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'), + max_length=128, allow_blank=True, required=False, write_only=True, label=_('Custom password'), + allow_null=True, + ) + + +class CustomSecretSerializer(RemoteAppSerializer): + custom_password = serializers.CharField( + max_length=128, allow_blank=True, required=False, read_only=True, label=_('Custom password'), allow_null=True, ) diff --git a/apps/applications/serializers/attrs/application_type/k8s.py b/apps/applications/serializers/attrs/application_type/k8s.py index ac1e023d3..cccfd67a0 100644 --- a/apps/applications/serializers/attrs/application_type/k8s.py +++ b/apps/applications/serializers/attrs/application_type/k8s.py @@ -1,6 +1,5 @@ from ..application_category import CloudSerializer - __all__ = ['K8SSerializer'] diff --git a/apps/applications/serializers/attrs/application_type/mariadb.py b/apps/applications/serializers/attrs/application_type/mariadb.py index d2bcb546f..e5693e429 100644 --- a/apps/applications/serializers/attrs/application_type/mariadb.py +++ b/apps/applications/serializers/attrs/application_type/mariadb.py @@ -1,6 +1,5 @@ from .mysql import MySQLSerializer - __all__ = ['MariaDBSerializer'] diff --git a/apps/applications/serializers/attrs/application_type/mysql.py b/apps/applications/serializers/attrs/application_type/mysql.py index bb4e56c33..78f312ebe 100644 --- a/apps/applications/serializers/attrs/application_type/mysql.py +++ b/apps/applications/serializers/attrs/application_type/mysql.py @@ -3,13 +3,9 @@ from django.utils.translation import ugettext_lazy as _ from ..application_category import DBSerializer - __all__ = ['MySQLSerializer'] class MySQLSerializer(DBSerializer): port = serializers.IntegerField(default=3306, label=_('Port'), allow_null=True) - - - diff --git a/apps/applications/serializers/attrs/application_type/mysql_workbench.py b/apps/applications/serializers/attrs/application_type/mysql_workbench.py index 8025ec80f..bc41a689b 100644 --- a/apps/applications/serializers/attrs/application_type/mysql_workbench.py +++ b/apps/applications/serializers/attrs/application_type/mysql_workbench.py @@ -3,8 +3,7 @@ from rest_framework import serializers from ..application_category import RemoteAppSerializer - -__all__ = ['MySQLWorkbenchSerializer'] +__all__ = ['MySQLWorkbenchSerializer', 'MySQLWorkbenchSecretSerializer'] class MySQLWorkbenchSerializer(RemoteAppSerializer): @@ -27,10 +26,17 @@ class MySQLWorkbenchSerializer(RemoteAppSerializer): allow_null=True, ) mysql_workbench_username = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Username'), + max_length=128, allow_blank=True, required=False, label=_('Mysql workbench username'), allow_null=True, ) mysql_workbench_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'), + max_length=128, allow_blank=True, required=False, write_only=True, label=_('Mysql workbench password'), + allow_null=True, + ) + + +class MySQLWorkbenchSecretSerializer(RemoteAppSerializer): + mysql_workbench_password = serializers.CharField( + max_length=128, allow_blank=True, required=False, read_only=True, label=_('Mysql workbench password'), allow_null=True, ) diff --git a/apps/applications/serializers/attrs/application_type/oracle.py b/apps/applications/serializers/attrs/application_type/oracle.py index 63b905714..c87c4904d 100644 --- a/apps/applications/serializers/attrs/application_type/oracle.py +++ b/apps/applications/serializers/attrs/application_type/oracle.py @@ -3,10 +3,8 @@ from django.utils.translation import ugettext_lazy as _ from ..application_category import DBSerializer - __all__ = ['OracleSerializer'] class OracleSerializer(DBSerializer): port = serializers.IntegerField(default=1521, label=_('Port'), allow_null=True) - diff --git a/apps/applications/serializers/attrs/application_type/pgsql.py b/apps/applications/serializers/attrs/application_type/pgsql.py index 1b3cc2ef4..0434a0423 100644 --- a/apps/applications/serializers/attrs/application_type/pgsql.py +++ b/apps/applications/serializers/attrs/application_type/pgsql.py @@ -3,10 +3,8 @@ from django.utils.translation import ugettext_lazy as _ from ..application_category import DBSerializer - __all__ = ['PostgreSerializer'] class PostgreSerializer(DBSerializer): port = serializers.IntegerField(default=5432, label=_('Port'), allow_null=True) - diff --git a/apps/applications/serializers/attrs/application_type/redis.py b/apps/applications/serializers/attrs/application_type/redis.py index 3ba9ea715..06cd1ae3b 100644 --- a/apps/applications/serializers/attrs/application_type/redis.py +++ b/apps/applications/serializers/attrs/application_type/redis.py @@ -3,13 +3,9 @@ from django.utils.translation import ugettext_lazy as _ from ..application_category import DBSerializer - __all__ = ['RedisSerializer'] class RedisSerializer(DBSerializer): port = serializers.IntegerField(default=6379, label=_('Port'), allow_null=True) - - - diff --git a/apps/applications/serializers/attrs/application_type/sqlserver.py b/apps/applications/serializers/attrs/application_type/sqlserver.py index af8246bd0..5f9b5d2bf 100644 --- a/apps/applications/serializers/attrs/application_type/sqlserver.py +++ b/apps/applications/serializers/attrs/application_type/sqlserver.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from ..application_category import DBSerializer - __all__ = ['SQLServerSerializer'] diff --git a/apps/applications/serializers/attrs/application_type/vmware_client.py b/apps/applications/serializers/attrs/application_type/vmware_client.py index 1ed44e656..6ec3975cb 100644 --- a/apps/applications/serializers/attrs/application_type/vmware_client.py +++ b/apps/applications/serializers/attrs/application_type/vmware_client.py @@ -3,8 +3,7 @@ from rest_framework import serializers from ..application_category import RemoteAppSerializer - -__all__ = ['VMwareClientSerializer'] +__all__ = ['VMwareClientSerializer', 'VMwareClientSecretSerializer'] class VMwareClientSerializer(RemoteAppSerializer): @@ -23,10 +22,17 @@ class VMwareClientSerializer(RemoteAppSerializer): allow_null=True ) vmware_username = serializers.CharField( - max_length=128, allow_blank=True, required=False, label=_('Username'), + max_length=128, allow_blank=True, required=False, label=_('Vmware username'), allow_null=True ) vmware_password = serializers.CharField( - max_length=128, allow_blank=True, required=False, write_only=True, label=_('Password'), + max_length=128, allow_blank=True, required=False, write_only=True, label=_('Vmware password'), + allow_null=True + ) + + +class VMwareClientSecretSerializer(RemoteAppSerializer): + vmware_password = serializers.CharField( + max_length=128, allow_blank=True, required=False, read_only=True, label=_('Vmware password'), allow_null=True ) diff --git a/apps/applications/serializers/attrs/attrs.py b/apps/applications/serializers/attrs/attrs.py index f249d7816..a1ca0a3da 100644 --- a/apps/applications/serializers/attrs/attrs.py +++ b/apps/applications/serializers/attrs/attrs.py @@ -1,15 +1,15 @@ -from rest_framework import serializers +import copy + from applications import const from . import application_category, application_type - __all__ = [ 'category_serializer_classes_mapping', 'type_serializer_classes_mapping', 'get_serializer_class_by_application_type', + 'type_secret_serializer_classes_mapping' ] - # define `attrs` field `category serializers mapping` # --------------------------------------------------- @@ -30,15 +30,32 @@ type_serializer_classes_mapping = { const.AppType.oracle.value: application_type.OracleSerializer, const.AppType.pgsql.value: application_type.PostgreSerializer, const.AppType.sqlserver.value: application_type.SQLServerSerializer, - # remote-app - const.AppType.chrome.value: application_type.ChromeSerializer, - const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSerializer, - const.AppType.vmware_client.value: application_type.VMwareClientSerializer, - const.AppType.custom.value: application_type.CustomSerializer, # cloud const.AppType.k8s.value: application_type.K8SSerializer } +remote_app_serializer_classes_mapping = { + # remote-app + const.AppType.chrome.value: application_type.ChromeSerializer, + const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSerializer, + const.AppType.vmware_client.value: application_type.VMwareClientSerializer, + const.AppType.custom.value: application_type.CustomSerializer +} + +type_serializer_classes_mapping.update(remote_app_serializer_classes_mapping) + +remote_app_secret_serializer_classes_mapping = { + # remote-app + const.AppType.chrome.value: application_type.ChromeSecretSerializer, + const.AppType.mysql_workbench.value: application_type.MySQLWorkbenchSecretSerializer, + const.AppType.vmware_client.value: application_type.VMwareClientSecretSerializer, + const.AppType.custom.value: application_type.CustomSecretSerializer +} + +type_secret_serializer_classes_mapping = copy.deepcopy(type_serializer_classes_mapping) + +type_secret_serializer_classes_mapping.update(remote_app_secret_serializer_classes_mapping) + def get_serializer_class_by_application_type(_application_type): return type_serializer_classes_mapping.get(_application_type) diff --git a/apps/assets/api/accounts.py b/apps/assets/api/accounts.py index e05bee3d2..0c4e01ff2 100644 --- a/apps/assets/api/accounts.py +++ b/apps/assets/api/accounts.py @@ -64,9 +64,7 @@ class AccountViewSet(OrgBulkModelViewSet): permission_classes = (IsOrgAdmin,) def get_queryset(self): - queryset = super().get_queryset() \ - .annotate(ip=F('asset__ip')) \ - .annotate(hostname=F('asset__hostname')) + queryset = AuthBook.get_queryset() return queryset @action(methods=['post'], detail=True, url_path='verify') diff --git a/apps/assets/models/authbook.py b/apps/assets/models/authbook.py index 2707583da..c489ab608 100644 --- a/apps/assets/models/authbook.py +++ b/apps/assets/models/authbook.py @@ -2,6 +2,7 @@ # from django.db import models +from django.db.models import F from django.utils.translation import ugettext_lazy as _ from simple_history.models import HistoricalRecords @@ -116,6 +117,15 @@ class AuthBook(BaseUser, AbsConnectivity): self.asset.save() logger.debug('Update asset admin user: {} {}'.format(self.asset, self.systemuser)) + @classmethod + def get_queryset(cls): + queryset = cls.objects.all() \ + .annotate(ip=F('asset__ip')) \ + .annotate(hostname=F('asset__hostname')) \ + .annotate(platform=F('asset__platform__name')) \ + .annotate(protocols=F('asset__protocols')) + return queryset + def __str__(self): return self.smart_name diff --git a/apps/assets/serializers/account.py b/apps/assets/serializers/account.py index 2f80ab936..5de57474b 100644 --- a/apps/assets/serializers/account.py +++ b/apps/assets/serializers/account.py @@ -11,10 +11,18 @@ from .utils import validate_password_contains_left_double_curly_bracket class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): ip = serializers.ReadOnlyField(label=_("IP")) hostname = serializers.ReadOnlyField(label=_("Hostname")) + platform = serializers.ReadOnlyField(label=_("Platform")) + protocols = serializers.SerializerMethodField(label=_("Protocols")) + date_created = serializers.DateTimeField( + label=_('Date created'), format="%Y/%m/%d %H:%M:%S", read_only=True + ) + date_updated = serializers.DateTimeField( + label=_('Date updated'), format="%Y/%m/%d %H:%M:%S", read_only=True + ) class Meta: model = AuthBook - fields_mini = ['id', 'username', 'ip', 'hostname', 'version'] + fields_mini = ['id', 'username', 'ip', 'hostname', 'platform', 'protocols', 'version'] fields_write_only = ['password', 'private_key', "public_key", 'passphrase'] fields_other = ['date_created', 'date_updated', 'connectivity', 'date_verified', 'comment'] fields_small = fields_mini + fields_write_only + fields_other @@ -32,6 +40,9 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): } ref_name = 'AssetAccountSerializer' + def get_protocols(self, v): + return v.protocols.replace(' ', ', ') + @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ @@ -45,6 +56,10 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class AccountSecretSerializer(AccountSerializer): class Meta(AccountSerializer.Meta): + fields_backup = [ + 'hostname', 'ip', 'platform', 'protocols', 'username', 'password', + 'private_key', 'public_key', 'date_created', 'date_updated', 'version' + ] extra_kwargs = { 'password': {'write_only': False}, 'private_key': {'write_only': False}, diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index de95e3b80..427d0e470 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -5,8 +5,6 @@ from django.core.validators import RegexValidator from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer -from users.models import User, UserGroup -from perms.models import AssetPermission from ..models import Asset, Node, Platform, SystemUser __all__ = [ diff --git a/apps/assets/task_handlers/backup/handlers.py b/apps/assets/task_handlers/backup/handlers.py index c1b62a730..91bae54d1 100644 --- a/apps/assets/task_handlers/backup/handlers.py +++ b/apps/assets/task_handlers/backup/handlers.py @@ -1,15 +1,18 @@ import os import time import pandas as pd -from collections import defaultdict +from collections import defaultdict, OrderedDict from django.conf import settings from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers -from assets.models import AuthBook, Asset, BaseUser, ProtocolsMixin +from assets.models import AuthBook +from assets.serializers import AccountSecretSerializer from assets.notifications import AccountBackupExecutionTaskMsg -from applications.models import Account, Application +from applications.models import Account from applications.const import AppType +from applications.serializers import AppAccountSecretSerializer from users.models import User from common.utils import get_logger from common.utils.timezone import local_now_display @@ -20,7 +23,46 @@ logger = get_logger(__file__) PATH = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp') -class AssetAccountHandler: +class BaseAccountHandler: + @classmethod + def unpack_data(cls, serializer_data, data=None): + if data is None: + data = {} + for k, v in serializer_data.items(): + if isinstance(v, OrderedDict): + cls.unpack_data(v, data) + else: + data[k] = v + return data + + @classmethod + def get_header_fields(cls, serializer: serializers.Serializer): + try: + backup_fields = getattr(serializer, 'Meta').fields_backup + except AttributeError: + backup_fields = serializer.fields.keys() + header_fields = {} + for field in backup_fields: + v = serializer.fields[field] + if isinstance(v, serializers.Serializer): + _fields = cls.get_header_fields(v) + header_fields.update(_fields) + else: + header_fields[field] = v.label + return header_fields + + @classmethod + def create_row(cls, account, serializer_cls): + serializer = serializer_cls(account) + data = cls.unpack_data(serializer.data) + header_fields = cls.get_header_fields(serializer) + row_dict = {} + for field, header_name in header_fields.items(): + row_dict[header_name] = data[field] + return row_dict + + +class AssetAccountHandler(BaseAccountHandler): @staticmethod def get_filename(plan_name): filename = os.path.join( @@ -28,32 +70,24 @@ class AssetAccountHandler: ) return filename - @staticmethod - def create_df(): + @classmethod + def create_df(cls): df_dict = defaultdict(list) - label_key = AuthBook._meta.verbose_name - accounts = AuthBook.objects.all().prefetch_related('systemuser', 'asset') + sheet_name = AuthBook._meta.verbose_name + accounts = AuthBook.get_queryset() for account in accounts: account.load_auth() - protocol = account.asset.protocol - protocol_label = getattr(ProtocolsMixin.Protocol, protocol).label - row = { - getattr(Asset, 'hostname').field.verbose_name: account.asset.hostname, - getattr(Asset, 'ip').field.verbose_name: account.asset.ip, - } - secret_row = AccountBackupHandler.create_secret_row(account) - row.update(secret_row) - row.update({ - getattr(Asset, 'protocol').field.verbose_name: protocol_label, - getattr(AuthBook, 'version').field.verbose_name: account.version - }) - df_dict[label_key].append(row) + row = cls.create_row(account, AccountSecretSerializer) + df_dict[sheet_name].append(row) + for k, v in df_dict.items(): df_dict[k] = pd.DataFrame(v) + + logger.info('\n\033[33m- 共收集{}条资产账号\033[0m'.format(accounts.count())) return df_dict -class AppAccountHandler: +class AppAccountHandler(BaseAccountHandler): @staticmethod def get_filename(plan_name): filename = os.path.join( @@ -61,33 +95,23 @@ class AppAccountHandler: ) return filename - @staticmethod - def create_df(): + @classmethod + def create_df(cls): df_dict = defaultdict(list) - accounts = Account.objects.all().prefetch_related('systemuser', 'app') + accounts = Account.get_queryset() for account in accounts: account.load_auth() - app_type = account.app.type - if app_type == 'postgresql': - label_key = getattr(AppType, 'pgsql').label - else: - label_key = getattr(AppType, app_type).label - row = { - getattr(Application, 'name').field.verbose_name: account.app.name, - getattr(Application, 'attrs').field.verbose_name: account.app.attrs - } - secret_row = AccountBackupHandler.create_secret_row(account) - row.update(secret_row) - row.update({ - getattr(Account, 'version').field.verbose_name: account.version - }) - df_dict[label_key].append(row) + app_type = account.type + sheet_name = AppType.get_label(app_type) + row = cls.create_row(account, AppAccountSecretSerializer) + df_dict[sheet_name].append(row) for k, v in df_dict.items(): df_dict[k] = pd.DataFrame(v) + logger.info('\n\033[33m- 共收集{}条应用账号\033[0m'.format(accounts.count())) return df_dict -HANDLER_MAP = { +handler_map = { 'asset': AssetAccountHandler, 'application': AppAccountHandler } @@ -102,31 +126,37 @@ class AccountBackupHandler: def create_excel(self): logger.info( '\n' - '\033[32m>>> 正在生成资产及应用相关备份信息文件\033[0m' + '\033[32m>>> 正在生成资产或应用相关备份信息文件\033[0m' '' ) # Print task start date time_start = time.time() - info = {} + files = [] for account_type in self.execution.types: - if account_type in HANDLER_MAP: - account_handler = HANDLER_MAP[account_type] - df = account_handler.create_df() - filename = account_handler.get_filename(self.plan_name) - info[filename] = df - for filename, df_dict in info.items(): + handler = handler_map.get(account_type) + if not handler: + continue + + df_dict = handler.create_df() + if not df_dict: + continue + + filename = handler.get_filename(self.plan_name) with pd.ExcelWriter(filename) as w: for sheet, df in df_dict.items(): sheet = sheet.replace(' ', '-') getattr(df, 'to_excel')(w, sheet_name=sheet, index=False) + files.append(filename) timedelta = round((time.time() - time_start), 2) logger.info('步骤完成: 用时 {}s'.format(timedelta)) - return list(info.keys()) + return files def send_backup_mail(self, files): recipients = self.execution.plan_snapshot.get('recipients') if not recipients: return + if not files: + return recipients = User.objects.filter(id__in=list(recipients)) logger.info( '\n' @@ -191,13 +221,3 @@ class AccountBackupHandler: logger.info('\n任务结束: {}'.format(local_now_display())) timedelta = round((time.time() - time_start), 2) logger.info('用时: {}'.format(timedelta)) - - @staticmethod - def create_secret_row(instance): - row = { - getattr(BaseUser, 'username').field.verbose_name: instance.username, - getattr(BaseUser, 'password').field.verbose_name: instance.password, - getattr(BaseUser, 'private_key').field.verbose_name: instance.private_key, - getattr(BaseUser, 'public_key').field.verbose_name: instance.public_key - } - return row diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 63dee9b87..80e3fbe45 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65ae747dcbddab2bbf9238b0ee589037805c9cf04a6c3a2e312d4c6c5e486b2d -size 96320 +oid sha256:041711683ed0cfbf9ffd58f402f0acb98f77a1edde5f4582314a2568d539212c +size 96641 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 4aaec0cae..7d568ab2b 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-01-13 16:57+0800\n" +"POT-Creation-Date: 2022-01-15 22:47+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -123,14 +123,14 @@ msgid "Login acl" msgstr "登录访问控制" #: acls/models/login_asset_acl.py:21 -#: applications/serializers/application.py:108 -#: applications/serializers/application.py:139 +#: applications/serializers/application.py:122 +#: applications/serializers/application.py:165 msgid "System User" msgstr "系统用户" #: acls/models/login_asset_acl.py:22 -#: applications/serializers/attrs/application_category/remote_app.py:37 -#: assets/models/asset.py:356 assets/models/authbook.py:18 +#: applications/serializers/attrs/application_category/remote_app.py:36 +#: assets/models/asset.py:356 assets/models/authbook.py:19 #: assets/models/backup.py:31 assets/models/cmd_filter.py:34 #: assets/models/gathered_user.py:14 assets/serializers/system_user.py:264 #: audits/models.py:38 perms/models/asset_permission.py:24 @@ -157,13 +157,9 @@ msgid "Format for comma-delimited string, with * indicating a match all. " msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: acls/serializers/login_acl.py:15 acls/serializers/login_asset_acl.py:17 -#: acls/serializers/login_asset_acl.py:51 -#: applications/serializers/attrs/application_type/chrome.py:20 -#: applications/serializers/attrs/application_type/custom.py:21 -#: applications/serializers/attrs/application_type/mysql_workbench.py:30 -#: applications/serializers/attrs/application_type/vmware_client.py:26 -#: assets/models/base.py:176 assets/models/gathered_user.py:15 -#: audits/models.py:105 authentication/forms.py:15 authentication/forms.py:17 +#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176 +#: assets/models/gathered_user.py:15 audits/models.py:105 +#: authentication/forms.py:15 authentication/forms.py:17 #: authentication/templates/authentication/_msg_different_city.html:9 #: authentication/templates/authentication/_msg_oauth_bind.html:9 #: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:547 @@ -185,7 +181,7 @@ msgstr "" "10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64 (支持网域)" #: acls/serializers/login_asset_acl.py:31 acls/serializers/rules/rules.py:33 -#: applications/serializers/attrs/application_type/mysql_workbench.py:18 +#: applications/serializers/attrs/application_type/mysql_workbench.py:17 #: assets/models/asset.py:211 assets/models/domain.py:61 #: assets/serializers/account.py:12 #: authentication/templates/authentication/_msg_oauth_bind.html:12 @@ -252,9 +248,9 @@ msgstr "时段" msgid "My applications" msgstr "我的应用" -#: applications/const.py:8 applications/models/account.py:10 +#: applications/const.py:8 applications/models/account.py:12 #: applications/serializers/attrs/application_category/db.py:14 -#: applications/serializers/attrs/application_type/mysql_workbench.py:26 +#: applications/serializers/attrs/application_type/mysql_workbench.py:25 #: xpack/plugins/change_auth_plan/models/app.py:32 msgid "Database" msgstr "数据库" @@ -267,7 +263,7 @@ msgstr "远程应用" msgid "Custom" msgstr "自定义" -#: applications/models/account.py:11 assets/models/authbook.py:19 +#: applications/models/account.py:15 assets/models/authbook.py:20 #: assets/models/cmd_filter.py:38 assets/models/user.py:302 audits/models.py:39 #: perms/models/application_permission.py:32 #: perms/models/asset_permission.py:26 templates/_nav.html:45 @@ -284,12 +280,12 @@ msgstr "自定义" msgid "System user" msgstr "系统用户" -#: applications/models/account.py:12 assets/models/authbook.py:20 +#: applications/models/account.py:17 assets/models/authbook.py:21 #: settings/serializers/auth/cas.py:15 msgid "Version" msgstr "版本" -#: applications/models/account.py:18 xpack/plugins/cloud/models.py:82 +#: applications/models/account.py:23 xpack/plugins/cloud/models.py:82 #: xpack/plugins/cloud/serializers/task.py:66 msgid "Account" msgstr "账户" @@ -299,7 +295,7 @@ msgid "Applications" msgstr "应用管理" #: applications/models/application.py:204 -#: applications/serializers/application.py:88 assets/models/label.py:21 +#: applications/serializers/application.py:99 assets/models/label.py:21 #: perms/models/application_permission.py:20 #: perms/serializers/application/user_permission.py:33 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:22 @@ -308,7 +304,7 @@ msgid "Category" msgstr "类别" #: applications/models/application.py:207 -#: applications/serializers/application.py:90 assets/models/backup.py:49 +#: applications/serializers/application.py:101 assets/models/backup.py:49 #: assets/models/cmd_filter.py:76 assets/models/user.py:210 #: perms/models/application_permission.py:23 #: perms/serializers/application/user_permission.py:34 @@ -335,15 +331,15 @@ msgstr "属性" msgid "Application" msgstr "应用程序" -#: applications/serializers/application.py:59 -#: applications/serializers/application.py:89 assets/serializers/label.py:13 +#: applications/serializers/application.py:70 +#: applications/serializers/application.py:100 assets/serializers/label.py:13 #: perms/serializers/application/permission.py:18 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:26 msgid "Category display" msgstr "类别名称" -#: applications/serializers/application.py:60 -#: applications/serializers/application.py:91 +#: applications/serializers/application.py:71 +#: applications/serializers/application.py:102 #: assets/serializers/system_user.py:27 audits/serializers.py:29 #: perms/serializers/application/permission.py:19 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 @@ -352,12 +348,31 @@ msgstr "类别名称" msgid "Type display" msgstr "类型名称" -#: applications/serializers/application.py:107 -#: applications/serializers/application.py:138 +#: applications/serializers/application.py:103 assets/models/asset.py:231 +#: assets/models/base.py:181 assets/models/cluster.py:26 +#: assets/models/domain.py:27 assets/models/gathered_user.py:19 +#: assets/models/group.py:22 assets/models/label.py:25 +#: assets/serializers/account.py:17 common/db/models.py:113 +#: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:29 +#: orgs/models.py:26 orgs/models.py:435 perms/models/base.py:92 +#: users/models/group.py:18 users/models/user.py:783 +#: xpack/plugins/cloud/models.py:122 +msgid "Date created" +msgstr "创建日期" + +#: applications/serializers/application.py:104 assets/models/base.py:182 +#: assets/models/gathered_user.py:20 assets/serializers/account.py:20 +#: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:39 +#: orgs/models.py:436 +msgid "Date updated" +msgstr "更新日期" + +#: applications/serializers/application.py:121 +#: applications/serializers/application.py:164 msgid "Application display" msgstr "应用名称" -#: applications/serializers/attrs/application_category/cloud.py:9 +#: applications/serializers/attrs/application_category/cloud.py:8 #: assets/models/cluster.py:40 msgid "Cluster" msgstr "集群" @@ -369,26 +384,26 @@ msgid "Host" msgstr "主机" #: applications/serializers/attrs/application_category/db.py:12 -#: applications/serializers/attrs/application_type/mysql.py:11 -#: applications/serializers/attrs/application_type/mysql_workbench.py:22 -#: applications/serializers/attrs/application_type/oracle.py:11 -#: applications/serializers/attrs/application_type/pgsql.py:11 -#: applications/serializers/attrs/application_type/redis.py:11 -#: applications/serializers/attrs/application_type/sqlserver.py:11 +#: applications/serializers/attrs/application_type/mysql.py:10 +#: applications/serializers/attrs/application_type/mysql_workbench.py:21 +#: applications/serializers/attrs/application_type/oracle.py:10 +#: applications/serializers/attrs/application_type/pgsql.py:10 +#: applications/serializers/attrs/application_type/redis.py:10 +#: applications/serializers/attrs/application_type/sqlserver.py:10 #: assets/models/asset.py:215 assets/models/domain.py:62 #: settings/serializers/auth/radius.py:15 #: xpack/plugins/cloud/serializers/account_attrs.py:69 msgid "Port" msgstr "端口" -#: applications/serializers/attrs/application_category/remote_app.py:40 -#: applications/serializers/attrs/application_type/chrome.py:14 -#: applications/serializers/attrs/application_type/mysql_workbench.py:14 -#: applications/serializers/attrs/application_type/vmware_client.py:18 +#: applications/serializers/attrs/application_category/remote_app.py:39 +#: applications/serializers/attrs/application_type/chrome.py:13 +#: applications/serializers/attrs/application_type/mysql_workbench.py:13 +#: applications/serializers/attrs/application_type/vmware_client.py:17 msgid "Application path" msgstr "应用路径" -#: applications/serializers/attrs/application_category/remote_app.py:45 +#: applications/serializers/attrs/application_category/remote_app.py:44 #: assets/serializers/system_user.py:163 #: xpack/plugins/change_auth_plan/serializers/asset.py:65 #: xpack/plugins/change_auth_plan/serializers/asset.py:68 @@ -398,37 +413,58 @@ msgstr "应用路径" msgid "This field is required." msgstr "该字段是必填项。" -#: applications/serializers/attrs/application_type/chrome.py:17 -#: applications/serializers/attrs/application_type/vmware_client.py:22 +#: applications/serializers/attrs/application_type/chrome.py:16 +#: applications/serializers/attrs/application_type/vmware_client.py:21 msgid "Target URL" msgstr "目标URL" -#: applications/serializers/attrs/application_type/chrome.py:23 -#: applications/serializers/attrs/application_type/custom.py:25 -#: applications/serializers/attrs/application_type/mysql_workbench.py:34 -#: applications/serializers/attrs/application_type/vmware_client.py:30 -#: assets/models/base.py:177 audits/signals_handler.py:65 -#: authentication/forms.py:22 -#: authentication/templates/authentication/login.html:151 -#: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21 -#: users/templates/users/_msg_user_created.html:13 -#: users/templates/users/user_password_update.html:43 -#: users/templates/users/user_password_verify.html:18 -#: xpack/plugins/change_auth_plan/models/base.py:39 -#: xpack/plugins/change_auth_plan/models/base.py:118 -#: xpack/plugins/change_auth_plan/models/base.py:193 -#: xpack/plugins/cloud/serializers/account_attrs.py:24 -msgid "Password" -msgstr "密码" +#: applications/serializers/attrs/application_type/chrome.py:19 +msgid "Chrome username" +msgstr "Chrome 用户名" -#: applications/serializers/attrs/application_type/custom.py:13 +#: applications/serializers/attrs/application_type/chrome.py:22 +#: applications/serializers/attrs/application_type/chrome.py:29 +msgid "Chrome password" +msgstr "Chrome 密码" + +#: applications/serializers/attrs/application_type/custom.py:11 msgid "Operating parameter" msgstr "运行参数" -#: applications/serializers/attrs/application_type/custom.py:17 +#: applications/serializers/attrs/application_type/custom.py:15 msgid "Target url" msgstr "目标URL" +#: applications/serializers/attrs/application_type/custom.py:19 +#, fuzzy +#| msgid "Custom username" +msgid "Custom Username" +msgstr "自定义用户名" + +#: applications/serializers/attrs/application_type/custom.py:23 +#: applications/serializers/attrs/application_type/custom.py:30 +#: xpack/plugins/change_auth_plan/models/base.py:24 +msgid "Custom password" +msgstr "自定义密码" + +#: applications/serializers/attrs/application_type/mysql_workbench.py:29 +msgid "Mysql workbench username" +msgstr "Mysql 工作台 用户名" + +#: applications/serializers/attrs/application_type/mysql_workbench.py:33 +#: applications/serializers/attrs/application_type/mysql_workbench.py:40 +msgid "Mysql workbench password" +msgstr "Mysql 工作台 密码" + +#: applications/serializers/attrs/application_type/vmware_client.py:25 +msgid "Vmware username" +msgstr "Vmware 用户名" + +#: applications/serializers/attrs/application_type/vmware_client.py:29 +#: applications/serializers/attrs/application_type/vmware_client.py:36 +msgid "Vmware password" +msgstr "Vmware 密码" + #: assets/api/domain.py:52 msgid "Number required" msgstr "需要为数字" @@ -453,7 +489,7 @@ msgstr "基础" msgid "Charset" msgstr "编码" -#: assets/models/asset.py:142 assets/serializers/asset.py:178 +#: assets/models/asset.py:142 assets/serializers/asset.py:176 #: tickets/models/ticket.py:54 msgid "Meta" msgstr "元数据" @@ -463,7 +499,8 @@ msgid "Internal" msgstr "内部的" #: assets/models/asset.py:163 assets/models/asset.py:217 -#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:43 +#: assets/serializers/account.py:14 assets/serializers/asset.py:63 +#: perms/serializers/asset/user_permission.py:43 msgid "Platform" msgstr "系统平台" @@ -523,8 +560,8 @@ msgstr "系统架构" msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:216 assets/serializers/asset.py:67 -#: perms/serializers/asset/user_permission.py:41 +#: assets/models/asset.py:216 assets/serializers/account.py:15 +#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41 #: xpack/plugins/cloud/models.py:104 xpack/plugins/cloud/serializers/task.py:42 msgid "Protocols" msgstr "协议组" @@ -569,17 +606,7 @@ msgstr "标签管理" msgid "Created by" msgstr "创建者" -#: assets/models/asset.py:231 assets/models/base.py:181 -#: assets/models/cluster.py:26 assets/models/domain.py:27 -#: assets/models/gathered_user.py:19 assets/models/group.py:22 -#: assets/models/label.py:25 common/db/models.py:113 common/mixins/models.py:50 -#: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:26 -#: orgs/models.py:435 perms/models/base.py:92 users/models/group.py:18 -#: users/models/user.py:783 xpack/plugins/cloud/models.py:122 -msgid "Date created" -msgstr "创建日期" - -#: assets/models/authbook.py:26 +#: assets/models/authbook.py:27 msgid "AuthBook" msgstr "账号" @@ -678,6 +705,20 @@ msgstr "可连接性" msgid "Date verified" msgstr "校验日期" +#: assets/models/base.py:177 audits/signals_handler.py:65 +#: authentication/forms.py:22 +#: authentication/templates/authentication/login.html:151 +#: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21 +#: users/templates/users/_msg_user_created.html:13 +#: users/templates/users/user_password_update.html:43 +#: users/templates/users/user_password_verify.html:18 +#: xpack/plugins/change_auth_plan/models/base.py:39 +#: xpack/plugins/change_auth_plan/models/base.py:118 +#: xpack/plugins/change_auth_plan/models/base.py:193 +#: xpack/plugins/cloud/serializers/account_attrs.py:24 +msgid "Password" +msgstr "密码" + #: assets/models/base.py:178 xpack/plugins/change_auth_plan/models/asset.py:53 #: xpack/plugins/change_auth_plan/models/asset.py:130 #: xpack/plugins/change_auth_plan/models/asset.py:206 @@ -690,12 +731,6 @@ msgstr "SSH密钥" msgid "SSH public key" msgstr "SSH公钥" -#: assets/models/base.py:182 assets/models/gathered_user.py:20 -#: common/db/models.py:114 common/mixins/models.py:51 ops/models/adhoc.py:39 -#: orgs/models.py:436 -msgid "Date updated" -msgstr "更新日期" - #: assets/models/cluster.py:20 msgid "Bandwidth" msgstr "带宽" @@ -965,39 +1000,39 @@ msgstr "" "{} - 账号备份任务已完成: 未设置加密密码 - 请前往个人信息 -> 文件加密密码中设" "置加密密码" -#: assets/serializers/account.py:31 assets/serializers/account.py:52 +#: assets/serializers/account.py:39 assets/serializers/account.py:67 msgid "System user display" msgstr "系统用户名称" -#: assets/serializers/asset.py:22 +#: assets/serializers/asset.py:20 msgid "Protocol format should {}/{}" msgstr "协议格式 {}/{}" -#: assets/serializers/asset.py:39 +#: assets/serializers/asset.py:37 msgid "Protocol duplicate: {}" msgstr "协议重复: {}" -#: assets/serializers/asset.py:68 +#: assets/serializers/asset.py:66 msgid "Domain name" msgstr "网域名称" -#: assets/serializers/asset.py:70 +#: assets/serializers/asset.py:68 msgid "Nodes name" msgstr "节点名称" -#: assets/serializers/asset.py:73 +#: assets/serializers/asset.py:71 msgid "Labels name" msgstr "标签名称" -#: assets/serializers/asset.py:107 +#: assets/serializers/asset.py:105 msgid "Hardware info" msgstr "硬件信息" -#: assets/serializers/asset.py:108 +#: assets/serializers/asset.py:106 msgid "Admin user display" msgstr "特权用户名称" -#: assets/serializers/asset.py:109 +#: assets/serializers/asset.py:107 msgid "CPU info" msgstr "CPU信息" @@ -1622,11 +1657,11 @@ msgstr "{ApplicationPermission} 移除 {SystemUser}" msgid "Invalid token" msgstr "无效的令牌" -#: authentication/api/mfa.py:50 +#: authentication/api/mfa.py:63 msgid "Current user not support mfa type: {}" msgstr "当前用户不支持 MFA 类型: {}" -#: authentication/api/mfa.py:97 +#: authentication/api/mfa.py:110 msgid "Code is invalid, {}" msgstr "验证码无效: {}" @@ -1795,15 +1830,15 @@ msgstr "该 时间段 不被允许登录" msgid "SSO auth closed" msgstr "SSO 认证关闭了" -#: authentication/errors.py:300 authentication/mixins.py:359 +#: authentication/errors.py:300 authentication/mixins.py:364 msgid "Your password is too simple, please change it for security" msgstr "你的密码过于简单,为了安全,请修改" -#: authentication/errors.py:309 authentication/mixins.py:366 +#: authentication/errors.py:309 authentication/mixins.py:371 msgid "You should to change your password before login" msgstr "登录完成前,请先修改密码" -#: authentication/errors.py:318 authentication/mixins.py:373 +#: authentication/errors.py:318 authentication/mixins.py:378 msgid "Your password has expired, please reset before logging in" msgstr "您的密码已过期,先修改再登录" @@ -1899,7 +1934,7 @@ msgstr "清空手机号码禁用" msgid "The MFA type ({}) is not enabled" msgstr "该 MFA ({}) 方式没有启用" -#: authentication/mixins.py:349 +#: authentication/mixins.py:354 msgid "Please change your password" msgstr "请修改密码" @@ -2824,7 +2859,7 @@ msgstr "用户组数量" msgid "System users amount" msgstr "系统用户数量" -#: perms/serializers/application/permission.py:88 +#: perms/serializers/application/permission.py:79 msgid "" "The application list contains applications that are different from the " "permission type. ({})" @@ -5755,10 +5790,6 @@ msgstr "改密计划执行" msgid "Change auth plan task" msgstr "改密计划任务" -#: xpack/plugins/change_auth_plan/models/base.py:24 -msgid "Custom password" -msgstr "自定义密码" - #: xpack/plugins/change_auth_plan/models/base.py:25 msgid "All assets use the same random password" msgstr "使用相同的随机密码"