From e1919d0a623519fdc891fffb6c10a9a92f8f81b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Mon, 16 Dec 2019 16:53:29 +0800 Subject: [PATCH] Asset meta (#3539) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更改了资产表单,影响 - 资产创建和更新 - 增加了资产平台数据库,影响 - 平台创建更新和删除 - 更改了资产的platform字段,又一个字符字段,改为一个外键,影响 - 资产创建和更新 - 资产连接 [windows,linux] - 测试连接等ansible任务 - 自动化云导入 - 更改了资产的序列化器,影响 - 资产创建更新列表 - 统一了树列表基础模板,影响 - 资产列表页,权限列表页,vault页,资产收集页 - 统一了导入导出组件,影响 - 资产导入导出 - 用户导入导出 - 用户组导入导出 - 系统用户导入导出 - 管理用户导入导出 - vault导出导出 - 收集用户列表导入导出 - 修改用户更新密码信号,影响 - 修改用户密码产生的改密日志 - 新增Model instance序列化工具函数,影响 - 操作日志生成 - 修改api mixin,新增 serializer_classes字段,serializer_classes = {"default": "", "display": "", "list": .., "other_action": ""}, 根据用户请求的方式返回不同的serializer_class,影响 - 用户的viewset - 资产权限的viewset - 统一系统配置中的tab切换 - 统一没有nav的页面,影响 - 重置密码 - 忘记密码 - 重置中设置密码 - 独立的message页面 - 修改用户组列表页,不再返还用户组下的用户,仅有数量 - 组织的一些方法变为layzproperty,避免重复计算 - 修改用户组详情页,影响 - 用户组增加删除用户 --- apps/assets/api/asset.py | 43 +- apps/assets/api/node.py | 2 +- apps/assets/forms/__init__.py | 1 + apps/assets/forms/asset.py | 78 +- apps/assets/forms/platform.py | 42 + apps/assets/migrations/0044_platform.py | 45 + .../migrations/0045_auto_20191206_1607.py | 47 + apps/assets/models/asset.py | 72 +- apps/assets/serializers/asset.py | 25 +- .../assets/_admin_user_import_modal.html | 6 - .../assets/_admin_user_update_modal.html | 4 - .../templates/assets/_asset_import_modal.html | 6 - .../templates/assets/_asset_list_modal.html | 4 +- .../assets/_system_user_import_modal.html | 6 - .../assets/_system_user_update_modal.html | 4 - .../templates/assets/admin_user_list.html | 99 +-- apps/assets/templates/assets/asset_list.html | 251 +----- .../assets/platform_create_update.html | 79 ++ .../templates/assets/platform_detail.html | 75 ++ .../templates/assets/platform_list.html | 75 ++ .../templates/assets/system_user_list.html | 151 +--- apps/assets/urls/api_urls.py | 3 + apps/assets/urls/views_urls.py | 5 + apps/assets/views/__init__.py | 1 + apps/assets/views/asset.py | 4 +- apps/assets/views/domain.py | 5 +- apps/assets/views/platform.py | 74 ++ apps/audits/signals_handler.py | 35 +- .../templates/audits/login_log_list.html | 80 +- apps/authentication/forms.py | 3 - apps/common/mixins/api.py | 15 +- apps/common/signals_handlers.py | 17 +- apps/common/utils/common.py | 6 +- apps/jumpserver/conf.py | 5 +- apps/jumpserver/settings/logging.py | 5 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 84224 -> 84035 bytes apps/locale/zh/LC_MESSAGES/django.po | 814 +++++++++--------- .../ops/command_execution_create.html | 12 +- apps/perms/api/asset_permission.py | 6 +- .../perms/asset_permission_list.html | 76 +- apps/perms/urls/asset_permission.py | 79 +- .../templates/settings/_setting_tabs.html | 37 + .../templates/settings/basic_setting.html | 21 +- .../settings/email_content_setting.html | 60 +- .../templates/settings/email_setting.html | 21 +- .../templates/settings/ldap_setting.html | 21 +- .../templates/settings/security_setting.html | 21 +- .../templates/settings/terminal_setting.html | 25 +- .../css/plugins/ladda/ladda-themeless.min.css | 7 + apps/static/css/plugins/ladda/ladda.min.css | 9 + apps/static/js/jumpserver.js | 72 +- .../js/plugins/ladda/ladda.jquery.min.js | 8 + apps/static/js/plugins/ladda/ladda.min.js | 8 + apps/static/js/plugins/ladda/spin.min.js | 1 + apps/templates/_base_asset_tree_list.html | 55 ++ apps/templates/_base_only_content.html | 47 + apps/templates/_csv_import_export.html | 62 ++ apps/templates/_csv_import_modal.html | 52 ++ apps/templates/_csv_update_modal.html | 54 ++ apps/templates/_nav.html | 3 + apps/templates/_without_nav_base.html | 43 + apps/templates/flash_message_standalone.html | 97 +-- .../migrations/0019_auto_20191206_1000.py | 18 + apps/users/api/group.py | 22 +- apps/users/api/user.py | 13 +- apps/users/forms/__init__.py | 5 + apps/users/forms/group.py | 44 + apps/users/forms/profile.py | 152 ++++ apps/users/{forms.py => forms/user.py} | 201 +---- apps/users/models/group.py | 5 + apps/users/models/user.py | 25 +- apps/users/serializers/group.py | 37 +- apps/users/serializers/user.py | 14 +- apps/users/signals.py | 1 + apps/users/signals_handler.py | 2 +- apps/users/templates/users/_base_otp.html | 72 +- .../users/_user_groups_import_modal.html | 6 - .../users/_user_groups_update_modal.html | 4 - .../templates/users/_user_import_modal.html | 6 - .../templates/users/_user_update_modal.html | 4 - apps/users/templates/users/first_login.html | 6 +- .../templates/users/first_login_done.html | 2 +- .../templates/users/forgot_password.html | 80 +- .../users/templates/users/reset_password.html | 194 ++--- apps/users/templates/users/user_detail.html | 108 +-- ...hentication.html => user_disable_mfa.html} | 0 .../templates/users/user_group_detail.html | 97 +-- .../templates/users/user_group_list.html | 182 +--- apps/users/templates/users/user_list.html | 106 +-- ...tication.html => user_password_check.html} | 5 - apps/users/urls/api_urls.py | 4 +- apps/users/urls/views_urls.py | 4 +- apps/users/utils.py | 15 + apps/users/views/login.py | 66 +- apps/users/views/profile.py | 51 +- jms | 2 +- 96 files changed, 2323 insertions(+), 2314 deletions(-) create mode 100644 apps/assets/forms/platform.py create mode 100644 apps/assets/migrations/0044_platform.py create mode 100644 apps/assets/migrations/0045_auto_20191206_1607.py delete mode 100644 apps/assets/templates/assets/_admin_user_import_modal.html delete mode 100644 apps/assets/templates/assets/_admin_user_update_modal.html delete mode 100644 apps/assets/templates/assets/_asset_import_modal.html delete mode 100644 apps/assets/templates/assets/_system_user_import_modal.html delete mode 100644 apps/assets/templates/assets/_system_user_update_modal.html create mode 100644 apps/assets/templates/assets/platform_create_update.html create mode 100644 apps/assets/templates/assets/platform_detail.html create mode 100644 apps/assets/templates/assets/platform_list.html create mode 100644 apps/assets/views/platform.py create mode 100644 apps/settings/templates/settings/_setting_tabs.html create mode 100755 apps/static/css/plugins/ladda/ladda-themeless.min.css create mode 100755 apps/static/css/plugins/ladda/ladda.min.css create mode 100755 apps/static/js/plugins/ladda/ladda.jquery.min.js create mode 100755 apps/static/js/plugins/ladda/ladda.min.js create mode 100755 apps/static/js/plugins/ladda/spin.min.js create mode 100644 apps/templates/_base_asset_tree_list.html create mode 100644 apps/templates/_base_only_content.html create mode 100644 apps/templates/_csv_import_export.html create mode 100644 apps/templates/_csv_import_modal.html create mode 100644 apps/templates/_csv_update_modal.html create mode 100644 apps/templates/_without_nav_base.html create mode 100644 apps/terminal/migrations/0019_auto_20191206_1000.py create mode 100644 apps/users/forms/__init__.py create mode 100644 apps/users/forms/group.py create mode 100644 apps/users/forms/profile.py rename apps/users/{forms.py => forms/user.py} (52%) delete mode 100644 apps/users/templates/users/_user_groups_import_modal.html delete mode 100644 apps/users/templates/users/_user_groups_update_modal.html delete mode 100644 apps/users/templates/users/_user_import_modal.html delete mode 100644 apps/users/templates/users/_user_update_modal.html rename apps/users/templates/users/{user_otp_authentication.html => user_disable_mfa.html} (100%) rename apps/users/templates/users/{user_password_authentication.html => user_password_check.html} (77%) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 64fdc16dc..0b63522ea 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -4,24 +4,27 @@ 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 from common.utils import get_logger, get_object_or_none -from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser +from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsSuperUser from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics -from ..models import Asset, Node +from ..models import Asset, Node, Platform from .. import serializers -from ..tasks import update_asset_hardware_info_manual, \ - test_asset_connectivity_manual +from ..tasks import ( + update_asset_hardware_info_manual, test_asset_connectivity_manual +) from ..filters import AssetByNodeFilterBackend, LabelFilterBackend logger = get_logger(__file__) __all__ = [ - 'AssetViewSet', + 'AssetViewSet', 'AssetPlatformRetrieveApi', 'AssetRefreshHardwareApi', 'AssetAdminUserTestApi', - 'AssetGatewayApi', + 'AssetGatewayApi', 'AssetPlatformViewSet', ] @@ -53,6 +56,34 @@ class AssetViewSet(OrgBulkModelViewSet): self.set_assets_node(assets) +class AssetPlatformRetrieveApi(RetrieveAPIView): + queryset = Platform.objects.all() + permission_classes = (IsOrgAdminOrAppUser,) + serializer_class = serializers.PlatformSerializer + + def get_object(self): + asset_pk = self.kwargs.get('pk') + asset = get_object_or_404(Asset, pk=asset_pk) + return asset.platform + + +class AssetPlatformViewSet(ModelViewSet): + queryset = Platform.objects.all() + permission_classes = (IsSuperUser,) + serializer_class = serializers.PlatformSerializer + filterset_fields = ['name', 'base'] + search_fields = ['name'] + + def check_object_permissions(self, request, obj): + if request.method.lower() in ['delete', 'put', 'patch'] and \ + obj.internal: + self.permission_denied( + request, message={"detail": "Internal platform"} + ) + + return super().check_object_permissions(request, obj) + + class AssetRefreshHardwareApi(generics.RetrieveAPIView): """ Refresh asset hardware info diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 1451650d2..24661bad6 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -177,7 +177,7 @@ class NodeChildrenAsTreeApi(NodeChildrenApi): if not include_assets: return queryset assets = self.instance.get_assets().only( - "id", "hostname", "ip", 'platform', "os", + "id", "hostname", "ip", "os", "org_id", "protocols", ) for asset in assets: diff --git a/apps/assets/forms/__init__.py b/apps/assets/forms/__init__.py index a086cb12c..39b39a45a 100644 --- a/apps/assets/forms/__init__.py +++ b/apps/assets/forms/__init__.py @@ -5,3 +5,4 @@ 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 index 805f49f24..46c137d70 100644 --- a/apps/assets/forms/asset.py +++ b/apps/assets/forms/asset.py @@ -6,13 +6,13 @@ from django.utils.translation import gettext_lazy as _ from common.utils import get_logger from orgs.mixins.forms import OrgModelForm -from ..models import Asset, Node +from ..models import Asset from ..const import GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT logger = get_logger(__file__) __all__ = [ - 'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm', + 'AssetCreateUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm', ] @@ -27,17 +27,27 @@ class ProtocolForm(forms.Form): ) -class AssetCreateForm(OrgModelForm): +class AssetCreateUpdateForm(OrgModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - if self.data: - return + 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_field.choices = [(n.id, n.full_value) for n in - self.instance.nodes.all()] - else: - nodes_field.choices = [] + nodes_choices = [ + (n.id, n.full_value) for n in + self.instance.nodes.all() + ] + nodes_field.choices = nodes_choices + + def set_platform_to_name(self): + platform_field = self.fields['platform'] + platform_field.to_field_name = 'name' + if self.instance: + self.initial['platform'] = self.instance.platform.name def add_nodes_initial(self, node): nodes_field = self.fields['nodes'] @@ -49,7 +59,7 @@ class AssetCreateForm(OrgModelForm): fields = [ 'hostname', 'ip', 'public_ip', 'protocols', 'comment', 'nodes', 'is_active', 'admin_user', 'labels', 'platform', - 'domain', + 'domain', 'number', ] widgets = { 'nodes': forms.SelectMultiple(attrs={ @@ -64,52 +74,8 @@ class AssetCreateForm(OrgModelForm): 'domain': forms.Select(attrs={ 'class': 'select2', 'data-placeholder': _('Domain') }), - } - labels = { - 'nodes': _("Node"), - } - help_texts = { - 'hostname': GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT, - '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 AssetUpdateForm(OrgModelForm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - if self.data: - return - nodes_field = self.fields['nodes'] - if self.instance: - nodes_field.choices = ((n.id, n.full_value) for n in - self.instance.nodes.all()) - else: - nodes_field.choices = [] - - class Meta: - model = Asset - fields = [ - 'hostname', 'ip', 'protocols', 'nodes', 'is_active', 'platform', - 'public_ip', 'number', 'comment', 'admin_user', 'labels', - 'domain', - ] - widgets = { - 'nodes': forms.SelectMultiple(attrs={ - 'class': 'nodes-select2', 'data-placeholder': _('Node') - }), - '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 = { diff --git a/apps/assets/forms/platform.py b/apps/assets/forms/platform.py new file mode 100644 index 000000000..88c4365d4 --- /dev/null +++ b/apps/assets/forms/platform.py @@ -0,0 +1,42 @@ +# -*- 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/migrations/0044_platform.py b/apps/assets/migrations/0044_platform.py new file mode 100644 index 000000000..f66d00642 --- /dev/null +++ b/apps/assets/migrations/0044_platform.py @@ -0,0 +1,45 @@ +# Generated by Django 2.2.7 on 2019-12-06 07:26 + +import common.fields.model +from django.db import migrations, models + + +def create_internal_platform(apps, schema_editor): + model = apps.get_model("assets", "Platform") + db_alias = schema_editor.connection.alias + type_platforms = ( + ('Linux', 'Linux', None), + ('Unix', 'Unix', None), + ('MacOS', 'MacOS', None), + ('BSD', 'BSD', None), + ('Windows', 'Windows', None), + ('Windows2016', 'Windows', {'security': 'tls'}), + ('Other', 'Other', None), + ) + for name, base, meta in type_platforms: + model.objects.using(db_alias).create( + name=name, base=base, internal=True, meta=meta + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0043_auto_20191114_1111'), + ] + + operations = [ + migrations.CreateModel( + name='Platform', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.SlugField(allow_unicode=True, unique=True, verbose_name='Name')), + ('base', models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=16, verbose_name='Base')), + ('charset', models.CharField(choices=[('utf8', 'UTF-8'), ('gbk', 'GBK')], default='utf8', max_length=8, verbose_name='Charset')), + ('meta', common.fields.model.JsonDictTextField(blank=True, null=True, verbose_name='Meta')), + ('internal', models.BooleanField(default=False, verbose_name='Internal')), + ('comment', models.TextField(blank=True, null=True, verbose_name='Comment')), + ], + ), + migrations.RunPython(create_internal_platform) + ] diff --git a/apps/assets/migrations/0045_auto_20191206_1607.py b/apps/assets/migrations/0045_auto_20191206_1607.py new file mode 100644 index 000000000..f51839289 --- /dev/null +++ b/apps/assets/migrations/0045_auto_20191206_1607.py @@ -0,0 +1,47 @@ +# Generated by Django 2.2.7 on 2019-12-06 08:07 + +import assets.models.asset +from django.db import migrations, models +import django.db.models.deletion + + +def migrate_platform_to_asset_type(apps, schema_editor): + asset_model = apps.get_model("assets", "Asset") + platform_model = apps.get_model("assets", "Platform") + db_alias = schema_editor.connection.alias + + platforms = platform_model.objects.using(db_alias).all() + platforms_map = {p.name: p for p in platforms} + for name, p in platforms_map.items(): + asset_model.objects.using(db_alias)\ + .filter(_platform=name)\ + .update(platform=p) + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0044_platform'), + ] + + operations = [ + migrations.RenameField( + model_name='asset', + old_name='platform', + new_name='_platform', + ), + migrations.AddField( + model_name='asset', + name='platform', + field=models.ForeignKey( + default=assets.models.asset.Platform.default, + on_delete=django.db.models.deletion.PROTECT, + related_name='assets', to='assets.Platform', + verbose_name='Platform'), + ), + migrations.RunPython(migrate_platform_to_asset_type), + migrations.RemoveField( + model_name='asset', + name='_platform', + ), + ] diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index d6e09786e..08a44e93c 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -11,10 +11,12 @@ from collections import OrderedDict from django.db import models from django.utils.translation import ugettext_lazy as _ -from .utils import Connectivity +from common.fields.model import JsonDictTextField +from common.utils import lazyproperty from orgs.mixins.models import OrgModelMixin, OrgManager +from .utils import Connectivity -__all__ = ['Asset', 'ProtocolsMixin'] +__all__ = ['Asset', 'ProtocolsMixin', 'Platform'] logger = logging.getLogger(__name__) @@ -37,6 +39,13 @@ def default_node(): return None +class AssetManager(OrgManager): + def get_queryset(self): + return super().get_queryset().annotate( + platform_base=models.F('platform__base') + ) + + class AssetQuerySet(models.QuerySet): def active(self): return self.filter(is_active=True) @@ -119,6 +128,41 @@ class NodesRelationMixin: return nodes +class Platform(models.Model): + CHARSET_CHOICES = ( + ('utf8', 'UTF-8'), + ('gbk', 'GBK'), + ) + BASE_CHOICES = ( + ('Linux', 'Linux'), + ('Unix', 'Unix'), + ('MacOS', 'MacOS'), + ('BSD', 'BSD'), + ('Windows', 'Windows'), + ('Other', 'Other'), + ) + name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True) + base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base")) + charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset")) + meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta")) + internal = models.BooleanField(default=False, verbose_name=_("Internal")) + comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) + + @classmethod + def default(cls): + linux, created = cls.objects.get_or_create( + defaults={'name': 'Linux'}, name='Linux' + ) + return linux.id + + def __str__(self): + return self.name + + class Meta: + verbose_name = _("Platform") + # ordering = ('name',) + + class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): # Important PLATFORM_CHOICES = ( @@ -138,9 +182,8 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): choices=ProtocolsMixin.PROTOCOL_CHOICES, verbose_name=_('Protocol')) port = models.IntegerField(default=22, verbose_name=_('Port')) - protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols")) - platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform')) + platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets') domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL) nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes")) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) @@ -175,7 +218,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) - objects = OrgManager.from_queryset(AssetQuerySet)() + objects = AssetManager.from_queryset(AssetQuerySet)() _connectivity = None def __str__(self): @@ -191,19 +234,20 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): return True, warning def is_windows(self): - if self.platform in ("Windows", "Windows2016"): - return True - else: - return False + return self.platform_base == "Windows" def is_unixlike(self): - if self.platform not in ("Windows", "Windows2016", "Other"): + if self.platform_base not in ("Windows", "Windows2016", "Other"): return True else: return False def is_support_ansible(self): - return self.has_protocol('ssh') and self.platform not in ("Other",) + return self.has_protocol('ssh') and self.platform_base not in ("Other",) + + @lazyproperty + def platform_base(self): + return self.platform.base @property def cpu_info(self): @@ -264,9 +308,9 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): def as_tree_node(self, parent_node): from common.tree import TreeNode icon_skin = 'file' - if self.platform.lower() == 'windows': + if self.platform_base.lower() == 'windows': icon_skin = 'windows' - elif self.platform.lower() == 'linux': + elif self.platform_base.lower() == 'linux': icon_skin = 'linux' data = { 'id': str(self.id), @@ -283,7 +327,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin): 'hostname': self.hostname, 'ip': self.ip, 'protocols': self.protocols_as_list, - 'platform': self.platform, + 'platform': self.platform_base, } } } diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index a24374c13..6948f20e2 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer from common.serializers import AdaptedBulkListSerializer -from ..models import Asset, Node, Label +from ..models import Asset, Node, Label, Platform from ..const import ( GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN, GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_ERROR_MSG @@ -16,7 +16,8 @@ from .base import ConnectivitySerializer __all__ = [ 'AssetSerializer', 'AssetSimpleSerializer', - 'ProtocolsField', + 'ProtocolsField', 'PlatformSerializer', + 'AssetDetailSerializer', ] @@ -65,6 +66,9 @@ class ProtocolsField(serializers.ListField): class AssetSerializer(BulkOrgResourceModelSerializer): + platform = serializers.SlugRelatedField( + slug_field='name', queryset=Platform.objects.all(), label=_("Platform") + ) protocols = ProtocolsField(label=_('Protocols'), required=False) connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity")) @@ -111,7 +115,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): 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') + ).select_related('admin_user', 'domain', 'platform') return queryset def compatible_with_old_protocol(self, validated_data): @@ -139,6 +143,21 @@ class AssetSerializer(BulkOrgResourceModelSerializer): return super().update(instance, validated_data) +class PlatformSerializer(serializers.ModelSerializer): + meta = serializers.DictField(required=False, allow_null=True) + + class Meta: + model = Platform + fields = [ + 'id', 'name', 'base', 'charset', + 'internal', 'meta', 'comment' + ] + + +class AssetDetailSerializer(AssetSerializer): + platform = PlatformSerializer(read_only=True) + + class AssetSimpleSerializer(serializers.ModelSerializer): connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity")) diff --git a/apps/assets/templates/assets/_admin_user_import_modal.html b/apps/assets/templates/assets/_admin_user_import_modal.html deleted file mode 100644 index a4afc1a14..000000000 --- a/apps/assets/templates/assets/_admin_user_import_modal.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends '_import_modal.html' %} -{% load i18n %} - -{% block modal_title%}{% trans "Import admin user" %}{% endblock %} - -{% block import_modal_download_template_url %}{% url "api-assets:admin-user-list" %}{% endblock %} diff --git a/apps/assets/templates/assets/_admin_user_update_modal.html b/apps/assets/templates/assets/_admin_user_update_modal.html deleted file mode 100644 index 9af051dd2..000000000 --- a/apps/assets/templates/assets/_admin_user_update_modal.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends '_update_modal.html' %} -{% load i18n %} - -{% block modal_title%}{% trans "Update admin user" %}{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/_asset_import_modal.html b/apps/assets/templates/assets/_asset_import_modal.html deleted file mode 100644 index 2460cb053..000000000 --- a/apps/assets/templates/assets/_asset_import_modal.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends '_import_modal.html' %} -{% load i18n %} - -{% block modal_title%}{% trans "Import assets" %}{% endblock %} - -{% block import_modal_download_template_url %}{% url "api-assets:asset-list" %}{% endblock %} diff --git a/apps/assets/templates/assets/_asset_list_modal.html b/apps/assets/templates/assets/_asset_list_modal.html index a50934e9c..dea2c3e1e 100644 --- a/apps/assets/templates/assets/_asset_list_modal.html +++ b/apps/assets/templates/assets/_asset_list_modal.html @@ -25,7 +25,7 @@
-
+
@@ -37,7 +37,7 @@
-
+
diff --git a/apps/assets/templates/assets/_system_user_import_modal.html b/apps/assets/templates/assets/_system_user_import_modal.html deleted file mode 100644 index b8687d696..000000000 --- a/apps/assets/templates/assets/_system_user_import_modal.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends '_import_modal.html' %} -{% load i18n %} - -{% block modal_title%}{% trans "Import system user" %}{% endblock %} - -{% block import_modal_download_template_url %}{% url "api-assets:system-user-list" %}{% endblock %} diff --git a/apps/assets/templates/assets/_system_user_update_modal.html b/apps/assets/templates/assets/_system_user_update_modal.html deleted file mode 100644 index 9e2920e6a..000000000 --- a/apps/assets/templates/assets/_system_user_update_modal.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends '_update_modal.html' %} -{% load i18n %} - -{% block modal_title%}{% trans "Update system user" %}{% endblock %} \ No newline at end of file diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html index 8063d6de6..f86352862 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -5,28 +5,7 @@ {% 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 %} @@ -42,9 +21,6 @@ -{# #} -{# #} -{# #} @@ -52,8 +28,6 @@
{% trans 'Name' %} {% trans 'Username' %} {% trans 'Asset' %}{% trans 'Reachable' %}{% trans 'Unreachable' %}{% trans 'Ratio' %}{% trans 'Comment' %} {% trans 'Action' %}
- {% include 'assets/_admin_user_import_modal.html' %} - {% include 'assets/_admin_user_update_modal.html' %} {% endblock %} {% block content_bottom_left %}{% endblock %} {% block custom_foot_js %} @@ -80,14 +54,13 @@ function initTable() { {data: "comment"}, {data: "id", orderable: false, width: "100px"} ] }; - admin_user_table = jumpserver.initServerSideDataTable(options); - return admin_user_table + return jumpserver.initServerSideDataTable(options); } $(document).ready(function(){ - initTable(); + admin_user_table = initTable(); + initCsvImportExport(admin_user_table, "{% trans "Admin user" %}") }) - .on('click', '.btn_admin_user_delete', function () { var $this = $(this); var $data_table = $("#admin_user_list_table").DataTable(); @@ -100,69 +73,5 @@ $(document).ready(function(){ }, 3000); }) -.on('click', '.btn_export', function(){ - var admin_users = admin_user_table.selected; - var data = { - 'resources': admin_users - }; - var search = $("input[type='search']").val(); - var props = { - method: "POST", - body: JSON.stringify(data), - success_url: "{% url 'api-assets:admin-user-list' %}", - format: "csv", - params: { - search: search - } - }; - APIExportData(props); -}).on('click', '#btn_import_confirm',function () { - var url = "{% url 'api-assets:admin-user-list' %}"; - var file = document.getElementById('id_file').files[0]; - if(!file){ - toastr.error("{% trans "Please select file" %}"); - return - } - var data_table = $('#admin_user_list_table').DataTable(); - APIImportData({ - url: url, - method: "POST", - body: file, - data_table: data_table - }); -}) -.on('click', '#download_update_template', function () { - var admin_users = admin_user_table.selected; - var data = { - 'resources': admin_users - }; - var search = $("input[type='search']").val(); - var props = { - method: "POST", - body: JSON.stringify(data), - success_url: "{% url 'api-assets:admin-user-list' %}?format=csv&template=update", - format: 'csv', - params: { - search: search - } - }; - APIExportData(props); -}) -.on('click', '#btn_update_confirm', function () { - var file = document.getElementById('update_file').files[0]; - if(!file){ - toastr.error("{% trans "Please select file" %}"); - return - } - var url = "{% url 'api-assets:admin-user-list' %}"; - var data_table = $('#admin_user_list_table').DataTable(); - - APIImportData({ - url: url, - method: "PUT", - body: file, - data_table: data_table - }); -}) {% endblock %} diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index 66bc18f26..bf4186412 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -1,131 +1,52 @@ -{% extends 'base.html' %} +{% 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 custom_head_css_js %} -{# #} -{# #} - - - -{% endblock %} - -{% block content %} -
-
-
- {% include 'assets/_node_tree.html' %} -
-
-
-
- -
-
-
- - -
- - -
- - - - - - - - - - - - - -
{% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Hardware' %}{% trans 'Reachable' %}{% trans 'Action' %}
-
-
- -
- -
-
-
+{% block table_container %} + + {% include '_csv_import_export.html' %} +
+ + +
+ + + + + + + + + + + + + +
{% trans 'Hostname' %}{% trans 'IP' %}{% trans 'Hardware' %}{% trans 'Reachable' %}{% trans 'Action' %}
+
+
+ +
+
-
- -{% include 'assets/_asset_update_modal.html' %} -{% include 'assets/_asset_import_modal.html' %} {% include 'assets/_asset_list_modal.html' %} {% include 'assets/_node_detail_modal.html' %} {% endblock %} @@ -205,22 +126,6 @@ function initTree() { }) } - -function toggle() { - if (show === 0) { - $("#split-left").hide(500, function () { - $("#split-right").attr("class", "col-lg-12"); - $("#toggle-icon").attr("class", "fa fa-angle-right fa-x"); - show = 1; - }); - } else { - $("#split-right").attr("class", "col-lg-9"); - $("#toggle-icon").attr("class", "fa fa-angle-left fa-x"); - $("#split-left").show(500); - show = 0; - } -} - function onNodeSelected(event, treeNode) { current_node = treeNode; current_node_id = treeNode.meta.node.id; @@ -261,7 +166,8 @@ function onAssetModalConfirmAddAssetToNode(table) { } $(document).ready(function(){ - initTable(); + asset_table = initTable(); + initCsvImportExport(asset_table, "{% trans "Asset" %}"); initTree(); if(getCookie('show_current_asset') === '1'){ @@ -282,81 +188,6 @@ $(document).ready(function(){ $("#asset_list_table_filter input").val(val); asset_table.search(val).draw(); }) -.on('click', '.btn_export', function () { - var assets = asset_table.selected; - var data = { - 'resources': assets - }; - var search = $("input[type='search']").val(); - var props = { - method: "POST", - body: JSON.stringify(data), - success_url: "{% url 'api-assets:asset-list' %}", - format: 'csv', - params: { - search: search, - node_id: current_node_id || '', - show_current_asset: getCookie('show_current_asset') - } - }; - APIExportData(props); -}) -.on('click', '#btn_import_confirm', function () { - var file = document.getElementById('id_file').files[0]; - if(!file){ - toastr.error("{% trans "Please select file" %}"); - return - } - var url = "{% url 'api-assets:asset-list' %}"; - if (current_node_id){ - url = setUrlParam(url, 'node_id', current_node_id); - } - var data_table = $('#asset_list_table').DataTable(); - - APIImportData({ - url: url, - method: "POST", - body: file, - data_table: data_table - }); -}) -.on('click', '#download_update_template', function () { - var assets = asset_table.selected; - var data = { - 'resources': assets - }; - var search = $("input[type='search']").val(); - var props = { - method: "POST", - body: JSON.stringify(data), - success_url: "{% url 'api-assets:asset-list' %}?format=csv&template=update", - format: 'csv', - params: { - search: search, - node_id: current_node_id || '' - } - }; - APIExportData(props); -}) -.on('click', '#btn_update_confirm', function () { - var file = document.getElementById('update_file').files[0]; - if(!file){ - toastr.error("{% trans "Please select file" %}"); - return - } - var url = "{% url 'api-assets:asset-list' %}"; - if (current_node_id){ - url = setUrlParam(url, 'node_id', current_node_id); - } - var data_table = $('#asset_list_table').DataTable(); - - APIImportData({ - url: url, - method: "PUT", - body: file, - data_table: data_table - }); -}) .on('click', '.btn-create-asset', function () { var url = "{% url 'assets:asset-create' %}"; if (current_node_id) { diff --git a/apps/assets/templates/assets/platform_create_update.html b/apps/assets/templates/assets/platform_create_update.html new file mode 100644 index 000000000..e130e9e97 --- /dev/null +++ b/apps/assets/templates/assets/platform_create_update.html @@ -0,0 +1,79 @@ +{% 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 new file mode 100644 index 000000000..86d7f93e1 --- /dev/null +++ b/apps/assets/templates/assets/platform_detail.html @@ -0,0 +1,75 @@ +{% 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 new file mode 100644 index 000000000..44f19f1d3 --- /dev/null +++ b/apps/assets/templates/assets/platform_list.html @@ -0,0 +1,75 @@ +{% 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_list.html b/apps/assets/templates/assets/system_user_list.html index be3f606d2..ff282ab81 100644 --- a/apps/assets/templates/assets/system_user_list.html +++ b/apps/assets/templates/assets/system_user_list.html @@ -8,28 +8,7 @@ {% endblock %} {% block table_search %} - + {% include '_csv_import_export.html' %} {% endblock %} {% block table_container %} @@ -57,8 +36,6 @@ - {% include 'assets/_system_user_import_modal.html' %} - {% include 'assets/_system_user_update_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 055f2dc7a..653b71447 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -12,6 +12,7 @@ app_name = 'assets' router = BulkRouter() router.register(r'assets', api.AssetViewSet, 'asset') +router.register(r'platforms', api.AssetPlatformViewSet, 'platform') router.register(r'admin-users', api.AdminUserViewSet, 'admin-user') router.register(r'system-users', api.SystemUserViewSet, 'system-user') router.register(r'labels', api.LabelViewSet, 'label') @@ -37,6 +38,8 @@ urlpatterns = [ api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'), path('assets//gateway/', api.AssetGatewayApi.as_view(), name='asset-gateway'), + path('assets//platform/', + api.AssetPlatformRetrieveApi.as_view(), name='asset-platform-detail'), path('asset-users/auth-info/', api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'), diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py index 71483a4df..eec9dcc0d 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -16,6 +16,11 @@ urlpatterns = [ # 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'), diff --git a/apps/assets/views/__init__.py b/apps/assets/views/__init__.py index 04fc6c31c..74055a76c 100644 --- a/apps/assets/views/__init__.py +++ b/apps/assets/views/__init__.py @@ -1,5 +1,6 @@ # coding:utf-8 from .asset import * +from .platform import * from .system_user import * from .admin_user import * from .label import * diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index f08c08db8..63179f4f8 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -74,7 +74,7 @@ class UserAssetListView(PermissionsMixin, TemplateView): class AssetCreateView(PermissionsMixin, FormMixin, TemplateView): model = Asset - form_class = forms.AssetCreateForm + form_class = forms.AssetCreateUpdateForm template_name = 'assets/asset_create.html' success_url = reverse_lazy('assets:asset-list') permission_classes = [IsOrgAdmin] @@ -110,7 +110,7 @@ class AssetCreateView(PermissionsMixin, FormMixin, TemplateView): class AssetUpdateView(PermissionsMixin, UpdateView): model = Asset - form_class = forms.AssetUpdateForm + form_class = forms.AssetCreateUpdateForm template_name = 'assets/asset_update.html' success_url = reverse_lazy('assets:asset-list') permission_classes = [IsOrgAdmin] diff --git a/apps/assets/views/domain.py b/apps/assets/views/domain.py index 67626b094..ad7fad1b6 100644 --- a/apps/assets/views/domain.py +++ b/apps/assets/views/domain.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- # -from django.views.generic import TemplateView, CreateView, \ - UpdateView, DeleteView, DetailView +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 diff --git a/apps/assets/views/platform.py b/apps/assets/views/platform.py new file mode 100644 index 000000000..2e3aff49c --- /dev/null +++ b/apps/assets/views/platform.py @@ -0,0 +1,74 @@ +# -*- 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 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/audits/signals_handler.py b/apps/audits/signals_handler.py index a1c17a8bc..772a7fcd5 100644 --- a/apps/audits/signals_handler.py +++ b/apps/audits/signals_handler.py @@ -11,6 +11,7 @@ from rest_framework.request import Request from jumpserver.utils import current_request from common.utils import get_request_ip, get_logger, get_syslogger from users.models import User +from users.signals import post_user_change_password from authentication.signals import post_auth_failed, post_auth_success from terminal.models import Session, Command from common.utils.encode import model_to_json @@ -18,7 +19,7 @@ from . import models from .tasks import write_login_log_async logger = get_logger(__name__) -sys_logger = get_syslogger("audits") +sys_logger = get_syslogger(__name__) json_render = JSONRenderer() @@ -50,7 +51,7 @@ def create_operate_log(action, sender, resource): logger.error("Create operate log error: {}".format(e)) -@receiver(post_save, dispatch_uid="my_unique_identifier") +@receiver(post_save) def on_object_created_or_update(sender, instance=None, created=False, update_fields=None, **kwargs): if instance._meta.object_name == 'User' and \ update_fields and 'last_login' in update_fields: @@ -62,21 +63,27 @@ def on_object_created_or_update(sender, instance=None, created=False, update_fie create_operate_log(action, sender, instance) -@receiver(post_delete, dispatch_uid="my_unique_identifier") +@receiver(post_delete) def on_object_delete(sender, instance=None, **kwargs): create_operate_log(models.OperateLog.ACTION_DELETE, sender, instance) -@receiver(post_save, sender=User, dispatch_uid="my_unique_identifier") -def on_user_change_password(sender, instance=None, **kwargs): - if hasattr(instance, '_set_password'): - if not current_request or not current_request.user.is_authenticated: - return - with transaction.atomic(): - models.PasswordChangeLog.objects.create( - user=instance, change_by=current_request.user, - remote_addr=get_request_ip(current_request), - ) +@receiver(post_user_change_password, sender=User) +def on_user_change_password(sender, user=None, **kwargs): + if not current_request: + remote_addr = '127.0.0.1' + change_by = 'System' + else: + remote_addr = get_request_ip(current_request) + if not current_request.user.is_authenticated: + change_by = str(user) + else: + change_by = str(current_request.user) + with transaction.atomic(): + models.PasswordChangeLog.objects.create( + user=str(user), change_by=change_by, + remote_addr=remote_addr, + ) def on_audits_log_create(sender, instance=None, **kwargs): @@ -95,7 +102,7 @@ def on_audits_log_create(sender, instance=None, **kwargs): else: return - data = model_to_json(instance) + data = model_to_json(instance, indent=None) msg = "{} - {}".format(category, data) sys_logger.info(msg) diff --git a/apps/audits/templates/audits/login_log_list.html b/apps/audits/templates/audits/login_log_list.html index 0533f8aef..1ac74d311 100644 --- a/apps/audits/templates/audits/login_log_list.html +++ b/apps/audits/templates/audits/login_log_list.html @@ -102,47 +102,47 @@ {% block custom_foot_js %} - - + + }) + {% endblock %} diff --git a/apps/authentication/forms.py b/apps/authentication/forms.py index 1b83a3a55..d14cde515 100644 --- a/apps/authentication/forms.py +++ b/apps/authentication/forms.py @@ -2,11 +2,8 @@ # from django import forms -from django.contrib.auth.forms import AuthenticationForm from django.utils.translation import gettext_lazy as _ from captcha.fields import CaptchaField -from django.conf import settings -from users.utils import get_login_failed_count class UserLoginForm(forms.Form): diff --git a/apps/common/mixins/api.py b/apps/common/mixins/api.py index 64a26fdcb..694c5606d 100644 --- a/apps/common/mixins/api.py +++ b/apps/common/mixins/api.py @@ -27,10 +27,17 @@ class IDSpmFilterMixin: class SerializerMixin: def get_serializer_class(self): - if self.request.method.lower() == 'get' and\ - self.request.query_params.get('draw') \ - and hasattr(self, 'serializer_display_class'): - return self.serializer_display_class + 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'): + 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() diff --git a/apps/common/signals_handlers.py b/apps/common/signals_handlers.py index 2c4a95c7a..d1cf1c7fa 100644 --- a/apps/common/signals_handlers.py +++ b/apps/common/signals_handlers.py @@ -2,6 +2,7 @@ # import re import os +import logging from collections import defaultdict from django.conf import settings from django.dispatch import receiver @@ -10,13 +11,13 @@ from django.db import connection from django.conf import LazySettings from django.db.utils import ProgrammingError, OperationalError +from jumpserver.utils import get_current_request -from common.utils import get_logger from .local import thread_local from .signals import django_ready pattern = re.compile(r'FROM `(\w+)`') -logger = get_logger(__name__) +logger = logging.getLogger("jumpserver.common") DEBUG_DB = os.environ.get('DEBUG_DB', '0') == '1' @@ -50,19 +51,29 @@ def on_request_finished_logging_db_query(sender, **kwargs): counters['total'].time += float(time) counters = sorted(counters.items(), key=lambda x: x[1]) + if not counters: + return + method = 'GET' + path = '/Unknown' + current_request = get_current_request() + if current_request: + method = current_request.method + path = current_request.get_full_path() + logger.debug(">>> [{}] {}".format(method, path)) for name, counter in counters: logger.debug("Query {:3} times using {:.2f}s {}".format( counter.counter, counter.time, name) ) -@receiver(request_finished) def on_request_finished_release_local(sender, **kwargs): thread_local.__release_local__() if settings.DEBUG and DEBUG_DB: request_finished.connect(on_request_finished_logging_db_query) +else: + request_finished.connect(on_request_finished_release_local) @receiver(django_ready) diff --git a/apps/common/utils/common.py b/apps/common/utils/common.py index a024b7ac6..07116ea8b 100644 --- a/apps/common/utils/common.py +++ b/apps/common/utils/common.py @@ -27,12 +27,12 @@ def combine_seq(s1, s2, callback=None): return seq -def get_logger(name=None): +def get_logger(name=''): return logging.getLogger('jumpserver.%s' % name) -def get_syslogger(name=None): - return logging.getLogger('jms.%s' % name) +def get_syslogger(name=''): + return logging.getLogger('syslog.%s' % name) def timesince(dt, since='', default="just now"): diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 9496de6a3..70876623e 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -184,6 +184,7 @@ class Config(dict): 'ASSETS_PERM_CACHE_ENABLE': False, 'SYSLOG_ADDR': '', # '192.168.0.1:514' 'SYSLOG_FACILITY': 'user', + 'SYSLOG_SOCKTYPE': 2, 'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False, 'WINDOWS_SSH_DEFAULT_SHELL': 'cmd', 'FLOWER_URL': "127.0.0.1:5555", @@ -282,10 +283,10 @@ class DynamicConfig: ] if self.get('AUTH_LDAP'): backends.insert(0, 'authentication.backends.ldap.LDAPAuthorizationBackend') - if self.get('AUTH_OPENID'): + if self.static_config.get('AUTH_OPENID'): backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationPasswordBackend') backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationCodeBackend') - if self.get('AUTH_RADIUS'): + if self.static_config.get('AUTH_RADIUS'): backends.insert(0, 'authentication.backends.radius.RadiusBackend') return backends diff --git a/apps/jumpserver/settings/logging.py b/apps/jumpserver/settings/logging.py index 0fef4e6f9..7de84ab7e 100644 --- a/apps/jumpserver/settings/logging.py +++ b/apps/jumpserver/settings/logging.py @@ -81,7 +81,7 @@ LOGGING = { 'propagate': False, }, 'jumpserver': { - 'handlers': ['console', 'file', 'syslog'], + 'handlers': ['console', 'file'], 'level': LOG_LEVEL, }, 'ops.ansible_api': { @@ -92,7 +92,7 @@ LOGGING = { 'handlers': ['console', 'file'], 'level': "INFO", }, - 'jms.audits': { + 'syslog': { 'handlers': ['syslog'], 'level': 'INFO' }, @@ -110,6 +110,7 @@ if CONFIG.SYSLOG_ADDR != '' and len(CONFIG.SYSLOG_ADDR.split(':')) == 2: 'class': 'logging.handlers.SysLogHandler', 'facility': CONFIG.SYSLOG_FACILITY, 'address': (host, int(port)), + 'socktype': CONFIG.SYSLOG_SOCKTYPE, }) if not os.path.isdir(LOG_DIR): diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 1c25bc1068a001db43941804e5b2f6919312ce85..95c6818911960ffb212ccb65b4d8975e6b023f01 100644 GIT binary patch delta 24755 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�jZyj~j^hVoK_hT74Yqh7jjuA58rS+)nNU z?Gn&tI!kW6YU;m4ae5@76uK$E0)vipF#qa6CB9|0;_QofS9c(%7hwq@9{~ z@Tzv|5#6nqD|Bn#DY{2YrYTKBf}(pgj~VdOw!oNbKQ9dq^^Y3ap zx?AUtkv-bB>mJ#ubF1i>KNrSI8uMz)kkHs4X3%qA%D|xLR=Fn)K2RX$^nt_)V;76e zSEzWwd@=X`&Y2=6&x7xR65p6Rz`Hhk6I|OEb8Xzpm<5mj4Gg)l<;NRy22S!lD-(12 z*@WQuH)1y5*gV@>w0Qkpob^|O0-{5bUmG^`*5E}qW(@IeObHwz6Y z5fW$U;OkRXULUi3{q)2Ey93v^iwNiu9N!IcZN$`@a~G{Yn>?UFaH6Z5$KD(@=*FOp zHx_QbHT{?MEz<<-3SVC`PeA?9|8wN^KNJi2BOv73`qek*Ety)YL_mS{^GgJLo#6ig DY^n1j delta 24894 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 RmTbJcdFj?$\n" "Language-Team: Jumpserver team\n" @@ -76,7 +76,7 @@ msgstr "运行参数" #: applications/templates/applications/remote_app_list.html:20 #: applications/templates/applications/user_remote_app_list.html:18 #: assets/forms/domain.py:15 assets/forms/label.py:13 -#: assets/models/asset.py:295 assets/models/authbook.py:24 +#: assets/models/asset.py:330 assets/models/authbook.py:24 #: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:32 #: assets/serializers/asset_user.py:82 assets/serializers/system_user.py:45 #: assets/templates/assets/admin_user_list.html:44 @@ -100,12 +100,12 @@ msgstr "运行参数" #: users/templates/users/user_asset_permission.html:90 #: xpack/plugins/change_auth_plan/forms.py:73 #: xpack/plugins/change_auth_plan/models.py:419 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:44 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:42 #: 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:307 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:60 #: xpack/plugins/orgs/templates/orgs/org_list.html:17 #: xpack/plugins/vault/forms.py:13 xpack/plugins/vault/forms.py:15 msgid "Asset" @@ -116,11 +116,10 @@ msgstr "资产" #: applications/templates/applications/remote_app_list.html:18 #: applications/templates/applications/user_remote_app_list.html:16 #: assets/forms/asset.py:21 assets/forms/domain.py:77 assets/forms/user.py:75 -#: assets/forms/user.py:95 assets/models/base.py:28 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/forms/user.py:95 assets/models/asset.py:136 assets/models/base.py:28 +#: 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/admin_user_detail.html:51 #: assets/templates/assets/admin_user_list.html:42 #: assets/templates/assets/cmd_filter_detail.html:56 #: assets/templates/assets/cmd_filter_list.html:22 @@ -128,6 +127,7 @@ msgstr "资产" #: 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_list.html:16 #: assets/templates/assets/system_user_detail.html:53 #: assets/templates/assets/system_user_list.html:45 ops/models/adhoc.py:37 #: ops/templates/ops/task_detail.html:58 ops/templates/ops/task_list.html:11 @@ -146,7 +146,7 @@ msgstr "资产" #: terminal/models.py:350 terminal/templates/terminal/base_storage_list.html:32 #: terminal/templates/terminal/terminal_detail.html:43 #: terminal/templates/terminal/terminal_list.html:30 users/forms.py:162 -#: users/models/group.py:14 users/models/user.py:443 +#: users/models/group.py:14 users/models/user.py:433 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:54 #: users/templates/users/user_asset_permission.html:174 @@ -158,16 +158,16 @@ msgstr "资产" #: users/templates/users/user_pubkey_update.html:57 #: xpack/plugins/change_auth_plan/forms.py:56 #: xpack/plugins/change_auth_plan/models.py:64 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61 +#: 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:59 xpack/plugins/cloud/models.py:144 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:50 +#: 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:56 +#: 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/models.py:28 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:16 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:52 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:47 #: xpack/plugins/orgs/templates/orgs/org_list.html:12 msgid "Name" msgstr "名称" @@ -190,7 +190,7 @@ msgstr "参数" #: applications/models/remote_app.py:39 #: applications/templates/applications/remote_app_detail.html:68 -#: assets/models/asset.py:174 assets/models/base.py:36 +#: assets/models/asset.py:209 assets/models/base.py:36 #: 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 @@ -202,10 +202,10 @@ msgstr "参数" #: orgs/models.py:16 perms/models/base.py:54 #: perms/templates/perms/asset_permission_detail.html:93 #: perms/templates/perms/remote_app_permission_detail.html:85 -#: users/models/user.py:484 users/serializers/group.py:32 +#: users/models/user.py:474 users/serializers/group.py:32 #: users/templates/users/user_detail.html:112 #: xpack/plugins/change_auth_plan/models.py:109 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:111 #: xpack/plugins/cloud/models.py:80 xpack/plugins/cloud/models.py:179 #: xpack/plugins/gathered_user/models.py:46 msgid "Created by" @@ -215,7 +215,7 @@ msgstr "创建者" # msgstr "创建者" #: applications/models/remote_app.py:42 #: applications/templates/applications/remote_app_detail.html:64 -#: assets/models/asset.py:175 assets/models/base.py:34 +#: assets/models/asset.py:210 assets/models/base.py:34 #: 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 @@ -230,11 +230,11 @@ msgstr "创建者" #: terminal/templates/terminal/terminal_detail.html:59 #: tickets/templates/tickets/ticket_detail.html:52 users/models/group.py:17 #: users/templates/users/user_group_detail.html:58 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:105 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:103 #: xpack/plugins/cloud/models.py:83 xpack/plugins/cloud/models.py:182 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:66 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:101 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:60 +#: 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 "创建日期" @@ -244,11 +244,12 @@ msgstr "创建日期" #: applications/templates/applications/remote_app_detail.html:72 #: applications/templates/applications/remote_app_list.html:21 #: applications/templates/applications/user_remote_app_list.html:19 -#: assets/models/asset.py:176 assets/models/base.py:33 -#: 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/models/asset.py:141 assets/models/asset.py:211 +#: assets/models/base.py:33 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:48 #: assets/templates/assets/asset_detail.html:128 #: assets/templates/assets/cmd_filter_detail.html:60 @@ -257,6 +258,7 @@ msgstr "创建日期" #: assets/templates/assets/domain_detail.html:71 #: assets/templates/assets/domain_gateway_list.html:67 #: assets/templates/assets/domain_list.html:24 +#: assets/templates/assets/platform_list.html:18 #: assets/templates/assets/system_user_detail.html:99 #: assets/templates/assets/system_user_list.html:53 ops/models/adhoc.py:43 #: orgs/models.py:18 perms/models/base.py:56 @@ -266,20 +268,20 @@ msgstr "创建日期" #: terminal/models.py:357 terminal/templates/terminal/base_storage_list.html:34 #: terminal/templates/terminal/terminal_detail.html:63 #: tickets/templates/tickets/ticket_detail.html:104 users/models/group.py:15 -#: users/models/user.py:476 users/templates/users/user_detail.html:130 +#: users/models/user.py:466 users/templates/users/user_detail.html:130 #: users/templates/users/user_group_detail.html:62 #: users/templates/users/user_group_list.html:37 #: users/templates/users/user_profile.html:138 #: xpack/plugins/change_auth_plan/models.py:105 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117 +#: 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:77 xpack/plugins/cloud/models.py:173 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:70 +#: 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:105 +#: 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:42 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:64 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:59 #: xpack/plugins/orgs/templates/orgs/org_list.html:23 msgid "Comment" msgstr "备注" @@ -305,14 +307,15 @@ msgstr "远程应用" #: 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:16 #: perms/templates/perms/asset_permission_create_update.html:81 #: perms/templates/perms/remote_app_permission_create_update.html:82 -#: settings/templates/settings/basic_setting.html:64 -#: settings/templates/settings/email_content_setting.html:54 -#: settings/templates/settings/email_setting.html:65 -#: settings/templates/settings/ldap_setting.html:64 -#: settings/templates/settings/security_setting.html:73 -#: settings/templates/settings/terminal_setting.html:76 +#: 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 @@ -324,13 +327,13 @@ 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:69 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:33 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:53 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:44 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:67 +#: 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:33 -#: xpack/plugins/vault/templates/vault/vault_create.html:45 +#: xpack/plugins/orgs/templates/orgs/org_create_update.html:29 +#: xpack/plugins/vault/templates/vault/vault_create.html:41 msgid "Reset" msgstr "重置" @@ -345,15 +348,16 @@ msgstr "重置" #: 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:17 #: audits/templates/audits/login_log_list.html:95 #: perms/templates/perms/asset_permission_create_update.html:82 #: perms/templates/perms/remote_app_permission_create_update.html:83 -#: settings/templates/settings/basic_setting.html:65 -#: settings/templates/settings/email_content_setting.html:55 -#: settings/templates/settings/email_setting.html:66 -#: settings/templates/settings/ldap_setting.html:67 -#: settings/templates/settings/security_setting.html:74 -#: settings/templates/settings/terminal_setting.html:78 +#: 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:48 +#: 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:47 #: terminal/templates/terminal/session_list.html:50 @@ -361,13 +365,13 @@ msgstr "重置" #: users/templates/users/_user.html:52 #: users/templates/users/forgot_password.html:42 #: users/templates/users/user_bulk_update.html:24 -#: users/templates/users/user_list.html:61 +#: users/templates/users/user_list.html:57 #: 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:70 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:68 #: xpack/plugins/interface/templates/interface/interface.html:74 -#: xpack/plugins/vault/templates/vault/vault_create.html:46 +#: xpack/plugins/vault/templates/vault/vault_create.html:42 msgid "Submit" msgstr "提交" @@ -389,8 +393,8 @@ 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:17 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:20 +#: 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 @@ -405,7 +409,7 @@ msgstr "详情" #: assets/templates/assets/admin_user_list.html:72 #: assets/templates/assets/asset_detail.html:24 #: assets/templates/assets/asset_list.html:78 -#: assets/templates/assets/asset_list.html:168 +#: assets/templates/assets/asset_list.html:167 #: 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 @@ -414,6 +418,7 @@ msgstr "详情" #: 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_list.html:40 #: assets/templates/assets/system_user_detail.html:21 #: assets/templates/assets/system_user_list.html:27 #: assets/templates/assets/system_user_list.html:79 audits/models.py:34 @@ -431,19 +436,19 @@ msgstr "详情" #: users/templates/users/user_group_list.html:20 #: users/templates/users/user_group_list.html:71 #: users/templates/users/user_list.html:20 -#: users/templates/users/user_list.html:107 -#: users/templates/users/user_list.html:110 +#: users/templates/users/user_list.html:103 +#: users/templates/users/user_list.html:106 #: users/templates/users/user_profile.html:181 #: users/templates/users/user_profile.html:191 #: users/templates/users/user_profile.html:201 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:29 +#: 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:23 +#: 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:29 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:26 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:57 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:46 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:25 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:20 #: xpack/plugins/orgs/templates/orgs/org_list.html:93 msgid "Update" msgstr "更新" @@ -453,7 +458,7 @@ msgstr "更新" #: assets/templates/assets/admin_user_detail.html:23 #: assets/templates/assets/admin_user_list.html:73 #: assets/templates/assets/asset_detail.html:28 -#: assets/templates/assets/asset_list.html:169 +#: assets/templates/assets/asset_list.html:168 #: 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 @@ -462,6 +467,7 @@ msgstr "更新" #: 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_list.html:41 #: assets/templates/assets/system_user_detail.html:25 #: assets/templates/assets/system_user_list.html:80 audits/models.py:35 #: authentication/templates/authentication/_access_key_modal.html:65 @@ -477,16 +483,16 @@ msgstr "更新" #: users/templates/users/user_detail.html:31 #: users/templates/users/user_group_detail.html:27 #: users/templates/users/user_group_list.html:73 -#: users/templates/users/user_list.html:117 -#: users/templates/users/user_list.html:121 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:33 +#: users/templates/users/user_list.html:111 +#: users/templates/users/user_list.html:115 +#: 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:27 +#: 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:33 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:30 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:58 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:47 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:29 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:24 #: xpack/plugins/orgs/templates/orgs/org_list.html:95 msgid "Delete" msgstr "删除" @@ -520,6 +526,7 @@ msgstr "创建远程应用" #: 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:54 audits/models.py:39 #: audits/templates/audits/operate_log_list.html:45 #: audits/templates/audits/operate_log_list.html:71 @@ -546,7 +553,7 @@ msgstr "创建远程应用" #: 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:72 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:67 #: 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 @@ -604,13 +611,13 @@ msgstr "不能包含特殊字符:[ {} ]" msgid "* The contains characters that are not allowed" msgstr "* 包含不被允许的字符" -#: assets/forms/asset.py:25 assets/models/asset.py:140 +#: assets/forms/asset.py:25 assets/models/asset.py:176 #: assets/models/domain.py:50 #: assets/templates/assets/domain_gateway_list.html:64 msgid "Port" msgstr "端口" -#: assets/forms/asset.py:56 assets/models/asset.py:145 +#: assets/forms/asset.py:56 assets/models/asset.py:180 #: assets/models/user.py:110 assets/templates/assets/asset_detail.html:186 #: assets/templates/assets/asset_detail.html:194 #: assets/templates/assets/system_user_assets.html:87 @@ -622,10 +629,10 @@ msgid "Nodes" msgstr "节点" #: assets/forms/asset.py:59 assets/forms/asset.py:106 -#: assets/models/asset.py:149 assets/models/cluster.py:19 +#: assets/models/asset.py:184 assets/models/cluster.py:19 #: assets/models/user.py:68 assets/templates/assets/asset_detail.html:72 #: templates/_nav.html:44 xpack/plugins/cloud/models.py:161 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:68 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:65 #: xpack/plugins/orgs/templates/orgs/org_list.html:19 msgid "Admin user" msgstr "管理用户" @@ -639,7 +646,7 @@ msgid "Label" msgstr "标签" #: assets/forms/asset.py:65 assets/forms/asset.py:112 -#: assets/models/asset.py:144 assets/models/domain.py:26 +#: assets/models/asset.py:179 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 @@ -661,8 +668,8 @@ msgstr "网域" #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:55 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:15 #: xpack/plugins/cloud/models.py:157 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:64 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:64 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:61 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:61 msgid "Node" msgstr "节点" @@ -687,7 +694,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域, #: assets/forms/domain.py:17 assets/forms/label.py:15 #: perms/templates/perms/asset_permission_asset.html:74 #: xpack/plugins/change_auth_plan/forms.py:64 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:74 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:70 msgid "Select assets" msgstr "选择资产" @@ -719,7 +726,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: ops/models/adhoc.py:189 perms/templates/perms/asset_permission_list.html:205 #: perms/templates/perms/remote_app_permission_user.html:50 #: settings/templates/settings/_ldap_list_users_modal.html:31 users/forms.py:14 -#: users/forms.py:161 users/models/user.py:441 +#: users/forms.py:161 users/models/user.py:431 #: users/templates/users/_select_user_modal.html:14 #: users/templates/users/user_detail.html:68 #: users/templates/users/user_list.html:36 @@ -727,14 +734,18 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: xpack/plugins/change_auth_plan/forms.py:58 #: xpack/plugins/change_auth_plan/models.py:66 #: xpack/plugins/change_auth_plan/models.py:415 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65 +#: 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:64 +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:69 msgid "Username" msgstr "用户名" +#: assets/forms/platform.py:19 assets/templates/assets/platform_list.html:17 +msgid "Base platform" +msgstr "基础平台" + #: assets/forms/user.py:26 msgid "Password or private key passphrase" msgstr "密码或密钥密码" @@ -760,7 +771,7 @@ msgstr "密码" #: assets/forms/user.py:30 assets/serializers/asset_user.py:71 #: assets/templates/assets/_asset_user_auth_update_modal.html:27 -#: users/models/user.py:470 +#: users/models/user.py:460 msgid "Private key" msgstr "ssh私钥" @@ -800,7 +811,23 @@ msgstr "如果选择手动登录模式,用户名和密码可以不填写" msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig" msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" -#: assets/models/asset.py:135 assets/models/domain.py:49 +#: assets/models/asset.py:137 +msgid "Base" +msgstr "基础" + +#: assets/models/asset.py:138 +msgid "Charset" +msgstr "编码" + +#: assets/models/asset.py:139 tickets/models/ticket.py:38 +msgid "Meta" +msgstr "元数据" + +#: assets/models/asset.py:140 +msgid "Internal" +msgstr "内部的" + +#: assets/models/asset.py:171 assets/models/domain.py:49 #: assets/serializers/asset_user.py:28 #: assets/templates/assets/_asset_list_modal.html:47 #: assets/templates/assets/_asset_user_list.html:20 @@ -812,12 +839,12 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" #: perms/templates/perms/asset_permission_list.html:207 #: settings/forms/terminal.py:16 users/templates/users/_granted_assets.html:31 #: users/templates/users/user_asset_permission.html:176 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:54 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:63 +#: 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:68 msgid "IP" msgstr "IP" -#: assets/models/asset.py:136 assets/serializers/asset_user.py:27 +#: assets/models/asset.py:172 assets/serializers/asset_user.py:27 #: assets/serializers/gathered_user.py:20 #: assets/templates/assets/_asset_list_modal.html:46 #: assets/templates/assets/_asset_user_auth_update_modal.html:9 @@ -829,12 +856,12 @@ msgstr "IP" #: perms/templates/perms/asset_permission_list.html:208 #: settings/forms/terminal.py:15 users/templates/users/_granted_assets.html:30 #: users/templates/users/user_asset_permission.html:177 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:53 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:62 +#: 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:67 msgid "Hostname" msgstr "主机名" -#: assets/models/asset.py:139 assets/models/domain.py:51 +#: assets/models/asset.py:175 assets/models/domain.py:51 #: assets/models/user.py:113 assets/templates/assets/asset_detail.html:68 #: assets/templates/assets/domain_gateway_list.html:65 #: assets/templates/assets/system_user_detail.html:65 @@ -844,91 +871,91 @@ msgstr "主机名" msgid "Protocol" msgstr "协议" -#: assets/models/asset.py:142 assets/serializers/asset.py:68 +#: assets/models/asset.py:177 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:48 msgid "Protocols" msgstr "协议组" -#: assets/models/asset.py:143 assets/templates/assets/asset_detail.html:100 +#: assets/models/asset.py:178 assets/templates/assets/asset_detail.html:100 #: assets/templates/assets/user_asset_list.html:78 msgid "Platform" msgstr "系统平台" -#: assets/models/asset.py:146 assets/models/authbook.py:27 +#: assets/models/asset.py:181 assets/models/authbook.py:27 #: 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:152 assets/templates/assets/asset_detail.html:64 +#: assets/models/asset.py:187 assets/templates/assets/asset_detail.html:64 msgid "Public IP" msgstr "公网IP" -#: assets/models/asset.py:153 assets/templates/assets/asset_detail.html:116 +#: assets/models/asset.py:188 assets/templates/assets/asset_detail.html:116 msgid "Asset number" msgstr "资产编号" -#: assets/models/asset.py:156 assets/templates/assets/asset_detail.html:80 +#: assets/models/asset.py:191 assets/templates/assets/asset_detail.html:80 msgid "Vendor" msgstr "制造商" -#: assets/models/asset.py:157 assets/templates/assets/asset_detail.html:84 +#: assets/models/asset.py:192 assets/templates/assets/asset_detail.html:84 msgid "Model" msgstr "型号" -#: assets/models/asset.py:158 assets/templates/assets/asset_detail.html:112 +#: assets/models/asset.py:193 assets/templates/assets/asset_detail.html:112 msgid "Serial number" msgstr "序列号" -#: assets/models/asset.py:160 +#: assets/models/asset.py:195 msgid "CPU model" msgstr "CPU型号" -#: assets/models/asset.py:161 -#: xpack/plugins/license/templates/license/license_detail.html:71 +#: assets/models/asset.py:196 +#: xpack/plugins/license/templates/license/license_detail.html:80 msgid "CPU count" msgstr "CPU数量" -#: assets/models/asset.py:162 +#: assets/models/asset.py:197 msgid "CPU cores" msgstr "CPU核数" -#: assets/models/asset.py:163 +#: assets/models/asset.py:198 msgid "CPU vcpus" msgstr "CPU总数" -#: assets/models/asset.py:164 assets/templates/assets/asset_detail.html:92 +#: assets/models/asset.py:199 assets/templates/assets/asset_detail.html:92 msgid "Memory" msgstr "内存" -#: assets/models/asset.py:165 +#: assets/models/asset.py:200 msgid "Disk total" msgstr "硬盘大小" -#: assets/models/asset.py:166 +#: assets/models/asset.py:201 msgid "Disk info" msgstr "硬盘信息" -#: assets/models/asset.py:168 assets/templates/assets/asset_detail.html:104 +#: assets/models/asset.py:203 assets/templates/assets/asset_detail.html:104 msgid "OS" msgstr "操作系统" -#: assets/models/asset.py:169 +#: assets/models/asset.py:204 msgid "OS version" msgstr "系统版本" -#: assets/models/asset.py:170 +#: assets/models/asset.py:205 msgid "OS arch" msgstr "系统架构" -#: assets/models/asset.py:171 +#: assets/models/asset.py:206 msgid "Hostname raw" msgstr "主机名原始" -#: assets/models/asset.py:173 assets/templates/assets/asset_create.html:46 +#: assets/models/asset.py:208 assets/templates/assets/asset_create.html:46 #: assets/templates/assets/asset_detail.html:220 templates/_nav.html:46 msgid "Labels" msgstr "标签管理" @@ -962,8 +989,8 @@ msgstr "ssh公钥" #: assets/models/base.py:35 assets/models/gathered_user.py:20 #: assets/templates/assets/cmd_filter_detail.html:68 common/mixins/models.py:52 #: ops/models/adhoc.py:46 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:68 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:107 +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:71 msgid "Date updated" msgstr "更新日期" @@ -975,7 +1002,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:462 +#: assets/models/cluster.py:22 users/models/user.py:452 #: users/templates/users/user_detail.html:77 msgid "Phone" msgstr "手机" @@ -1001,7 +1028,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:585 +#: users/models/user.py:575 msgid "System" msgstr "系统" @@ -1092,17 +1119,15 @@ msgid "Gateway" msgstr "网关" #: assets/models/gathered_user.py:16 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:67 +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:70 msgid "Present" msgstr "存在" #: assets/models/gathered_user.py:17 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:65 msgid "Date last login" msgstr "最后登录日期" #: assets/models/gathered_user.py:18 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:66 msgid "IP last login" msgstr "最后登录IP" @@ -1142,14 +1167,14 @@ 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.py:339 -#: users/models/user.py:148 users/models/user.py:164 users/models/user.py:573 +#: users/models/user.py:148 users/models/user.py:164 users/models/user.py:563 #: users/serializers/group.py:21 #: users/templates/users/user_asset_permission.html:55 #: users/templates/users/user_asset_permission.html:84 #: users/templates/users/user_group_detail.html:73 #: users/templates/users/user_group_list.html:36 users/views/profile.py:68 #: xpack/plugins/orgs/forms.py:28 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:113 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:108 #: xpack/plugins/orgs/templates/orgs/org_list.html:15 msgid "User" msgstr "用户" @@ -1179,7 +1204,7 @@ msgstr "空" msgid "favorite" msgstr "收藏夹" -#: assets/models/node.py:452 assets/templates/assets/_node_detail_modal.html:39 +#: assets/models/node.py:452 msgid "Key" msgstr "键" @@ -1203,14 +1228,16 @@ msgstr "手动登录" #: 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:30 -#: assets/views/domain.py:47 assets/views/domain.py:65 -#: assets/views/domain.py:80 assets/views/domain.py:106 -#: assets/views/domain.py:135 assets/views/domain.py:156 +#: 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/system_user.py:29 assets/views/system_user.py:46 -#: assets/views/system_user.py:63 assets/views/system_user.py:79 -#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:71 +#: assets/views/platform.py:17 assets/views/platform.py:32 +#: assets/views/platform.py:47 assets/views/system_user.py:29 +#: assets/views/system_user.py:46 assets/views/system_user.py:63 +#: assets/views/system_user.py:79 templates/_nav.html:39 +#: xpack/plugins/change_auth_plan/models.py:71 msgid "Assets" msgstr "资产管理" @@ -1284,17 +1311,17 @@ msgstr "协议格式 {}/{}" msgid "Protocol duplicate: {}" msgstr "协议重复: {}" -#: assets/serializers/asset.py:69 assets/serializers/asset.py:143 +#: assets/serializers/asset.py:70 assets/serializers/asset.py:155 #: assets/serializers/asset_user.py:29 #: assets/templates/assets/_asset_user_list.html:23 msgid "Connectivity" msgstr "连接" -#: assets/serializers/asset.py:95 +#: assets/serializers/asset.py:96 msgid "Hardware info" msgstr "硬件信息" -#: assets/serializers/asset.py:96 orgs/mixins/serializers.py:27 +#: assets/serializers/asset.py:97 orgs/mixins/serializers.py:27 msgid "Org name" msgstr "组织名称" @@ -1303,7 +1330,7 @@ msgid "Backend" msgstr "后端" #: assets/serializers/asset_user.py:67 users/forms.py:282 -#: users/models/user.py:473 users/templates/users/first_login.html:42 +#: users/models/user.py:463 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 @@ -1478,7 +1505,7 @@ msgstr "资产列表" #: ops/templates/ops/command_execution_create.html:124 #: 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:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:62 msgid "Loading" msgstr "加载中" @@ -1509,7 +1536,6 @@ 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 "复制成功" @@ -1519,7 +1545,6 @@ msgid "Get auth info error" msgstr "获取认证信息错误" #: assets/templates/assets/_asset_user_auth_view_modal.html:97 -#: assets/templates/assets/_node_detail_modal.html:67 #: assets/templates/assets/_user_asset_detail_modal.html:23 #: authentication/templates/authentication/_access_key_modal.html:142 #: authentication/templates/authentication/_mfa_confirm_modal.html:53 @@ -1540,7 +1565,7 @@ msgid "Datetime" msgstr "日期" #: assets/templates/assets/_asset_user_list.html:41 -#: assets/templates/assets/asset_list.html:138 +#: assets/templates/assets/asset_list.html:137 msgid "Test datetime: " msgstr "测试日期: " @@ -1576,27 +1601,6 @@ msgstr "SSH端口" 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:203 -msgid "Node detail" -msgstr "节点详情" - -#: 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:64 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:60 -msgid "ID" -msgstr "ID" - -#: assets/templates/assets/_node_detail_modal.html:33 -msgid "Full name" -msgstr "全名" - #: assets/templates/assets/_node_tree.html:49 msgid "Add node" msgstr "新建节点" @@ -1630,9 +1634,9 @@ msgstr "重命名成功" #: assets/templates/assets/gateway_create_update.html:33 #: perms/templates/perms/asset_permission_create_update.html:36 #: perms/templates/perms/remote_app_permission_create_update.html:37 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:41 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:27 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:27 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:39 +#: 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 "基本" @@ -1653,9 +1657,9 @@ msgstr "自动生成密钥" #: perms/templates/perms/asset_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:65 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:48 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:39 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:63 +#: 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 "其它" @@ -1681,7 +1685,7 @@ 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:31 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:27 msgid "Asset list of " msgstr "资产列表" @@ -1706,14 +1710,14 @@ msgstr "替换资产的管理员" #: assets/templates/assets/admin_user_detail.html:86 #: perms/templates/perms/asset_permission_asset.html:99 #: xpack/plugins/change_auth_plan/forms.py:68 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:99 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:95 #: xpack/plugins/gathered_user/forms.py:36 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:427 +#: assets/templates/assets/asset_list.html:424 #: assets/templates/assets/cmd_filter_detail.html:101 #: assets/templates/assets/system_user_assets.html:101 #: assets/templates/assets/system_user_detail.html:177 @@ -1727,12 +1731,12 @@ msgstr "选择节点" #: users/templates/users/user_detail.html:548 #: users/templates/users/user_group_create_update.html:28 #: users/templates/users/user_group_list.html:120 -#: users/templates/users/user_list.html:276 -#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:54 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:45 +#: users/templates/users/user_list.html:256 +#: 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:34 +#: xpack/plugins/orgs/templates/orgs/org_create_update.html:30 msgid "Confirm" msgstr "确认" @@ -1755,8 +1759,8 @@ msgstr "Jumpserver 使用该用户来 `推送系统用户`、`获取资产硬件 #: audits/templates/audits/login_log_list.html:91 #: users/templates/users/user_group_list.html:10 #: users/templates/users/user_list.html:10 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:49 -#: xpack/plugins/vault/templates/vault/vault.html:47 +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:54 +#: xpack/plugins/vault/templates/vault/vault.html:53 msgid "Export" msgstr "导出" @@ -1766,8 +1770,8 @@ msgstr "导出" #: settings/templates/settings/_ldap_list_users_modal.html:172 #: users/templates/users/user_group_list.html:15 #: users/templates/users/user_list.html:15 -#: xpack/plugins/license/templates/license/license_detail.html:101 -#: xpack/plugins/vault/templates/vault/vault.html:52 +#: xpack/plugins/license/templates/license/license_detail.html:110 +#: xpack/plugins/vault/templates/vault/vault.html:58 msgid "Import" msgstr "导入" @@ -1778,15 +1782,15 @@ msgstr "创建管理用户" #: assets/templates/assets/admin_user_list.html:123 #: assets/templates/assets/admin_user_list.html:154 -#: assets/templates/assets/asset_list.html:307 -#: assets/templates/assets/asset_list.html:344 +#: assets/templates/assets/asset_list.html:304 +#: assets/templates/assets/asset_list.html:341 #: assets/templates/assets/system_user_list.html:186 #: assets/templates/assets/system_user_list.html:217 #: users/templates/users/user_group_list.html:164 #: users/templates/users/user_group_list.html:195 -#: users/templates/users/user_list.html:184 -#: users/templates/users/user_list.html:216 -#: xpack/plugins/vault/templates/vault/vault.html:200 +#: users/templates/users/user_list.html:165 +#: users/templates/users/user_list.html:197 +#: xpack/plugins/vault/templates/vault/vault.html:222 msgid "Please select file" msgstr "选择文件" @@ -1804,9 +1808,9 @@ msgstr "资产用户" #: terminal/templates/terminal/session_detail.html:85 #: users/templates/users/user_detail.html:141 #: users/templates/users/user_profile.html:150 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:128 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:132 -#: xpack/plugins/license/templates/license/license_detail.html:93 +#: 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:102 msgid "Quick modify" msgstr "快速修改" @@ -1875,12 +1879,12 @@ msgid "Hardware" msgstr "硬件" #: assets/templates/assets/asset_list.html:109 -#: users/templates/users/user_list.html:51 +#: users/templates/users/user_list.html:50 msgid "Delete selected" msgstr "批量删除" #: assets/templates/assets/asset_list.html:110 -#: users/templates/users/user_list.html:55 +#: users/templates/users/user_list.html:51 msgid "Update selected" msgstr "批量更新" @@ -1889,75 +1893,75 @@ msgid "Remove from this node" msgstr "从节点移除" #: assets/templates/assets/asset_list.html:112 -#: users/templates/users/user_list.html:56 +#: users/templates/users/user_list.html:52 msgid "Deactive selected" msgstr "禁用所选" #: assets/templates/assets/asset_list.html:113 -#: users/templates/users/user_list.html:57 +#: users/templates/users/user_list.html:53 msgid "Active selected" msgstr "激活所选" -#: assets/templates/assets/asset_list.html:194 +#: assets/templates/assets/asset_list.html:193 msgid "Add assets to node" msgstr "添加资产到节点" -#: assets/templates/assets/asset_list.html:195 +#: assets/templates/assets/asset_list.html:194 msgid "Move assets to node" msgstr "移动资产到节点" -#: assets/templates/assets/asset_list.html:197 +#: assets/templates/assets/asset_list.html:196 msgid "Refresh node hardware info" msgstr "更新节点资产硬件信息" -#: assets/templates/assets/asset_list.html:198 +#: assets/templates/assets/asset_list.html:197 msgid "Test node connective" msgstr "测试节点资产可连接性" -#: assets/templates/assets/asset_list.html:200 +#: assets/templates/assets/asset_list.html:199 msgid "Display only current node assets" msgstr "仅显示当前节点资产" -#: assets/templates/assets/asset_list.html:201 +#: assets/templates/assets/asset_list.html:200 msgid "Displays all child node assets" msgstr "显示所有子节点资产" -#: assets/templates/assets/asset_list.html:421 +#: assets/templates/assets/asset_list.html:418 #: assets/templates/assets/system_user_list.html:127 #: users/templates/users/user_detail.html:448 #: users/templates/users/user_detail.html:474 #: users/templates/users/user_detail.html:542 #: users/templates/users/user_group_list.html:114 -#: users/templates/users/user_list.html:270 +#: users/templates/users/user_list.html:250 #: xpack/plugins/interface/templates/interface/interface.html:97 msgid "Are you sure?" msgstr "你确认吗?" -#: assets/templates/assets/asset_list.html:422 +#: assets/templates/assets/asset_list.html:419 msgid "This will delete the selected assets !!!" msgstr "删除选择资产" -#: assets/templates/assets/asset_list.html:425 +#: assets/templates/assets/asset_list.html:422 #: assets/templates/assets/system_user_list.html:131 #: users/templates/users/user_detail.html:452 #: users/templates/users/user_detail.html:478 #: users/templates/users/user_detail.html:546 #: users/templates/users/user_group_list.html:118 -#: users/templates/users/user_list.html:274 +#: users/templates/users/user_list.html:254 #: xpack/plugins/interface/templates/interface/interface.html:101 msgid "Cancel" msgstr "取消" -#: assets/templates/assets/asset_list.html:436 +#: assets/templates/assets/asset_list.html:433 msgid "Asset Deleted." msgstr "已被删除" -#: assets/templates/assets/asset_list.html:437 -#: assets/templates/assets/asset_list.html:445 +#: assets/templates/assets/asset_list.html:434 +#: assets/templates/assets/asset_list.html:442 msgid "Asset Delete" msgstr "删除" -#: assets/templates/assets/asset_list.html:444 +#: assets/templates/assets/asset_list.html:441 msgid "Asset Deleting failed." msgstr "删除失败" @@ -2034,14 +2038,14 @@ msgid "Gateway list" msgstr "网关列表" #: assets/templates/assets/domain_gateway_list.html:51 -#: assets/views/domain.py:136 +#: 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:64 -#: settings/templates/settings/ldap_setting.html:65 +#: settings/templates/settings/email_setting.html:45 +#: settings/templates/settings/ldap_setting.html:46 msgid "Test connection" msgstr "测试连接" @@ -2062,7 +2066,7 @@ msgstr "" msgid "JMS => Domain gateway => Target assets" msgstr "JMS => 网域网关 => 目标资产" -#: assets/templates/assets/domain_list.html:13 assets/views/domain.py:48 +#: assets/templates/assets/domain_list.html:13 assets/views/domain.py:49 msgid "Create domain" msgstr "创建网域" @@ -2070,6 +2074,10 @@ msgstr "创建网域" msgid "Create label" msgstr "创建标签" +#: assets/templates/assets/platform_list.html:8 assets/views/platform.py:33 +msgid "Create platform" +msgstr "创建系统平台" + #: assets/templates/assets/system_user_assets.html:35 msgid "Assets of " msgstr "资产" @@ -2194,23 +2202,23 @@ msgstr "创建命令过滤器规则" msgid "Update command filter rule" msgstr "更新命令过滤器规则" -#: assets/views/domain.py:31 templates/_nav.html:43 +#: assets/views/domain.py:32 templates/_nav.html:43 msgid "Domain list" msgstr "网域列表" -#: assets/views/domain.py:66 +#: assets/views/domain.py:67 msgid "Update domain" msgstr "更新网域" -#: assets/views/domain.py:81 +#: assets/views/domain.py:82 msgid "Domain detail" msgstr "网域详情" -#: assets/views/domain.py:107 +#: assets/views/domain.py:108 msgid "Domain gateway list" msgstr "域网关列表" -#: assets/views/domain.py:157 +#: assets/views/domain.py:158 msgid "Update gateway" msgstr "创建网关" @@ -2226,6 +2234,14 @@ msgstr "提示: 请避免使用内部预留标签名: {}" msgid "Update label" msgstr "更新标签" +#: assets/views/platform.py:18 +msgid "Platform list" +msgstr "平台列表" + +#: assets/views/platform.py:48 +msgid "Update platform" +msgstr "更新系统平台" + #: assets/views/system_user.py:30 msgid "System user list" msgstr "系统用户列表" @@ -2273,7 +2289,7 @@ msgstr "成功" #: audits/models.py:33 #: authentication/templates/authentication/_access_key_modal.html:22 -#: xpack/plugins/vault/templates/vault/vault.html:38 +#: xpack/plugins/vault/templates/vault/vault.html:44 msgid "Create" msgstr "创建" @@ -2326,7 +2342,7 @@ msgstr "Agent" #: audits/models.py:86 audits/templates/audits/login_log_list.html:62 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms.py:194 users/models/user.py:465 +#: users/forms.py:194 users/models/user.py:455 #: users/templates/users/first_login.html:45 msgid "MFA" msgstr "MFA" @@ -2335,7 +2351,7 @@ msgstr "MFA" #: xpack/plugins/change_auth_plan/models.py:423 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15 #: xpack/plugins/cloud/models.py:278 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:64 msgid "Reason" msgstr "原因" @@ -2344,8 +2360,8 @@ msgstr "原因" #: tickets/templates/tickets/ticket_list.html:36 #: tickets/templates/tickets/ticket_list.html:104 #: xpack/plugins/cloud/models.py:275 xpack/plugins/cloud/models.py:310 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:65 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62 msgid "Status" msgstr "状态" @@ -2365,7 +2381,7 @@ msgstr "登录日期" #: xpack/plugins/change_auth_plan/models.py:426 #: 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:140 +#: xpack/plugins/gathered_user/models.py:143 msgid "Date start" msgstr "开始日期" @@ -2382,11 +2398,22 @@ msgstr "选择用户" #: 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 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:52 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:48 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:47 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:45 msgid "Search" msgstr "搜索" +#: 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:59 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:57 +msgid "ID" +msgstr "ID" + #: audits/templates/audits/login_log_list.html:59 msgid "UA" msgstr "Agent" @@ -2581,14 +2608,14 @@ msgid "Show" msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: users/models/user.py:365 users/templates/users/user_profile.html:94 +#: users/models/user.py:355 users/templates/users/user_profile.html:94 #: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:166 msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:366 users/templates/users/user_profile.html:92 +#: users/models/user.py:356 users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:170 msgid "Enable" msgstr "启用" @@ -2804,8 +2831,10 @@ msgid "discard time" msgstr "" #: common/utils/ipip/utils.py:15 +#, fuzzy +#| msgid "Invalid file." msgid "Invalid ip" -msgstr "无效 IP" +msgstr "文件不合法" #: common/validators.py:11 msgid "Special char not allowed" @@ -2908,8 +2937,8 @@ msgid "Become" msgstr "Become" #: ops/models/adhoc.py:191 users/templates/users/user_group_detail.html:54 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:62 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:56 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:59 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:51 msgid "Create by" msgstr "创建者" @@ -2935,7 +2964,7 @@ msgstr "完成时间" #: xpack/plugins/change_auth_plan/models.py:429 #: 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:143 +#: xpack/plugins/gathered_user/models.py:146 msgid "Time" msgstr "时间" @@ -2978,8 +3007,10 @@ msgid "Task end" msgstr "任务结束" #: ops/tasks.py:63 +#, fuzzy +#| msgid "Sync task history" msgid "Clean task history period" -msgstr "定期清除任务历史" +msgstr "同步历史列表" #: ops/tasks.py:76 msgid "Clean celery log period" @@ -3168,9 +3199,9 @@ msgid "Versions" msgstr "版本" #: ops/templates/ops/task_list.html:68 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:137 +#: 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:141 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:138 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:55 #: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:44 msgid "Run" @@ -3235,7 +3266,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件" #: perms/templates/perms/asset_permission_list.html:206 #: perms/templates/perms/remote_app_permission_list.html:16 #: templates/_nav.html:21 users/forms.py:313 users/models/group.py:26 -#: users/models/user.py:449 users/templates/users/_select_user_modal.html:16 +#: users/models/user.py:439 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_asset_permission.html:56 #: users/templates/users/user_asset_permission.html:87 #: users/templates/users/user_detail.html:222 @@ -3283,7 +3314,7 @@ msgstr "资产授权" #: perms/models/base.py:53 #: perms/templates/perms/asset_permission_detail.html:85 #: perms/templates/perms/remote_app_permission_detail.html:77 -#: users/models/user.py:481 users/templates/users/user_detail.html:108 +#: users/models/user.py:471 users/templates/users/user_detail.html:108 #: users/templates/users/user_profile.html:120 msgid "Date expired" msgstr "失效日期" @@ -3304,8 +3335,8 @@ 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:20 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.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 "资产或节点" @@ -3323,9 +3354,9 @@ 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:80 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:93 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:130 +#: 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 "添加" @@ -3335,7 +3366,7 @@ msgstr "添加节点" #: perms/templates/perms/asset_permission_asset.html:105 #: users/templates/users/user_detail.html:239 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:105 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:101 msgid "Join" msgstr "加入" @@ -3351,7 +3382,7 @@ msgstr "有效期" #: perms/templates/perms/asset_permission_detail.html:61 #: perms/templates/perms/remote_app_permission_detail.html:61 -#: xpack/plugins/license/templates/license/license_detail.html:67 +#: xpack/plugins/license/templates/license/license_detail.html:76 msgid "User count" msgstr "用户数量" @@ -3361,7 +3392,7 @@ msgid "User group count" msgstr "用户组数量" #: perms/templates/perms/asset_permission_detail.html:69 -#: xpack/plugins/license/templates/license/license_detail.html:63 +#: xpack/plugins/license/templates/license/license_detail.html:72 msgid "Asset count" msgstr "资产数量" @@ -3388,7 +3419,7 @@ msgstr "刷新授权缓存" #: users/templates/users/user_asset_permission.html:60 #: users/templates/users/user_asset_permission.html:175 #: users/templates/users/user_list.html:40 xpack/plugins/cloud/models.py:74 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:58 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:55 #: xpack/plugins/cloud/templates/cloud/account_list.html:14 msgid "Validity" msgstr "有效" @@ -3858,7 +3889,7 @@ msgid "Refresh cache" msgstr "刷新缓存" #: settings/templates/settings/_ldap_list_users_modal.html:33 -#: users/models/user.py:445 users/templates/users/user_detail.html:72 +#: users/models/user.py:435 users/templates/users/user_detail.html:72 #: users/templates/users/user_profile.html:59 msgid "Email" msgstr "邮件" @@ -3872,71 +3903,41 @@ msgid "" "User is not currently selected, please check the user you want to import" msgstr "当前无勾选用户,请勾选你想要导入的用户" -#: settings/templates/settings/basic_setting.html:15 -#: settings/templates/settings/email_content_setting.html:15 -#: settings/templates/settings/email_setting.html:15 -#: settings/templates/settings/ldap_setting.html:15 -#: settings/templates/settings/security_setting.html:15 -#: settings/templates/settings/terminal_setting.html:21 -#: settings/templates/settings/terminal_setting.html:54 settings/views.py:20 +#: 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/basic_setting.html:18 -#: settings/templates/settings/email_content_setting.html:18 -#: settings/templates/settings/email_setting.html:18 -#: settings/templates/settings/ldap_setting.html:18 -#: settings/templates/settings/security_setting.html:18 -#: settings/templates/settings/terminal_setting.html:25 settings/views.py:47 +#: settings/templates/settings/_setting_tabs.html:7 settings/views.py:47 msgid "Email setting" msgstr "邮件设置" -#: settings/templates/settings/basic_setting.html:21 -#: settings/templates/settings/email_content_setting.html:21 -#: settings/templates/settings/email_setting.html:21 -#: settings/templates/settings/ldap_setting.html:21 -#: settings/templates/settings/security_setting.html:21 -#: settings/templates/settings/terminal_setting.html:28 settings/views.py:162 +#: settings/templates/settings/_setting_tabs.html:10 settings/views.py:162 msgid "Email content setting" msgstr "邮件内容设置" -#: settings/templates/settings/basic_setting.html:24 -#: settings/templates/settings/email_content_setting.html:24 -#: settings/templates/settings/email_setting.html:24 -#: settings/templates/settings/ldap_setting.html:24 -#: settings/templates/settings/security_setting.html:24 -#: settings/templates/settings/terminal_setting.html:32 settings/views.py:74 +#: settings/templates/settings/_setting_tabs.html:13 settings/views.py:74 msgid "LDAP setting" msgstr "LDAP设置" -#: settings/templates/settings/basic_setting.html:27 -#: settings/templates/settings/email_content_setting.html:27 -#: settings/templates/settings/email_setting.html:27 -#: settings/templates/settings/ldap_setting.html:27 -#: settings/templates/settings/security_setting.html:27 -#: settings/templates/settings/terminal_setting.html:36 settings/views.py:106 +#: settings/templates/settings/_setting_tabs.html:16 settings/views.py:106 msgid "Terminal setting" msgstr "终端设置" -#: settings/templates/settings/basic_setting.html:30 -#: settings/templates/settings/email_content_setting.html:30 -#: settings/templates/settings/email_setting.html:30 -#: settings/templates/settings/ldap_setting.html:30 -#: settings/templates/settings/security_setting.html:30 -#: settings/templates/settings/security_setting.html:45 -#: settings/templates/settings/terminal_setting.html:39 settings/views.py:135 +#: 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:45 +#: settings/templates/settings/email_content_setting.html:26 msgid "Create User setting" msgstr "创建用户设置" -#: settings/templates/settings/ldap_setting.html:66 +#: settings/templates/settings/ldap_setting.html:47 msgid "Bulk import" msgstr "一键导入" -#: settings/templates/settings/security_setting.html:49 +#: settings/templates/settings/security_setting.html:30 msgid "Password check rule" msgstr "密码校验规则" @@ -4428,8 +4429,8 @@ msgid "" msgstr "" #: terminal/forms/storage.py:143 xpack/plugins/cloud/models.py:304 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:109 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:62 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:106 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:59 msgid "Region" msgstr "地域" @@ -4710,10 +4711,6 @@ msgstr "标题" msgid "Body" msgstr "内容" -#: tickets/models/ticket.py:38 -msgid "Meta" -msgstr "" - #: tickets/models/ticket.py:39 tickets/templates/tickets/ticket_detail.html:51 msgid "Assignee" msgstr "处理人" @@ -4827,11 +4824,11 @@ msgstr "工单列表" msgid "Ticket detail" msgstr "工单详情" -#: users/api/user.py:180 +#: users/api/user.py:174 msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" -#: users/forms.py:47 users/models/user.py:453 +#: users/forms.py:47 users/models/user.py:443 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:88 #: users/templates/users/user_list.html:37 @@ -4839,7 +4836,7 @@ msgstr "不能再该页面重置MFA, 请去个人信息页面重置" msgid "Role" msgstr "角色" -#: users/forms.py:51 users/models/user.py:488 +#: users/forms.py:51 users/models/user.py:478 #: users/templates/users/user_detail.html:104 #: users/templates/users/user_list.html:39 #: users/templates/users/user_profile.html:102 @@ -4884,8 +4881,8 @@ msgid "Set password" msgstr "设置密码" #: users/forms.py:152 xpack/plugins/change_auth_plan/models.py:89 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:49 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:47 +#: 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" @@ -4957,7 +4954,7 @@ msgstr "选择用户" msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" -#: users/models/user.py:147 users/models/user.py:581 +#: users/models/user.py:147 users/models/user.py:571 msgid "Administrator" msgstr "管理员" @@ -4978,27 +4975,27 @@ msgstr "组织管理员" msgid "Org auditor" msgstr "组织审计员" -#: users/models/user.py:367 users/templates/users/user_profile.html:90 +#: users/models/user.py:357 users/templates/users/user_profile.html:90 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:433 +#: users/models/user.py:423 msgid "Local" msgstr "数据库" -#: users/models/user.py:456 +#: users/models/user.py:446 msgid "Avatar" msgstr "头像" -#: users/models/user.py:459 users/templates/users/user_detail.html:83 +#: users/models/user.py:449 users/templates/users/user_detail.html:83 msgid "Wechat" msgstr "微信" -#: users/models/user.py:492 +#: users/models/user.py:482 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:584 +#: users/models/user.py:574 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -5057,7 +5054,7 @@ msgstr "安全令牌验证" #: users/templates/users/_base_otp.html:44 users/templates/users/_user.html:13 #: users/templates/users/user_profile_update.html:55 #: xpack/plugins/cloud/models.py:147 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:60 +#: 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 "账户" @@ -5347,7 +5344,7 @@ msgid "User group detail" msgstr "用户组详情" #: users/templates/users/user_group_detail.html:81 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:121 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:116 msgid "Add user" msgstr "添加用户" @@ -5372,52 +5369,28 @@ msgstr "用户组删除" msgid "UserGroup Deleting failed." msgstr "用户组删除失败" -#: users/templates/users/user_list.html:53 -msgid "Remove selected" -msgstr "批量移除" - -#: users/templates/users/user_list.html:129 -#: users/templates/users/user_list.html:133 -msgid "Remove" -msgstr "移除" - -#: users/templates/users/user_list.html:271 +#: users/templates/users/user_list.html:251 msgid "This will delete the selected users !!!" msgstr "删除选中用户 !!!" -#: users/templates/users/user_list.html:282 -msgid "User Deleting failed." -msgstr "用户删除失败" +#: users/templates/users/user_list.html:262 +msgid "User Deleted." +msgstr "已被删除" -#: users/templates/users/user_list.html:283 +#: users/templates/users/user_list.html:263 +#: users/templates/users/user_list.html:267 msgid "User Delete" msgstr "删除" -#: users/templates/users/user_list.html:305 -msgid "This will remove the selected users !!" -msgstr "移除选中用户 !!!" +#: users/templates/users/user_list.html:266 +msgid "User Deleting failed." +msgstr "用户删除失败" -#: users/templates/users/user_list.html:307 -msgid "User Removing failed." -msgstr "用户移除失败" - -#: users/templates/users/user_list.html:308 -msgid "User Remove" -msgstr "用户移除" - -#: users/templates/users/user_list.html:357 -msgid "Are you sure about removing it?" -msgstr "您确定移除吗?" - -#: users/templates/users/user_list.html:358 -msgid "Remove the success" -msgstr "移除成功" - -#: users/templates/users/user_list.html:363 +#: users/templates/users/user_list.html:327 msgid "User is expired" msgstr "用户已失效" -#: users/templates/users/user_list.html:366 +#: users/templates/users/user_list.html:330 msgid "User is inactive" msgstr "用户已禁用" @@ -5804,16 +5777,16 @@ msgid "Password length" msgstr "密码长度" #: xpack/plugins/change_auth_plan/forms.py:75 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:58 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:81 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:56 +#: 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/forms.py:33 xpack/plugins/cloud/forms.py:87 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:41 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:72 +#: 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/forms.py:13 #: xpack/plugins/gathered_user/forms.py:41 -#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:32 +#: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:28 msgid "Periodic perform" msgstr "定时执行" @@ -5869,9 +5842,9 @@ msgstr "所有资产使用不同的随机密码" #: xpack/plugins/change_auth_plan/models.py:79 #: xpack/plugins/change_auth_plan/models.py:148 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:100 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:98 #: xpack/plugins/cloud/models.py:165 xpack/plugins/cloud/models.py:219 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:91 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:88 #: xpack/plugins/gathered_user/models.py:35 #: xpack/plugins/gathered_user/models.py:72 msgid "Cycle perform" @@ -5879,16 +5852,16 @@ msgstr "周期执行" #: xpack/plugins/change_auth_plan/models.py:84 #: xpack/plugins/change_auth_plan/models.py:146 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:92 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:90 #: xpack/plugins/cloud/models.py:170 xpack/plugins/cloud/models.py:217 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:80 #: xpack/plugins/gathered_user/models.py:40 #: xpack/plugins/gathered_user/models.py:70 msgid "Regularly perform" msgstr "定期执行" #: xpack/plugins/change_auth_plan/models.py:93 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:72 msgid "Password rules" msgstr "密码规则" @@ -5942,46 +5915,46 @@ msgstr "* 密码长度范围 6-30 位" msgid "* Please enter a valid crontab expression" msgstr "* 请输入有效的 crontab 表达式" -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:23 -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:26 +#: 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:66 +#: 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:91 +#: 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:11 +#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:8 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:76 +#: 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:84 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:75 +#: 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 "是" -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:86 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:77 +#: 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 "否" -#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:134 +#: 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:178 +#: 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" @@ -5992,7 +5965,7 @@ 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:89 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:84 msgid "Log" msgstr "日志" @@ -6071,7 +6044,7 @@ msgid "Unavailable" msgstr "无效" #: xpack/plugins/cloud/models.py:63 -#: xpack/plugins/cloud/templates/cloud/account_detail.html:54 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:51 #: xpack/plugins/cloud/templates/cloud/account_list.html:13 msgid "Provider" msgstr "云服务商" @@ -6085,7 +6058,7 @@ msgid "Access key secret" msgstr "" #: xpack/plugins/cloud/models.py:88 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:30 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:26 msgid "Cloud account" msgstr "云账号" @@ -6098,7 +6071,7 @@ msgid "Instances" msgstr "实例" #: xpack/plugins/cloud/models.py:176 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:97 +#: 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 "最后同步日期" @@ -6116,8 +6089,8 @@ msgid "Partial succeed" msgstr "" #: xpack/plugins/cloud/models.py:281 xpack/plugins/cloud/models.py:313 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:71 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:63 msgid "Date sync" msgstr "同步日期" @@ -6134,8 +6107,8 @@ msgid "Sync instance task history" msgstr "同步实例任务历史" #: xpack/plugins/cloud/models.py:301 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:117 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:61 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:114 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:58 msgid "Instance" msgstr "实例" @@ -6155,7 +6128,7 @@ msgstr "AWS (国际)" msgid "Qcloud" msgstr "腾讯云" -#: xpack/plugins/cloud/templates/cloud/account_detail.html:20 +#: xpack/plugins/cloud/templates/cloud/account_detail.html:17 #: xpack/plugins/cloud/views.py:79 msgid "Account detail" msgstr "账户详情" @@ -6165,61 +6138,61 @@ msgstr "账户详情" msgid "Create account" msgstr "创建账户" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:33 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:29 msgid "Region & Instance" msgstr "地域 & 实例" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:37 +#: 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:67 +#: 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:20 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:25 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:21 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:17 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:20 +#: 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:23 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:28 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:24 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:20 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:23 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:21 #: xpack/plugins/cloud/views.py:160 msgid "Sync task history" msgstr "同步历史列表" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:26 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:31 -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:27 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:23 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:26 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:24 #: xpack/plugins/cloud/views.py:212 msgid "Sync instance list" msgstr "同步实例列表" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:138 +#: 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:181 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:178 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_list.html:99 msgid "Sync success" msgstr "同步成功" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:65 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:60 msgid "Total count" msgstr "总数" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:66 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:61 msgid "Succeed count" msgstr "成功" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:67 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:62 msgid "Failed count" msgstr "失败" -#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:68 +#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:63 msgid "Exist count" msgstr "存在" @@ -6267,19 +6240,19 @@ msgid "Periodic" msgstr "定时执行" #: xpack/plugins/gathered_user/models.py:57 -#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:38 +#: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:43 msgid "Gather user task" msgstr "收集用户任务" -#: xpack/plugins/gathered_user/models.py:137 +#: xpack/plugins/gathered_user/models.py:140 msgid "Task" msgstr "任务" -#: xpack/plugins/gathered_user/models.py:149 +#: xpack/plugins/gathered_user/models.py:152 msgid "gather user task execution" msgstr "收集用户执行" -#: xpack/plugins/gathered_user/models.py:155 +#: xpack/plugins/gathered_user/models.py:158 msgid "Assets is empty, please change nodes" msgstr "资产为空,请更改节点" @@ -6377,8 +6350,8 @@ 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/templates/license/license_detail.html:50 +#: xpack/plugins/license/templates/license/license_detail.html:55 #: xpack/plugins/license/views.py:32 msgid "License" msgstr "许可证" @@ -6392,7 +6365,7 @@ msgid "Enterprise edition" msgstr "企业版" #: xpack/plugins/license/templates/license/_license_import_modal.html:4 -#: xpack/plugins/license/templates/license/license_detail.html:99 +#: xpack/plugins/license/templates/license/license_detail.html:108 msgid "Import license" msgstr "导入许可证" @@ -6400,64 +6373,64 @@ msgstr "导入许可证" msgid "License file" msgstr "许可证文件" -#: xpack/plugins/license/templates/license/license_detail.html:11 +#: xpack/plugins/license/templates/license/license_detail.html:12 msgid "Please Import License" msgstr "请导入许可证" -#: xpack/plugins/license/templates/license/license_detail.html:13 -#: xpack/plugins/license/templates/license/license_detail.html:47 +#: xpack/plugins/license/templates/license/license_detail.html:17 +#: xpack/plugins/license/templates/license/license_detail.html:56 msgid "License has expired" msgstr "许可证已经过期" -#: xpack/plugins/license/templates/license/license_detail.html:15 +#: xpack/plugins/license/templates/license/license_detail.html:22 msgid "The license will at " msgstr "许可证将在 " -#: xpack/plugins/license/templates/license/license_detail.html:15 +#: xpack/plugins/license/templates/license/license_detail.html:22 msgid " expired." msgstr " 过期。" -#: xpack/plugins/license/templates/license/license_detail.html:28 +#: xpack/plugins/license/templates/license/license_detail.html:37 #: xpack/plugins/license/views.py:33 msgid "License detail" msgstr "许可证详情" -#: xpack/plugins/license/templates/license/license_detail.html:42 +#: xpack/plugins/license/templates/license/license_detail.html:51 msgid "No license" msgstr "暂无许可证" -#: xpack/plugins/license/templates/license/license_detail.html:51 +#: xpack/plugins/license/templates/license/license_detail.html:60 msgid "Subscription ID" msgstr "订阅授权ID" -#: xpack/plugins/license/templates/license/license_detail.html:55 +#: xpack/plugins/license/templates/license/license_detail.html:64 msgid "Corporation" msgstr "公司" -#: xpack/plugins/license/templates/license/license_detail.html:59 +#: xpack/plugins/license/templates/license/license_detail.html:68 msgid "Expired" msgstr "过期时间" -#: xpack/plugins/license/templates/license/license_detail.html:64 -#: xpack/plugins/license/templates/license/license_detail.html:68 -#: xpack/plugins/license/templates/license/license_detail.html:72 -#: xpack/plugins/license/templates/license/license_detail.html:76 +#: xpack/plugins/license/templates/license/license_detail.html:73 +#: xpack/plugins/license/templates/license/license_detail.html:77 +#: xpack/plugins/license/templates/license/license_detail.html:81 +#: xpack/plugins/license/templates/license/license_detail.html:85 msgid "Unlimited" msgstr "无限制" -#: xpack/plugins/license/templates/license/license_detail.html:75 +#: xpack/plugins/license/templates/license/license_detail.html:84 msgid "Concurrent connections" msgstr "并发连接" -#: xpack/plugins/license/templates/license/license_detail.html:80 +#: xpack/plugins/license/templates/license/license_detail.html:89 msgid "Edition" msgstr "版本" -#: xpack/plugins/license/templates/license/license_detail.html:106 +#: xpack/plugins/license/templates/license/license_detail.html:115 msgid "Technology consulting" msgstr "技术咨询" -#: xpack/plugins/license/templates/license/license_detail.html:109 +#: xpack/plugins/license/templates/license/license_detail.html:118 msgid "Consult" msgstr "咨询" @@ -6474,7 +6447,7 @@ msgid "Select auditor" msgstr "选择审计员" #: xpack/plugins/orgs/forms.py:29 -#: xpack/plugins/orgs/templates/orgs/org_detail.html:76 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:71 #: xpack/plugins/orgs/templates/orgs/org_list.html:13 msgid "Admin" msgstr "管理员" @@ -6485,12 +6458,12 @@ msgstr "管理员" msgid "Organizations" msgstr "组织管理" -#: xpack/plugins/orgs/templates/orgs/org_detail.html:22 +#: 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:84 +#: xpack/plugins/orgs/templates/orgs/org_detail.html:79 msgid "Add admin" msgstr "添加管理员" @@ -6527,8 +6500,15 @@ msgstr "密码匣子" msgid "vault create" msgstr "创建" -#~ msgid "User Deleted." -#~ msgstr "已被删除" +#, fuzzy +#~| msgid "Platform list" +#~ msgid "Platform create" +#~ msgstr "平台列表" + +#, fuzzy +#~| msgid "Password update" +#~ msgid "Platform update" +#~ msgstr "密码更新" #~ msgid "Search no entry matched in ou {}" #~ msgstr "在ou:{}中没有匹配条目" @@ -6583,6 +6563,9 @@ msgstr "创建" #~ msgid "Delete failed" #~ msgstr "删除失败" +#~ msgid "Are you sure about deleting it?" +#~ msgstr "您确定删除吗?" + #~ msgid "The connection fails" #~ msgstr "连接失败" @@ -6612,6 +6595,9 @@ msgstr "创建" #~ msgid "Approve selected" #~ msgstr "同意所选" +#~ msgid "Reject selected" +#~ msgstr "拒绝所选" + #~ msgid "" #~ "\n" #~ "
\n" diff --git a/apps/ops/templates/ops/command_execution_create.html b/apps/ops/templates/ops/command_execution_create.html index 86c489c09..f89dc98f6 100644 --- a/apps/ops/templates/ops/command_execution_create.html +++ b/apps/ops/templates/ops/command_execution_create.html @@ -58,7 +58,7 @@ {% block content %}
-
+
@@ -71,7 +71,7 @@
-
+
@@ -87,14 +87,14 @@ style="height: 100%;width: 100%">
-
+
-
+
+
+ + +
+

+

+

+

+
+ + +{% endblock %} + + diff --git a/apps/templates/_csv_update_modal.html b/apps/templates/_csv_update_modal.html new file mode 100644 index 000000000..c4c31abda --- /dev/null +++ b/apps/templates/_csv_update_modal.html @@ -0,0 +1,54 @@ +{% extends '_modal.html' %} +{% load i18n %} + +{% block modal_id %}csv_update_modal{% endblock %} +{% block modal_confirm_id %}btn_csv_update_confirm{% endblock %} +{% block modal_title%}csv {% trans 'Update' %}{% endblock %} + +{% block modal_body %} +
+ {% csrf_token %} +
+ + {% trans 'Download the update template' %} +
+ +
+ + +
+
+ +
+

+

+

+

+
+ + +{% endblock %} diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 03f9d83cb..b2c00e09d 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -45,6 +45,9 @@
  • {% trans 'System user' %}
  • {% trans 'Labels' %}
  • {% trans 'Command filters' %}
  • + {% if request.user.is_superuser %} +
  • {% trans 'Platform list' %}
  • + {% endif %} {% endif %} diff --git a/apps/templates/_without_nav_base.html b/apps/templates/_without_nav_base.html new file mode 100644 index 000000000..f20324b5a --- /dev/null +++ b/apps/templates/_without_nav_base.html @@ -0,0 +1,43 @@ +{% load static %} +{% load i18n %} + + + + + + {{ JMS_TITLE }} + +{# #} + + + + + + +
    + + +
    + + {% block body %} + {% endblock %} + +
    +
    + {% include '_copyright.html' %} +
    +
    + + + diff --git a/apps/templates/flash_message_standalone.html b/apps/templates/flash_message_standalone.html index 304a8bd0a..84ef58cb5 100644 --- a/apps/templates/flash_message_standalone.html +++ b/apps/templates/flash_message_standalone.html @@ -1,83 +1,64 @@ -{% load i18n %} +{% extends '_base_only_content.html' %} {% load static %} - - +{% load i18n %} +{% block html_title %} {{ title }} {% endblock %} +{% block title %} {{ title }}{% endblock %} - - - +{% block custom_head_css_js %} + +{% endblock %} - {{ title }} - - {% include '_head_css_js.html' %} - - - - - - - -
    -
    -
    -
    -
    - -

    - {{ JMS_TITLE }} -

    -
    - {% if errors %} -

    -

    - {{ errors }} -
    -

    - {% endif %} - - {% if messages %} -

    -

    - {{ messages|safe }} -
    -

    - {% endif %} - -
    +{% block content %} +
    + {% if errors %} +

    +

    + {{ errors }}
    -
    -
    +

    + {% endif %} + + {% if messages %} +

    +

    + {{ messages|safe }} +
    +

    + {% endif %}
    -
    - {% include '_copyright.html' %} +
    - +{% endblock %} + +{% block custom_foot_js %} - +{% endblock %} + diff --git a/apps/terminal/migrations/0019_auto_20191206_1000.py b/apps/terminal/migrations/0019_auto_20191206_1000.py new file mode 100644 index 000000000..069920085 --- /dev/null +++ b/apps/terminal/migrations/0019_auto_20191206_1000.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-06 02:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0018_auto_20191202_1010'), + ] + + operations = [ + migrations.AlterField( + model_name='replaystorage', + name='type', + field=models.CharField(choices=[('null', 'Null'), ('server', 'Server'), ('s3', 'S3'), ('ceph', 'Ceph'), ('swift', 'Swift'), ('oss', 'OSS'), ('azure', 'Azure')], default='server', max_length=16, verbose_name='Type'), + ), + ] diff --git a/apps/users/api/group.py b/apps/users/api/group.py index eb00ea220..860ca36b4 100644 --- a/apps/users/api/group.py +++ b/apps/users/api/group.py @@ -1,35 +1,19 @@ # -*- coding: utf-8 -*- # -from ..serializers import ( - UserGroupSerializer, - UserGroupListSerializer, - UserGroupUpdateMemberSerializer, -) +from ..serializers import UserGroupSerializer from ..models import UserGroup from orgs.mixins.api import OrgBulkModelViewSet -from orgs.mixins import generics from common.permissions import IsOrgAdmin -__all__ = ['UserGroupViewSet', 'UserGroupUpdateUserApi'] +__all__ = ['UserGroupViewSet'] class UserGroupViewSet(OrgBulkModelViewSet): model = UserGroup filter_fields = ("name",) search_fields = filter_fields + permission_classes = (IsOrgAdmin,) serializer_class = UserGroupSerializer - permission_classes = (IsOrgAdmin,) - def get_serializer_class(self): - if self.action in ("list", 'retrieve') and \ - self.request.query_params.get("display"): - return UserGroupListSerializer - return self.serializer_class - - -class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView): - model = UserGroup - serializer_class = UserGroupUpdateMemberSerializer - permission_classes = (IsOrgAdmin,) diff --git a/apps/users/api/user.py b/apps/users/api/user.py index c23fc4b65..f41ce152d 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -24,7 +24,7 @@ from ..signals import post_user_create logger = get_logger(__name__) __all__ = [ - 'UserViewSet', 'UserChangePasswordApi', 'UserUpdateGroupApi', + 'UserViewSet', 'UserChangePasswordApi', 'UserResetPasswordApi', 'UserResetPKApi', 'UserUpdatePKApi', 'UserUnblockPKApi', 'UserProfileApi', 'UserResetOTPApi', ] @@ -39,8 +39,10 @@ class UserQuerysetMixin: class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet): filter_fields = ('username', 'email', 'name', 'id') search_fields = filter_fields - serializer_class = serializers.UserSerializer - serializer_display_class = serializers.UserDisplaySerializer + serializer_classes = { + 'default': serializers.UserSerializer, + 'display': serializers.UserDisplaySerializer + } permission_classes = (IsOrgAdmin, CanUpdateDeleteUser) def get_queryset(self): @@ -99,11 +101,6 @@ class UserChangePasswordApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView): user.save() -class UserUpdateGroupApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView): - serializer_class = serializers.UserUpdateGroupSerializer - permission_classes = (IsOrgAdmin,) - - class UserResetPasswordApi(UserQuerysetMixin, generics.UpdateAPIView): queryset = User.objects.all() serializer_class = serializers.UserSerializer diff --git a/apps/users/forms/__init__.py b/apps/users/forms/__init__.py new file mode 100644 index 000000000..8b4d5d888 --- /dev/null +++ b/apps/users/forms/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# +from .user import * +from .group import * +from .profile import * diff --git a/apps/users/forms/group.py b/apps/users/forms/group.py new file mode 100644 index 000000000..8d026a777 --- /dev/null +++ b/apps/users/forms/group.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# +from django import forms +from django.utils.translation import gettext_lazy as _ + +from orgs.mixins.forms import OrgModelForm +from ..models import User, UserGroup + +__all__ = ['UserGroupForm'] + + +class UserGroupForm(OrgModelForm): + users = forms.ModelMultipleChoiceField( + queryset=User.objects.none(), + label=_("User"), + widget=forms.SelectMultiple( + attrs={ + 'class': 'users-select2', + 'data-placeholder': _('Select users') + } + ), + required=False, + ) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.set_fields_queryset() + + def set_fields_queryset(self): + users_field = self.fields.get('users') + if self.instance: + users_field.initial = self.instance.users.all() + users_field.queryset = self.instance.users.all() + else: + users_field.queryset = User.objects.none() + + def save(self, commit=True): + raise Exception("Save by restful api") + + class Meta: + model = UserGroup + fields = [ + 'name', 'users', 'comment', + ] diff --git a/apps/users/forms/profile.py b/apps/users/forms/profile.py new file mode 100644 index 000000000..bd1047733 --- /dev/null +++ b/apps/users/forms/profile.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +# +from django import forms +from django.utils.translation import gettext_lazy as _ +from captcha.fields import CaptchaField + +from common.utils import validate_ssh_public_key +from ..models import User + + +__all__ = [ + 'UserProfileForm', 'UserMFAForm', 'UserFirstLoginFinishForm', + 'UserPasswordForm', 'UserPublicKeyForm', 'FileForm', + 'UserTokenResetPasswordForm', 'UserForgotPasswordForm', +] + + +class UserProfileForm(forms.ModelForm): + username = forms.CharField(disabled=True, label=_("Username")) + name = forms.CharField(disabled=True, label=_("Name")) + email = forms.CharField(disabled=True) + + class Meta: + model = User + fields = [ + 'username', 'name', 'email', + 'wechat', 'phone', + ] + + +UserProfileForm.verbose_name = _("Profile") + + +class UserMFAForm(forms.ModelForm): + + mfa_description = _( + 'When enabled, ' + 'you will enter the MFA binding process the next time you log in. ' + 'you can also directly bind in ' + '"personal information -> quick modification -> change MFA Settings"!') + + class Meta: + model = User + fields = ['mfa_level'] + widgets = {'mfa_level': forms.RadioSelect()} + help_texts = { + 'mfa_level': _('* Enable MFA authentication ' + 'to make the account more secure.'), + } + + +UserMFAForm.verbose_name = _("MFA") + + +class UserFirstLoginFinishForm(forms.Form): + finish_description = _( + 'In order to protect you and your company, ' + 'please keep your account, ' + 'password and key sensitive information properly. ' + '(for example: setting complex password, enabling MFA authentication)' + ) + + +UserFirstLoginFinishForm.verbose_name = _("Finish") + + +class UserTokenResetPasswordForm(forms.Form): + new_password = forms.CharField( + min_length=5, max_length=128, + widget=forms.PasswordInput, + label=_("New password") + ) + confirm_password = forms.CharField( + min_length=5, max_length=128, + widget=forms.PasswordInput, + label=_("Confirm password") + ) + + def clean_confirm_password(self): + new_password = self.cleaned_data['new_password'] + confirm_password = self.cleaned_data['confirm_password'] + + if new_password != confirm_password: + raise forms.ValidationError(_('Password does not match')) + return confirm_password + + +class UserForgotPasswordForm(forms.Form): + email = forms.EmailField(label=_("Email")) + captcha = CaptchaField(label=_("Captcha")) + + +class UserPasswordForm(UserTokenResetPasswordForm): + old_password = forms.CharField( + max_length=128, widget=forms.PasswordInput, + label=_("Old password") + ) + + def __init__(self, *args, **kwargs): + self.instance = kwargs.pop('instance') + super().__init__(*args, **kwargs) + + def clean_old_password(self): + old_password = self.cleaned_data['old_password'] + if not self.instance.check_password(old_password): + raise forms.ValidationError(_('Old password error')) + return old_password + + def save(self): + password = self.cleaned_data['new_password'] + self.instance.reset_password(new_password=password) + return self.instance + + +class UserPublicKeyForm(forms.Form): + pubkey_description = _('Automatically configure and download the SSH key') + public_key = forms.CharField( + label=_('ssh public key'), max_length=5000, required=False, + widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}), + help_text=_('Paste your id_rsa.pub here.') + ) + + def __init__(self, *args, **kwargs): + if 'instance' in kwargs: + self.instance = kwargs.pop('instance') + else: + self.instance = None + super().__init__(*args, **kwargs) + + def clean_public_key(self): + public_key = self.cleaned_data['public_key'] + if self.instance.public_key and public_key == self.instance.public_key: + msg = _('Public key should not be the same as your old one.') + raise forms.ValidationError(msg) + + if public_key and not validate_ssh_public_key(public_key): + raise forms.ValidationError(_('Not a valid ssh public key')) + return public_key + + def save(self): + public_key = self.cleaned_data['public_key'] + if public_key: + self.instance.public_key = public_key + self.instance.save() + return self.instance + + +UserPublicKeyForm.verbose_name = _("Public key") + + +class FileForm(forms.Form): + file = forms.FileField() diff --git a/apps/users/forms.py b/apps/users/forms/user.py similarity index 52% rename from apps/users/forms.py rename to apps/users/forms/user.py index 6b360fb71..a58e1fef1 100644 --- a/apps/users/forms.py +++ b/apps/users/forms/user.py @@ -1,39 +1,19 @@ -# ~*~ coding: utf-8 ~*~ from django import forms from django.utils.translation import gettext_lazy as _ -from django.conf import settings from common.utils import validate_ssh_public_key from orgs.mixins.forms import OrgModelForm -from .models import User, UserGroup -from .utils import check_password_rules, get_current_org_members +from ..models import User +from ..utils import ( + check_password_rules, get_current_org_members, get_source_choices +) -class UserCheckPasswordForm(forms.Form): - username = forms.CharField(label=_('Username'), max_length=100) - password = forms.CharField( - label=_('Password'), widget=forms.PasswordInput, - max_length=128, strip=False - ) - - -class UserCheckOtpCodeForm(forms.Form): - otp_code = forms.CharField(label=_('MFA code'), max_length=6) - - -def get_source_choices(): - choices_all = dict(User.SOURCE_CHOICES) - choices = [ - (User.SOURCE_LOCAL, choices_all[User.SOURCE_LOCAL]), - ] - if settings.AUTH_LDAP: - choices.append((User.SOURCE_LDAP, choices_all[User.SOURCE_LDAP])) - if settings.AUTH_OPENID: - choices.append((User.SOURCE_OPENID, choices_all[User.SOURCE_OPENID])) - if settings.AUTH_RADIUS: - choices.append((User.SOURCE_RADIUS, choices_all[User.SOURCE_RADIUS])) - return choices +__all__ = [ + 'UserCreateForm', 'UserUpdateForm', 'UserBulkUpdateForm', + 'UserCheckOtpCodeForm', 'UserCheckPasswordForm' +] class UserCreateUpdateFormMixin(OrgModelForm): @@ -157,131 +137,6 @@ class UserUpdateForm(UserCreateUpdateFormMixin): pass -class UserProfileForm(forms.ModelForm): - username = forms.CharField(disabled=True, label=_("Username")) - name = forms.CharField(disabled=True, label=_("Name")) - email = forms.CharField(disabled=True) - - class Meta: - model = User - fields = [ - 'username', 'name', 'email', - 'wechat', 'phone', - ] - - -UserProfileForm.verbose_name = _("Profile") - - -class UserMFAForm(forms.ModelForm): - - mfa_description = _( - 'When enabled, ' - 'you will enter the MFA binding process the next time you log in. ' - 'you can also directly bind in ' - '"personal information -> quick modification -> change MFA Settings"!') - - class Meta: - model = User - fields = ['mfa_level'] - widgets = {'mfa_level': forms.RadioSelect()} - help_texts = { - 'mfa_level': _('* Enable MFA authentication ' - 'to make the account more secure.'), - } - - -UserMFAForm.verbose_name = _("MFA") - - -class UserFirstLoginFinishForm(forms.Form): - finish_description = _( - 'In order to protect you and your company, ' - 'please keep your account, ' - 'password and key sensitive information properly. ' - '(for example: setting complex password, enabling MFA authentication)' - ) - - -UserFirstLoginFinishForm.verbose_name = _("Finish") - - -class UserPasswordForm(forms.Form): - old_password = forms.CharField( - max_length=128, widget=forms.PasswordInput, - label=_("Old password") - ) - new_password = forms.CharField( - min_length=5, max_length=128, - widget=forms.PasswordInput, - label=_("New password") - ) - confirm_password = forms.CharField( - min_length=5, max_length=128, - widget=forms.PasswordInput, - label=_("Confirm password") - ) - - def __init__(self, *args, **kwargs): - self.instance = kwargs.pop('instance') - super().__init__(*args, **kwargs) - - def clean_old_password(self): - old_password = self.cleaned_data['old_password'] - if not self.instance.check_password(old_password): - raise forms.ValidationError(_('Old password error')) - return old_password - - def clean_confirm_password(self): - new_password = self.cleaned_data['new_password'] - confirm_password = self.cleaned_data['confirm_password'] - - if new_password != confirm_password: - raise forms.ValidationError(_('Password does not match')) - return confirm_password - - def save(self): - password = self.cleaned_data['new_password'] - self.instance.reset_password(new_password=password) - return self.instance - - -class UserPublicKeyForm(forms.Form): - pubkey_description = _('Automatically configure and download the SSH key') - public_key = forms.CharField( - label=_('ssh public key'), max_length=5000, required=False, - widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}), - help_text=_('Paste your id_rsa.pub here.') - ) - - def __init__(self, *args, **kwargs): - if 'instance' in kwargs: - self.instance = kwargs.pop('instance') - else: - self.instance = None - super().__init__(*args, **kwargs) - - def clean_public_key(self): - public_key = self.cleaned_data['public_key'] - if self.instance.public_key and public_key == self.instance.public_key: - msg = _('Public key should not be the same as your old one.') - raise forms.ValidationError(msg) - - if public_key and not validate_ssh_public_key(public_key): - raise forms.ValidationError(_('Not a valid ssh public key')) - return public_key - - def save(self): - public_key = self.cleaned_data['public_key'] - if public_key: - self.instance.public_key = public_key - self.instance.save() - return self.instance - - -UserPublicKeyForm.verbose_name = _("Public key") - - class UserBulkUpdateForm(OrgModelForm): users = forms.ModelMultipleChoiceField( required=True, @@ -333,40 +188,12 @@ class UserBulkUpdateForm(OrgModelForm): return users -class UserGroupForm(OrgModelForm): - users = forms.ModelMultipleChoiceField( - queryset=User.objects.none(), - label=_("User"), - widget=forms.SelectMultiple( - attrs={ - 'class': 'users-select2', - 'data-placeholder': _('Select users') - } - ), - required=False, +class UserCheckPasswordForm(forms.Form): + password = forms.CharField( + label=_('Password'), widget=forms.PasswordInput, + max_length=128, strip=False ) - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.set_fields_queryset() - def set_fields_queryset(self): - users_field = self.fields.get('users') - if self.instance: - users_field.initial = self.instance.users.all() - users_field.queryset = self.instance.users.all() - else: - users_field.queryset = User.objects.none() - - def save(self, commit=True): - raise Exception("Save by restful api") - - class Meta: - model = UserGroup - fields = [ - 'name', 'users', 'comment', - ] - - -class FileForm(forms.Form): - file = forms.FileField() +class UserCheckOtpCodeForm(forms.Form): + otp_code = forms.CharField(label=_('MFA code'), max_length=6) diff --git a/apps/users/models/group.py b/apps/users/models/group.py index e211759bc..0ae636f76 100644 --- a/apps/users/models/group.py +++ b/apps/users/models/group.py @@ -4,6 +4,7 @@ import uuid from django.db import models, IntegrityError from django.utils.translation import ugettext_lazy as _ +from common.utils import lazyproperty from orgs.mixins.models import OrgModelMixin __all__ = ['UserGroup'] @@ -20,6 +21,10 @@ class UserGroup(OrgModelMixin): def __str__(self): return self.name + @lazyproperty + def users_amount(self): + return self.users.count() + class Meta: ordering = ['name'] unique_together = [('org_id', 'name'),] diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 51d5e2502..af2de523b 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -19,6 +19,7 @@ from django.shortcuts import reverse from orgs.utils import current_org from common.utils import get_signer, date_expired_default, get_logger, lazyproperty from common import fields +from ..signals import post_user_change_password __all__ = ['User'] @@ -43,14 +44,10 @@ class AuthMixin: self.set_password(password_raw_) def set_password(self, raw_password): - self._set_password = True if self.can_update_password(): self.date_password_last_updated = timezone.now() + post_user_change_password.send(self.__class__, user=self) super().set_password(raw_password) - else: - error = _("User auth from {}, go there change password").format( - self.source) - raise PermissionError(error) def can_update_password(self): return self.is_local @@ -196,22 +193,22 @@ class RoleMixin: def is_app(self): return self.role == 'App' - @property + @lazyproperty def user_orgs(self): from orgs.models import Organization return Organization.get_user_user_orgs(self) - @property + @lazyproperty def admin_orgs(self): from orgs.models import Organization return Organization.get_user_admin_orgs(self) - @property + @lazyproperty def audit_orgs(self): from orgs.models import Organization return Organization.get_user_audit_orgs(self) - @property + @lazyproperty def admin_or_audit_orgs(self): from orgs.models import Organization return Organization.get_user_admin_or_audit_orgs(self) @@ -223,26 +220,26 @@ class RoleMixin: else: return False - @property + @lazyproperty def is_org_auditor(self): if self.is_super_auditor or self.related_audit_orgs.exists(): return True else: return False - @property + @lazyproperty def can_admin_current_org(self): return current_org.can_admin_by(self) - @property + @lazyproperty def can_audit_current_org(self): return current_org.can_audit_by(self) - @property + @lazyproperty def can_user_current_org(self): return current_org.can_user_by(self) - @property + @lazyproperty def can_admin_or_audit_current_org(self): return self.can_admin_current_org or self.can_audit_current_org diff --git a/apps/users/serializers/group.py b/apps/users/serializers/group.py index c817de289..67b21668c 100644 --- a/apps/users/serializers/group.py +++ b/apps/users/serializers/group.py @@ -4,29 +4,29 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from common.fields import StringManyToManyField from common.serializers import AdaptedBulkListSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from django.db.models import Count from ..models import User, UserGroup from .. import utils __all__ = [ - 'UserGroupSerializer', 'UserGroupListSerializer', - 'UserGroupUpdateMemberSerializer', + 'UserGroupSerializer', ] class UserGroupSerializer(BulkOrgResourceModelSerializer): users = serializers.PrimaryKeyRelatedField( - required=False, many=True, queryset=User.objects, label=_('User') + required=False, many=True, queryset=User.objects, label=_('User'), + write_only=True ) class Meta: model = UserGroup list_serializer_class = AdaptedBulkListSerializer fields = [ - 'id', 'name', 'users', 'comment', 'date_created', - 'created_by', + 'id', 'name', 'users', 'users_amount', 'comment', + 'date_created', 'created_by', ] extra_kwargs = { 'created_by': {'label': _('Created by'), 'read_only': True} @@ -47,23 +47,8 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer): raise serializers.ValidationError(msg) return users - -class UserGroupListSerializer(UserGroupSerializer): - users = StringManyToManyField(many=True, read_only=True) - - -class UserGroupUpdateMemberSerializer(serializers.ModelSerializer): - users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects) - - class Meta: - model = UserGroup - fields = ['id', 'users'] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_fields_queryset() - - def set_fields_queryset(self): - users_field = self.fields['users'] - users_field.child_relation.queryset = utils.get_current_org_members() - + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.annotate(users_amount=Count('users')) + return queryset diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 256dc073d..2363c39ed 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -7,11 +7,11 @@ from common.utils import validate_ssh_public_key from common.mixins import BulkSerializerMixin from common.serializers import AdaptedBulkListSerializer from common.permissions import CanUpdateDeleteUser -from ..models import User, UserGroup +from ..models import User __all__ = [ - 'UserSerializer', 'UserPKUpdateSerializer', 'UserUpdateGroupSerializer', + 'UserSerializer', 'UserPKUpdateSerializer', 'ChangeUserPasswordSerializer', 'ResetOTPSerializer', 'UserProfileSerializer', 'UserDisplaySerializer', ] @@ -123,16 +123,6 @@ class UserPKUpdateSerializer(serializers.ModelSerializer): return value -class UserUpdateGroupSerializer(serializers.ModelSerializer): - groups = serializers.PrimaryKeyRelatedField( - many=True, queryset=UserGroup.objects - ) - - class Meta: - model = User - fields = ['id', 'groups'] - - class ChangeUserPasswordSerializer(serializers.ModelSerializer): class Meta: diff --git a/apps/users/signals.py b/apps/users/signals.py index b9084b7dc..37969f839 100644 --- a/apps/users/signals.py +++ b/apps/users/signals.py @@ -2,3 +2,4 @@ from django.dispatch import Signal post_user_create = Signal(providing_args=('user',)) +post_user_change_password = Signal(providing_args=('user',)) diff --git a/apps/users/signals_handler.py b/apps/users/signals_handler.py index a33d9eec9..902dfa4d7 100644 --- a/apps/users/signals_handler.py +++ b/apps/users/signals_handler.py @@ -2,7 +2,7 @@ # from django.dispatch import receiver -from django.db.models.signals import post_save, m2m_changed +from django.db.models.signals import m2m_changed from common.utils import get_logger from .signals import post_user_create diff --git a/apps/users/templates/users/_base_otp.html b/apps/users/templates/users/_base_otp.html index ac511efb0..89ac8d656 100644 --- a/apps/users/templates/users/_base_otp.html +++ b/apps/users/templates/users/_base_otp.html @@ -1,60 +1,20 @@ +{% extends '_without_nav_base.html' %} {% load static %} {% load i18n %} - - - - - {{ JMS_TITLE }} - - - - - - - - - - -
    - - -
    - - -
    -
    -

    - {% block small_title %} - {% endblock %} -

    -
    -
    -
    {% trans 'Security token validation' %}  {% trans 'Account' %} {{ user.username }}  {% trans 'Follow these steps to complete the binding operation' %}
    -
    - {% block content %} +{% block body %} +
    +
    +

    + {% block small_title %} {% endblock %} -

    -
    - - -
    -
    - {% include '_copyright.html' %} -
    -
    - - - - + +
    +
    +
    {% trans 'Security token validation' %}  {% trans 'Account' %} {{ user.username }}  {% trans 'Follow these steps to complete the binding operation' %}
    +
    + {% block content %} + {% endblock %} +
    +
    +{% endblock %} diff --git a/apps/users/templates/users/_user_groups_import_modal.html b/apps/users/templates/users/_user_groups_import_modal.html deleted file mode 100644 index 63d057215..000000000 --- a/apps/users/templates/users/_user_groups_import_modal.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends '_import_modal.html' %} -{% load i18n %} - -{% block modal_title%}{% trans "Import user groups" %}{% endblock %} - -{% block import_modal_download_template_url %}{% url "api-users:user-group-list" %}{% endblock %} \ No newline at end of file diff --git a/apps/users/templates/users/_user_groups_update_modal.html b/apps/users/templates/users/_user_groups_update_modal.html deleted file mode 100644 index a07c0f82c..000000000 --- a/apps/users/templates/users/_user_groups_update_modal.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends '_update_modal.html' %} -{% load i18n %} - -{% block modal_title%}{% trans "Update user group" %}{% endblock %} \ No newline at end of file diff --git a/apps/users/templates/users/_user_import_modal.html b/apps/users/templates/users/_user_import_modal.html deleted file mode 100644 index e53d67fa7..000000000 --- a/apps/users/templates/users/_user_import_modal.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends '_import_modal.html' %} -{% load i18n %} - -{% block modal_title%}{% trans "Import users" %}{% endblock %} - -{% block import_modal_download_template_url %}{% url "api-users:user-list" %}{% endblock %} diff --git a/apps/users/templates/users/_user_update_modal.html b/apps/users/templates/users/_user_update_modal.html deleted file mode 100644 index 9dfe60c96..000000000 --- a/apps/users/templates/users/_user_update_modal.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends '_update_modal.html' %} -{% load i18n %} - -{% block modal_title%}{% trans "Update user" %}{% endblock %} \ No newline at end of file diff --git a/apps/users/templates/users/first_login.html b/apps/users/templates/users/first_login.html index 1038fb8c8..fb8af6257 100644 --- a/apps/users/templates/users/first_login.html +++ b/apps/users/templates/users/first_login.html @@ -13,7 +13,7 @@ {% block content %}
    -
    +
    {% trans 'First Login' %}
    @@ -55,7 +55,7 @@
    -
    + {% csrf_token %} {{ wizard.management_form }} {#{% if wizard.form.forms %}#} @@ -88,7 +88,7 @@ {% endif %}
    -
    +
    diff --git a/apps/users/templates/users/first_login_done.html b/apps/users/templates/users/first_login_done.html index 5639d2c5d..ae43d032b 100644 --- a/apps/users/templates/users/first_login_done.html +++ b/apps/users/templates/users/first_login_done.html @@ -13,7 +13,7 @@ {% block content %}
    -
    +
    {% trans 'First Login' %}
    diff --git a/apps/users/templates/users/forgot_password.html b/apps/users/templates/users/forgot_password.html index 3f3c7fabb..d48cf0277 100644 --- a/apps/users/templates/users/forgot_password.html +++ b/apps/users/templates/users/forgot_password.html @@ -1,60 +1,34 @@ +{% extends '_base_only_content.html' %} {% load static %} {% load i18n %} - - +{% load bootstrap3 %} +{% block custom_head_css_js %} + +{% endblock %} +{% block html_title %}{% trans 'Forgot password' %}{% endblock %} +{% block title %} {% trans 'Forgot password' %}?{% endblock %} - - - - - {% trans 'Forgot password' %} +{% block content %} + {% if errors %} +

    {{ errors }}

    + {% endif %} +

    + {% trans 'Input your email, that will send a mail to your' %} +

    - {% include '_head_css_js.html' %} - - - - - - -
    -
    - -
    -
    - -

    {% trans 'Forgot password' %} ?

    -

    - {% if errors %} -

    {{ errors }}

    - {% endif %} -

    - {% trans 'Input your email, that will send a mail to your' %} -

    - -
    -
    -
    - {% csrf_token %} -
    - -
    - - - -
    -
    -
    -
    -
    -
    -
    -
    -
    - {% include '_copyright.html' %} -
    +
    +
    +
    + {% csrf_token %} + {% bootstrap_field form.email layout="horizontal" %} + {% bootstrap_field form.captcha layout="horizontal" %} + +
    +{% endblock %} - - - diff --git a/apps/users/templates/users/reset_password.html b/apps/users/templates/users/reset_password.html index 6817f0a75..ecad676bf 100644 --- a/apps/users/templates/users/reset_password.html +++ b/apps/users/templates/users/reset_password.html @@ -1,136 +1,80 @@ +{% extends '_base_only_content.html' %} {% load static %} {% load i18n %} - - +{% load bootstrap3 %} +{% block html_title %}{% trans 'Reset password' %}{% endblock %} +{% block title %}{% trans 'Reset password' %}{% endblock %} - - - - {{ JMS_TITLE }} - - {% include '_head_css_js.html' %} - - - - - - - - - -
    -
    - -
    -

    {% trans 'Welcome to the Jumpserver open source fortress' %}

    - -

    - {% trans 'Jumpserver is an open source desktop system developed using Python and Django that helps Internet businesses with efficient users, assets, permissions, and audit management' %} -

    - -

    - {% trans 'We are from all over the world, we have great admiration and worship for the spirit of open source, we have endless pursuit for perfection, neatness and elegance' %} -

    - -

    - {% trans 'We focus on automatic operation and maintenance, and strive to build an easy-to-use, stable, safe and automatic board hopping machine, which is our unremitting pursuit and power' %} -

    - -

    - {% trans 'Always young, always with tears in my eyes. Stay foolish Stay hungry' %} -

    - -
    -
    -
    -
    {% trans 'Reset password' %}
    -
    - {% csrf_token %} - {% if errors %} -

    {{ errors }}

    - {% endif %} -
    - - {# 密码popover #} -
    - -
    -
    -
    - -
    - - - - Forgot password? - - -

    -

    -
    -

    -

    +{% block content %} +
    + {% csrf_token %} + {% if errors %} +

    {{ errors }}

    + {% endif %} + {% if not token_invalid %} +
    + {% bootstrap_field form.new_password %} + {% bootstrap_field form.confirm_password %} + {# 密码popover #} +
    +
    -
    -
    -
    - {% include '_copyright.html' %} -
    -
    -
    + + {% endif %} + +{% endblock %} - +{% block custom_foot_js %} + + +{% endblock %} diff --git a/apps/users/templates/users/user_detail.html b/apps/users/templates/users/user_detail.html index 246232279..dab389362 100644 --- a/apps/users/templates/users/user_detail.html +++ b/apps/users/templates/users/user_detail.html @@ -244,10 +244,10 @@ {% for group in user_object.groups.all %} - {{ group.name }} + {{ group.name }} - + {% endfor %} @@ -307,38 +307,8 @@ {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/users/templates/users/user_group_list.html b/apps/users/templates/users/user_group_list.html index 3eb70e319..d06cbec6b 100644 --- a/apps/users/templates/users/user_group_list.html +++ b/apps/users/templates/users/user_group_list.html @@ -1,28 +1,7 @@ {% extends '_base_list.html' %} {% load i18n static %} {% block table_search %} - + {% include '_csv_import_export.html' %} {% endblock %} {% block table_container %} @@ -39,14 +18,13 @@ -{% include "users/_user_groups_import_modal.html" %} -{% include "users/_user_groups_update_modal.html" %} {% endblock %} {% block content_bottom_left %}{% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index 844e5815d..41c66f615 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -1,28 +1,7 @@ {% extends '_base_list.html' %} {% load i18n static %} {% block table_search %} - + {% include '_csv_import_export.html' %} {% endblock %} {% block table_container %} @@ -63,14 +42,12 @@
    -{% include "users/_user_import_modal.html" %} -{% include "users/_user_update_modal.html" %} {% endblock %} {% block content_bottom_left %}{% endblock %} {% block custom_foot_js %}