From dafc416783d4a08eee07d95e7022088f969e15ee Mon Sep 17 00:00:00 2001
From: fit2bot <68588906+fit2bot@users.noreply.github.com>
Date: Wed, 2 Mar 2022 20:48:43 +0800
Subject: [PATCH] Fix rbac (#7728)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* perf: 重命名 signal handlers

* fix: 修复 ticket processor 问题

* perf: 修改 ticket 处理人api

* fix: 修复创建系统账号bug

* fix: 升级celery_beat==2.2.1和flower==1.0.0;修改celery进程启动参数先后顺序

* perf: 修改 authentication token

* fix: 修复上传权限bug

* fix: 登录页面增加i18n切换;

* fix: 系统角色删除限制

* perf: 修改一下 permissions tree

* perf: 生成 i18n

* perf: 修改一点点

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: feng626 <1304903146@qq.com>
Co-authored-by: Jiangjie.Bai <bugatti_it@163.com>
---
 apps/assets/api/cmd_filter.py                 |   2 +-
 apps/assets/apps.py                           |   4 +-
 apps/assets/models/asset.py                   |   4 +-
 .../__init__.py                               |   0
 .../asset.py                                  |   0
 .../authbook.py                               |   0
 .../common.py                                 |   0
 .../node_assets_amount.py                     |   0
 .../node_assets_mapping.py                    |   0
 .../system_user.py                            |   0
 apps/audits/apps.py                           |   2 +-
 ...{signals_handler.py => signal_handlers.py} |   0
 apps/authentication/api/connection_token.py   |  16 +-
 apps/authentication/apps.py                   |   2 +-
 .../migrations/0007_connectiontoken.py        |  12 +-
 .../migrations/0008_auto_20220217_2135.py     |  21 -
 .../migrations/0008_superconnectiontoken.py   |  25 +
 apps/authentication/models.py                 |  11 +-
 apps/authentication/serializers.py            |  14 +-
 ...signals_handlers.py => signal_handlers.py} |   0
 .../templates/authentication/login.html       |  27 +
 .../management/commands/expire_caches.py      |   2 +-
 .../management/commands/services/command.py   |   2 +-
 .../commands/services/services/celery_base.py |   5 +-
 .../commands/services/services/flower.py      |   3 +-
 apps/jumpserver/context_processor.py          |   2 +-
 apps/jumpserver/urls.py                       |   3 +-
 apps/locale/zh/LC_MESSAGES/django.mo          |   4 +-
 apps/locale/zh/LC_MESSAGES/django.po          | 582 ++++++++++--------
 apps/notifications/apps.py                    |   2 +-
 ...{signals_handler.py => signal_handlers.py} |   0
 apps/notifications/ws.py                      |   2 +-
 apps/ops/apps.py                              |   4 +-
 apps/ops/notifications.py                     |   2 +-
 ...{signals_handler.py => signal_handlers.py} |   0
 apps/orgs/apps.py                             |   4 +-
 .../__init__.py                               |   0
 .../cache.py                                  |   0
 .../common.py                                 |   0
 apps/perms/apps.py                            |   4 +-
 .../serializers/application/permission.py     |   2 +-
 .../__init__.py                               |   0
 .../app_permission.py                         |   0
 .../asset_permission.py                       |   0
 .../refresh_perms.py                          |   0
 apps/rbac/api/rolebinding.py                  |  16 +-
 apps/rbac/const.py                            |  10 +-
 apps/rbac/models/permission.py                | 193 +-----
 apps/rbac/permissions.py                      |   1 +
 apps/rbac/tree.py                             | 387 ++++++++++++
 apps/settings/apps.py                         |   2 +-
 ...{signals_handler.py => signal_handlers.py} |   0
 apps/terminal/apps.py                         |   2 +-
 .../migrations/0047_auto_20220302_1951.py     |  17 +
 apps/terminal/models/replay.py                |   1 +
 ...{signals_handler.py => signal_handlers.py} |   0
 apps/tickets/api/super_ticket.py              |  11 +-
 apps/tickets/apps.py                          |   2 +-
 apps/tickets/models/ticket.py                 |   7 +-
 apps/tickets/serializers/super_ticket.py      |  13 +-
 .../__init__.py                               |   0
 .../comment.py                                |   0
 .../ticket.py                                 |   0
 apps/users/apps.py                            |   2 +-
 apps/users/serializers/user.py                |   4 +-
 ...{signals_handler.py => signal_handlers.py} |   0
 jms                                           |  10 +-
 requirements/requirements.txt                 |   4 +-
 utils/start_celery_beat.py                    |   3 +-
 69 files changed, 929 insertions(+), 519 deletions(-)
 rename apps/assets/{signals_handler => signal_handlers}/__init__.py (100%)
 rename apps/assets/{signals_handler => signal_handlers}/asset.py (100%)
 rename apps/assets/{signals_handler => signal_handlers}/authbook.py (100%)
 rename apps/assets/{signals_handler => signal_handlers}/common.py (100%)
 rename apps/assets/{signals_handler => signal_handlers}/node_assets_amount.py (100%)
 rename apps/assets/{signals_handler => signal_handlers}/node_assets_mapping.py (100%)
 rename apps/assets/{signals_handler => signal_handlers}/system_user.py (100%)
 rename apps/audits/{signals_handler.py => signal_handlers.py} (100%)
 delete mode 100644 apps/authentication/migrations/0008_auto_20220217_2135.py
 create mode 100644 apps/authentication/migrations/0008_superconnectiontoken.py
 rename apps/authentication/{signals_handlers.py => signal_handlers.py} (100%)
 rename apps/notifications/{signals_handler.py => signal_handlers.py} (100%)
 rename apps/ops/{signals_handler.py => signal_handlers.py} (100%)
 rename apps/orgs/{signals_handler => signal_handlers}/__init__.py (100%)
 rename apps/orgs/{signals_handler => signal_handlers}/cache.py (100%)
 rename apps/orgs/{signals_handler => signal_handlers}/common.py (100%)
 rename apps/perms/{signals_handler => signal_handlers}/__init__.py (100%)
 rename apps/perms/{signals_handler => signal_handlers}/app_permission.py (100%)
 rename apps/perms/{signals_handler => signal_handlers}/asset_permission.py (100%)
 rename apps/perms/{signals_handler => signal_handlers}/refresh_perms.py (100%)
 create mode 100644 apps/rbac/tree.py
 rename apps/settings/{signals_handler.py => signal_handlers.py} (100%)
 create mode 100644 apps/terminal/migrations/0047_auto_20220302_1951.py
 rename apps/terminal/{signals_handler.py => signal_handlers.py} (100%)
 rename apps/tickets/{signals_handler => signal_handlers}/__init__.py (100%)
 rename apps/tickets/{signals_handler => signal_handlers}/comment.py (100%)
 rename apps/tickets/{signals_handler => signal_handlers}/ticket.py (100%)
 rename apps/users/{signals_handler.py => signal_handlers.py} (100%)

diff --git a/apps/assets/api/cmd_filter.py b/apps/assets/api/cmd_filter.py
index c9fad91a0..dcb2d77c9 100644
--- a/apps/assets/api/cmd_filter.py
+++ b/apps/assets/api/cmd_filter.py
@@ -53,7 +53,7 @@ class CommandConfirmAPI(CreateAPIView):
             run_command=self.serializer.data.get('run_command'),
             session=self.serializer.session,
             cmd_filter_rule=self.serializer.cmd_filter_rule,
-            org_id=self.serializer.org.id
+            org_id=self.serializer.org.id,
         )
         return ticket
 
diff --git a/apps/assets/apps.py b/apps/assets/apps.py
index 5b44e2e92..e1bb43544 100644
--- a/apps/assets/apps.py
+++ b/apps/assets/apps.py
@@ -6,11 +6,11 @@ from django.apps import AppConfig
 
 class AssetsConfig(AppConfig):
     name = 'assets'
-    verbose_name = _('Assets')
+    verbose_name = _('App assets')
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
     def ready(self):
         super().ready()
-        from . import signals_handler
+        from . import signal_handlers
diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py
index 905b70aa5..b0872ad0d 100644
--- a/apps/assets/models/asset.py
+++ b/apps/assets/models/asset.py
@@ -355,6 +355,6 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin
         verbose_name = _("Asset")
         ordering = ["hostname", ]
         permissions = [
-            ('test_assetconnectivity', 'Can test asset connectivity'),
-            ('push_assetsystemuser', 'Can push system user to asset'),
+            ('test_assetconnectivity', _('Can test asset connectivity')),
+            ('push_assetsystemuser', _('Can push system user to asset')),
         ]
diff --git a/apps/assets/signals_handler/__init__.py b/apps/assets/signal_handlers/__init__.py
similarity index 100%
rename from apps/assets/signals_handler/__init__.py
rename to apps/assets/signal_handlers/__init__.py
diff --git a/apps/assets/signals_handler/asset.py b/apps/assets/signal_handlers/asset.py
similarity index 100%
rename from apps/assets/signals_handler/asset.py
rename to apps/assets/signal_handlers/asset.py
diff --git a/apps/assets/signals_handler/authbook.py b/apps/assets/signal_handlers/authbook.py
similarity index 100%
rename from apps/assets/signals_handler/authbook.py
rename to apps/assets/signal_handlers/authbook.py
diff --git a/apps/assets/signals_handler/common.py b/apps/assets/signal_handlers/common.py
similarity index 100%
rename from apps/assets/signals_handler/common.py
rename to apps/assets/signal_handlers/common.py
diff --git a/apps/assets/signals_handler/node_assets_amount.py b/apps/assets/signal_handlers/node_assets_amount.py
similarity index 100%
rename from apps/assets/signals_handler/node_assets_amount.py
rename to apps/assets/signal_handlers/node_assets_amount.py
diff --git a/apps/assets/signals_handler/node_assets_mapping.py b/apps/assets/signal_handlers/node_assets_mapping.py
similarity index 100%
rename from apps/assets/signals_handler/node_assets_mapping.py
rename to apps/assets/signal_handlers/node_assets_mapping.py
diff --git a/apps/assets/signals_handler/system_user.py b/apps/assets/signal_handlers/system_user.py
similarity index 100%
rename from apps/assets/signals_handler/system_user.py
rename to apps/assets/signal_handlers/system_user.py
diff --git a/apps/audits/apps.py b/apps/audits/apps.py
index e66faa467..904739ec7 100644
--- a/apps/audits/apps.py
+++ b/apps/audits/apps.py
@@ -9,6 +9,6 @@ class AuditsConfig(AppConfig):
     verbose_name = _('Audits')
 
     def ready(self):
-        from . import signals_handler
+        from . import signal_handlers
         if settings.SYSLOG_ENABLE:
             post_save.connect(signals_handler.on_audits_log_create)
diff --git a/apps/audits/signals_handler.py b/apps/audits/signal_handlers.py
similarity index 100%
rename from apps/audits/signals_handler.py
rename to apps/audits/signal_handlers.py
diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py
index 3712bdf51..47ce04ece 100644
--- a/apps/authentication/api/connection_token.py
+++ b/apps/authentication/api/connection_token.py
@@ -302,16 +302,26 @@ class SecretDetailMixin:
             user=user, system_user=system_user,
             expired_at=expired_at, actions=actions
         )
+        cmd_filter_kwargs = {
+            'system_user_id': system_user.id,
+            'user_id': user.id,
+        }
         if asset:
             asset_detail = self._get_asset_secret_detail(asset)
             system_user.load_asset_more_auth(asset.id, user.username, user.id)
             data['type'] = 'asset'
             data.update(asset_detail)
+            cmd_filter_kwargs['asset_id'] = asset.id
         else:
             app_detail = self._get_application_secret_detail(app)
             system_user.load_app_more_auth(app.id, user.username, user.id)
             data['type'] = 'application'
             data.update(app_detail)
+            cmd_filter_kwargs['application_id'] = app.id
+
+        from assets.models import CommandFilterRule
+        cmd_filter_rules = CommandFilterRule.get_queryset(**cmd_filter_kwargs)
+        data['cmd_filter_rules'] = cmd_filter_rules
 
         serializer = self.get_serializer(data)
         return Response(data=serializer.data, status=200)
@@ -350,8 +360,10 @@ class UserConnectionTokenViewSet(
         return True
 
     def create_token(self, user, asset, application, system_user, ttl=5 * 60):
-        if not self.request.user.is_superuser and user != self.request.user:
-            raise PermissionDenied('Only super user can create user token')
+        # 再次强调一下权限
+        perm_required = 'authentication.add_superconnectiontoken'
+        if user != self.request.user and not self.request.user.has_perm(perm_required):
+            raise PermissionDenied('Only can create user token')
         self.check_resource_permission(user, asset, application, system_user)
         token = random_string(36)
         secret = random_string(16)
diff --git a/apps/authentication/apps.py b/apps/authentication/apps.py
index 527c54871..6516ed70e 100644
--- a/apps/authentication/apps.py
+++ b/apps/authentication/apps.py
@@ -7,7 +7,7 @@ class AuthenticationConfig(AppConfig):
     verbose_name = _('Authentication')
 
     def ready(self):
-        from . import signals_handlers
+        from . import signal_handlers
         from . import notifications
         super().ready()
 
diff --git a/apps/authentication/migrations/0007_connectiontoken.py b/apps/authentication/migrations/0007_connectiontoken.py
index c017c5d76..86341ff5b 100644
--- a/apps/authentication/migrations/0007_connectiontoken.py
+++ b/apps/authentication/migrations/0007_connectiontoken.py
@@ -19,8 +19,14 @@ class Migration(migrations.Migration):
                 ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
                 ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
             ],
-            options={
-                'permissions': [('add_superconnectiontoken', 'Can add super connection token'), ('view_connectiontokensecret', 'Can view connect token secret')],
-            },
+            options={'verbose_name': 'Connection token'},
+        ),
+        migrations.AlterModelOptions(
+            name='accesskey',
+            options={'verbose_name': 'Access key'},
+        ),
+        migrations.AlterModelOptions(
+            name='ssotoken',
+            options={'verbose_name': 'SSO token'},
         ),
     ]
diff --git a/apps/authentication/migrations/0008_auto_20220217_2135.py b/apps/authentication/migrations/0008_auto_20220217_2135.py
deleted file mode 100644
index 3f6b55248..000000000
--- a/apps/authentication/migrations/0008_auto_20220217_2135.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Generated by Django 3.1.13 on 2022-02-17 13:35
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('authentication', '0007_connectiontoken'),
-    ]
-
-    operations = [
-        migrations.AlterModelOptions(
-            name='accesskey',
-            options={'verbose_name': 'Access key'},
-        ),
-        migrations.AlterModelOptions(
-            name='ssotoken',
-            options={'verbose_name': 'SSO token'},
-        ),
-    ]
diff --git a/apps/authentication/migrations/0008_superconnectiontoken.py b/apps/authentication/migrations/0008_superconnectiontoken.py
new file mode 100644
index 000000000..82e956a24
--- /dev/null
+++ b/apps/authentication/migrations/0008_superconnectiontoken.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.1.14 on 2022-03-02 11:53
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('authentication', '0007_connectiontoken'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='SuperConnectionToken',
+            fields=[
+            ],
+            options={
+                'verbose_name': 'Super connection token',
+                'proxy': True,
+                'indexes': [],
+                'constraints': [],
+            },
+            bases=('authentication.connectiontoken',),
+        ),
+    ]
diff --git a/apps/authentication/models.py b/apps/authentication/models.py
index f263973fa..7e407a1dd 100644
--- a/apps/authentication/models.py
+++ b/apps/authentication/models.py
@@ -58,7 +58,10 @@ class ConnectionToken(models.JMSBaseModel):
     # Todo: add connection token 可能要授权给 普通用户, 或者放开就行
 
     class Meta:
-        permissions = [
-            ('add_superconnectiontoken', _('Can add super connection token')),
-            ('view_connectiontokensecret', _('Can view connect token secret'))
-        ]
+        verbose_name = _('Connection token')
+
+
+class SuperConnectionToken(ConnectionToken):
+    class Meta:
+        proxy = True
+        verbose_name = _("Super connection token")
diff --git a/apps/authentication/serializers.py b/apps/authentication/serializers.py
index 678b7763b..f6661ba38 100644
--- a/apps/authentication/serializers.py
+++ b/apps/authentication/serializers.py
@@ -5,7 +5,7 @@ from rest_framework import serializers
 
 from common.utils import get_object_or_none
 from users.models import User
-from assets.models import Asset, SystemUser, Gateway, Domain
+from assets.models import Asset, SystemUser, Gateway, Domain, CommandFilterRule
 from applications.models import Application
 from users.serializers import UserProfileSerializer
 from assets.serializers import ProtocolsField
@@ -200,6 +200,17 @@ class ConnectionTokenDomainSerializer(serializers.ModelSerializer):
         fields = ['id', 'name', 'gateways']
 
 
+class ConnectionTokenFilterRuleSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = CommandFilterRule
+        fields = [
+            'id', 'type', 'content', 'ignore_case', 'pattern',
+            'priority', 'action',
+            'date_created',
+        ]
+
+
 class ConnectionTokenSecretSerializer(serializers.Serializer):
     id = serializers.CharField(read_only=True)
     secret = serializers.CharField(read_only=True)
@@ -209,6 +220,7 @@ class ConnectionTokenSecretSerializer(serializers.Serializer):
     remote_app = ConnectionTokenRemoteAppSerializer(read_only=True)
     application = ConnectionTokenApplicationSerializer(read_only=True)
     system_user = ConnectionTokenSystemUserSerializer(read_only=True)
+    cmd_filter_rules = ConnectionTokenFilterRuleSerializer(many=True)
     domain = ConnectionTokenDomainSerializer(read_only=True)
     gateway = ConnectionTokenGatewaySerializer(read_only=True)
     actions = ActionsField()
diff --git a/apps/authentication/signals_handlers.py b/apps/authentication/signal_handlers.py
similarity index 100%
rename from apps/authentication/signals_handlers.py
rename to apps/authentication/signal_handlers.py
diff --git a/apps/authentication/templates/authentication/login.html b/apps/authentication/templates/authentication/login.html
index b74217cee..3e24aa610 100644
--- a/apps/authentication/templates/authentication/login.html
+++ b/apps/authentication/templates/authentication/login.html
@@ -115,10 +115,21 @@
         .mfa-div {
             width: 100%;
         }
+
+        .login-page-language {
+            margin-right: -11px !important;
+            padding-top: 12px !important;
+            padding-left: 0 !important;
+            padding-bottom: 8px !important;
+            color: #666 !important;
+            font-weight: 350 !important;
+            min-height: auto !important;
+        }
     </style>
 </head>
 
 <body>
+
 <div class="login-content">
     <div class="right-image-box">
         <a href="{% if not XPACK_ENABLED %}https://github.com/jumpserver/jumpserver{% endif %}">
@@ -127,6 +138,22 @@
     </div>
     <div class="left-form-box {% if not form.challenge and not form.captcha %} no-captcha-challenge {% endif %}">
         <div style="background-color: white">
+            <ul class="nav navbar-top-links navbar-right">
+                <li class="dropdown">
+                    <a class="dropdown-toggle login-page-language" data-toggle="dropdown" href="#" target="_blank">
+                        <i class="fa fa-globe fa-lg" style="margin-right: 2px"></i>
+                        {% ifequal request.COOKIES.django_language 'en' %}
+                            <span>English<b class="caret"></b></span>
+                        {% else %}
+                            <span>中文(简体)<b class="caret"></b></span>
+                        {% endifequal %}
+                    </a>
+                    <ul class="dropdown-menu profile-dropdown dropdown-menu-right">
+                        <li> <a id="switch_cn" href="{% url 'i18n-switch' lang='zh-hans' %}"> <span>中文(简体)</span> </a> </li>
+                        <li> <a id="switch_en" href="{% url 'i18n-switch' lang='en' %}"> <span>English</span> </a> </li>
+                    </ul>
+                </li>
+            </ul>
             <div class="jms-title">
                 <span style="font-size: 21px;font-weight:400;color: #151515;letter-spacing: 0;">{{ JMS_TITLE }}</span>
             </div>
diff --git a/apps/common/management/commands/expire_caches.py b/apps/common/management/commands/expire_caches.py
index 37fe7605b..bc20a3cf5 100644
--- a/apps/common/management/commands/expire_caches.py
+++ b/apps/common/management/commands/expire_caches.py
@@ -1,6 +1,6 @@
 from django.core.management.base import BaseCommand
 
-from assets.signals_handler.node_assets_mapping import expire_node_assets_mapping_for_memory
+from assets.signal_handlers.node_assets_mapping import expire_node_assets_mapping_for_memory
 from orgs.caches import OrgResourceStatisticsCache
 from orgs.models import Organization
 
diff --git a/apps/common/management/commands/services/command.py b/apps/common/management/commands/services/command.py
index 170eb69ae..dd2cd9cdb 100644
--- a/apps/common/management/commands/services/command.py
+++ b/apps/common/management/commands/services/command.py
@@ -52,7 +52,7 @@ class Services(TextChoices):
 
     @classmethod
     def export_services_values(cls):
-        return [cls.all.value, cls.web.value, cls.task.value]
+        return [cls.all.value, cls.web.value, cls.task.value] + [s.value for s in cls.all_services()]
 
     @classmethod
     def get_service_objects(cls, service_names, **kwargs):
diff --git a/apps/common/management/commands/services/services/celery_base.py b/apps/common/management/commands/services/services/celery_base.py
index 5542fd72f..435e4ebe2 100644
--- a/apps/common/management/commands/services/services/celery_base.py
+++ b/apps/common/management/commands/services/services/celery_base.py
@@ -23,9 +23,10 @@ class CeleryBaseService(BaseService):
             server_hostname = '%h'
 
         cmd = [
-            'celery', 'worker',
-            '-P', 'threads',
+            'celery',
             '-A', 'ops',
+            'worker',
+            '-P', 'threads',
             '-l', 'INFO',
             '-c', str(self.num),
             '-Q', self.queue,
diff --git a/apps/common/management/commands/services/services/flower.py b/apps/common/management/commands/services/services/flower.py
index df2230776..402e9f37d 100644
--- a/apps/common/management/commands/services/services/flower.py
+++ b/apps/common/management/commands/services/services/flower.py
@@ -16,8 +16,9 @@ class FlowerService(BaseService):
         if os.getuid() == 0:
             os.environ.setdefault('C_FORCE_ROOT', '1')
         cmd = [
-            'celery', 'flower',
+            'celery',
             '-A', 'ops',
+            'flower',
             '-l', 'INFO',
             '--url_prefix=/core/flower',
             '--auto_refresh=False',
diff --git a/apps/jumpserver/context_processor.py b/apps/jumpserver/context_processor.py
index f714759f0..54a7b856e 100644
--- a/apps/jumpserver/context_processor.py
+++ b/apps/jumpserver/context_processor.py
@@ -2,7 +2,7 @@
 #
 from django.templatetags.static import static
 from django.conf import settings
-from django.utils.translation import ugettext as _
+from django.utils.translation import ugettext_lazy as _
 
 default_context = {
     'DEFAULT_PK': '00000000-0000-0000-0000-000000000000',
diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py
index 80948f9f6..6a9ae69a9 100644
--- a/apps/jumpserver/urls.py
+++ b/apps/jumpserver/urls.py
@@ -33,7 +33,8 @@ app_view_patterns = [
     path('ops/', include('ops.urls.view_urls'), name='ops'),
     path('common/', include('common.urls.view_urls'), name='common'),
     re_path(r'flower/(?P<path>.*)', views.celery_flower_view, name='flower-view'),
-    path('download/', views.ResourceDownload.as_view(), name='download')
+    path('download/', views.ResourceDownload.as_view(), name='download'),
+    path('i18n/<str:lang>/', views.I18NView.as_view(), name='i18n-switch'),
 ]
 
 if settings.XPACK_ENABLED:
diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo
index 7a5c32de9..7c0f80e7b 100644
--- a/apps/locale/zh/LC_MESSAGES/django.mo
+++ b/apps/locale/zh/LC_MESSAGES/django.mo
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6144c6aa78bcaf3c282ee63f9e48b11fb8c327192aa8ea8f1ff1c2080084304f
-size 100589
+oid sha256:8bd2394fc5d9bb9254965db4273a09d4ddabd8051b4855b9642476ff9cab836b
+size 101898
diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po
index d93918379..4924dda74 100644
--- a/apps/locale/zh/LC_MESSAGES/django.po
+++ b/apps/locale/zh/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: JumpServer 0.3.3\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-02-22 19:38+0800\n"
+"POT-Creation-Date: 2022-03-02 19:46+0800\n"
 "PO-Revision-Date: 2021-05-20 10:54+0800\n"
 "Last-Translator: ibuler <ibuler@qq.com>\n"
 "Language-Team: JumpServer team<ibuler@qq.com>\n"
@@ -19,7 +19,7 @@ msgstr ""
 
 #: acls/apps.py:7
 msgid "Acls"
-msgstr ""
+msgstr "访问控制"
 
 #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47
 #: applications/models/application.py:202 assets/models/asset.py:138
@@ -30,7 +30,7 @@ msgstr ""
 #: settings/models.py:29 settings/serializers/sms.py:6
 #: terminal/models/storage.py:23 terminal/models/task.py:16
 #: terminal/models/terminal.py:100 users/forms/profile.py:32
-#: users/models/group.py:15 users/models/user.py:537
+#: users/models/group.py:15 users/models/user.py:550
 #: users/templates/users/_select_user_modal.html:13
 #: users/templates/users/user_asset_permission.html:37
 #: users/templates/users/user_asset_permission.html:154
@@ -65,8 +65,8 @@ msgstr "激活中"
 #: assets/models/label.py:23 ops/models/adhoc.py:38 orgs/models.py:15
 #: perms/models/base.py:93 rbac/models/role.py:37 settings/models.py:34
 #: terminal/models/storage.py:26 terminal/models/terminal.py:114
-#: tickets/models/comment.py:24 tickets/models/ticket.py:78
-#: users/models/group.py:16 users/models/user.py:574
+#: tickets/models/comment.py:24 tickets/models/ticket.py:154
+#: users/models/group.py:16 users/models/user.py:587
 #: xpack/plugins/change_auth_plan/models/base.py:44
 #: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:113
 #: xpack/plugins/gathered_user/models.py:26
@@ -90,12 +90,12 @@ msgstr "登录复核"
 #: assets/models/cmd_filter.py:30 assets/models/label.py:15 audits/models.py:37
 #: audits/models.py:60 audits/models.py:85 audits/serializers.py:100
 #: authentication/models.py:50 orgs/models.py:196 perms/models/base.py:84
-#: rbac/builtin.py:87 rbac/models/rolebinding.py:33 templates/index.html:78
+#: rbac/builtin.py:89 rbac/models/rolebinding.py:33 templates/index.html:78
 #: terminal/backends/command/models.py:19
-#: terminal/backends/command/serializers.py:12 terminal/models/session.py:41
+#: terminal/backends/command/serializers.py:12 terminal/models/session.py:42
 #: terminal/notifications.py:88 terminal/notifications.py:136
-#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:757
-#: users/models/user.py:788 users/serializers/group.py:19
+#: tickets/models/comment.py:17 users/const.py:14 users/models/user.py:769
+#: users/models/user.py:800 users/serializers/group.py:19
 #: users/templates/users/user_asset_permission.html:38
 #: users/templates/users/user_asset_permission.html:64
 #: users/templates/users/user_database_app_permission.html:37
@@ -140,7 +140,7 @@ msgstr "系统用户"
 #: assets/models/gathered_user.py:14 assets/serializers/system_user.py:264
 #: audits/models.py:39 perms/models/asset_permission.py:23
 #: templates/index.html:82 terminal/backends/command/models.py:20
-#: terminal/backends/command/serializers.py:13 terminal/models/session.py:43
+#: terminal/backends/command/serializers.py:13 terminal/models/session.py:44
 #: terminal/notifications.py:87
 #: users/templates/users/user_asset_permission.html:40
 #: users/templates/users/user_asset_permission.html:70
@@ -168,7 +168,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
 #: authentication/forms.py:15 authentication/forms.py:17
 #: authentication/templates/authentication/_msg_different_city.html:9
 #: authentication/templates/authentication/_msg_oauth_bind.html:9
-#: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:535
+#: ops/models/adhoc.py:159 users/forms/profile.py:31 users/models/user.py:548
 #: users/templates/users/_msg_user_created.html:12
 #: users/templates/users/_select_user_modal.html:14
 #: xpack/plugins/change_auth_plan/models/asset.py:34
@@ -283,7 +283,7 @@ msgstr "应用程序"
 #: assets/models/cmd_filter.py:42 assets/models/user.py:325 audits/models.py:40
 #: perms/models/application_permission.py:32
 #: perms/models/asset_permission.py:25 terminal/backends/command/models.py:21
-#: terminal/backends/command/serializers.py:14 terminal/models/session.py:45
+#: terminal/backends/command/serializers.py:14 terminal/models/session.py:46
 #: users/templates/users/_granted_assets.html:27
 #: users/templates/users/user_asset_permission.html:42
 #: users/templates/users/user_asset_permission.html:76
@@ -303,7 +303,7 @@ msgstr "版本"
 
 #: applications/models/account.py:23
 msgid "Application account"
-msgstr "应用数量"
+msgstr "应用账号"
 
 #: applications/models/account.py:26 applications/models/account.py:27
 msgid "Can view application account secret"
@@ -324,7 +324,7 @@ msgstr "类别"
 #: perms/models/application_permission.py:23
 #: perms/serializers/application/user_permission.py:34
 #: terminal/models/storage.py:55 terminal/models/storage.py:119
-#: tickets/models/flow.py:56 tickets/models/ticket.py:55
+#: tickets/models/flow.py:56 tickets/models/ticket.py:131
 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29
 #: xpack/plugins/change_auth_plan/models/app.py:28
 #: xpack/plugins/change_auth_plan/models/app.py:153
@@ -368,7 +368,7 @@ msgstr "类型名称"
 #: assets/serializers/account.py:18 common/db/models.py:113
 #: common/mixins/models.py:50 ops/models/adhoc.py:39 ops/models/command.py:30
 #: orgs/models.py:14 orgs/models.py:199 perms/models/base.py:92
-#: users/models/group.py:18 users/models/user.py:789
+#: users/models/group.py:18 users/models/user.py:801
 #: xpack/plugins/cloud/models.py:122
 msgid "Date created"
 msgstr "创建日期"
@@ -493,11 +493,9 @@ msgstr "不能删除根节点 ({})"
 msgid "Deletion failed and the node contains assets"
 msgstr "删除失败,节点包含资产"
 
-#: assets/apps.py:9 assets/models/user.py:227 assets/serializers/domain.py:29
-#: terminal/templates/terminal/_msg_command_execute_alert.html:16
-#: xpack/plugins/change_auth_plan/models/asset.py:39
-msgid "Assets"
-msgstr "资产"
+#: assets/apps.py:9
+msgid "App assets"
+msgstr "资产管理"
 
 #: assets/models/asset.py:139
 msgid "Base"
@@ -508,7 +506,7 @@ msgid "Charset"
 msgstr "编码"
 
 #: assets/models/asset.py:141 assets/serializers/asset.py:176
-#: tickets/models/ticket.py:57
+#: tickets/models/ticket.py:133
 msgid "Meta"
 msgstr "元数据"
 
@@ -530,7 +528,7 @@ msgstr "制造商"
 msgid "Model"
 msgstr "型号"
 
-#: assets/models/asset.py:170 tickets/models/ticket.py:83
+#: assets/models/asset.py:170 tickets/models/ticket.py:159
 msgid "Serial number"
 msgstr "序列号"
 
@@ -617,24 +615,32 @@ msgstr "标签管理"
 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:52
 #: assets/models/cmd_filter.py:99 assets/models/group.py:21
 #: common/db/models.py:111 common/mixins/models.py:49 orgs/models.py:13
-#: orgs/models.py:201 perms/models/base.py:91 users/models/user.py:582
+#: orgs/models.py:201 perms/models/base.py:91 users/models/user.py:595
 #: users/serializers/group.py:33
 #: xpack/plugins/change_auth_plan/models/base.py:48
 #: xpack/plugins/cloud/models.py:119 xpack/plugins/gathered_user/models.py:30
 msgid "Created by"
 msgstr "创建者"
 
+#: assets/models/asset.py:358
+msgid "Can test asset connectivity"
+msgstr "可以测试资产连接性"
+
+#: assets/models/asset.py:359
+msgid "Can push system user to asset"
+msgstr "可以推送系统用户到资产"
+
 #: assets/models/authbook.py:27
 msgid "AuthBook"
-msgstr "账号"
+msgstr "资产账号"
 
 #: assets/models/authbook.py:30
 msgid "Can view asset account secret"
-msgstr ""
+msgstr "可以查看资产账号密码"
 
 #: assets/models/authbook.py:31
 msgid "Can change asset account secret"
-msgstr ""
+msgstr "可以更改资产账号密码"
 
 #: assets/models/backup.py:30 perms/models/base.py:54
 #: settings/serializers/terminal.py:12
@@ -650,7 +656,7 @@ msgstr "收件人"
 
 #: assets/models/backup.py:62 assets/models/backup.py:124
 msgid "Account backup plan"
-msgstr "账户备份计划"
+msgstr "账号备份计划"
 
 #: assets/models/backup.py:100
 #: xpack/plugins/change_auth_plan/models/base.py:107
@@ -663,7 +669,7 @@ msgid "Timing trigger"
 msgstr "定时触发"
 
 #: assets/models/backup.py:105 audits/models.py:44 ops/models/command.py:31
-#: perms/models/base.py:89 terminal/models/session.py:55
+#: perms/models/base.py:89 terminal/models/session.py:56
 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:55
 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:57
 #: xpack/plugins/change_auth_plan/models/base.py:112
@@ -736,7 +742,7 @@ msgstr "可连接性"
 msgid "Date verified"
 msgstr "校验日期"
 
-#: assets/models/base.py:177 audits/signals_handler.py:68
+#: assets/models/base.py:177 audits/signal_handlers.py:68
 #: authentication/forms.py:22
 #: authentication/templates/authentication/login.html:151
 #: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21
@@ -770,7 +776,7 @@ msgstr "带宽"
 msgid "Contact"
 msgstr "联系人"
 
-#: assets/models/cluster.py:22 users/models/user.py:557
+#: assets/models/cluster.py:22 users/models/user.py:570
 msgid "Phone"
 msgstr "手机"
 
@@ -796,7 +802,7 @@ msgid "Default"
 msgstr "默认"
 
 #: assets/models/cluster.py:36 assets/models/label.py:14 rbac/const.py:6
-#: users/models/user.py:774
+#: users/models/user.py:786
 msgid "System"
 msgstr "系统"
 
@@ -805,7 +811,7 @@ msgid "Default Cluster"
 msgstr "默认Cluster"
 
 #: assets/models/cmd_filter.py:34 perms/models/base.py:86
-#: users/models/group.py:31 users/models/user.py:543
+#: users/models/group.py:31 users/models/user.py:556
 #: users/templates/users/_select_user_modal.html:16
 #: users/templates/users/user_asset_permission.html:39
 #: users/templates/users/user_asset_permission.html:67
@@ -823,7 +829,7 @@ msgid "Regex"
 msgstr "正则表达式"
 
 #: assets/models/cmd_filter.py:68 ops/models/command.py:26
-#: terminal/backends/command/serializers.py:15 terminal/models/session.py:52
+#: terminal/backends/command/serializers.py:15 terminal/models/session.py:53
 #: terminal/templates/terminal/_msg_command_alert.html:12
 #: terminal/templates/terminal/_msg_command_execute_alert.html:10
 msgid "Command"
@@ -959,6 +965,12 @@ msgstr "普通用户"
 msgid "Username same with user"
 msgstr "用户名与用户相同"
 
+#: assets/models/user.py:227 assets/serializers/domain.py:29
+#: terminal/templates/terminal/_msg_command_execute_alert.html:16
+#: xpack/plugins/change_auth_plan/models/asset.py:39
+msgid "Assets"
+msgstr "资产"
+
 #: assets/models/user.py:231 users/apps.py:9
 msgid "Users"
 msgstr "用户管理"
@@ -1101,7 +1113,6 @@ msgid "Assets amount"
 msgstr "资产数量"
 
 #: assets/serializers/domain.py:14
-#: perms/serializers/application/permission.py:46
 msgid "Applications amount"
 msgstr "应用数量"
 
@@ -1126,6 +1137,7 @@ msgid "SSH key fingerprint"
 msgstr "密钥指纹"
 
 #: assets/serializers/system_user.py:30
+#: perms/serializers/application/permission.py:46
 msgid "Apps amount"
 msgstr "应用数量"
 
@@ -1315,8 +1327,7 @@ msgstr "日志审计"
 
 #: audits/models.py:27 audits/models.py:57
 #: authentication/templates/authentication/_access_key_modal.html:65
-#: rbac/models/permission.py:129
-#: users/templates/users/user_asset_permission.html:128
+#: rbac/tree.py:254 users/templates/users/user_asset_permission.html:128
 #: users/templates/users/user_database_app_permission.html:111
 msgid "Delete"
 msgstr "删除"
@@ -1346,7 +1357,7 @@ msgid "Symlink"
 msgstr "建立软链接"
 
 #: audits/models.py:38 audits/models.py:64 audits/models.py:87
-#: terminal/models/session.py:48 terminal/models/sharing.py:82
+#: terminal/models/session.py:49 terminal/models/sharing.py:82
 msgid "Remote addr"
 msgstr "远端地址"
 
@@ -1370,12 +1381,12 @@ msgstr "文件管理"
 
 #: audits/models.py:55
 #: authentication/templates/authentication/_access_key_modal.html:22
-#: rbac/models/permission.py:126
+#: rbac/tree.py:251
 msgid "Create"
 msgstr "创建"
 
-#: audits/models.py:56 rbac/models/permission.py:128
-#: templates/_csv_import_export.html:18 templates/_csv_update_modal.html:6
+#: audits/models.py:56 rbac/tree.py:253 templates/_csv_import_export.html:18
+#: templates/_csv_update_modal.html:6
 #: users/templates/users/user_asset_permission.html:127
 #: users/templates/users/user_database_app_permission.html:110
 msgid "Update"
@@ -1438,13 +1449,13 @@ msgstr "用户代理"
 
 #: audits/models.py:124
 #: authentication/templates/authentication/_mfa_confirm_modal.html:14
-#: users/forms/profile.py:64 users/models/user.py:560
+#: users/forms/profile.py:64 users/models/user.py:573
 #: users/serializers/profile.py:121
 msgid "MFA"
 msgstr "MFA"
 
 #: audits/models.py:126 terminal/models/status.py:33
-#: tickets/models/ticket.py:64 xpack/plugins/cloud/models.py:172
+#: tickets/models/ticket.py:140 xpack/plugins/cloud/models.py:172
 #: xpack/plugins/cloud/models.py:224
 msgid "Status"
 msgstr "状态"
@@ -1486,7 +1497,7 @@ msgstr "主机名称"
 msgid "Result"
 msgstr "结果"
 
-#: audits/serializers.py:98 terminal/serializers/storage.py:151
+#: audits/serializers.py:98 terminal/serializers/storage.py:161
 msgid "Hosts"
 msgstr "主机"
 
@@ -1502,195 +1513,195 @@ msgstr "运行用户名称"
 msgid "User display"
 msgstr "用户名称"
 
-#: audits/signals_handler.py:67
+#: audits/signal_handlers.py:67
 msgid "SSH Key"
 msgstr "SSH 密钥"
 
-#: audits/signals_handler.py:69
+#: audits/signal_handlers.py:69
 msgid "SSO"
 msgstr ""
 
-#: audits/signals_handler.py:70
+#: audits/signal_handlers.py:70
 msgid "Auth Token"
 msgstr "认证令牌"
 
-#: audits/signals_handler.py:71 authentication/notifications.py:73
+#: audits/signal_handlers.py:71 authentication/notifications.py:73
 #: authentication/views/login.py:164 authentication/views/wecom.py:158
-#: notifications/backends/__init__.py:11 users/models/user.py:596
+#: notifications/backends/__init__.py:11 users/models/user.py:609
 msgid "WeCom"
 msgstr "企业微信"
 
-#: audits/signals_handler.py:72 authentication/views/dingtalk.py:160
+#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:160
 #: authentication/views/login.py:170 notifications/backends/__init__.py:12
-#: users/models/user.py:597
+#: users/models/user.py:610
 msgid "DingTalk"
 msgstr "钉钉"
 
-#: audits/signals_handler.py:106
+#: audits/signal_handlers.py:106
 msgid "User and Group"
 msgstr "用户与用户组"
 
-#: audits/signals_handler.py:107
+#: audits/signal_handlers.py:107
 #, python-brace-format
 msgid "{User} JOINED {UserGroup}"
 msgstr "{User} 加入 {UserGroup}"
 
-#: audits/signals_handler.py:108
+#: audits/signal_handlers.py:108
 #, python-brace-format
 msgid "{User} LEFT {UserGroup}"
 msgstr "{User} 离开 {UserGroup}"
 
-#: audits/signals_handler.py:111
+#: audits/signal_handlers.py:111
 msgid "Asset and SystemUser"
 msgstr "资产与系统用户"
 
-#: audits/signals_handler.py:112
+#: audits/signal_handlers.py:112
 #, python-brace-format
 msgid "{Asset} ADD {SystemUser}"
 msgstr "{Asset} 添加 {SystemUser}"
 
-#: audits/signals_handler.py:113
+#: audits/signal_handlers.py:113
 #, python-brace-format
 msgid "{Asset} REMOVE {SystemUser}"
 msgstr "{Asset} 移除 {SystemUser}"
 
-#: audits/signals_handler.py:116
+#: audits/signal_handlers.py:116
 msgid "Node and Asset"
 msgstr "节点与资产"
 
-#: audits/signals_handler.py:117
+#: audits/signal_handlers.py:117
 #, python-brace-format
 msgid "{Node} ADD {Asset}"
 msgstr "{Node} 添加 {Asset}"
 
-#: audits/signals_handler.py:118
+#: audits/signal_handlers.py:118
 #, python-brace-format
 msgid "{Node} REMOVE {Asset}"
 msgstr "{Node} 移除 {Asset}"
 
-#: audits/signals_handler.py:121
+#: audits/signal_handlers.py:121
 msgid "User asset permissions"
 msgstr "用户资产授权"
 
-#: audits/signals_handler.py:122
+#: audits/signal_handlers.py:122
 #, python-brace-format
 msgid "{AssetPermission} ADD {User}"
 msgstr "{AssetPermission} 添加 {User}"
 
-#: audits/signals_handler.py:123
+#: audits/signal_handlers.py:123
 #, python-brace-format
 msgid "{AssetPermission} REMOVE {User}"
 msgstr "{AssetPermission} 移除 {User}"
 
-#: audits/signals_handler.py:126
+#: audits/signal_handlers.py:126
 msgid "User group asset permissions"
 msgstr "用户组资产授权"
 
-#: audits/signals_handler.py:127
+#: audits/signal_handlers.py:127
 #, python-brace-format
 msgid "{AssetPermission} ADD {UserGroup}"
 msgstr "{AssetPermission} 添加 {UserGroup}"
 
-#: audits/signals_handler.py:128
+#: audits/signal_handlers.py:128
 #, python-brace-format
 msgid "{AssetPermission} REMOVE {UserGroup}"
 msgstr "{AssetPermission} 移除 {UserGroup}"
 
-#: audits/signals_handler.py:131 perms/models/asset_permission.py:29
+#: audits/signal_handlers.py:131 perms/models/asset_permission.py:29
 #: users/templates/users/_user_detail_nav_header.html:31
 msgid "Asset permission"
 msgstr "资产授权"
 
-#: audits/signals_handler.py:132
+#: audits/signal_handlers.py:132
 #, python-brace-format
 msgid "{AssetPermission} ADD {Asset}"
 msgstr "{AssetPermission} 添加 {Asset}"
 
-#: audits/signals_handler.py:133
+#: audits/signal_handlers.py:133
 #, python-brace-format
 msgid "{AssetPermission} REMOVE {Asset}"
 msgstr "{AssetPermission} 移除 {Asset}"
 
-#: audits/signals_handler.py:136
+#: audits/signal_handlers.py:136
 msgid "Node permission"
 msgstr "节点授权"
 
-#: audits/signals_handler.py:137
+#: audits/signal_handlers.py:137
 #, python-brace-format
 msgid "{AssetPermission} ADD {Node}"
 msgstr "{AssetPermission} 添加 {Node}"
 
-#: audits/signals_handler.py:138
+#: audits/signal_handlers.py:138
 #, python-brace-format
 msgid "{AssetPermission} REMOVE {Node}"
 msgstr "{AssetPermission} 移除 {Node}"
 
-#: audits/signals_handler.py:141
+#: audits/signal_handlers.py:141
 msgid "Asset permission and SystemUser"
 msgstr "资产授权与系统用户"
 
-#: audits/signals_handler.py:142
+#: audits/signal_handlers.py:142
 #, python-brace-format
 msgid "{AssetPermission} ADD {SystemUser}"
 msgstr "{AssetPermission} 添加 {SystemUser}"
 
-#: audits/signals_handler.py:143
+#: audits/signal_handlers.py:143
 #, python-brace-format
 msgid "{AssetPermission} REMOVE {SystemUser}"
 msgstr "{AssetPermission} 移除 {SystemUser}"
 
-#: audits/signals_handler.py:146
+#: audits/signal_handlers.py:146
 msgid "User application permissions"
 msgstr "用户应用授权"
 
-#: audits/signals_handler.py:147
+#: audits/signal_handlers.py:147
 #, python-brace-format
 msgid "{ApplicationPermission} ADD {User}"
 msgstr "{ApplicationPermission} 添加 {User}"
 
-#: audits/signals_handler.py:148
+#: audits/signal_handlers.py:148
 #, python-brace-format
 msgid "{ApplicationPermission} REMOVE {User}"
 msgstr "{ApplicationPermission} 移除 {User}"
 
-#: audits/signals_handler.py:151
+#: audits/signal_handlers.py:151
 msgid "User group application permissions"
 msgstr "用户组应用授权"
 
-#: audits/signals_handler.py:152
+#: audits/signal_handlers.py:152
 #, python-brace-format
 msgid "{ApplicationPermission} ADD {UserGroup}"
 msgstr "{ApplicationPermission} 添加 {UserGroup}"
 
-#: audits/signals_handler.py:153
+#: audits/signal_handlers.py:153
 #, python-brace-format
 msgid "{ApplicationPermission} REMOVE {UserGroup}"
 msgstr "{ApplicationPermission} 移除 {UserGroup}"
 
-#: audits/signals_handler.py:156 perms/models/application_permission.py:37
+#: audits/signal_handlers.py:156 perms/models/application_permission.py:37
 msgid "Application permission"
 msgstr "应用授权"
 
-#: audits/signals_handler.py:157
+#: audits/signal_handlers.py:157
 #, python-brace-format
 msgid "{ApplicationPermission} ADD {Application}"
 msgstr "{ApplicationPermission} 添加 {Application}"
 
-#: audits/signals_handler.py:158
+#: audits/signal_handlers.py:158
 #, python-brace-format
 msgid "{ApplicationPermission} REMOVE {Application}"
 msgstr "{ApplicationPermission} 移除 {Application}"
 
-#: audits/signals_handler.py:161
+#: audits/signal_handlers.py:161
 msgid "Application permission and SystemUser"
 msgstr "应用授权与系统用户"
 
-#: audits/signals_handler.py:162
+#: audits/signal_handlers.py:162
 #, python-brace-format
 msgid "{ApplicationPermission} ADD {SystemUser}"
 msgstr "{ApplicationPermission} 添加 {SystemUser}"
 
-#: audits/signals_handler.py:163
+#: audits/signal_handlers.py:163
 #, python-brace-format
 msgid "{ApplicationPermission} REMOVE {SystemUser}"
 msgstr "{ApplicationPermission} 移除 {SystemUser}"
@@ -1711,54 +1722,54 @@ msgstr "验证码无效: {}"
 msgid "Authentication"
 msgstr "认证"
 
-#: authentication/backends/api.py:70
+#: authentication/backends/drf.py:56
 msgid "Invalid signature header. No credentials provided."
 msgstr ""
 
-#: authentication/backends/api.py:73
+#: authentication/backends/drf.py:59
 msgid "Invalid signature header. Signature string should not contain spaces."
 msgstr ""
 
-#: authentication/backends/api.py:80
+#: authentication/backends/drf.py:66
 msgid "Invalid signature header. Format like AccessKeyId:Signature"
 msgstr ""
 
-#: authentication/backends/api.py:84
+#: authentication/backends/drf.py:70
 msgid ""
 "Invalid signature header. Signature string should not contain invalid "
 "characters."
 msgstr ""
 
-#: authentication/backends/api.py:104 authentication/backends/api.py:120
+#: authentication/backends/drf.py:90 authentication/backends/drf.py:106
 msgid "Invalid signature."
 msgstr ""
 
-#: authentication/backends/api.py:111
+#: authentication/backends/drf.py:97
 msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT"
 msgstr ""
 
-#: authentication/backends/api.py:116
+#: authentication/backends/drf.py:102
 msgid "Expired, more than 15 minutes"
 msgstr ""
 
-#: authentication/backends/api.py:123
+#: authentication/backends/drf.py:109
 msgid "User disabled."
 msgstr "用户已禁用"
 
-#: authentication/backends/api.py:141
+#: authentication/backends/drf.py:127
 msgid "Invalid token header. No credentials provided."
 msgstr ""
 
-#: authentication/backends/api.py:144
+#: authentication/backends/drf.py:130
 msgid "Invalid token header. Sign string should not contain spaces."
 msgstr ""
 
-#: authentication/backends/api.py:151
+#: authentication/backends/drf.py:137
 msgid ""
 "Invalid token header. Sign string should not contain invalid characters."
 msgstr ""
 
-#: authentication/backends/api.py:162
+#: authentication/backends/drf.py:148
 msgid "Invalid token or cache refreshed."
 msgstr ""
 
@@ -1792,11 +1803,11 @@ msgstr "禁用或失效"
 
 #: authentication/errors.py:33
 msgid "This account is inactive."
-msgstr "此账户已禁用"
+msgstr "此账号已禁用"
 
 #: authentication/errors.py:34
 msgid "This account is expired"
-msgstr "此账户已过期"
+msgstr "此账号已过期"
 
 #: authentication/errors.py:35
 msgid "Auth backend not match"
@@ -1876,15 +1887,15 @@ msgstr "该 时间段 不被允许登录"
 msgid "SSO auth closed"
 msgstr "SSO 认证关闭了"
 
-#: authentication/errors.py:300 authentication/mixins.py:364
+#: authentication/errors.py:300 authentication/mixins.py:372
 msgid "Your password is too simple, please change it for security"
 msgstr "你的密码过于简单,为了安全,请修改"
 
-#: authentication/errors.py:309 authentication/mixins.py:371
+#: authentication/errors.py:309 authentication/mixins.py:379
 msgid "You should to change your password before login"
 msgstr "登录完成前,请先修改密码"
 
-#: authentication/errors.py:318 authentication/mixins.py:378
+#: authentication/errors.py:318 authentication/mixins.py:386
 msgid "Your password has expired, please reset before logging in"
 msgstr "您的密码已过期,先修改再登录"
 
@@ -1976,11 +1987,11 @@ msgstr "设置手机号码启用"
 msgid "Clear phone number to disable"
 msgstr "清空手机号码禁用"
 
-#: authentication/mixins.py:314
+#: authentication/mixins.py:322
 msgid "The MFA type ({}) is not enabled"
 msgstr "该 MFA ({}) 方式没有启用"
 
-#: authentication/mixins.py:354
+#: authentication/mixins.py:362
 msgid "Please change your password"
 msgstr "请修改密码"
 
@@ -2000,11 +2011,15 @@ msgstr "过期时间"
 msgid "SSO token"
 msgstr ""
 
-#: authentication/models.py:62
+#: authentication/models.py:61
+msgid "Connection token"
+msgstr ""
+
+#: authentication/models.py:63
 msgid "Can add super connection token"
 msgstr "可以添加 超级连接Token"
 
-#: authentication/models.py:63
+#: authentication/models.py:64
 msgid "Can view connect token secret"
 msgstr "可以查看 连接Token 密文"
 
@@ -2305,7 +2320,7 @@ msgid "The FeiShu is already bound to another user"
 msgstr "该飞书已经绑定其他用户"
 
 #: authentication/views/feishu.py:148 authentication/views/login.py:176
-#: notifications/backends/__init__.py:14 users/models/user.py:598
+#: notifications/backends/__init__.py:14 users/models/user.py:611
 msgid "FeiShu"
 msgstr "飞书"
 
@@ -2337,7 +2352,7 @@ msgstr "正在跳转到 {} 认证"
 msgid "Please enable cookies and try again."
 msgstr "设置你的浏览器支持cookie"
 
-#: authentication/views/login.py:265
+#: authentication/views/login.py:263
 msgid ""
 "Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
 "                  Don't close this page"
@@ -2345,15 +2360,15 @@ msgstr ""
 "等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
 "                  不要关闭本页面"
 
-#: authentication/views/login.py:270
+#: authentication/views/login.py:268
 msgid "No ticket found"
 msgstr "没有发现工单"
 
-#: authentication/views/login.py:304
+#: authentication/views/login.py:302
 msgid "Logout success"
 msgstr "退出登录成功"
 
-#: authentication/views/login.py:305
+#: authentication/views/login.py:303
 msgid "Logout success, return login page"
 msgstr "退出登录成功,返回到登录页面"
 
@@ -2480,7 +2495,7 @@ msgstr "编码数据为 text"
 msgid "Encrypt field using Secret Key"
 msgstr "加密的字段"
 
-#: common/mixins/api/action.py:53
+#: common/mixins/api/action.py:52
 msgid "Request file format may be wrong"
 msgstr "上传的文件格式错误 或 其它类型资源的文件"
 
@@ -2492,6 +2507,10 @@ msgstr "忽略的"
 msgid "discard time"
 msgstr "忽略时间"
 
+#: common/models.py:8
+msgid "Can view resource statistics"
+msgstr "可以查看资源统计"
+
 #: common/sdk/im/exceptions.py:23
 msgid "Network error, please contact system administrator"
 msgstr "网络错误,请联系系统管理员"
@@ -2558,11 +2577,11 @@ msgstr "手机号格式不正确"
 
 #: jumpserver/conf.py:292
 msgid "Create account successfully"
-msgstr "创建账户成功"
+msgstr "创建账号成功"
 
 #: jumpserver/conf.py:294
 msgid "Your account has been created successfully"
-msgstr "你的账户已创建成功"
+msgstr "你的账号已创建成功"
 
 #: jumpserver/context_processor.py:17
 msgid "JumpServer Open Source Bastion Host"
@@ -2600,7 +2619,7 @@ msgid "Notifications"
 msgstr "通知"
 
 #: notifications/backends/__init__.py:10 users/forms/profile.py:101
-#: users/models/user.py:539
+#: users/models/user.py:552
 msgid "Email"
 msgstr "邮件"
 
@@ -2617,8 +2636,8 @@ msgid "Not has host {} permission"
 msgstr "没有该主机 {} 权限"
 
 #: ops/apps.py:9 ops/notifications.py:16
-msgid "Operations"
-msgstr "运维"
+msgid "App ops"
+msgstr "作业中心"
 
 #: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162
 #: settings/serializers/auth/ldap.py:66
@@ -2808,8 +2827,8 @@ msgid "The organization have resource ({}) cannot be deleted"
 msgstr "组织存在资源 ({}) 不能被删除"
 
 #: orgs/apps.py:7
-msgid "Organizations"
-msgstr "组织"
+msgid "App organizations"
+msgstr "组织管理"
 
 #: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:27
 #: orgs/models.py:193 rbac/const.py:7 rbac/models/rolebinding.py:40
@@ -2823,16 +2842,16 @@ msgstr "全局组织"
 
 #: orgs/models.py:29
 msgid "Can view root org"
-msgstr ""
+msgstr "可以查看全局组织"
 
 #: orgs/models.py:198 rbac/models/role.py:46 rbac/models/rolebinding.py:36
-#: users/models/user.py:547 users/templates/users/_select_user_modal.html:15
+#: users/models/user.py:560 users/templates/users/_select_user_modal.html:15
 msgid "Role"
 msgstr "角色"
 
-#: perms/apps.py:9 rbac/models/role.py:34
-msgid "Permissions"
-msgstr "授权"
+#: perms/apps.py:9
+msgid "App permissions"
+msgstr "授权管理"
 
 #: perms/exceptions.py:9
 msgid "The administrator is modifying permissions. Please wait"
@@ -2913,7 +2932,7 @@ msgstr "剪贴板复制粘贴"
 #: perms/models/base.py:90
 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:58
 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:60
-#: users/models/user.py:579
+#: users/models/user.py:592
 msgid "Date expired"
 msgstr "失效日期"
 
@@ -2956,15 +2975,15 @@ msgstr "组织 ({}) 的应用授权"
 #: perms/serializers/application/permission.py:20
 #: perms/serializers/application/permission.py:41
 #: perms/serializers/asset/permission.py:19
-#: perms/serializers/asset/permission.py:45 users/serializers/user.py:134
+#: perms/serializers/asset/permission.py:45 users/serializers/user.py:133
 msgid "Is valid"
-msgstr "账户是否有效"
+msgstr "账号是否有效"
 
 #: perms/serializers/application/permission.py:21
 #: perms/serializers/application/permission.py:40
 #: perms/serializers/asset/permission.py:20
-#: perms/serializers/asset/permission.py:44 users/serializers/user.py:83
-#: users/serializers/user.py:136
+#: perms/serializers/asset/permission.py:44 users/serializers/user.py:82
+#: users/serializers/user.py:135
 msgid "Is expired"
 msgstr "已过期"
 
@@ -3034,31 +3053,35 @@ msgstr ""
 msgid "Internal role, can't be update"
 msgstr ""
 
+#: rbac/api/rolebinding.py:46
+msgid "{} at least one system role"
+msgstr "{} 至少有一个系统角色"
+
 #: rbac/apps.py:7
 msgid "RBAC"
 msgstr "RBAC"
 
-#: rbac/builtin.py:78
+#: rbac/builtin.py:80
 msgid "SystemAdmin"
 msgstr "系统管理员"
 
-#: rbac/builtin.py:81
+#: rbac/builtin.py:83
 msgid "SystemAuditor"
 msgstr "系统审计员"
 
-#: rbac/builtin.py:84
+#: rbac/builtin.py:86
 msgid "SystemComponent"
 msgstr "系统组件"
 
-#: rbac/builtin.py:90
+#: rbac/builtin.py:92
 msgid "OrgAdmin"
 msgstr "组织管理员"
 
-#: rbac/builtin.py:93
+#: rbac/builtin.py:95
 msgid "OrgAuditor"
 msgstr "组织审计员"
 
-#: rbac/builtin.py:96
+#: rbac/builtin.py:98
 msgid "OrgUser"
 msgstr "组织用户"
 
@@ -3067,22 +3090,18 @@ msgid "Menu permission"
 msgstr "菜单授权"
 
 #: rbac/models/menu.py:15
-msgid "Console view"
-msgstr "控制台"
+msgid "view console view"
+msgstr "查看控制台"
 
 #: rbac/models/menu.py:16
-msgid "Audit view"
-msgstr "安全审计"
+msgid "view audit view"
+msgstr "查看安全审计"
 
 #: rbac/models/menu.py:17
-msgid "Workspace view"
-msgstr "控制台"
+msgid "view workspace view"
+msgstr "查看工作台"
 
-#: rbac/models/permission.py:127
-msgid "View"
-msgstr "视图"
-
-#: rbac/models/permission.py:210
+#: rbac/models/permission.py:22
 msgid "Permission"
 msgstr "授权"
 
@@ -3090,6 +3109,10 @@ msgstr "授权"
 msgid "Scope"
 msgstr "范围"
 
+#: rbac/models/role.py:34
+msgid "Permissions"
+msgstr "授权"
+
 #: rbac/models/role.py:36
 msgid "Built-in"
 msgstr "内置"
@@ -3116,7 +3139,7 @@ msgstr "用户最后一个角色,不能删除,你可以将用户从组织移
 msgid "Organization role binding"
 msgstr "组织角色绑定"
 
-#: rbac/models/rolebinding.py:133
+#: rbac/models/rolebinding.py:132
 msgid "System role binding"
 msgstr "系统角色绑定"
 
@@ -3140,6 +3163,62 @@ msgstr "角色显示"
 msgid "Has bound this role"
 msgstr "已经绑定"
 
+#: rbac/tree.py:16 rbac/tree.py:17
+msgid "All permissions"
+msgstr "所有权限"
+
+#: rbac/tree.py:24
+msgid "Console view"
+msgstr "控制台"
+
+#: rbac/tree.py:28
+msgid "Workspace view"
+msgstr "工作台"
+
+#: rbac/tree.py:32
+msgid "Audit view"
+msgstr "安全审计"
+
+#: rbac/tree.py:36 settings/models.py:140
+msgid "System setting"
+msgstr "系统设置"
+
+#: rbac/tree.py:40
+msgid "Other"
+msgstr ""
+
+#: rbac/tree.py:59
+msgid "Accounts"
+msgstr "账号管理"
+
+#: rbac/tree.py:76
+msgid "Session audits"
+msgstr "会话审计"
+
+#: rbac/tree.py:104
+msgid "Cloud import"
+msgstr "云同步"
+
+#: rbac/tree.py:109
+msgid "Backup account"
+msgstr "备份账号"
+
+#: rbac/tree.py:114
+msgid "Gather account"
+msgstr "收集账号"
+
+#: rbac/tree.py:119
+msgid "App change auth"
+msgstr "应用改密"
+
+#: rbac/tree.py:124
+msgid "Asset change auth"
+msgstr "资产改密"
+
+#: rbac/tree.py:252
+msgid "View"
+msgstr "查看"
+
 #: settings/api/alibaba_sms.py:31 settings/api/tencent_sms.py:35
 msgid "test_phone is required"
 msgstr "测试手机号 该字段是必填项。"
@@ -3178,10 +3257,6 @@ msgstr "成功导入 {} 个用户 ( 组织: {} )"
 msgid "Settings"
 msgstr "系统设置"
 
-#: settings/models.py:196
-msgid "System setting"
-msgstr "系统设置"
-
 #: settings/serializers/auth/base.py:10
 msgid "CAS Auth"
 msgstr "CAS 认证"
@@ -4179,11 +4254,11 @@ msgid ""
 "            "
 msgstr ""
 "\n"
-"                您的账户已经过期,请联系管理员。            "
+"                您的账号已经过期,请联系管理员。            "
 
 #: templates/_message.html:13
 msgid "Your account will at"
-msgstr "您的账户将于"
+msgstr "您的账号将于"
 
 #: templates/_message.html:13 templates/_message.html:30
 msgid "expired. "
@@ -4465,19 +4540,19 @@ msgstr "Jmservisor 是在 windows 远程应用发布服务器中用来拉起远
 msgid "Filters"
 msgstr "过滤"
 
-#: terminal/api/session.py:250
+#: terminal/api/session.py:211
 msgid "Session does not exist: {}"
 msgstr "会话不存在: {}"
 
-#: terminal/api/session.py:253
+#: terminal/api/session.py:214
 msgid "Session is finished or the protocol not supported"
 msgstr "会话已经完成或协议不支持"
 
-#: terminal/api/session.py:258
+#: terminal/api/session.py:219
 msgid "User does not exist: {}"
 msgstr "用户不存在: {}"
 
-#: terminal/api/session.py:266
+#: terminal/api/session.py:227
 msgid "User does not have permission"
 msgstr "用户没有权限"
 
@@ -4511,7 +4586,7 @@ msgstr "测试成功"
 
 #: terminal/api/storage.py:125
 msgid "Test failure: Account invalid"
-msgstr "测试失败: 账户无效"
+msgstr "测试失败: 账号无效"
 
 #: terminal/api/terminal.py:39
 msgid "Have online sessions"
@@ -4569,20 +4644,20 @@ msgstr "时间戳"
 msgid "Remote Address"
 msgstr "远端地址"
 
-#: terminal/const.py:32
+#: terminal/const.py:33
 msgid "Critical"
 msgstr "严重"
 
-#: terminal/const.py:33
+#: terminal/const.py:34
 msgid "High"
 msgstr "较高"
 
-#: terminal/const.py:34 users/templates/users/reset_password.html:50
+#: terminal/const.py:35 users/templates/users/reset_password.html:50
 #: users/templates/users/user_password_update.html:104
 msgid "Normal"
 msgstr "正常"
 
-#: terminal/const.py:35
+#: terminal/const.py:36
 msgid "Offline"
 msgstr "离线"
 
@@ -4598,43 +4673,47 @@ msgstr "存储无效"
 msgid "Command record"
 msgstr "命令记录"
 
-#: terminal/models/replay.py:13
+#: terminal/models/replay.py:12
+msgid "Session replay"
+msgstr "会话录像"
+
+#: terminal/models/replay.py:14
 msgid "Can upload session replay"
 msgstr "可以上传会话录像"
 
-#: terminal/models/replay.py:14
+#: terminal/models/replay.py:15
 msgid "Can download session replay"
 msgstr "可以下载会话录像"
 
-#: terminal/models/session.py:47 terminal/models/sharing.py:87
+#: terminal/models/session.py:48 terminal/models/sharing.py:87
 msgid "Login from"
 msgstr "登录来源"
 
-#: terminal/models/session.py:51
+#: terminal/models/session.py:52
 msgid "Replay"
 msgstr "回放"
 
-#: terminal/models/session.py:56
+#: terminal/models/session.py:57
 msgid "Date end"
 msgstr "结束日期"
 
-#: terminal/models/session.py:241
+#: terminal/models/session.py:242
 msgid "Session record"
 msgstr "会话"
 
-#: terminal/models/session.py:243
+#: terminal/models/session.py:244
 msgid "Can monitor session"
 msgstr "可以监控会话"
 
-#: terminal/models/session.py:244
+#: terminal/models/session.py:245
 msgid "Can share session"
 msgstr "可以分享会话"
 
-#: terminal/models/session.py:245
+#: terminal/models/session.py:246
 msgid "Can terminate session"
 msgstr "可以中断会话"
 
-#: terminal/models/session.py:246
+#: terminal/models/session.py:247
 msgid "Can validate session action perm"
 msgstr "可以验证会话动作权限"
 
@@ -4811,12 +4890,13 @@ msgstr "端点无效: 移除路径 `{}`"
 msgid "Bucket"
 msgstr "桶名称"
 
-#: terminal/serializers/storage.py:34 users/models/user.py:571
+#: terminal/serializers/storage.py:34 users/models/user.py:584
 msgid "Secret key"
 msgstr "密钥"
 
 #: terminal/serializers/storage.py:39 terminal/serializers/storage.py:51
 #: terminal/serializers/storage.py:81 terminal/serializers/storage.py:91
+#: terminal/serializers/storage.py:99
 msgid "Endpoint"
 msgstr "端点"
 
@@ -4824,43 +4904,43 @@ msgstr "端点"
 msgid "Region"
 msgstr "地域"
 
-#: terminal/serializers/storage.py:101
+#: terminal/serializers/storage.py:110
 msgid "Container name"
 msgstr "容器名称"
 
-#: terminal/serializers/storage.py:103
+#: terminal/serializers/storage.py:112
 msgid "Account name"
-msgstr "账户名称"
+msgstr "账号名称"
 
-#: terminal/serializers/storage.py:104
+#: terminal/serializers/storage.py:113
 msgid "Account key"
-msgstr "账户密钥"
+msgstr "账号密钥"
 
-#: terminal/serializers/storage.py:107
+#: terminal/serializers/storage.py:116
 msgid "Endpoint suffix"
 msgstr "端点后缀"
 
-#: terminal/serializers/storage.py:128
+#: terminal/serializers/storage.py:138
 msgid "The address format is incorrect"
 msgstr "地址格式不正确"
 
-#: terminal/serializers/storage.py:135
+#: terminal/serializers/storage.py:145
 msgid "Host invalid"
 msgstr "主机无效"
 
-#: terminal/serializers/storage.py:138
+#: terminal/serializers/storage.py:148
 msgid "Port invalid"
 msgstr "端口无效"
 
-#: terminal/serializers/storage.py:154
+#: terminal/serializers/storage.py:164
 msgid "Index"
 msgstr "索引"
 
-#: terminal/serializers/storage.py:156
+#: terminal/serializers/storage.py:166
 msgid "Doc type"
 msgstr "文档类型"
 
-#: terminal/serializers/storage.py:158
+#: terminal/serializers/storage.py:168
 msgid "Ignore Certificate Verification"
 msgstr "忽略证书认证"
 
@@ -5087,7 +5167,7 @@ msgid "Body"
 msgstr "内容"
 
 #: tickets/models/flow.py:19 tickets/models/flow.py:61
-#: tickets/models/ticket.py:29
+#: tickets/models/ticket.py:30
 msgid "Approve level"
 msgstr "审批级别"
 
@@ -5107,50 +5187,54 @@ msgstr "工单批准信息"
 msgid "Ticket flow"
 msgstr "工单流程"
 
-#: tickets/models/ticket.py:34
+#: tickets/models/ticket.py:35
 msgid "Ticket step"
 msgstr "工单步骤"
 
-#: tickets/models/ticket.py:45
+#: tickets/models/ticket.py:46
 msgid "Ticket assignee"
 msgstr "工单受理人"
 
-#: tickets/models/ticket.py:52
+#: tickets/models/ticket.py:128
 msgid "Title"
 msgstr "标题"
 
-#: tickets/models/ticket.py:60
+#: tickets/models/ticket.py:136
 msgid "State"
 msgstr "状态"
 
-#: tickets/models/ticket.py:68
+#: tickets/models/ticket.py:144
 msgid "Approval step"
 msgstr "审批步骤"
 
-#: tickets/models/ticket.py:73
+#: tickets/models/ticket.py:149
 msgid "Applicant"
 msgstr "申请人"
 
-#: tickets/models/ticket.py:75
+#: tickets/models/ticket.py:151
 msgid "Applicant display"
 msgstr "申请人名称"
 
-#: tickets/models/ticket.py:76
+#: tickets/models/ticket.py:152
 msgid "Process"
 msgstr "流程"
 
-#: tickets/models/ticket.py:81
+#: tickets/models/ticket.py:157
 msgid "TicketFlow"
 msgstr "工单流程"
 
-#: tickets/models/ticket.py:87
+#: tickets/models/ticket.py:163
 msgid "Ticket"
 msgstr "工单管理"
 
-#: tickets/models/ticket.py:301
+#: tickets/models/ticket.py:311
 msgid "Please try again"
 msgstr "请再次尝试"
 
+#: tickets/models/ticket.py:319
+msgid "Super ticket"
+msgstr "超级工单"
+
 #: tickets/notifications.py:57
 msgid "Your has a new ticket, applicant - {}"
 msgstr "你有一个新的工单, 申请人 - {}"
@@ -5167,6 +5251,10 @@ msgstr "你的工单已被处理, 处理人 - {}"
 msgid "Ticket has processed - {} ({})"
 msgstr "你的工单已被处理, 处理人 - {} ({})"
 
+#: tickets/serializers/super_ticket.py:11
+msgid "Processor"
+msgstr "处理人"
+
 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:18
 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:19
 msgid "Apply name"
@@ -5298,7 +5386,7 @@ msgstr "当前组织已存在该类型"
 msgid "Click here to review"
 msgstr "点击查看"
 
-#: users/api/user.py:179
+#: users/api/user.py:175
 msgid "Could not reset self otp, use profile reset instead"
 msgstr "不能在该页面重置 MFA 多因子认证, 请去个人信息页面重置"
 
@@ -5353,7 +5441,7 @@ msgid ""
 "and key sensitive information properly. (for example: setting complex "
 "password, enabling MFA)"
 msgstr ""
-"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
+"为了保护您和公司的安全,请妥善保管您的账号、密码和密钥等重要敏感信息;(如:"
 "设置复杂密码,并启用 MFA 多因子认证)"
 
 #: users/forms/profile.py:76
@@ -5405,7 +5493,7 @@ msgstr "不能和原来的密钥相同"
 msgid "Not a valid ssh public key"
 msgstr "SSH密钥不合法"
 
-#: users/forms/profile.py:160 users/models/user.py:568
+#: users/forms/profile.py:160 users/models/user.py:581
 #: users/templates/users/user_password_update.html:48
 msgid "Public key"
 msgstr "SSH公钥"
@@ -5418,55 +5506,55 @@ msgstr "强制启用"
 msgid "Local"
 msgstr "数据库"
 
-#: users/models/user.py:549 users/serializers/user.py:135
+#: users/models/user.py:562 users/serializers/user.py:134
 msgid "Is service account"
 msgstr "服务账号"
 
-#: users/models/user.py:551
+#: users/models/user.py:564
 msgid "Avatar"
 msgstr "头像"
 
-#: users/models/user.py:554
+#: users/models/user.py:567
 msgid "Wechat"
 msgstr "微信"
 
-#: users/models/user.py:565
+#: users/models/user.py:578
 msgid "Private key"
 msgstr "ssh私钥"
 
-#: users/models/user.py:587
+#: users/models/user.py:600
 msgid "Source"
 msgstr "来源"
 
-#: users/models/user.py:591
+#: users/models/user.py:604
 msgid "Date password last updated"
 msgstr "最后更新密码日期"
 
-#: users/models/user.py:594
+#: users/models/user.py:607
 msgid "Need update password"
 msgstr "需要更新密码"
 
-#: users/models/user.py:759
+#: users/models/user.py:771
 msgid "Can invite user"
 msgstr "可以邀请用户"
 
-#: users/models/user.py:760
+#: users/models/user.py:772
 msgid "Can remove user"
 msgstr "可以移除用户"
 
-#: users/models/user.py:761
+#: users/models/user.py:773
 msgid "Can match user"
 msgstr "可以匹配用户"
 
-#: users/models/user.py:770
+#: users/models/user.py:782
 msgid "Administrator"
 msgstr "管理员"
 
-#: users/models/user.py:773
+#: users/models/user.py:785
 msgid "Administrator is the super user of system"
 msgstr "Administrator是初始的超级管理员"
 
-#: users/models/user.py:798
+#: users/models/user.py:810
 msgid "User password history"
 msgstr "用户密码历史"
 
@@ -5517,97 +5605,97 @@ msgstr "新密码不能是最近 {} 次的密码"
 msgid "The newly set password is inconsistent"
 msgstr "两次密码不一致"
 
-#: users/serializers/profile.py:141 users/serializers/user.py:133
+#: users/serializers/profile.py:141 users/serializers/user.py:132
 msgid "Is first login"
 msgstr "首次登录"
 
-#: users/serializers/user.py:25 users/serializers/user.py:31
+#: users/serializers/user.py:24 users/serializers/user.py:30
 msgid "System roles"
 msgstr "系统角色"
 
-#: users/serializers/user.py:29 users/serializers/user.py:32
+#: users/serializers/user.py:28 users/serializers/user.py:31
 msgid "Org roles"
 msgstr "组织角色"
 
-#: users/serializers/user.py:75
+#: users/serializers/user.py:74
 #: xpack/plugins/change_auth_plan/models/base.py:35
 #: xpack/plugins/change_auth_plan/serializers/base.py:22
 msgid "Password strategy"
 msgstr "密码策略"
 
-#: users/serializers/user.py:77
+#: users/serializers/user.py:76
 msgid "MFA enabled"
 msgstr "MFA"
 
-#: users/serializers/user.py:78
+#: users/serializers/user.py:77
 msgid "MFA force enabled"
 msgstr "强制 MFA"
 
-#: users/serializers/user.py:80
+#: users/serializers/user.py:79
 msgid "MFA level display"
 msgstr "MFA 等级名称"
 
-#: users/serializers/user.py:82
+#: users/serializers/user.py:81
 msgid "Login blocked"
 msgstr "登录被阻塞"
 
-#: users/serializers/user.py:85
+#: users/serializers/user.py:84
 msgid "Can public key authentication"
 msgstr "能否公钥认证"
 
-#: users/serializers/user.py:137
+#: users/serializers/user.py:136
 msgid "Avatar url"
 msgstr "头像路径"
 
-#: users/serializers/user.py:139
+#: users/serializers/user.py:138
 msgid "Groups name"
 msgstr "用户组名"
 
-#: users/serializers/user.py:140
+#: users/serializers/user.py:139
 msgid "Source name"
 msgstr "用户来源名"
 
-#: users/serializers/user.py:141
+#: users/serializers/user.py:140
 msgid "Organization role name"
 msgstr "组织角色名称"
 
-#: users/serializers/user.py:142
+#: users/serializers/user.py:141
 msgid "Super role name"
 msgstr "超级角色名称"
 
-#: users/serializers/user.py:143
+#: users/serializers/user.py:142
 msgid "Total role name"
 msgstr "汇总角色名称"
 
-#: users/serializers/user.py:145
+#: users/serializers/user.py:144
 msgid "Is wecom bound"
 msgstr "是否绑定了企业微信"
 
-#: users/serializers/user.py:146
+#: users/serializers/user.py:145
 msgid "Is dingtalk bound"
 msgstr "是否绑定了钉钉"
 
-#: users/serializers/user.py:147
+#: users/serializers/user.py:146
 msgid "Is feishu bound"
 msgstr "是否绑定了飞书"
 
-#: users/serializers/user.py:148
+#: users/serializers/user.py:147
 msgid "Is OTP bound"
 msgstr "是否绑定了虚拟 MFA"
 
-#: users/serializers/user.py:150
+#: users/serializers/user.py:149
 msgid "System role name"
 msgstr "系统角色名称"
 
-#: users/serializers/user.py:246
+#: users/serializers/user.py:235
 msgid "Select users"
 msgstr "选择用户"
 
-#: users/serializers/user.py:247
+#: users/serializers/user.py:236
 msgid "For security, only list several users"
 msgstr "为了安全,仅列出几个用户"
 
-#: users/serializers/user.py:280
+#: users/serializers/user.py:269
 msgid "name not unique"
 msgstr "名称重复"
 
@@ -5976,7 +6064,7 @@ msgstr "应用"
 
 #: xpack/plugins/change_auth_plan/models/app.py:156
 msgid "Application change auth plan task"
-msgstr "用用改密计划任务"
+msgstr "应用改密计划任务"
 
 #: xpack/plugins/change_auth_plan/models/app.py:180
 #: xpack/plugins/change_auth_plan/models/asset.py:263
@@ -6220,13 +6308,10 @@ msgstr "云管中心"
 msgid "Provider"
 msgstr "云服务商"
 
-#: xpack/plugins/cloud/models.py:39
-msgid "Cloud account"
-msgstr "云账号"
-
-#: xpack/plugins/cloud/models.py:82 xpack/plugins/cloud/serializers/task.py:66
+#: xpack/plugins/cloud/models.py:39 xpack/plugins/cloud/models.py:82
+#: xpack/plugins/cloud/serializers/task.py:66
 msgid "Account"
-msgstr "账户"
+msgstr "账号"
 
 #: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:37
 msgid "Regions"
@@ -6483,7 +6568,7 @@ msgstr "用户域"
 
 #: xpack/plugins/cloud/serializers/account_attrs.py:113
 msgid "Service account key"
-msgstr "账户密钥"
+msgstr "服务账号密钥"
 
 #: xpack/plugins/cloud/serializers/account_attrs.py:114
 msgid "The file is in JSON format"
@@ -6517,7 +6602,7 @@ msgstr "定时执行"
 
 #: xpack/plugins/cloud/utils.py:68
 msgid "Account unavailable"
-msgstr "账户无效"
+msgstr "账号无效"
 
 #: xpack/plugins/gathered_user/meta.py:11
 msgid "Gathered user"
@@ -6603,6 +6688,9 @@ msgstr "旗舰版"
 msgid "Community edition"
 msgstr "社区版"
 
+#~ msgid "AppAsset"
+#~ msgstr "资产管理"
+
 #~ msgid "User and Organization"
 #~ msgstr "用户与组织"
 
@@ -6660,7 +6748,7 @@ msgstr "社区版"
 #~ msgstr "XPack"
 
 #~ msgid "Account list"
-#~ msgstr "账户列表"
+#~ msgstr "账号列表"
 
 #~ msgid "Sync instance"
 #~ msgstr "同步实例"
diff --git a/apps/notifications/apps.py b/apps/notifications/apps.py
index 1c64497a1..306f2b55b 100644
--- a/apps/notifications/apps.py
+++ b/apps/notifications/apps.py
@@ -7,6 +7,6 @@ class NotificationsConfig(AppConfig):
     verbose_name = _('Notifications')
 
     def ready(self):
-        from . import signals_handler
+        from . import signal_handlers
         from . import notifications
         super().ready()
diff --git a/apps/notifications/signals_handler.py b/apps/notifications/signal_handlers.py
similarity index 100%
rename from apps/notifications/signals_handler.py
rename to apps/notifications/signal_handlers.py
diff --git a/apps/notifications/ws.py b/apps/notifications/ws.py
index e3d48f79d..391bb659d 100644
--- a/apps/notifications/ws.py
+++ b/apps/notifications/ws.py
@@ -5,7 +5,7 @@ from channels.generic.websocket import JsonWebsocketConsumer
 from common.utils import get_logger
 from common.db.utils import safe_db_connection
 from .site_msg import SiteMessageUtil
-from .signals_handler import new_site_msg_chan
+from .signal_handlers import new_site_msg_chan
 
 logger = get_logger(__name__)
 
diff --git a/apps/ops/apps.py b/apps/ops/apps.py
index 43496ebf2..819a23002 100644
--- a/apps/ops/apps.py
+++ b/apps/ops/apps.py
@@ -6,13 +6,13 @@ from django.apps import AppConfig
 
 class OpsConfig(AppConfig):
     name = 'ops'
-    verbose_name = _('Operations')
+    verbose_name = _('App ops')
 
     def ready(self):
         from orgs.models import Organization
         from orgs.utils import set_current_org
         set_current_org(Organization.root())
         from .celery import signal_handler
-        from . import signals_handler
+        from . import signal_handlers
         from . import notifications
         super().ready()
diff --git a/apps/ops/notifications.py b/apps/ops/notifications.py
index e76c2037d..86c7ab188 100644
--- a/apps/ops/notifications.py
+++ b/apps/ops/notifications.py
@@ -13,7 +13,7 @@ __all__ = ('ServerPerformanceMessage', 'ServerPerformanceCheckUtil')
 
 class ServerPerformanceMessage(SystemMessage):
     category = 'Operations'
-    category_label = _('Operations')
+    category_label = _('App ops')
     message_type_label = _('Server performance')
 
     def __init__(self, terms_with_errors):
diff --git a/apps/ops/signals_handler.py b/apps/ops/signal_handlers.py
similarity index 100%
rename from apps/ops/signals_handler.py
rename to apps/ops/signal_handlers.py
diff --git a/apps/orgs/apps.py b/apps/orgs/apps.py
index 872fb8eb4..14a48203c 100644
--- a/apps/orgs/apps.py
+++ b/apps/orgs/apps.py
@@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy as _
 
 class OrgsConfig(AppConfig):
     name = 'orgs'
-    verbose_name = _('Organizations')
+    verbose_name = _('App organizations')
 
     def ready(self):
-        from . import signals_handler
+        from . import signal_handlers
diff --git a/apps/orgs/signals_handler/__init__.py b/apps/orgs/signal_handlers/__init__.py
similarity index 100%
rename from apps/orgs/signals_handler/__init__.py
rename to apps/orgs/signal_handlers/__init__.py
diff --git a/apps/orgs/signals_handler/cache.py b/apps/orgs/signal_handlers/cache.py
similarity index 100%
rename from apps/orgs/signals_handler/cache.py
rename to apps/orgs/signal_handlers/cache.py
diff --git a/apps/orgs/signals_handler/common.py b/apps/orgs/signal_handlers/common.py
similarity index 100%
rename from apps/orgs/signals_handler/common.py
rename to apps/orgs/signal_handlers/common.py
diff --git a/apps/perms/apps.py b/apps/perms/apps.py
index 664765029..432c194cd 100644
--- a/apps/perms/apps.py
+++ b/apps/perms/apps.py
@@ -6,9 +6,9 @@ from django.utils.translation import ugettext_lazy as _
 
 class PermsConfig(AppConfig):
     name = 'perms'
-    verbose_name = _('Permissions')
+    verbose_name = _('App permissions')
 
     def ready(self):
         super().ready()
-        from . import signals_handler
+        from . import signal_handlers
         from . import notifications
diff --git a/apps/perms/serializers/application/permission.py b/apps/perms/serializers/application/permission.py
index f3e6443a0..c7ebdf769 100644
--- a/apps/perms/serializers/application/permission.py
+++ b/apps/perms/serializers/application/permission.py
@@ -43,7 +43,7 @@ class ApplicationPermissionSerializer(BasePermissionSerializer):
             'users_amount': {'label': _('Users amount')},
             'user_groups_amount': {'label': _('User groups amount')},
             'system_users_amount': {'label': _('System users amount')},
-            'applications_amount': {'label': _('Applications amount')},
+            'applications_amount': {'label': _('Apps amount')},
         }
 
     def _filter_actions_choices(self, choices):
diff --git a/apps/perms/signals_handler/__init__.py b/apps/perms/signal_handlers/__init__.py
similarity index 100%
rename from apps/perms/signals_handler/__init__.py
rename to apps/perms/signal_handlers/__init__.py
diff --git a/apps/perms/signals_handler/app_permission.py b/apps/perms/signal_handlers/app_permission.py
similarity index 100%
rename from apps/perms/signals_handler/app_permission.py
rename to apps/perms/signal_handlers/app_permission.py
diff --git a/apps/perms/signals_handler/asset_permission.py b/apps/perms/signal_handlers/asset_permission.py
similarity index 100%
rename from apps/perms/signals_handler/asset_permission.py
rename to apps/perms/signal_handlers/asset_permission.py
diff --git a/apps/perms/signals_handler/refresh_perms.py b/apps/perms/signal_handlers/refresh_perms.py
similarity index 100%
rename from apps/perms/signals_handler/refresh_perms.py
rename to apps/perms/signal_handlers/refresh_perms.py
diff --git a/apps/rbac/api/rolebinding.py b/apps/rbac/api/rolebinding.py
index 7d31d6415..3e9165323 100644
--- a/apps/rbac/api/rolebinding.py
+++ b/apps/rbac/api/rolebinding.py
@@ -1,9 +1,10 @@
-
+from django.utils.translation import ugettext as _
 from django.db.models import F, Value
 from django.db.models.functions import Concat
 
 from orgs.mixins.api import OrgBulkModelViewSet
 from orgs.utils import current_org
+from common.exceptions import JMSException
 from .. import serializers
 from ..models import RoleBinding, SystemRoleBinding, OrgRoleBinding
 
@@ -22,7 +23,7 @@ class RoleBindingViewSet(OrgBulkModelViewSet):
     ]
 
     def get_queryset(self):
-        queryset = super().get_queryset()\
+        queryset = super().get_queryset() \
             .prefetch_related('user', 'role') \
             .annotate(
                 user_display=Concat(
@@ -38,6 +39,17 @@ class SystemRoleBindingViewSet(RoleBindingViewSet):
     model = SystemRoleBinding
     serializer_class = serializers.SystemRoleBindingSerializer
 
+    def perform_destroy(self, instance):
+        user = instance.user
+        role_qs = self.model.objects.filter(user=user)
+        if role_qs.count() == 1:
+            msg = _('{} at least one system role').format(user)
+            raise JMSException(
+                code='system_role_delete_error',
+                detail=msg
+            )
+        super().perform_destroy(instance)
+
 
 class OrgRoleBindingViewSet(RoleBindingViewSet):
     model = OrgRoleBinding
diff --git a/apps/rbac/const.py b/apps/rbac/const.py
index b7c76a261..084b82cf1 100644
--- a/apps/rbac/const.py
+++ b/apps/rbac/const.py
@@ -16,10 +16,12 @@ exclude_permissions = (
     ('contenttypes', '*', '*', '*'),
     ('django_cas_ng', '*', '*', '*'),
     ('django_celery_beat', '*', '*', '*'),
+    ('jms_oidc_rp', '*', '*', '*'),
     ('admin', '*', '*', '*'),
     ('sessions', '*', '*', '*'),
     ('notifications', '*', '*', '*'),
 
+    ('applications', 'applicationuser', '*', '*'),
     ('applications', 'historicalaccount', '*', '*'),
     ('applications', 'databaseapp', '*', '*'),
     ('applications', 'k8sapp', '*', '*'),
@@ -51,9 +53,15 @@ exclude_permissions = (
     ('audits', 'userloginlog', 'change,delete,change', 'userloginlog'),
     ('audits', 'ftplog', 'change,delete', 'ftplog'),
     ('terminal', 'session', 'delete', 'session'),
+    ('terminal', 'session', 'delete,change', 'command'),
     ('tickets', 'ticket', '*', '*'),
     ('users', 'userpasswordhistory', '*', '*'),
-    ('xpack', 'interface', 'add,delete', 'interface'),
+    ('xpack', 'interface', '*', '*'),
+    ('xpack', 'license', '*', '*'),
+    ('common', 'permission', 'add,delete,view,change', 'permission'),
+    ('terminal', 'command', 'delete,change', 'command'),
+    ('terminal', 'sessionjoinrecord', 'delete', 'sessionjoinrecord'),
+    ('terminal', 'sessionreplay', 'delete', 'sessionreplay'),
 )
 
 
diff --git a/apps/rbac/models/permission.py b/apps/rbac/models/permission.py
index 0ba84f315..4b506168b 100644
--- a/apps/rbac/models/permission.py
+++ b/apps/rbac/models/permission.py
@@ -1,12 +1,8 @@
-from typing import Callable
-
-from django.db.models import F, Count, Q
-from django.apps import apps
-from django.utils.translation import ugettext_lazy as _, ugettext
+from django.db.models import Q
+from django.utils.translation import ugettext_lazy as _
 from django.contrib.auth.models import Permission as DjangoPermission
 from django.contrib.auth.models import ContentType as DjangoContentType
 
-from common.tree import TreeNode
 from .. import const
 
 Scope = const.Scope
@@ -19,190 +15,6 @@ class ContentType(DjangoContentType):
         proxy = True
 
 
-class PermissionTreeUtil:
-    get_permissions: Callable
-
-    def __init__(self, permissions, scope, check_disabled=False):
-        self.permissions = self.prefetch_permissions(permissions)
-        self.all_permissions = self.prefetch_permissions(
-            Permission.get_permissions(scope)
-        )
-        self.check_disabled = check_disabled
-
-    @staticmethod
-    def prefetch_permissions(perms):
-        return perms.select_related('content_type') \
-            .annotate(app=F('content_type__app_label')) \
-            .annotate(model=F('content_type__model'))
-
-    def _create_apps_tree_nodes(self):
-        app_counts = self.all_permissions.values('app')\
-            .order_by('app').annotate(count=Count('app'))
-        app_checked_counts = self.permissions.values('app')\
-            .order_by('app').annotate(count=Count('app'))
-        app_checked_counts_mapper = {
-            i['app']: i['count']
-            for i in app_checked_counts
-        }
-        all_apps = apps.get_app_configs()
-        apps_name_mapper = {
-            app.name: app.verbose_name
-            for app in all_apps if hasattr(app, 'verbose_name')
-        }
-        nodes = []
-        
-        for i in app_counts:
-            app = i['app']
-            total_counts = i['count']
-            check_counts = app_checked_counts_mapper.get(app, 0)
-            name = apps_name_mapper.get(app, app)
-            full_name = f'{name}({check_counts}/{total_counts})'
-
-            node = TreeNode(**{
-                'id': app,
-                'name': full_name,
-                'title': name,
-                'pId': '$ROOT$',
-                'isParent': True,
-                'open': False,
-                'chkDisabled': self.check_disabled,
-                'checked': total_counts == check_counts,
-                'iconSkin': '',
-                'meta': {
-                    'type': 'app',
-                }
-            })
-            nodes.append(node)
-        return nodes
-
-    def _create_models_tree_nodes(self):
-        content_types = ContentType.objects.all()
-
-        model_counts = self.all_permissions \
-            .values('model', 'app', 'content_type') \
-            .order_by('content_type') \
-            .annotate(count=Count('content_type'))
-        model_check_counts = self.permissions \
-            .values('content_type', 'model') \
-            .order_by('content_type') \
-            .annotate(count=Count('content_type'))
-        model_counts_mapper = {
-            i['content_type']: i['count']
-            for i in model_counts
-        }
-        model_check_counts_mapper = {
-            i['content_type']: i['count']
-            for i in model_check_counts
-        }
-
-        nodes = []
-        for ct in content_types:
-            total_counts = model_counts_mapper.get(ct.id, 0)
-            if total_counts == 0:
-                continue
-            check_counts = model_check_counts_mapper.get(ct.id, 0)
-            model_id = f'{ct.app_label}_{ct.model}'
-            name = f'{ct.name}({check_counts}/{total_counts})'
-            node = TreeNode(**{
-                'id': model_id,
-                'name': name,
-                'title': name,
-                'pId': ct.app_label,
-                'chkDisabled': self.check_disabled,
-                'isParent': True,
-                'open': False,
-                'checked': total_counts == check_counts,
-                'meta': {
-                    'type': 'model',
-                }
-            })
-            nodes.append(node)
-        return nodes
-
-    @staticmethod
-    def _get_permission_name(p, content_types_name_mapper):
-        code_name = p.codename
-        action_mapper = {
-            'add': ugettext('Create'),
-            'view': ugettext('View'),
-            'change': ugettext('Update'),
-            'delete': ugettext('Delete')
-        }
-        name = ''
-        ct = ''
-        if 'add_' in p.codename:
-            name = action_mapper['add']
-            ct = code_name.replace('add_', '')
-        elif 'view_' in p.codename:
-            name = action_mapper['view']
-            ct = code_name.replace('view_', '')
-        elif 'change_' in p.codename:
-            name = action_mapper['change']
-            ct = code_name.replace('change_', '')
-        elif 'delete' in code_name:
-            name = action_mapper['delete']
-            ct = code_name.replace('delete_', '')
-
-        if ct in content_types_name_mapper:
-            name += content_types_name_mapper[ct]
-        else:
-            name = p.name
-        return name
-
-    def _create_perms_tree_nodes(self):
-        permissions_id = self.permissions.values_list('id', flat=True)
-        nodes = []
-        content_types = ContentType.objects.all()
-        content_types_name_mapper = {ct.model: ct.name for ct in content_types}
-        for p in self.all_permissions:
-            model_id = f'{p.app}_{p.model}'
-            name = self._get_permission_name(p, content_types_name_mapper)
-
-            node = TreeNode(**{
-                'id': p.id,
-                'name': name + '({})'.format(p.app_label_codename),
-                'title': p.name,
-                'pId': model_id,
-                'isParent': False,
-                'chkDisabled': self.check_disabled,
-                'iconSkin': 'file',
-                'checked': p.id in permissions_id,
-                'open': False,
-                'meta': {
-                    'type': 'perm',
-                }
-            })
-            nodes.append(node)
-        return nodes
-
-    def _create_root_tree_node(self):
-        total_counts = self.all_permissions.count()
-        check_counts = self.permissions.count()
-        node = TreeNode(**{
-            'id': '$ROOT$',
-            'name': f'所有权限({check_counts}/{total_counts})',
-            'title': '所有权限',
-            'pId': '',
-            'chkDisabled': self.check_disabled,
-            'isParent': True,
-            'checked': total_counts == check_counts,
-            'open': True,
-            'meta': {
-                'type': 'root',
-            }
-        })
-        return node
-
-    def create_tree_nodes(self):
-        nodes = [self._create_root_tree_node()]
-        apps_nodes = self._create_apps_tree_nodes()
-        models_nodes = self._create_models_tree_nodes()
-        perms_nodes = self._create_perms_tree_nodes()
-
-        nodes += apps_nodes + models_nodes + perms_nodes
-        return nodes
-
-
 class Permission(DjangoPermission):
     """ 权限类 """
     class Meta:
@@ -265,6 +77,7 @@ class Permission(DjangoPermission):
 
     @staticmethod
     def create_tree_nodes(permissions, scope, check_disabled=False):
+        from ..tree import PermissionTreeUtil
         util = PermissionTreeUtil(permissions, scope, check_disabled)
         return util.create_tree_nodes()
 
diff --git a/apps/rbac/permissions.py b/apps/rbac/permissions.py
index 717f79848..43210818f 100644
--- a/apps/rbac/permissions.py
+++ b/apps/rbac/permissions.py
@@ -16,6 +16,7 @@ class RBACPermission(permissions.DjangoModelPermissions):
         ('bulk_update', '%(app_label)s.change_%(model_name)s'),
         ('partial_bulk_update', '%(app_label)s.change_%(model_name)s'),
         ('bulk_destroy', '%(app_label)s.delete_%(model_name)s'),
+        ('render_to_json', '%(app_label)s.add_%(model_name)s'),
         ('metadata', ''),
         ('GET', '%(app_label)s.view_%(model_name)s'),
         ('OPTIONS', ''),
diff --git a/apps/rbac/tree.py b/apps/rbac/tree.py
new file mode 100644
index 000000000..c5cdfde32
--- /dev/null
+++ b/apps/rbac/tree.py
@@ -0,0 +1,387 @@
+#!/usr/bin/python
+from collections import defaultdict
+from typing import Callable
+
+from django.utils.translation import gettext_lazy as _, gettext
+from django.conf import settings
+from django.apps import apps
+from django.db.models import F, Count
+from django.utils.translation import ugettext
+
+from .models import Permission, ContentType
+from common.tree import TreeNode
+
+root_node_data = {
+    'id': '$ROOT$',
+    'name': _('All permissions'),
+    'title': _('All permissions'),
+    'pId': '',
+}
+
+view_nodes_data = [
+    {
+        'id': 'view_console',
+        'name': _('Console view'),
+    },
+    {
+        'id': 'view_workspace',
+        'name': _('Workspace view'),
+    },
+    {
+        'id': 'view_audit',
+        'name': _('Audit view'),
+    },
+    {
+        'id': 'view_setting',
+        'name': _('System setting'),
+    },
+    {
+        'id': 'view_other',
+        'name': _('Other'),
+    }
+]
+
+app_nodes_data = [
+    {
+        'id': 'users',
+        'view': 'view_console',
+    },
+    {
+        'id': 'assets',
+        'view': 'view_console',
+    },
+    {
+        'id': 'applications',
+        'view': 'view_console',
+    },
+    {
+        'id': 'accounts',
+        'name': _('Accounts'),
+        'view': 'view_console',
+    },
+    {
+        'id': 'perms',
+        'view': 'view_console',
+    },
+    {
+        'id': 'acls',
+        'view': 'view_console',
+    },
+    {
+        'id': 'ops',
+        'view': 'view_console',
+    },
+    {
+        'id': 'terminal',
+        'name': _('Session audits'),
+        'view': 'view_audit',
+    },
+    {
+        'id': 'audits',
+        'view': 'view_audit',
+    },
+    {
+        'id': 'rbac',
+        'view': 'view_console'
+    },
+    {
+        'id': 'settings',
+        'view': 'view_setting'
+    },
+    {
+        'id': 'tickets',
+        'view': 'view_other',
+    },
+    {
+        'id': 'authentication',
+        'view': 'view_other'
+    }
+]
+
+extra_nodes_data = [
+    {
+        "id": "cloud_import",
+        "name": _("Cloud import"),
+        "pId": "assets",
+    },
+    {
+        "id": "backup_account_node",
+        "name": _("Backup account"),
+        "pId": "accounts"
+    },
+    {
+        "id": "gather_account_node",
+        "name": _("Gather account"),
+        "pId": "accounts",
+    },
+    {
+        "id": "app_change_plan_node",
+        "name": _("App change auth"),
+        "pId": "accounts"
+    },
+    {
+        "id": "asset_change_plan_node",
+        "name": _("Asset change auth"),
+        "pId": "accounts"
+    },
+    {
+        "id": "terminal_node",
+        "name": _("Terminal"),
+        "pId": "view_setting"
+    }
+]
+
+special_pid_mapper = {
+    'common.permission': 'view_other',
+    "assets.authbook": "accounts",
+    "applications.account": "accounts",
+    'xpack.account': 'cloud_import',
+    'xpack.syncinstancedetail': 'cloud_import',
+    'xpack.syncinstancetask': 'cloud_import',
+    'xpack.syncinstancetaskexecution': 'cloud_import',
+    'assets.accountbackupplan': "backup_account_node",
+    'assets.accountbackupplanexecution': "backup_account_node",
+    'xpack.applicationchangeauthplan': 'app_change_plan_node',
+    'xpack.applicationchangeauthplanexecution': 'app_change_plan_node',
+    'xpack.applicationchangeauthplantask': 'app_change_plan_node',
+    'xpack.changeauthplan': 'asset_change_plan_node',
+    'xpack.changeauthplanexecution': 'asset_change_plan_node',
+    'xpack.changeauthplantask': 'asset_change_plan_node',
+    "assets.gathereduser": "gather_account_node",
+    'xpack.gatherusertask': 'gather_account_node',
+    'xpack.gatherusertaskexecution': 'gather_account_node',
+    'orgs.organization': 'view_setting',
+    'settings.setting': 'view_setting',
+    'terminal.terminal': 'terminal_node',
+    'terminal.commandstorage': 'terminal_node',
+    'terminal.replaystorage': 'terminal_node',
+    'terminal.status': 'terminal_node',
+    'terminal.task': 'terminal_node',
+}
+
+
+class PermissionTreeUtil:
+    get_permissions: Callable
+
+    def __init__(self, permissions, scope, check_disabled=False):
+        self.permissions = self.prefetch_permissions(permissions)
+        self.all_permissions = self.prefetch_permissions(
+            Permission.get_permissions(scope)
+        )
+        self.check_disabled = check_disabled
+        self.total_counts = defaultdict(int)
+        self.checked_counts = defaultdict(int)
+
+    @staticmethod
+    def prefetch_permissions(perms):
+        return perms.select_related('content_type') \
+            .annotate(app=F('content_type__app_label')) \
+            .annotate(model=F('content_type__model'))
+
+    def create_apps_nodes(self):
+        all_apps = apps.get_app_configs()
+        apps_name_mapper = {
+            app.name: app.verbose_name
+            for app in all_apps if hasattr(app, 'verbose_name')
+        }
+        nodes = []
+
+        for i in app_nodes_data:
+            app = i['id']
+            name = i.get('name') or apps_name_mapper.get(app, app)
+            view = i.get('view', 'other')
+
+            app_data = {
+                'id': app,
+                'name': name,
+                'pId': view,
+            }
+            total_count = self.total_counts[app]
+            checked_count = self.checked_counts[app]
+            self.total_counts[view] += total_count
+            self.checked_counts[view] += checked_count
+            node = self._create_node(
+                app_data, total_count, checked_count,
+                'app', is_open=False
+            )
+            nodes.append(node)
+        return nodes
+
+    def _get_model_counts_mapper(self):
+        model_counts = self.all_permissions \
+            .values('model', 'app', 'content_type') \
+            .order_by('content_type') \
+            .annotate(count=Count('content_type'))
+        model_check_counts = self.permissions \
+            .values('content_type', 'model') \
+            .order_by('content_type') \
+            .annotate(count=Count('content_type'))
+        model_counts_mapper = {
+            i['content_type']: i['count']
+            for i in model_counts
+        }
+        model_check_counts_mapper = {
+            i['content_type']: i['count']
+            for i in model_check_counts
+        }
+        return model_counts_mapper, model_check_counts_mapper
+
+    def _create_models_nodes(self):
+        content_types = ContentType.objects.all()
+        total_counts_mapper, checked_counts_mapper = self._get_model_counts_mapper()
+
+        nodes = []
+        for ct in content_types:
+            total_count = total_counts_mapper.get(ct.id, 0)
+            checked_count = checked_counts_mapper.get(ct.id, 0)
+            if total_count == 0:
+                continue
+
+            model_id = '{}.{}'.format(ct.app_label, ct.model)
+            app = ct.app_label
+            if special_pid_mapper.get(model_id):
+                app = special_pid_mapper[model_id]
+
+            self.total_counts[app] += total_count
+            self.checked_counts[app] += checked_count
+
+            name = f'{ct.name}'
+            node = self._create_node({
+                'id': model_id,
+                'name': name,
+                'pId': app,
+            }, total_count, checked_count, 'model', is_open=False)
+            nodes.append(node)
+        return nodes
+
+    @staticmethod
+    def _get_permission_name(p, content_types_name_mapper):
+        code_name = p.codename
+        action_mapper = {
+            'add': ugettext('Create'),
+            'view': ugettext('View'),
+            'change': ugettext('Update'),
+            'delete': ugettext('Delete')
+        }
+        name = ''
+        ct = ''
+        if 'add_' in p.codename:
+            name = action_mapper['add']
+            ct = code_name.replace('add_', '')
+        elif 'view_' in p.codename:
+            name = action_mapper['view']
+            ct = code_name.replace('view_', '')
+        elif 'change_' in p.codename:
+            name = action_mapper['change']
+            ct = code_name.replace('change_', '')
+        elif 'delete' in code_name:
+            name = action_mapper['delete']
+            ct = code_name.replace('delete_', '')
+
+        if ct in content_types_name_mapper:
+            name += content_types_name_mapper[ct]
+        else:
+            name = gettext(p.name)
+            name = name.replace('Can ', '').replace('可以', '')
+        return name
+
+    def _create_perms_nodes(self):
+        permissions_id = self.permissions.values_list('id', flat=True)
+        nodes = []
+        content_types = ContentType.objects.all()
+        content_types_name_mapper = {ct.model: ct.name for ct in content_types}
+
+        for p in self.all_permissions:
+            model_id = f'{p.app}.{p.model}'
+            name = self._get_permission_name(p, content_types_name_mapper)
+            if settings.DEBUG:
+                name += '({})'.format(p.app_label_codename)
+
+            node = TreeNode(**{
+                'id': p.id,
+                'name': name,
+                'title': p.name,
+                'pId': model_id,
+                'isParent': False,
+                'chkDisabled': self.check_disabled,
+                'iconSkin': 'file',
+                'checked': p.id in permissions_id,
+                'open': False,
+                'meta': {
+                    'type': 'perm',
+                }
+            })
+            nodes.append(node)
+        return nodes
+
+    def _create_node(self, data, total_count, checked_count, tp,
+                     is_parent=True, is_open=True, icon='', checked=None):
+        assert data.get('id')
+        assert data.get('name')
+        assert data.get('pId') is not None
+        if checked is None:
+            checked = total_count == checked_count
+        node_data = {
+            'isParent': is_parent,
+            'iconSkin': icon,
+            'open': is_open,
+            'chkDisabled': self.check_disabled,
+            'checked': checked,
+            'meta': {
+                'type':  tp,
+            },
+            **data
+        }
+        if not node_data.get('title'):
+            node_data['title'] = node_data['name']
+        node = TreeNode(**node_data)
+        node.name += f'({checked_count}/{total_count})'
+        return node
+
+    def _create_root_tree_node(self):
+        total_count = self.all_permissions.count()
+        checked_count = self.permissions.count()
+        node = self._create_node(root_node_data, total_count, checked_count, 'root')
+        return node
+
+    def _create_views_node(self):
+        nodes = []
+        for view_data in view_nodes_data:
+            view = view_data['id']
+            data = {
+                **view_data,
+                'pId': '$ROOT$',
+            }
+            total_count = self.total_counts[view]
+            checked_count = self.checked_counts[view]
+            node = self._create_node(data, total_count, checked_count, 'view')
+            nodes.append(node)
+        return nodes
+    
+    def _create_extra_nodes(self):
+        nodes = []
+        for data in extra_nodes_data:
+            i = data['id']
+            pid = data['pId']
+            checked_count = self.checked_counts[i]
+            total_count = self.total_counts[i]
+            self.total_counts[pid] += total_count
+            self.checked_counts[pid] += checked_count
+            node = self._create_node(
+                data, total_count, checked_count,
+                'extra', is_open=False
+            )
+            nodes.append(node)
+
+        return nodes
+
+    def create_tree_nodes(self):
+        nodes = [self._create_root_tree_node()]
+        perms_nodes = self._create_perms_nodes()
+        models_nodes = self._create_models_nodes()
+        apps_nodes = self.create_apps_nodes()
+        views_nodes = self._create_views_node()
+        extra_nodes = self._create_extra_nodes()
+
+        nodes += views_nodes + apps_nodes + models_nodes + perms_nodes + extra_nodes
+        return nodes
diff --git a/apps/settings/apps.py b/apps/settings/apps.py
index bcb1b7693..8bef81db3 100644
--- a/apps/settings/apps.py
+++ b/apps/settings/apps.py
@@ -7,4 +7,4 @@ class SettingsConfig(AppConfig):
     verbose_name = _('Settings')
 
     def ready(self):
-        from . import signals_handler
+        from . import signal_handlers
diff --git a/apps/settings/signals_handler.py b/apps/settings/signal_handlers.py
similarity index 100%
rename from apps/settings/signals_handler.py
rename to apps/settings/signal_handlers.py
diff --git a/apps/terminal/apps.py b/apps/terminal/apps.py
index ca67ab296..fa46ca9e2 100644
--- a/apps/terminal/apps.py
+++ b/apps/terminal/apps.py
@@ -9,6 +9,6 @@ class TerminalConfig(AppConfig):
     verbose_name = _('Terminals')
 
     def ready(self):
-        from . import signals_handler
+        from . import signal_handlers
         from . import notifications
         return super().ready()
diff --git a/apps/terminal/migrations/0047_auto_20220302_1951.py b/apps/terminal/migrations/0047_auto_20220302_1951.py
new file mode 100644
index 000000000..a67b260b8
--- /dev/null
+++ b/apps/terminal/migrations/0047_auto_20220302_1951.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.1.14 on 2022-03-02 11:51
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('terminal', '0046_auto_20220228_1744'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='sessionreplay',
+            options={'permissions': [('upload_sessionreplay', 'Can upload session replay'), ('download_sessionreplay', 'Can download session replay')], 'verbose_name': 'Session replay'},
+        ),
+    ]
diff --git a/apps/terminal/models/replay.py b/apps/terminal/models/replay.py
index adba14948..5f3e372af 100644
--- a/apps/terminal/models/replay.py
+++ b/apps/terminal/models/replay.py
@@ -9,6 +9,7 @@ class SessionReplay(CommonModelMixin):
     session = models.ForeignKey(Session, on_delete=models.CASCADE, verbose_name=_("Session"))
 
     class Meta:
+        verbose_name = _("Session replay")
         permissions = [
             ('upload_sessionreplay', _("Can upload session replay")),
             ('download_sessionreplay', _("Can download session replay")),
diff --git a/apps/terminal/signals_handler.py b/apps/terminal/signal_handlers.py
similarity index 100%
rename from apps/terminal/signals_handler.py
rename to apps/terminal/signal_handlers.py
diff --git a/apps/tickets/api/super_ticket.py b/apps/tickets/api/super_ticket.py
index bf96a6cc9..ea186bd1b 100644
--- a/apps/tickets/api/super_ticket.py
+++ b/apps/tickets/api/super_ticket.py
@@ -2,7 +2,7 @@ from rest_framework.generics import RetrieveDestroyAPIView
 
 from orgs.utils import tmp_to_root_org
 from ..serializers import SuperTicketSerializer
-from ..models import SuperTicket
+from ..models import Ticket
 
 
 __all__ = ['SuperTicketStatusAPI']
@@ -10,11 +10,14 @@ __all__ = ['SuperTicketStatusAPI']
 
 class SuperTicketStatusAPI(RetrieveDestroyAPIView):
     serializer_class = SuperTicketSerializer
+    rbac_perms = {
+        'GET': 'tickets.view_superticket',
+        'DELETE': 'tickets.change_superticket'
+    }
 
     def get_queryset(self):
         with tmp_to_root_org():
-            return SuperTicket.objects.all()
+            return Ticket.objects.all()
 
     def perform_destroy(self, instance):
-        ticket = self.get_object()
-        ticket.close(processor=ticket.applicant)
+        instance.close(processor=instance.applicant)
diff --git a/apps/tickets/apps.py b/apps/tickets/apps.py
index 8dd08d109..317ac592e 100644
--- a/apps/tickets/apps.py
+++ b/apps/tickets/apps.py
@@ -7,6 +7,6 @@ class TicketsConfig(AppConfig):
     verbose_name = _('Tickets')
 
     def ready(self):
-        from . import signals_handler
+        from . import signal_handlers
         from . import notifications
         return super().ready()
diff --git a/apps/tickets/models/ticket.py b/apps/tickets/models/ticket.py
index d3ee162bd..aa536584d 100644
--- a/apps/tickets/models/ticket.py
+++ b/apps/tickets/models/ticket.py
@@ -1,9 +1,10 @@
 # -*- coding: utf-8 -*-
 #
+from typing import Callable
+
 from django.db import models
 from django.db.models import Q
 from django.utils.translation import ugettext_lazy as _
-from datetime import timedelta
 from django.db.utils import IntegrityError
 
 from common.exceptions import JMSException
@@ -53,6 +54,7 @@ class StatusMixin:
     status: str
     applicant: models.ForeignKey
     current_node: models.Manager
+    save: Callable
 
     def set_state_approve(self):
         self.state = TicketState.approved
@@ -182,7 +184,8 @@ class Ticket(CommonModelMixin, StatusMixin, OrgModelMixin):
 
     @property
     def processor(self):
-        processor = self.current_node.first().ticket_assignees.exclude(state=ProcessStatus.notified).first()
+        processor = self.current_node.first().ticket_assignees\
+            .exclude(state=ProcessStatus.notified).first()
         return processor.assignee if processor else None
 
     def create_related_node(self):
diff --git a/apps/tickets/serializers/super_ticket.py b/apps/tickets/serializers/super_ticket.py
index a59bc51fb..9d84728e4 100644
--- a/apps/tickets/serializers/super_ticket.py
+++ b/apps/tickets/serializers/super_ticket.py
@@ -1,4 +1,5 @@
-from rest_framework.serializers import ModelSerializer
+from rest_framework import serializers
+from django.utils.translation import gettext_lazy as _
 
 from ..models import SuperTicket
 
@@ -6,7 +7,15 @@ from ..models import SuperTicket
 __all__ = ['SuperTicketSerializer']
 
 
-class SuperTicketSerializer(ModelSerializer):
+class SuperTicketSerializer(serializers.ModelSerializer):
+    processor = serializers.SerializerMethodField(label=_("Processor"))
+
     class Meta:
         model = SuperTicket
         fields = ['id', 'status', 'state', 'processor']
+
+    @staticmethod
+    def get_processor(ticket):
+        if not ticket.processor:
+            return ''
+        return str(ticket.processor)
diff --git a/apps/tickets/signals_handler/__init__.py b/apps/tickets/signal_handlers/__init__.py
similarity index 100%
rename from apps/tickets/signals_handler/__init__.py
rename to apps/tickets/signal_handlers/__init__.py
diff --git a/apps/tickets/signals_handler/comment.py b/apps/tickets/signal_handlers/comment.py
similarity index 100%
rename from apps/tickets/signals_handler/comment.py
rename to apps/tickets/signal_handlers/comment.py
diff --git a/apps/tickets/signals_handler/ticket.py b/apps/tickets/signal_handlers/ticket.py
similarity index 100%
rename from apps/tickets/signals_handler/ticket.py
rename to apps/tickets/signal_handlers/ticket.py
diff --git a/apps/users/apps.py b/apps/users/apps.py
index 2c37f4e42..bd569c9fe 100644
--- a/apps/users/apps.py
+++ b/apps/users/apps.py
@@ -9,6 +9,6 @@ class UsersConfig(AppConfig):
     verbose_name = _('Users')
 
     def ready(self):
-        from . import signals_handler
+        from . import signal_handlers
         from . import notifications
         super().ready()
diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py
index 293e3c89a..c89a01d21 100644
--- a/apps/users/serializers/user.py
+++ b/apps/users/serializers/user.py
@@ -131,7 +131,7 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, serializer
             'public_key': {'write_only': True},
             'is_first_login': {'label': _('Is first login'), 'read_only': True},
             'is_valid': {'label': _('Is valid')},
-            'is_service_account':  {'label': _('Is service account')},
+            'is_service_account': {'label': _('Is service account')},
             'is_expired': {'label': _('Is expired')},
             'avatar_url': {'label': _('Avatar url')},
             'created_by': {'read_only': True, 'allow_blank': True},
@@ -243,7 +243,7 @@ class InviteSerializer(RolesSerializerMixin, serializers.Serializer):
 class ServiceAccountSerializer(serializers.ModelSerializer):
     class Meta:
         model = User
-        fields = ['id', 'name', 'access_key']
+        fields = ['id', 'name', 'access_key', 'comment']
         read_only_fields = ['access_key']
 
     def __init__(self, *args, **kwargs):
diff --git a/apps/users/signals_handler.py b/apps/users/signal_handlers.py
similarity index 100%
rename from apps/users/signals_handler.py
rename to apps/users/signal_handlers.py
diff --git a/jms b/jms
index b4eeb7e8a..517fb0602 100755
--- a/jms
+++ b/jms
@@ -63,8 +63,8 @@ def check_database_connection():
             return
         except OperationalError:
             logging.info('Database not setup, retry')
-        except Exception as e:
-            logging.error('Unexpect error occur: {}'.format(str(e)))
+        except Exception as exc:
+            logging.error('Unexpect error occur: {}'.format(str(exc)))
         time.sleep(1)
     logging.error("Connection database failed, exit")
     sys.exit(10)
@@ -96,7 +96,7 @@ def collect_static():
         pass
 
 
-def compile_i81n_file():
+def compile_i18n_file():
     django_mo_file = os.path.join(BASE_DIR, 'apps', 'locale', 'zh', 'LC_MESSAGES', 'django.mo')
     if os.path.exists(django_mo_file):
         return
@@ -134,8 +134,8 @@ def start_services():
     except KeyboardInterrupt:
         logging.info('Cancel ...')
         time.sleep(2)
-    except Exception as e:
-        logging.error("Start service error {}: {}".format(services, e))
+    except Exception as exc:
+        logging.error("Start service error {}: {}".format(services, exc))
         time.sleep(2)
 
 
diff --git a/requirements/requirements.txt b/requirements/requirements.txt
index aea4e3184..a29be5520 100644
--- a/requirements/requirements.txt
+++ b/requirements/requirements.txt
@@ -17,7 +17,7 @@ decorator==4.1.2
 Django==3.1.14
 django-auth-ldap==2.2.0
 django-bootstrap3==14.2.0
-django-celery-beat==2.0
+django-celery-beat==2.2.1
 django-filter==2.4.0
 django-formtools==2.2
 django-ranged-response==0.2.0
@@ -84,7 +84,7 @@ python-daemon==2.2.3
 httpsig==1.3.0
 treelib==1.5.3
 django-proxy==1.2.1
-flower==0.9.3
+flower==1.0.0
 channels-redis==3.2.0
 channels==2.4.0
 daphne==2.4.1
diff --git a/utils/start_celery_beat.py b/utils/start_celery_beat.py
index 34887fb4d..236a61ba8 100644
--- a/utils/start_celery_beat.py
+++ b/utils/start_celery_beat.py
@@ -22,8 +22,9 @@ redis = Redis(host=CONFIG.REDIS_HOST, port=CONFIG.REDIS_PORT, password=CONFIG.RE
 scheduler = "django_celery_beat.schedulers:DatabaseScheduler"
 
 cmd = [
-    'celery', 'beat',
+    'celery',
     '-A', 'ops',
+    'beat',
     '-l', 'INFO',
     '--scheduler', scheduler,
     '--max-interval', '60'