perf: 账号备份优化 (#7503)

* perf: 账号备份优化

* feat: 优化账号备份获取有序备份字段列表

Co-authored-by: feng626 <1304903146@qq.com>
Co-authored-by: Michael Bai <baijiangjie@gmail.com>
pull/7498/head
fit2bot 2022-01-17 15:58:39 +08:00 committed by GitHub
parent d2b3edffca
commit def9bedd30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 362 additions and 228 deletions

View File

@ -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

View File

@ -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

View File

@ -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},

View File

@ -1,7 +1,6 @@
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
__all__ = ['CloudSerializer']

View File

@ -10,7 +10,6 @@ from assets.models import Asset
logger = get_logger(__file__)
__all__ = ['RemoteAppSerializer']

View File

@ -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
)

View File

@ -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,
)

View File

@ -1,6 +1,5 @@
from ..application_category import CloudSerializer
__all__ = ['K8SSerializer']

View File

@ -1,6 +1,5 @@
from .mysql import MySQLSerializer
__all__ = ['MariaDBSerializer']

View File

@ -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)

View File

@ -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,
)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _
from ..application_category import DBSerializer
__all__ = ['SQLServerSerializer']

View File

@ -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
)

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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},

View File

@ -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__ = [

View File

@ -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

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:65ae747dcbddab2bbf9238b0ee589037805c9cf04a6c3a2e312d4c6c5e486b2d
size 96320
oid sha256:041711683ed0cfbf9ffd58f402f0acb98f77a1edde5f4582314a2568d539212c
size 96641

View File

@ -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 <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\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 "使用相同的随机密码"